github-coordination sync listed every repo issue and pushed the epic label onto all of them (#3084). Scope the listing to issues carrying the policy's epic label plus issues whose body still holds the coordination marker (label-drift recovery), deduped by number, and reject an empty labels.epic in loadPolicy. Tests cover the filtered path and the recovery path with exact gh argv. Independent exact-head review passed with no P0/P1; CI 44/44 at the head.
Use literal matching for the forbidden documentation endpoint, pin lifecycle mode/content assertions to one descriptor, and inject parent races through the staged descriptor without forwarding arbitrary file-creation flags.
Close fixture-owned descriptors when Windows blocks a directory rename before open returns. Assert OS rejection preserves both settings files and releases staging handles. Move the rename-boundary injection after descriptor close so Windows exercises parent-identity validation without skipping race coverage.
Reject directory replacement after temporary file creation or staging, preserve unrelated files during cleanup, and retry settings edits observed before the final rename. Add three regression tests for the review findings.
install-plan.js and install-apply.js both require ./lib/install/config at
load time, and that module required ajv unconditionally at the top of the
file even though ajv is only actually used when validating an
ecc-install.json. When ECC is installed via the Claude Code plugin
marketplace, the marketplace directory is a bare git clone with no
node_modules, so requiring ajv crashes commands like --list-profiles that
never touch install-config validation at all.
Same root cause in scripts/lib/control-pane/state.js: sql.js and
@iarna/toml were required at module scope even though they are only used
inside openSqlDatabase() and readTomlConfig(), so control-pane.js --help
crashed too.
Make both requires lazy so they only load when the feature that actually
needs them runs. For the case where ajv/sql.js/js-yaml/@iarna-toml is
genuinely needed and still missing, add a small helper that turns the raw
MODULE_NOT_FOUND into an actionable message naming the package and the
install command, instead of a stack trace (install-apply.js) or, worse, an
unhandled crash with a usage banner tacked on that reads like a bad
argument (install-plan.js, control-pane.js). Applied the same helper to
memory-mcp.mjs, where ajv is genuinely load-bearing (it compiles every MCP
tool's JSON schema up front) so it can't be made lazy the same way.
Added a regression test that copies just scripts/, schemas/, and
manifests/ into a directory with no node_modules anywhere above it in the
filesystem, which reproduces the plugin-marketplace install exactly, and
asserts install-plan.js and control-pane.js still work.
The guided installer asks how ECC hooks should run, but that consent
lived only in the wizard path. Running install-apply directly with a
profile that includes hooks-runtime still materialized the hook runtime
with no disclosure and no decision.
Gate the apply layer instead, so every entry point is covered:
- disclose the six hook capability groups when a plan would materialize
the hook runtime, and refuse to apply until the caller decides
- --enable-hooks confirms the hook runtime; --no-hooks installs the rest
of the selection without it and records the reduced module closure in
install-state
- surface the pending decision as a dry-run warning
- show the same capability disclosure in the guided installer's plan
preview, so the wizard's hook question states what it is asking about
Plans that never materialize hooks (Kimi, --profile minimal,
--without baseline:hooks) are unaffected and need no flag. Repair and
uninstall operate on already-recorded state and stay unchanged.
The capability taxonomy and the held-materialization behavior come from
Samarjeet Singh Tomar's PR #2634, reworked to fit the single-decision
consent model that shipped with the guided installer in #2649.
Co-Authored-By: Samarjeet Singh Tomar <samar_tomar@hotmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The context signal always rendered "N% of <window> window", including when
the window size was the assumed 200k default rather than a detected value.
On a 1M session whose transcript carries no [1m] marker, that produced
lines like:
[StrategicCompact] Context ~194k tokens (97% of 200k window)
while actual usage was ~19%. The user compacts on a false alarm, loses
context, and the resulting quality drop reads as a model regression.
The gap is structural: the context threshold defaults to 80% of the
window (160k on 200k), so the signal fires precisely in the 160k-200k
band where the size cannot be determined — above 200k the observed-tokens
fallback correctly infers 1M, and below 160k nothing fires.
Model id alone cannot close this. A tier may ship both a 200k and a 1M
variant under one id, so neither the known-family table nor a new entry
can distinguish them, and the transcript records no window field.
So stop asserting what isn't known: resolveContextWindow() now reports
whether the size was detected (env override, [1m] marker, known family,
or observed tokens > 200k) or assumed, and the hook omits the percentage
and window label when it was assumed. The token count, threshold, and
firing behaviour are unchanged.
resolveContextWindowTokens() keeps its existing signature and semantics.
Note: 3 pre-existing failures in tests/hooks/suggest-compact.test.js
reproduce identically on unmodified main and are untouched here.
cost-estimate.js carries its own copy of the stale Opus/Haiku/Sonnet rate
table already reported in #2574, but grepping every .js/.json/.md file
outside node_modules turns up zero callers besides its own test. It was
added in 940135e alongside the statusline observability hooks and never
wired into any of them.
The maintainer's comment on #2656 named two acceptable outcomes: remove
the unused duplicate, or share one rate source with the live tracker.
cost-tracker.js's own fix (#2574) has not landed yet, so sharing its
table now would import numbers that are still wrong. Removing the dead
file is the smaller, immediately-correct step.
Fixes#2656