Commit Graph

590 Commits

Author SHA1 Message Date
devin-ai-integration[bot] 2d40baacbd
fix: resolve open-issue cluster (#2295, #2298, #2303–#2306, #2340) + createdTime fallback bug (#2408)
* fix: resolve issue cluster (#2295,#2298,#2303,#2304,#2305,#2306,#2340) + createdTime fallback bug

- session-manager: fix createdTime birthtime||ctime fallback that never fired
  (a Date is always truthy); use birthtimeMs>0 check via resolveCreatedTime()
- installer: rewrite source-relative rules/skills links for the injected
  ecc/ namespace so installed skills resolve correctly (#2340)
- continuous-learning-v2: drop unused mock import (#2305); standardize bash
  shebangs (#2303); poll for PID file instead of fixed sleep (#2295);
  rename _ecc_* -> _clv2_* (#2304); align promotion confidence docs (#2298);
  de-brittle Scope Decision Guide cross-reference (#2306)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ci): resync lockfiles with package.json (eslint 10) + migrate yarn.lock to Yarn 4 format

package.json requires eslint@^10.6.0 but the committed locks pinned 9.39.2, so
npm ci aborted and Yarn 4 hardened mode rejected the stale v1-classic yarn.lock
(YN0028). Regenerate package-lock.json and rewrite yarn.lock in Yarn 4 (berry)
format so npm ci and immutable yarn installs both pass.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ci): require clean probe exit for Windows shell/bash detection; add pyyaml dev dep

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(observer): portable mktemp template on BSD/macOS (#2417); correct false attribution-disabled claim in git-workflow docs (#2426) (#2430)

Co-authored-by: affaan <affaan@itomarkets.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: remove duplicate resolveCreatedTime introduced by merge (no-redeclare)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: restore heading-based Scope Decision Guide ref (line numbers drift) + keep behavioral #2340 install test

---------

Co-authored-by: affaan <affaan@itomarkets.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Affaan Mustafa <me@affaanmustafa.com>
2026-07-03 21:10:45 -07:00
Affaan Mustafa 3167852753
fix(gateguard): drop \x00 placeholder in glob-to-regex (eslint no-control-regex) (#2439)
Replace the NUL-byte '**' placeholder trick with split('**')/join('.*'),
which is semantically identical and lint-clean under eslint 10.
2026-07-03 20:51:49 -07:00
Alec Moldovan 0a35a0216b
feat(gateguard): add GATEGUARD_EXEMPT_GLOBS path exemptions (#2432)
The fact-forcing gate fires once per first-touched file per session. In
build-heavy sessions this costs a deny->retry round-trip on every new file,
including trees where the gate's questions ("who imports this? what schema?")
carry no signal: test files, generated artifacts, scratch dirs.

Add an opt-in, comma-separated glob allowlist read from GATEGUARD_EXEMPT_GLOBS.
A matching Edit/Write/MultiEdit target skips the first-touch gate; destructive-
Bash and routine-Bash gates are untouched. Default-off (unset => identical prior
behavior), fail-open (a malformed glob is dropped, never throws), and memoized on
the env value, matching the existing getExtraDestructiveRegex idiom.

  "env": { "GATEGUARD_EXEMPT_GLOBS": "**/tests/**,**/scratchpad/**" }

Adds 4 tests; all 144 gateguard tests pass.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:41:11 -07:00
Gaurav Dubey ff4a06dd91
feat(session-start): make instinct injection count and confidence threshold configurable (#2413)
* feat(session-start): make instinct injection count and confidence threshold configurable

Expose ECC_MAX_INJECTED_INSTINCTS and ECC_INSTINCT_CONFIDENCE_THRESHOLD so
operators can tune SessionStart instinct injection without editing source.
Defaults are unchanged (6 instincts, 0.7 confidence floor).

The two previously hardcoded constants become DEFAULT_-prefixed fallbacks,
resolved through getMaxInjectedInstincts() and getInstinctConfidenceThreshold(),
mirroring the existing getSessionRetentionDays() /
getSessionStartMaxContextChars() env-override pattern already in this file.
Invalid or out-of-range values fall back to the defaults.

Adds subprocess coverage in tests/hooks/hooks.test.js and documents both
variables in the README Hook Runtime Controls section.

Implements part (a) of #2371.

* fix(session-start): reject partial env values for instinct injection knobs

Parse ECC_MAX_INJECTED_INSTINCTS and ECC_INSTINCT_CONFIDENCE_THRESHOLD with
Number() (after trim) instead of parseInt/parseFloat, so malformed values
like "3.9", "6abc", or "0.7x" fall back to the default rather than silently
accepting the numeric prefix (parseInt("3.9")=3, parseFloat("0.7x")=0.7).
Adds a regression assertion that a non-integer count falls back to 6.

* fix(session-start): validate decimal grammar for instinct injection env vars

Number() still accepts non-decimal numeric syntax, so
ECC_INSTINCT_CONFIDENCE_THRESHOLD=0x1 resolved to 1 and
ECC_MAX_INJECTED_INSTINCTS=1e2 to 100. Gate each value on a strict format
(/^\d+(\.\d+)?$/ for the 0-1 threshold, /^\d+$/ for the positive-integer
count) before converting, so hex/exponent/partial values fall back to the
default. Adds regression assertions for 1e2 and 0x1.
2026-07-03 20:40:17 -07:00
Gaurav Dubey 0a926b45ba
fix(install): rewrite relative skill links for injected ecc namespace (#2399)
* fix(install): rewrite relative skill links for injected ecc namespace

Skill and rule markdown is byte-copied during a claude install, but the
home/project adapters inject an `ecc/` namespace segment
(skills/<id> -> skills/ecc/<id>, rules/<x> -> rules/ecc/<x>). Source-relative
links such as `../../rules/react/hooks.md` therefore broke after install: the
extra level changed what `../..` resolved to, and the link target itself moved
under rules/ecc/.

Rewrite relative links in namespaced markdown so they resolve to the file's
actual installed location, derived from the plan's own file placements (no
hardcoded namespace literal, so the links cannot drift from where files land).
Non-namespacing adapters and links to non-installed targets are left untouched;
URLs, anchors, absolute paths, and fenced code blocks are never rewritten.

Fixes #2340

* fix(install): keep non-namespaced markdown on the byte-for-byte copy path

Address review feedback: the markdown branch in applyInstallPlan diverted every
copy-file markdown operation through read+rewrite+write, so identity-mapped
markdown (source path == install path, no namespace injected) lost byte-for-byte
content and source mode bits even though no link rewrite was needed.

Gate the rewrite on isNamespacedSource() so only files whose install path
actually changed (e.g. skills/x -> skills/ecc/x) leave the copyFileSync path;
everything else is copied verbatim as before.

* test(install): emit failure stack in the link-rewrite test runner

Address review feedback: the local test() harness logged only error.message,
so a failing assertion lost its source line and diff. Print error.stack on
stderr on failure so broken rewrite cases stay diagnosable.
2026-07-03 20:39:24 -07:00
Gaurav Dubey c8c83ef428
fix(install): dedupe copy-file operations sharing a destination (#2429)
OpenCode ships override command files under .opencode/commands/ that
shadow the generic commands/*.md sources. The manifest install plan
recorded both writes against the same destination, so `ecc doctor`
reported perpetual drift for the 29 shadowed command files and `ecc
repair` "fixed" that phantom drift by copying the generic source over
the correct override, corrupting the installed command while never
clearing the warning.

Dedupe copy-file operations by destination in createManifestInstallPlan,
keeping the last writer to match the sequential apply order. install,
repair, and doctor all consume this one builder, so a fresh install is
clean and a single repair rewrites drifted state green.

Fixes #2414
2026-07-03 20:37:32 -07:00
devin-ai-integration[bot] 3af4676e99
refactor: consolidate duplicated hook-root resolver into shared resolveEccRoot() (#2368) (#2410)
* fix(ci): resync lockfiles with package.json (eslint 10) + migrate yarn.lock to Yarn 4 format

package.json requires eslint@^10.6.0 but the committed locks pinned 9.39.2, so
npm ci aborted and Yarn 4 hardened mode rejected the stale v1-classic yarn.lock
(YN0028). Regenerate package-lock.json and rewrite yarn.lock in Yarn 4 (berry)
format so npm ci and immutable yarn installs both pass.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ci): require clean probe exit for Windows shell/bash detection; add pyyaml dev dep

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor: consolidate duplicated hook-root resolver into shared resolveEccRoot() (#2368)

The inline node -e resolver blob was duplicated ~60x across hooks.json,
command docs, and translations. Each copy inlined the full ~700-char
plugin-root search using a spread over nested array literals
(p.join(d,'plugins',...s) over [['ecc'],...]), which breaks Windows hook
execution due to shell quoting (#2368).

Collapse every copy to a 250-char locator that loads the committed
resolve-ecc-root module and delegates to resolveEccRoot() — no spread, no
nested array literals, no escaped double quotes. The real search logic now
lives in one tested module. Also route session-start-bootstrap.js through
resolveEccRoot() instead of its own duplicated reimplementation, and fix
the auto-update.md 'marketplace' (singular) typo along the way.

Guard tests updated: discovery behavior is asserted against resolveEccRoot();
the inline is asserted to delegate and to contain no Windows-fragile
constructs.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(resolve-ecc-root): restore full env-unset discovery in inline resolver

Address Greptile review on #2410: when CLAUDE_PLUGIN_ROOT is unset the
delegating inline could only load the resolver module from ~/.claude,
returning ~/.claude without ever reaching the plugin/cache search. Restore
the old inline's discovery breadth (exact plugin roots + versioned cache)
Windows-safely (no spread, nested arrays, or escaped quotes), then delegate
the authoritative decision to resolveEccRoot(). Add regression tests for
plugin-subdir and versioned-cache bootstrap with env unset.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: affaan <affaan@itomarkets.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-03 20:01:17 -07:00
devin-ai-integration[bot] 6fac227f7a
fix(plan-orchestrate): detect ecc@ecc marketplace + emit ecc: agent prefix (#2316) (#2409)
* fix(plan-orchestrate): detect ecc@ecc marketplace + emit ecc: agent prefix (#2316)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ci): resync lockfiles with package.json (eslint 10) + migrate yarn.lock to Yarn 4 format

package.json requires eslint@^10.6.0 but the committed locks pinned 9.39.2, so
npm ci aborted and Yarn 4 hardened mode rejected the stale v1-classic yarn.lock
(YN0028). Regenerate package-lock.json and rewrite yarn.lock in Yarn 4 (berry)
format so npm ci and immutable yarn installs both pass.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ci): require clean probe exit for Windows shell/bash detection; add pyyaml dev dep

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: affaan <affaan@itomarkets.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-03 20:00:51 -07:00
Jun ec4925135c
fix(gateguard): finish tool-agnostic checklist across edit gate and SKILL.md copies (#2274)
b3268fef (#2272) made the write-gate "confirm no existing file" item
tool-agnostic in the JS hook, but the rest of the checklist surface still
names Glob/Grep. On hosts without those tools the agent still hits a dead
tool call on:
- the edit-gate "list importers" item in the hook (scripts/hooks/gateguard-fact-force.js)
- both checklist items in all three SKILL.md copies (en, ja-JP, zh-CN)

Apply the same wording b3268fef introduced — "(search the tree — Glob/Grep,
or find/grep via Bash)" — to those five remaining spots so the whole gate is
consistent. Prose-only; no logic change.

Follow-up to #2272 / b3268fef.
2026-06-29 18:38:24 -07:00
Hiroshi Tanaka c2950121c9
feat(session): LLM-powered session summary via claude -p (#2388)
Replace mechanical text extraction in session-end.js and pre-compact.js
with LLM-generated summaries using `claude -p`. Summaries now capture
design decisions, resolved bugs, changed files, and carry-over context
rather than just truncated user message snippets.

- Add scripts/lib/llm-summary.js: generateSessionSummary, extractConversationText,
  getContextRemainingPct, getContextThreshold, getLLMModel
- Update scripts/hooks/session-end.js: trigger LLM when context < 20% or
  every 50 messages (env-configurable via ECC_LLM_SUMMARY_*)
- Update scripts/hooks/pre-compact.js: generate LLM summary right before
  compaction and write it to the active session .tmp file
- Add tests/lib/llm-summary.test.js: 18 unit tests
- Update tests/hooks/hooks.test.js: 3 integration tests for new behaviour

Recursion guard: sets ECC_SKIP_LLM_SUMMARY=1 in subprocess env so Stop
hooks fired by the claude -p subprocess do not re-enter summarisation.
Requires no ANTHROPIC_API_KEY — reuses Claude Code's own authentication.

Co-authored-by: Hiroshi Tanaka <hiroshi_tanaka@MBAM3.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 15:55:01 -07:00
ChrisD 64797fd895
fix(windows): prefer PowerShell over bash to prevent zombie process accumulation (#2346)
* fix(windows): prefer PowerShell over bash to prevent zombie process accumulation

On Windows, ECC hook scripts were spawning bash.exe (MSYS2/Git Bash) on
every tool use via findShellBinary(). These processes were not reaped by
Windows, causing 40+ zombie bash.exe/conhost.exe processes per session with
noticeable system lag.

Changes to scripts/hooks/plugin-hook-bootstrap.js:
- Add isPowerShellBin(bin) helper: basename-based detection so full paths
  like C:\Windows\...\powershell.exe are handled correctly
- findShellBinary(): check BASH env var first (preserves escape hatch),
  then on win32 probe pwsh.exe -> powershell.exe -> bash.exe -> bash;
  use correct probe args per shell type; cache result in _cachedShell
- findBashBinary(): separate cached bash-only finder used by spawnShell
  .sh fallback; skips PowerShell binaries even if BASH points to one
- spawnShell(): use isPowerShellBin() to select -NoProfile -NonInteractive
  -File args for PowerShell; .sh scripts fall back to findBashBinary()
  with a skip-warning if no bash found on Windows

observe-runner.js is intentionally unchanged: it always invokes observe.sh
which is bash-only; routing it through PowerShell would silently break it.
The observe.sh -> observe.js migration is tracked separately.

Fixes #2345

* fix(windows): address CodeRabbit and Greptile review comments

- Add timeout: 30000 to all spawnSync probe calls in findShellBinary and
  findBashBinary to prevent hangs on broken/stalled shell candidates
- Add -ExecutionPolicy Bypass to PowerShell -File invocation to fix
  execution on machines with the default Restricted policy (Win10/11)
- Add PowerShell availability skip guard to PS selection test (mirrors
  existing bash skip guard)
- Fix no-bash test to keep PowerShell on PATH so the .sh fallback branch
  is actually exercised rather than hitting shell-unavailable early exit

* test: add timeout to spawnSync probes in Windows test skip guards

---------

Co-authored-by: Christopher J Diamond <diamondcj@leidos.com>
2026-06-29 15:54:58 -07:00
SSH._.WORLD 88f6894267
fix(hooks): guard doc-file-warning stdin listeners behind require.main (#2358)
* fix(hooks): guard doc-file-warning stdin listeners behind require.main

doc-file-warning.js registered process.stdin data/end listeners at module
scope while also exporting run(). run-with-flags.js require()s any hook that
exports run() for its in-process fast path, so importing this hook attached
stray stdin listeners to the dispatcher process, corrupting the PreToolUse
stdout JSON contract. This is the exact failure run-with-flags' own SAFETY
comment warns about, and 24 sibling hooks already guard against it.

- Move the stdin entrypoint into main() and gate it behind require.main === module
- pre-write-doc-warn.js now calls main() explicitly instead of relying on the
  import side effect
- Add regression tests: require() attaches no stdin listeners, run()/main()
  stay exported, and the pre-write-doc-warn shim still warns

* docs(hooks): add JSDoc for doc-file-warning main() entrypoint

Satisfies the docstring-coverage pre-merge check; documents the stdin
entrypoint and why it must not run on require().
2026-06-29 15:54:55 -07:00
phobicdotno b1d5d6366d
fix(hooks): quote args when probing Windows .cmd MCP servers via shell (#2343)
On Windows, when a bare-name MCP server command (e.g. codesys-mcp-sp21-plus)
falls back to the .cmd candidate, the probe sets shell:true to work around
Node 18.20+ CVE-2024-27980. However, passing an args array alongside
shell:true causes Node to concatenate the tokens without quoting (DEP0190),
so an arg containing a space (e.g. --codesys-path "C:\Program Files\...") is
re-split by cmd.exe at every space boundary. The child process receives a
truncated path, fails to launch, and the probe declares the server unavailable,
falsely blocking every MCP tool call to that server.

Fix: add a quoteWin() helper that double-quotes any token containing whitespace
or cmd metacharacters. In the useShell branch, build a single properly-quoted
command line string and pass it as the sole argument to spawn() with no separate
args array. The else branch (shell:false, all non-.cmd commands) is unchanged.

Regression test added: on Windows, creates a .cmd shim that echoes its first
positional argument to stderr, probes it with a space-containing path arg, and
asserts the probe succeeds and the arg was not split at the space boundary.

Co-authored-by: Karstein Phobic Nyvold Kvistad <karstein.kvistad@maritimerobotics.com>
2026-06-29 15:54:52 -07:00
Tahiti18 acd078f59e
fix(tests): resolve 10 failing tests on Windows (#2307)
- resolve-formatter: stop findProjectRoot walk before os.homedir() to
  avoid mistaking global dotfiles (e.g. ~/.prettierrc) for a project root
- instinct-cli-projects: detect python3/python binary at runtime; skip
  gracefully when Python 3 is unavailable instead of crashing with null status
- command-registry: regenerate COMMAND-REGISTRY.json (was stale)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 15:54:49 -07:00
Yang Cheng 73895b5d05
fix(release): derive video suite paths from version (#2384)
Co-authored-by: jan <jan@w-saxs001.local>
2026-06-29 15:50:57 -07:00
Yang Cheng 9896644dab
fix(release): derive approval gate paths from version (#2383)
Co-authored-by: jan <jan@w-saxs001.local>
2026-06-29 15:50:55 -07:00
Affaan Mustafa e8244d9ced
feat(control-pane): serve 3D agent-airspace viz + /api/proximity feed (#2320)
Adds the Layer 4 observability view to the control pane: a self-contained,
dependency-free 3D point-cloud of the agent airspace (positions from the
proximity embedding, sized by working set, colored by collision risk, links
for converging pairs) plus an XSS-safe advisory panel that polls every 5s.

- proximity-viz.js: renderProximityVizHtml() (canvas projection, no external JS)
- server.js: GET /proximity (page) + GET /api/proximity (snapshot.proximity feed)
- test: asserts both routes serve and the feed carries positions/links/advisories
2026-06-25 16:45:53 -07:00
Affaan Mustafa 71d22d0a77 feat(layer4): live messages-table wiring for proximity triggers
Finishes the steer/transmit loop — advisories now reach the agents' sessions.

- message-sink.js: createEccMessageSink() delivers via the canonical writer
  'ecc-tui messages send' (maps steer/hold -> conflict kind, transmit -> query),
  resolving the binary from override/env/built target/PATH. Injectable runner;
  best-effort (a missing binary/failed send is counted skipped, never blocks).
- proximity.js: createProximityDispatcher() adds per-trigger cooldown so a
  persistent collision fires once then stays quiet (agents get steered, not
  spammed); runProximityTick() builds the snapshot and dispatches.
- scripts/proximity-tick.js: thin CLI — one-shot, --dry-run, --watch <sec>.
  Messages are internal ECC agent-to-agent coordination, not any external channel.
- 14 new tests (sink argv/kind mapping, cooldown dedup, tick dispatch/dry-run,
  CLI parse). Full suite 2891/2891; lint green.
2026-06-20 20:49:17 -04:00
Affaan Mustafa bd1be0c1ce feat(layer4): line-range channel + trigger firing
- Line precision: parse git diff --unified=0 into per-file changed line ranges
  (defaultWorkingSetFor), so two agents in the SAME file but DIFFERENT functions
  no longer false-collide. Overlap channel now uses the overlap coefficient
  (|A∩B|/min(|A|,|B|)) — high when one edit sits inside the other's region, low
  for disjoint ranges; whole-file edit = 1. Docstring + design doc updated.
- Trigger firing: buildProximityTriggers() turns advisories into the concrete
  messages — transmit-intent to both on a Traffic Advisory, steer-away to the
  yielding agent + a hold notice on a Resolution Advisory. buildProximitySnapshot
  now returns triggers; dispatchProximityTriggers(triggers, {sendMessage}) delivers
  them through an injectable sink (the ECC messages table), best-effort.
- 12 new tests (line-range disjoint vs overlapping, parseDiffRanges, triggers,
  dispatch). Full suite 2881/2881; lint green.
2026-06-20 17:30:52 -04:00
Affaan Mustafa 7df803935a feat(control-pane): wire agent-proximity into the snapshot (opt-in, live)
Turns live sessions into the airspace scan: each worktree session's git diff
becomes its working set, the dependency graph is built over the touched files,
and scanAirspace() produces the TCAS advisories + 3D positions.

- scripts/lib/control-pane/proximity.js: sessionsToAgents() + buildProximitySnapshot();
  default working-set source shells `git diff --name-only <base>...HEAD` per
  worktree (injectable for tests, fails closed to []).
- state.js: opt-in `proximity` field on the snapshot (includeProximity flag) so
  the default hot path stays fast (git diffs only run when requested).
- 4 integration tests (same-file editors -> resolution, later agent steers,
  <2 participants -> no advisories, labels). Full suite 2873/2873; lint green.
2026-06-20 15:46:19 -04:00
Affaan Mustafa 726972d735 feat(layer4): agent-space distance metric + TCAS-style collision avoidance (v0)
The moat layer: spatial deconfliction for multiple agents (and humans) on one
codebase, modeled on aircraft TCAS — measure how close two agents are in
code-space, then transmit-intent (Traffic Advisory) and steer-away (Resolution
Advisory) before they collide at the git layer.

scripts/lib/agent-proximity/:
- distance.js — the math: per-channel collision probabilities combined via
  noisy-OR R = 1 - Π(1 - ω·r). Channels: edit overlap (file + line-range
  Jaccard), dependency coupling (γ^(d-1) over the import graph, direction-
  agnostic — catches 'edit there breaks here' even when tree-distant), and tree
  proximity (LCA-based, soft prior). TCAS advise(): clear / advisory(transmit) /
  resolution(steer), with deterministic right-of-way priority so the maneuver is
  coordinated. closureRate() for approach-speed escalation.
- graph.js — lightweight require/import dependency-graph builder (fs or in-memory).
- index.js — scanAirspace(): pairwise advisories + 3D vector embedding (space-
  filling path embedding pulled toward dependency neighbours) so a 'where are
  the agents' visualization can render the file-cloud and watch agents crawl /
  steer.

docs/design/agent-proximity.md — full mathematical formulation + protocol + viz
+ roadmap (v1 call-graph/symbol channels + live session-diff wiring; v2 cross-
machine airspace over Tailscale, the zero-conflict-swarm demo).

17 tests; full suite 2869/2869; lint green.
2026-06-20 15:40:40 -04:00
Affaan Mustafa ed251f958a style: apply repo formatter to the security-fix files (no behavior change) 2026-06-18 20:03:24 -04:00
Affaan Mustafa bd9083ca1e fix(security): gateguard classifier bypasses (GHSA-4v57) + Windows CI + claw ReDoS
- gateguard (GHSA-4v57-ph3x-gf55): add a quote-aware detection pass that
  dequotes command words and splits on UNQUOTED separators incl. newlines, so
  newline-separated commands, quoted command words ('rm'/"rm"), quoted
  find -exec, and sh/bash -c wrappers are all classified destructive. Additive —
  existing 133 cases still pass; +7 bypass regressions + a false-positive guard
  (rm inside a quoted echo arg stays allowed). 140/140.
- Windows CI: format-code.ts emitted backslash paths via path.normalize, breaking
  forward-slash assertions on all Windows matrix cells — force forward slashes.
- claw.js (CodeQL #1 js/polynomial-redos): bound parseTurns input so the lazy
  [\s\S]*? body can't drive O(n^2) scanning on adversarial history files.

Full suite 2852/2852; lint green.
2026-06-18 20:02:30 -04:00
Affaan Mustafa 5994d3fac1 fix(security): contain install-state file ops to trusted root — RCE fix (GHSA-hfpv-w6mp-5g95)
Critical: project-local install-state (e.g. a cloned repo's .cursor/ecc-install-state.json)
is attacker-controllable, and repair/uninstall/auto-update replayed its operations with
destinationPath validated only for non-emptiness — confirmed arbitrary file write/delete
and chained RCE (write ~/.bashrc, .git/hooks, or run a planted install-apply.js).

- New scripts/lib/path-safety.js: assertWithinTrustedRoot() canonicalizes (incl. symlink
  escape via nearest-existing-ancestor realpath) and fails closed unless the destination is
  within the adapter-derived trusted root.
- install-lifecycle.js: gate executeRepairOperation + executeUninstallOperation + the
  install-state removal against record.targetRoot (the adapter-resolved root, NOT the
  attacker-supplied state.target.root).
- auto-update.js: validateRepoRoot now requires package.json name to be an official ECC
  package, so a planted nested repo can't drive auto-update into executing attacker code.
- 7 containment regression tests. Existing install-lifecycle/repair/uninstall/auto-update
  suites still green (legit destinations are within the root).
2026-06-18 19:54:22 -04:00
Affaan Mustafa 5e4f5533d7 fix(security): discord bot SSRF/log-injection/DoS hardening + bump markdown-it/js-yaml
- ecc-bot.mjs: validate interaction id (snowflake) and token before building the
  callback fetch URL (clears CodeQL js/request-forgery #239/#240/#241); clamp the
  remote heartbeat_interval to [1s,10m] (js/resource-exhaustion #242); strip CR/LF
  from log args (js/log-injection #246).
- Bump transitive dev deps via overrides/resolutions to patch quadratic-complexity
  DoS: markdown-it >=14.2.0 (Dependabot #45/#46), js-yaml >=4.2.0 (#42/#43).
  Both lockfiles regenerated; npm reports 0 vulnerabilities.
2026-06-18 19:50:15 -04:00
Affaan Mustafa a03d63cba0 fix(security): close XSS in control-pane board controls
The interactive claim/move buttons concatenated work-item ids into inline
onclick JS with only single-quote escaping — a crafted id (ids/titles come from
GitHub sync and manual upserts, not a strict allowlist) could break out and
inject script, even on the localhost-only server.

Fix: emit the id/lane in HTML-escaped data-* attributes (escapeHtml encodes
&<>"'), attach delegated click listeners that read them via getAttribute, and
pass the raw value as a JS string arg — never concatenated into code. Adds a
regression assertion that no inline onclick handlers with interpolated ids
remain. Flagged by automated security review.

Full suite 2845/2845; lint green.
2026-06-18 18:25:28 -04:00
Affaan Mustafa 607ab02b1f feat(control-pane): interactive JIT board — claim/move cards from the webapp
The board was read-only; you can now drive the agent+human JIT workflow from the
local control pane.

- New shared scripts/lib/control-pane/work-item-mutations.js (claimWorkItem,
  moveWorkItem) so the CLI and server never diverge; work-items.js claim now
  delegates to it.
- server.js: gated POST /api/work-items/:id/claim and /:id/move (localhost-only,
  honors --read-only with 403). Claim sets owner + assigneeKind and moves to
  running; move retargets the kanban lane.
- ui.js: per-card Claim (on unassigned cards) + lane buttons that POST and
  refresh; 15s live auto-refresh (paused when the tab is hidden).
- Tests: interactive claim/move endpoints, read-only 403, invalid-lane 400, and
  snapshot reflects mutations.

Full suite 2845/2845; lint green.
2026-06-18 18:16:46 -04:00
Affaan Mustafa 7fd4ba95ae feat(work-items): add 'claim' command for JIT work pickup
Closes the agent+human JIT loop the control-pane board surfaces: the board shows
the unassigned (needs-owner) queue; 'claim' lets an agent or human pick up work.

  node scripts/work-items.js claim [<id>] --owner <name> [--as agent|human]

- No id: claims the highest-priority unassigned open item.
- With id: claims that specific item (re-assignable).
- Sets owner, records metadata.assigneeKind (agent|human), and moves the card to
  running so the board reflects that work has started.
- Refuses done items, requires --owner, validates --as. 5 CLI tests added.

Full suite 2844/2844; lint green.
2026-06-18 17:07:24 -04:00
Affaan Mustafa 1efc399ab4 feat(control-pane): add agent+human JIT assignment view to the work-items board
The kanban board tracked lanes (ready/running/blocked/done) but not WHO owns
each card, which is the missing piece for agent+human just-in-time team workflows.

- state.js: classifyAssignee() labels each work item agent | human | unassigned
  (session-linked or agent-pattern owners = agent; named owners = human; ownerless
  = unassigned), with an explicit metadata.assigneeKind override.
- summarizeWorkItems(): adds an assignment summary {agent,human,unassigned} over
  OPEN cards plus a priority-sorted needsAssignment queue — the JIT pickup list.
- ui.js: cards show an [agent]/[human]/[unassigned] badge; the board header shows
  agent/human split and 'N need owner'.
- Tests: assignment classification + JIT queue coverage in control-pane-state.

Full suite 2839/2839; lint green.
2026-06-18 16:59:30 -04:00
Affaan Mustafa b3268fef80 fix: resolve four bug reports (#2290, #2282, #2276, #2272)
- #2290 suggest-compact: honor ECC_CONTEXT_WINDOW_TOKENS / CLAUDE_CODE_AUTO_COMPACT_WINDOW
  so 400k-window models (Opus 4.x) no longer report ~double context usage; add
  override + isolation tests in transcript-context.test.js.
- #2282 install: bare-language syntax is legacy-only by design, but the error
  now distinguishes a supported-but-wrong-mode target (gemini/codex/…) from a
  genuinely unknown one and points to --profile/--modules/--skills.
- #2276 cost-report: the command + cost-tracking skill targeted a SQLite DB no
  tracker writes. Repoint both at the real ~/.claude/metrics/costs.jsonl (JSONL,
  estimated_cost_usd), reduce cumulative-per-session snapshots to latest-per-session,
  and use node instead of sqlite3 for cross-platform support.
- #2272 gateguard: make the 'confirm no existing file' checklist item
  tool-agnostic (Glob/Grep or find/grep via Bash) so hosts without a Glob tool
  don't get a dead tool call.

Full suite 2839/2839; lint green.
2026-06-18 16:49:58 -04:00
kapil971390 3cdc69a0ea
fix(gateguard): check isDestructiveFindExec on each command segment to close compound-command bypass (#2292)
* fix(gateguard): check isDestructiveFindExec on each command segment

`isDestructiveBash` called `isDestructiveFindExec` only on the raw full
command string. When the raw string starts with a non-find command (e.g.
`echo x && find . -exec rm {} \;`), `isDestructiveFindExec` checks
tokens[0] and returns false — then the per-segment loop never calls it
again, letting the destructive `find -exec rm` segment through silently.

Fix: call `isDestructiveFindExec(segment)` inside the per-segment loop so
compound commands (`&&`, `;`, `|`) cannot be used to prepend a harmless
command and bypass the find-exec destructive check.

Adds three regression tests covering `&&`, `;`, and `|` bypass patterns.

* fix(gateguard): use raw body segments for isDestructiveFindExec to close quoted-binary gap

The previous per-segment call passed quote-stripped output from
splitCommandSegments to isDestructiveFindExec, so a quoted exec binary
like find . -exec 'rm' {} \; would arrive as find . -exec  {} \; and
the check would silently miss it.

Switch to splitting collectExecutableBodies output on [;|&]+ without
quote-stripping first, so the find-exec binary name is always intact
when isDestructiveFindExec inspects it. This also covers || and
background & separators that the original tests did not exercise.

Adds a regression test for the || OR-chain bypass pattern.

Addresses Greptile review comments on PR #2292.

---------

Co-authored-by: kapilvus <kapilvus@gmail.com>
2026-06-18 16:30:46 -04:00
Affaan Mustafa 351ccc5a3c docs+chore: add README Security section; fix lint regressions on main
- README: add a visible ## Security section (official sources, vuln reporting via SECURITY.md, GateGuard/IOC/AgentShield guardrails, security guide); make stats line a plain paragraph to clear MD028
- eslint: empty catch comment in run-with-flags.js; drop unneeded escape in github-coordination/parsing.js; remove unused execFileSync import in its test (#2236 follow-ups)
- markdownlint: wrap bare URLs in rules/vue/*.md (#2250 follow-up)

npm run lint green; full suite 2836/2836.
2026-06-16 02:08:14 -04:00
leoeletronics d90d921137
fix: allow additional read-only git introspection commands (#2268) 2026-06-16 01:59:23 -04:00
leoeletronics a6ac0273e2
fix: detect destructive find -exec commands in gateguard (#2267)
* fix: detect destructive find exec commands in gateguard

* chore: ignore aider local files
2026-06-16 01:58:50 -04:00
Affaan Mustafa 6e2544ffa2 chore: reconcile publish/agent surfaces after PR batch
- agent.yaml: register epic-* commands (#2236) and vue-review (#2241)
- package.json files: drop stray skills/ml-adoption-playbook entry (follows orphan-skill publish pattern; not in install-modules.json)
- unicode-safety: strip decorative emoji from dashboard-web.js (#2100) and brand-discovery refs (#2221) to pass the CI gate
- agent-compress: raise catalog token canary 5000 -> 6000 for the 67-agent catalog

Full suite green (2836/2836).
2026-06-15 14:21:28 -04:00
Affaan Mustafa 1c0c780452 Merge pull request #2236 from Victor-Casado/feat/github-native-coordination
feat: add github-native coordination (epic-* commands + scripts + tests). Command registry + catalog reconciled.
2026-06-15 14:08:05 -04:00
Affaan Mustafa 1a08a21ac0 Merge pull request #2241 from itkdm/feat/add-vue-ecosystem
feat: add Vue ecosystem review support (vue-reviewer agent, /vue-review command, vue-patterns skill). Duplicate rules/vue/* kept from #2250; catalog counts reconciled.
2026-06-15 14:07:31 -04:00
mehmet turac 683d291aa3
fix: add plugin cache health check (#2249)
* fix: add plugin cache health check

* fix: harden plugin cache diagnostics

* fix: reject escaping plugin cache refs

* test: remove unused plugin cache fixture
2026-06-15 14:01:25 -04:00
Naomi 48608863ea
feat: add dry-run mode for hook execution (#2116) (#2188)
- Global --dry-run flag and ECC_DRY_RUN=1 env var
- Enriched preview: shows target file path, tool name, and command
- --dry-run stripped from argv so command routing works correctly
- Handles non-JSON and empty stdin gracefully (session/stop hooks)
- 10 tests covering isDryRun(), hook gating, enriched output, CLI routing
2026-06-15 14:01:21 -04:00
Md Ayan d24c7185fc
feat: add web capabilities dashboard (#2100)
* feat: add web capabilities dashboard with agents, skills, commands, MCPs, rules, and hooks

* fix: address code review - XSS, env exposure, port validation, error handling, packaging

* add tests for dashboard
2026-06-15 14:01:16 -04:00
@aaronjmars 1c3280dc0d
fix(security): add host/origin allowlist + validate git refs + quote workflow input (#2185)
Three defense-in-depth fixes around untrusted input flowing to subprocess execution:

1. **Control-pane HTTP server (scripts/lib/control-pane/server.js)**
   The local control-pane API binds to 127.0.0.1 but had no Host or Origin
   validation, so a DNS-rebinding attack from a malicious website could pivot
   into the loopback endpoints — including POST /api/actions/:id, which spawns
   'cargo run -- graph ...' with caller-supplied query strings. Add a hostname
   allowlist (loopback variants plus the explicitly configured --host) and
   reject mismatched Host (421) or non-loopback Origin (403) before any route
   handler runs.

2. **OpenCode git-summary tool (.opencode/tools/git-summary.ts)**
   The tool was building 'git diff ${baseBranch}...HEAD --stat' with execSync
   and a raw model-supplied baseBranch string. Switch run() to execFileSync
   with an args array (no shell), validate baseBranch against a conservative
   git-ref allowlist (rejects shell metacharacters, leading -, embedded ..),
   and clamp the depth arg to a small positive integer before interpolating
   into 'git log --oneline -<N>'.

3. **Reusable test workflow (.github/workflows/reusable-test.yml)**
   The 'Install dependencies' step interpolated ${{ inputs.package-manager }}
   directly into a bash 'case' and into an echo, so a downstream caller that
   forwarded attacker-controllable input could inject into the runner. Move
   the input into a PACKAGE_MANAGER env var and reference $PACKAGE_MANAGER
   inside the script per the GitHub script-injection guidance.

Detected by Aeon + semgrep p/security-audit (host check via threat-model
manual-review axis; git-summary via detect-child-process; workflow via
run-shell-injection).

Verification: node tests/run-all.js — 2686/2687 pre-existing tests pass; the
one failure (observe.sh legacy output fallback) reproduces on main without
this branch applied. Added 2 new control-pane tests covering the allowlist
classifier and the DNS-rebinding-gate behavior end-to-end.

---
Filed by [Aeon](https://github.com/aaronjmars/aeon-aaron).

Co-authored-by: aeonframework <aeon@aaronjmars.com>
2026-06-15 13:49:40 -04:00
daiki75 e3f18d2376
fix: prevent IOC scanner false positives on hook filenames and scan .cursor configs (#2245)
* fix: prevent IOC scanner false positives on hook filenames and scan .cursor configs

The supply-chain IOC scanner matched CRITICAL_TEXT_INDICATORS with plain
substring search, so legitimate hook filenames that merely end with a known
payload name (e.g. the stock Cursor hook before-shell-execution.js vs the
payload execution.js) were flagged as CRITICAL. Indicator matching now
requires a non-filename character before the match.

Also add .cursor/ to the special config paths so Cursor hooks.json files
(a known persistence vector already listed in PERSISTENCE_FILENAMES) are
actually inspected in normal checkouts - previously they were only scanned
by accident when the repo path happened to contain /.claude/.

* test: cover underscore-prefixed filenames in IOC boundary suppression

Make explicit that '_' is treated as a filename word character, so
snake_case hook names like post_execution.js are intentionally not
flagged by the execution.js indicator (real payload references appear
after '/', quotes, or whitespace).
2026-06-15 13:48:50 -04:00
fiedler-itlabs d293941643
fix(hooks): stop pre/post Bash dispatcher from echoing the input event (#2240)
runHooks() returned the unmodified raw stdin (the PreToolUse/PostToolUse
input event) on stdout whenever no sub-hook produced additionalContext.
Claude Code parses a hook's stdout as JSON and validates it against the
hook-output schema, so echoing the input object
({session_id, hook_event_name, tool_name, tool_input, ...}) fails with
"Hook JSON output validation failed — (root): Invalid input" on nearly
every Bash command.

Track whether a sub-hook deliberately set stdout (string / {stdout}, e.g.
GateGuard) via a rawModified flag and emit '' in the pass-through case
instead of the echoed input. Preserves GateGuard pass-through and
block-no-verify's exit-2 blocking.

Update the three dispatcher tests that codified the buggy echo behavior to
expect empty stdout, and add a regression test for a plain pass-through
command.

Fixes #2239

Co-authored-by: WOZCODE <contact@withwoz.com>
2026-06-15 13:48:46 -04:00
Bujidao b5c088d639 fix: address Vue review PR feedback 2026-06-12 19:44:39 +08:00
Affaan Mustafa 7777656bf5
fix: context-size /compact trigger, Codex marketplace plugin path, live README badges (#2237)
- suggest-compact hook now reads the latest usage record from the session
  transcript and suggests /compact at a window-scaled token threshold
  (160k/200k window, 250k/1M window; COMPACT_CONTEXT_THRESHOLD and
  COMPACT_CONTEXT_INTERVAL overridable), re-firing per 60k-token growth
  bucket; tool-call count stays as the secondary signal (#2155)
- Codex repo marketplace now points at ./plugins/ecc instead of ./ — Codex
  never discovers plugins whose local marketplace source.path is the
  marketplace root (verified on Codex CLI 0.137.0); plugins/ecc is a thin
  folder referencing root skills/.mcp.json per maintainer direction on
  #2097; docs flag plugin mode as experimental with the upstream blocker
  openai/codex#26037 linked (#2128)
- README badges for installs/stars/forks now use shields endpoint badges
  backed by api.ecc.tools (live install count 3,712 vs the stale static
  150), which also eliminates shields' 'Unable to select next GitHub token
  from pool' render in the stars badge

Closes #2155
Closes #2128
2026-06-11 16:21:53 -04:00
Victor Casado af0cf0d7c8 fix: guard upsertCoordinationWorkItem behind dryRun check in applySync
The store write was unconditional, persisting work items even during dry
runs. Move it inside the !dryRun block alongside editIssue and initialize
snapshot to null beforehand so results.push still receives snapshot: null
for dry runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 15:06:34 -04:00
Victor Casado 573ebe0918 fix: enforce policy.review.required gate in applyPublish
applyPublish was forcing review='approved' for any state that wasn't
'changes-requested', bypassing policy.review.required entirely. Add a
guard that throws before buildIssueStateFromAction when review approval
is required but not yet granted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 15:00:34 -04:00
Victor Casado 273b82c8ba fix: address code-review findings in github-coordination actions
- Remove circular validation-status check in applyValidate that prevented
  fresh claims (validation='pending') from ever reaching 'passed'
- Add staleCoordinationLabels helper to compute coordination:* labels to
  remove on state transitions; replaces hardcoded removeLabels:[] across
  all six editIssue call sites
- Fix duplicate label writes in applySync: syncIssueLabels already calls
  editIssue for labels, so the follow-up editIssue now only updates body
- Skip acquireLock finding: store.acquireLock does not exist; comment
  updated to explain why the fix was not applied

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:54:06 -04:00
Victor Casado 33f2219307 fix: address second round of code-review findings
actions.js:
- Add assertValidRepo/assertValidIssueNumber guards at the top of all
  action handlers (applyClaim, applySync, applyValidate, applyPublish,
  applyReview, applyDecompose, applyUnblock) for fast-fail validation
- applyValidate: fix status transition — set 'validated' unconditionally
  when ok=true instead of preserving 'blocked' (was inconsistent with
  projectState becoming 'ready')

gh-api.js:
- runGh: preserve GITHUB_TOKEN by default; only delete when caller
  explicitly sets options.stripGithubToken=true (was deleting by
  default, breaking CI)

parsing.js:
- extractCoordinationState: throw SyntaxError on malformed JSON instead
  of silently returning null — lets callers distinguish bad JSON from
  absent marker
- normalizeBodyForComparison: fix regex to match JSON-quoted form
  "lastSyncAt": ... instead of bare lastSyncAt: ...

policy.js:
- loadPolicy: validate that parsed JSON is a plain object before
  spreading; coerce nested fields (labels, review, validation,
  branchModel, project, fieldNames) to objects before merging

state.js:
- assertIssueClaimable: block re-claim on status alone (not status AND
  owner) to prevent {status:'claimed', owner:null} bypass; use
  state.owner || 'unknown' in error message
- getCoordinationState: catch SyntaxError from extractCoordinationState,
  log warning to stderr, fall back to default state

tests/lib:
- Update malformed-JSON test to expect SyntaxError throw instead of null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:25:58 -04:00
Victor Casado d4486a7a29 refactor: apply code-review findings to github-native coordination
scripts/github-coordination.js:
- parseArgs: replace 13-entry if/else chain with BOOL_FLAGS/VALUE_FLAGS
  lookup maps; shrinks from 119 to ~45 lines
- Extract dispatchCommand(options, ctx) and formatOutput(payload, options)
  from main(); main() shrinks to ~20 lines

scripts/lib/github-coordination.js:
- Split 1041-line monolith into 6 focused sub-modules under
  scripts/lib/github-coordination/ (policy, parsing, gh-api, state,
  actions, store); index becomes a thin re-export (~55 lines)
- Document ECC_GH_SHIM trust boundary in runGh() (gh-api.js)
- Document applyClaim() read→check→write race condition (actions.js)

tests/lib/github-coordination.test.js:
- Refactor runTests() to data-driven DESCRIPTORS array + runGroup()
  helper; runTests() shrinks to ~10 lines
- Add 5 new edge-case tests: normalizeRepo('') and normalizeRepo('   ')
  throw, desiredLabelsForState for blocked/ready statuses, and
  buildIssueStateFromAction for validate action (15 → 20 tests)

tests/scripts/github-coordination.test.js:
- Replace console.log in test runner with process.stdout.write

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:05:42 -04:00
Victor Casado 64470f4307 feat: add github-native coordination (epic-* commands + scripts + tests)
Adds a GitHub-native coordination layer on top of ECC:

Commands (7 new slash commands):
- epic-claim, epic-sync, epic-validate, epic-publish
- epic-review, epic-unblock, epic-decompose

Scripts:
- scripts/github-coordination.js  — CLI entry point
- scripts/lib/github-coordination.js  — core library (state machine, gh API wrappers)
- scripts/status.js  — coordination status reporter

Config:
- config/github-native-coordination.json  — labels, review policy, validation gates

Tests:
- tests/lib/github-coordination.test.js  — 15 unit tests for pure functions
- tests/scripts/github-coordination.test.js  — integration/CLI test suite

Registry:
- docs/COMMAND-REGISTRY.json  — adds 7 epic-* entries, totalCommands 84 → 91

No encoding changes, no prp-* modifications, no Windows shims.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 12:58:11 -04:00
Affaan Mustafa 6319c7d309
fix: stability batch — hook stdin truncation, Codex exa TOML, Stop hook JSON, GateGuard repetition (#2227)
* fix(hooks): fail open on oversized stdin instead of echoing truncated JSON (#2222)

run-with-flags.js capped stdin at 1MB but every fallthrough path still
echoed the truncated string to stdout. The harness parses hook stdout as
JSON, got a document cut mid-stream, and blocked the tool call — so any
Edit/Write with a >1MB hook payload was permanently blocked by every
registered pre-write hook, before ECC_HOOK_PROFILE / ECC_DISABLED_HOOKS
gating could run.

- Exit 0 with empty stdout (no opinion) when the stdin cap trips, before
  any echo or gating logic.
- Flush stdout via write callback before process.exit: exiting right
  after stdout.write() dropped everything past the ~64KB pipe buffer,
  cutting even sub-cap pass-through payloads mid-JSON.

Regression tests cover the enabled, disabled, and missing-arg paths for
oversized payloads plus full echo of sub-cap >64KB payloads.

* fix(codex): stop emitting invalid exa url entry, align merge with connector policy (#2224)

The Codex MCP merge declared exa with a url key, but Codex's
[mcp_servers.*] TOML schema is stdio-only — the url key makes the
entire config.toml fail to load, bricking both the codex CLI and the
desktop app. Every install/update re-injected the line because the
urlEntry branch treated the broken entry as present.

- ECC_SERVERS now emits only the current default set per
  docs/MCP-CONNECTOR-POLICY.md: chrome-devtools (stdio, command/args).
  Retired servers (supabase, playwright, context7, exa, github, memory,
  sequential-thinking) are never re-emitted; existing user-managed
  entries are untouched.
- The merge now repairs the exact ECC-emitted broken form (url-only
  exa entry) on every run so re-running the installer fixes broken
  configs instead of preserving them. User stdio exa entries
  (command + mcp-remote) are left alone.
- check-codex-global-state.sh requires chrome-devtools instead of the
  retired set, and flags url-only exa entries with a repair hint.

Tests cover repair, re-run idempotence, stdio-entry preservation, and
no-retired-server emission in add, update, dry-run, and disabled modes.

* fix(hooks): never echo truncated stdin from Stop hooks (#2090)

Stop hooks follow the ECC pass-through convention (echo stdin on
stdout), but every echoing Stop hook capped stdin and echoed the capped
string. The Stop payload carries last_assistant_message, so a long
final assistant message produced a JSON document cut mid-stream on
stdout, which the harness reports as 'Stop hook error: JSON validation
failed' across the whole Stop chain.

Reproduced: a Stop payload with a >64KB last_assistant_message run
through run-with-flags + cost-tracker emitted exactly 65536 bytes of
invalid JSON (cost-tracker capped stdin at 64KB — far below realistic
Stop payloads).

- cost-tracker: raise the cap to 1MB (matching all other hooks) and
  suppress the pass-through echo when stdin was truncated.
- check-console-log, stop-format-typecheck, desktop-notify: suppress
  the echo when stdin was truncated; flush stdout before process.exit
  so sub-cap payloads are not cut at the ~64KB pipe buffer.
- All hooks keep exiting 0 (fail-open); diagnostics go to stderr.

New stop-hooks-stdout test asserts the contract for every registered
Stop hook: stdout is empty or valid JSON, exit code 0 — for realistic
100KB payloads and oversized >1MB payloads, via the production runner
and via direct invocation. Updated the old hooks.test.js case that
codified the truncated-echo behavior.

* fix(hooks): dampen GateGuard fact-force repetition in long sessions (#2142)

In long autonomous sessions the fact-force gate produced 10+
near-identical 'state facts -> blocked -> restate -> retry' blocks in
one context window, which measurably raises the odds of the model
collapsing into a degenerate single-token repetition loop.

- Track a per-session fact_force_denials counter in GateGuard state
  (merged max across concurrent writers, reset with the session, robust
  to malformed on-disk values).
- The first GATEGUARD_FACT_FORCE_FULL_DENIALS denials (default 3) keep
  the full four-fact block; later denials emit a condensed single-line
  message that carries the denial ordinal, so consecutive denials are
  structurally different and never textually identical.
- True retries of the same target remain allowed without re-prompting
  (unchanged). Destructive-Bash and routine-Bash gates are unchanged,
  as are the ECC_GATEGUARD=off / ECC_DISABLED_HOOKS escape hatches.

Eight new tests cover budget counting, condensed format, ordinal
advancement, retry pass-through, env tuning, malformed state, MultiEdit
dampening, and destructive-gate exemption.

* fix(hooks): keep security hooks able to block on oversized stdin (#2222)

Refine the truncation fail-open: instead of skipping the hook entirely,
the runner now suppresses only its own raw-echo when stdin was
truncated. The hook still executes and receives the truncated flag
(run() context / ECC_HOOK_INPUT_TRUNCATED), so config-protection keeps
blocking truncated protected-config payloads (its test requires exit 2)
while pass-through hooks fail open with empty stdout as before.

* style: apply repo formatter to touched hook files
2026-06-11 00:31:33 -04:00
ECC Test 29edd57708 release: 2.0.0 — the agent harness operating system
Graduate 2.0.0-rc.1 to stable. Bump version across package, plugin,
marketplace, OpenCode, agent metadata, VERSION, and all localized docs.
Add 2.0.0 release notes + README sections (en/zh/pt-BR/tr), CHANGELOG
entry, and the ECC community Discord bot (dependency-free gateway client
+ guild command registrar). Update copilot-support and release-surface
tests for the sponsored-review migration and the 2.0.0 surface.
2026-06-09 21:40:40 -04:00
Affaan Mustafa edebcc89ef
feat(discord): release -> #announcements auto-post + pin + GitHub Discussions (#2201)
On a published GitHub release, post the notes to the ECC Discord
#announcements channel (via bot), pin it, and cross-post to GitHub
Discussions (Announcements category). Release data flows through env vars
(no shell interpolation of untrusted input). Secrets: DISCORD_BOT_TOKEN,
DISCORD_ANNOUNCE_CHANNEL_ID (repo secrets), GITHUB_TOKEN.

Ties the 2.0.0/1.11.0 official release to the community launch.

Co-authored-by: ECC Test <ecc@example.test>
2026-06-08 22:38:03 -04:00
Affaan Mustafa e755c5f72b
fix: make plugin hooks run on Node 21+ and green the suite under modern Node (#2184)
ROOT CAUSE: hooks load plugin-hook-bootstrap.js via
`node -e "...; process.argv.splice(1,0,s); require(s)"`. On Node 21+,
require.main is `undefined` under --eval, so the `if (require.main === module)`
guard was false and main() never ran — every plugin hook silently no-op'd
(e.g. the MCP-health PreToolUse hook stopped blocking). CI (Node 18/20) hid
this; it only surfaces on Node 21+. Fix: also run main() when require.main is
undefined (the eval-bootstrap case), while staying dormant on real imports.

Also clears pre-existing main debt the full local suite enforces:
- catalog:sync — README/docs agent+skill counts drifted after recent merges
- tests/ci/supply-chain-watch-workflow: update checkout SHA to the merged v6.0.3 (#2183)
- markdownlint + check-unicode-safety --write across docs/skills

Suite: 2683/2683 green under Node v25; lint + unicode clean.

Co-authored-by: ECC Test <ecc@example.test>
2026-06-07 16:05:28 +08:00
Tom Cruise Missile 6a40469408
feat: Cursor-independent ECC memory via ECC_AGENT_DATA_HOME (#2066)
* feat: auto-isolate ECC memory data for Cursor via ECC_AGENT_DATA_HOME

Add ECC_AGENT_DATA_HOME (defaults to ~/.claude) with Cursor-aware resolution,
sessionStart env injection, install scaffolds, and hook bootstrap so memory
hooks do not collide with Claude Code when both harnesses are used.

Closes #2065

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: log agent-data config errors and ship cursor sessionStart deps

Address CodeRabbit review: log invalid .cursor/ecc-agent-data.json parse
failures, and copy cursor-session-env.js plus lib deps on legacy Cursor
install so sessionStart hook path exists without hooks-runtime alone.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: resolve relative agentDataHome paths from project root

Project config values like ".ecc-data" now resolve against the
repository root (parent of .cursor/), not process.cwd(), so Cursor
hooks persist memory in the intended directory regardless of hook cwd.

Addresses cubic review on PR #2066.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: explain getHomeDir duplicate and docstring policy

Document why agent-data-home keeps a local home-dir helper (circular
require with utils.js) and list consolidation options for maintainers.
Note that CodeRabbit JSDoc coverage warnings are informational relative
to ECC's usual script documentation style.

Addresses cubic P2 context on PR #2066.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: isolate agent-data-home tests from dogfooded .cursor config

Use isolated temp cwd for default-resolution cases and assert
resolveAgentDataHome({ projectDir }) reads ecc-agent-data.json.
Document cwd/project caveats in the test file header.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 13:27:00 +08:00
Farzul Nizam Zolkifli 1e5fa96d75
fix(context-monitor): make cost warnings informational, not commands (#2091)
The PostToolUse cost warnings emit imperative text via additionalContext
("Stop and inform the user...", "Review whether...", "Consider whether...").
Subagents read additionalContext as an instruction and obey the "Stop",
abandoning their task and returning a prompt-for-direction instead of their
result — derailing multi-agent workflows. The main loop is also nudged to
halt mid-task.

Reword all three severities to pure-informational data: keep the
CRITICAL/WARNING/NOTICE label + the dollar figure (and the threshold), drop
the imperative sentence, and state plainly it is informational. No logic,
severity, or threshold change. Existing tests pass (they assert the labels +
severities, which are preserved).

Before: `COST CRITICAL: Session cost is $X. Stop and inform the user about high cost before continuing.`
After:  `COST CRITICAL: session total ~$X (over $50). Informational only — not an instruction to stop.`

Co-authored-by: OrenG Tools <tools@orengacademy.com>
2026-06-07 13:26:48 +08:00
satoshi-takano-bloom 80c63c88f0
feat(desktop-notify): route OSC 9 notifications through Ghostty (#2114)
Ghostty natively supports the OSC 9 desktop-notification escape
(ESC ] 9 ; <message> BEL), the same sequence already used for iTerm2.
Previously only TERM_PROGRAM === 'iTerm.app' took the escape path, so
Ghostty users fell through to the osascript path. That makes Script
Editor the notification owner, and clicking the notification just
launches Script Editor instead of focusing the terminal.

Adding 'ghostty' to the OSC 9-capable check makes Ghostty the owner,
so clicking the notification focuses the Ghostty window/tab where
Claude Code is running. Verified on Ghostty (TERM_PROGRAM=ghostty).

Co-authored-by: 高野智史 <satoshitakano@takanosatoshinoMacBook-Pro-522.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:26:32 +08:00
AHNINE Amine 4197ea545f
fix(hooks): stop false loop warnings and repeated identical context warnings (#2121)
* fix(hooks): stop false loop warnings and repeated identical context warnings

Two PostToolUse monitor defects surfaced during a long single-turn session:

1. ecc-metrics-bridge hashToolCall fingerprinted Edit/Write/MultiEdit on
   file_path ONLY, so several distinct edits to the same file produced the
   same hash and tripped the loop detector ("stuck loop") even though every
   edit was different. Now the hash includes the edit content
   (old_string/new_string/content/edits) so distinct edits to one file hash
   differently; identical edits still collide as intended.

2. ecc-context-monitor re-emitted the SAME warning every DEBOUNCE_CALLS (5)
   tool calls even when nothing changed. Because the cost figure only refreshes
   at Stop (turn) boundaries, a single stale value printed the identical
   warning ~20 times within one turn. Dedupe on message content instead: a
   warning surfaces only when its text changes (cost moved, new file count, new
   loop) or on first escalation to critical, and is otherwise suppressed.

Adds regression tests for the same-file/different-content hash case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks): address CodeRabbit review (#2121)

- ecc-context-monitor: clear dedupe state when warnings resolve, so the same
  warning text recurring in a later turn (context dips/recovers/dips, a loop
  that stops then restarts) is surfaced again instead of suppressed as a
  duplicate. Guarded so the no-warning hot path stays write-free.
- ecc-metrics-bridge: hash the FULL serialized edit payload and truncate the
  digest, not the input. Slicing the serialized string to HASH_INPUT_LIMIT
  first could collapse large edits sharing their first 2048 chars, reviving the
  false-loop collision for big Write/edit payloads.
- Add regression test for >2048-char edit divergence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:26:30 +08:00
Matt Van Horn 9adaa88999
fix: normalize POSIX CLAUDE_PLUGIN_ROOT to Windows path in hook bootstrap (#2139)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-07 13:26:17 +08:00
Matt Van Horn 80233f1b72
fix: shrink default OpenCode install surface and gate hooks-runtime (#2140)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-06-07 13:26:14 +08:00
Adilan Akhramovich 53d4b7d664
Fix claude-project target for legacy language installs (#2147) 2026-06-07 13:26:11 +08:00
bymle 0cb8907e14
fix(gateguard): gate force/path git checkout as destructive (#2158)
* fix(gateguard): gate force/path git checkout as destructive

The destructive-command gate's `checkout` handler only flagged
`git checkout -- <path>`. It missed `git checkout --force` / `-f <branch>`
and `git checkout .`, all of which discard uncommitted working-tree changes,
so they bypassed the gate (once the once-per-session routine-Bash gate is
satisfied, they ran with no challenge). The sibling `switch` handler already
covers these force forms; mirror it for `checkout`.

* test(gateguard): document Test 7b force-checkout case

---------

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-07 13:26:08 +08:00
skausage-ops 3e671ff848
fix: send claude prompt via stdin so Windows shell mode does not mangle it (#2174)
askClaude() passed the full multi-line prompt as a claude

Fix: keep only the short, safe flags (--model, -p) as args and send the prompt over stdin via spawnSync input. The prompt never touches the shell command line, so multi-line/special-char prompts arrive intact. claude -p reads stdin on macOS/Linux too, so behavior is unchanged there.

Verified on Windows 11 (Node 24, claude CLI via npm): real turns now return correct responses, and node tests/scripts/claw.test.js passes 19/19.

Co-authored-by: skausage-ops <268783127+skausage-ops@users.noreply.github.com>
2026-06-07 13:25:48 +08:00
bymle 7883da658b
fix(dev-server-block): stop blocking dev-<suffix> scripts (#2179)
`DEV_PATTERN`'s trailing `\b` treats a hyphen as a word boundary, so
`dev\b` matched the `dev` prefix of distinct npm scripts like
`dev-setup` / `dev-docs` / `dev-build` and blocked them with exit 2.
Replace the trailing `\b` with `(?![\w-])` so the dev server still
matches (`dev`, `dev;`, `dev:ssr`) but `dev-<suffix>` scripts pass.

Adds regression tests for dev-setup/dev-docs/dev-build (allowed) and
dev:ssr (still blocked).

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-07 13:25:39 +08:00
bymle e7e38cd508
fix(session-end): preserve $-sequences in user messages when rewriting summary (#2180)
The regenerated summary block embeds raw user-message text and was passed
as the *replacement* argument to String.prototype.replace, where $-sequences
($&, $$, $`, $') are special. A user message containing $& re-injected the
entire matched block (duplicating the summary markers) and $$ collapsed to $,
silently corrupting the persisted session summary. buildSummarySection only
escapes newlines and backticks, not $.

Fix: use function replacers (() => summaryBlock) at both rewrite sites so the
replacement text is treated literally. Adds an end-to-end regression test.

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-07 13:25:36 +08:00
bymle 9c35aef60f
fix(project-detect): match packageKeys on boundaries, not substrings (#2181)
Framework detection matched a dependency against a framework's packageKeys
with unbounded substring containment (dep.includes(key)), so any dependency
whose name merely contained a key was misclassified: `preact` and even
`reactive` were both detected as `react`.

Match only when the dependency equals the key, or the key is a prefix
immediately followed by a delimiter (/ . _ -). This still matches every real
case (react-dom, @remix-run/node, spring-boot-starter, org.springframework.boot,
github.com/labstack/echo/v4, phoenix_live_view) while excluding preact/reactive
(and incidentally nextra). Adds regression tests.

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
2026-06-07 13:25:34 +08:00
Gaurav Dubey 8dc43e5f60
fix(session-start): support ECC_SESSION_RETENTION_DAYS opt-out + document env var (#2151) (#2163)
* fix(session-start): support ECC_SESSION_RETENTION_DAYS opt-out + document env var

The retention pass for *-session.tmp files (issue #2151) landed previously,
but the env var that controls it was undocumented in the README and rejected
falsy values (0, off, disabled), silently falling back to the 30-day default.
Users who want to keep all sessions for forensic or research workflows had no
way to opt out.

This patch:

- Extends getSessionRetentionDays() so 0|off|false|disabled|never|none disables
  pruning entirely (returns null sentinel; default behavior unchanged).
- Updates the call site in main() to skip pruneExpiredSessions when retention
  is null and emits a clear "[SessionStart] Pruning disabled via
  ECC_SESSION_RETENTION_DAYS" log line so the operator can tell pruning is off.
- Documents ECC_SESSION_RETENTION_DAYS in the README "Hook Runtime Controls"
  section alongside the other ECC_SESSION_* knobs.
- Adds three regression tests in tests/hooks/hooks.test.js covering opt-out
  via 0, opt-out via off, and garbage-value fallback to default 30.

Verification:
- node tests/hooks/hooks.test.js  — 240/240 green (incl. 3 new retention tests)
- node tests/run-all.js           — 2622/2622 green
- npx eslint scripts/hooks/session-start.js tests/hooks/hooks.test.js — clean
- node scripts/ci/validate-no-personal-paths.js — clean
- node scripts/ci/check-unicode-safety.js       — clean
- node scripts/ci/validate-hooks.js — 28 matchers validated
- node scripts/ci/validate-rules.js — 115 files validated

Fixes #2151

* docs(readme): list all ECC_SESSION_RETENTION_DAYS opt-out values + add Windows example

Address reviewer feedback on PR #2163:
- CodeRabbit and cubic both flagged that the README docs only listed 3 of 6
  opt-out values accepted by getSessionRetentionDays() (0, off, disabled),
  while the implementation also accepts false, never, none.
- cubic also flagged the missing Windows PowerShell example for the new
  variable, breaking the parallel structure of the existing
  ECC_CONTEXT_MONITOR_COST_WARNINGS example block.

Updated the README to:
- Spell out all six opt-out values (0, off, false, disabled, never, none)
  and clarify they "keep all sessions (disable pruning)".
- Add an ECC_SESSION_RETENTION_DAYS line to the Windows PowerShell example.

No behavior change. README only.

Verification:
- npx markdownlint README.md — clean
- npx eslint scripts/hooks/session-start.js tests/hooks/hooks.test.js — clean
2026-06-07 13:01:33 +08:00
Gaurav Dubey 4afdb90800
feat(gateguard): add env knobs for routine bash gate + extra destructive patterns (#2161)
* feat(gateguard): add env knobs for routine bash gate + extra destructive patterns

The JS port of gateguard-fact-force has two bash gates: a destructive
gate (rm -rf, drop table, git push --force, etc.) that operators want
to keep, and a once-per-session routine gate that fires on the very
first bash invocation regardless of intent. Operators on hosts where
the routine gate is friction without signal (Cursor, OpenCode, etc.)
have been maintaining local patches that get clobbered on every plugin
update; the Python upstream gateguard-ai already exposes equivalent
config via .gateguard.yml.

Adds two env vars, both off-by-default so existing behavior is
preserved:

- GATEGUARD_BASH_ROUTINE_DISABLED — truthy values (1, true, on, yes,
  enabled) skip the routine bash gate. Destructive gate is unaffected.
- GATEGUARD_BASH_EXTRA_DESTRUCTIVE — regex source string for additional
  destructive patterns. Matches against the same quote-stripped,
  subshell-flattened command the built-in DESTRUCTIVE_SQL_DD regex sees,
  so a custom phrase inside $(...) or backticks is also caught. A
  malformed regex is logged once to stderr and treated as not configured
  rather than crashing the hook (hooks must never block tool execution
  unexpectedly).

Twelve new tests pin both env vars (truthy aliases, falsy values, unset
baseline, destructive-gate-still-fires, alternation members, malformed
regex degrades safely, custom phrase inside command substitution).
Existing 2619/2619 tests still pass; eslint clean.

Fixes #2078

* fix(gateguard): reset extra-destructive warn-once gate when env value changes

Both reviewers (CodeRabbit + cubic) flagged that
extraDestructiveWarnLogged was never reset when GATEGUARD_BASH_EXTRA_DESTRUCTIVE
flipped from one invalid regex to a different invalid regex. The
sticky boolean meant a long-running process saw bad-pattern-a's
warning then silently swallowed bad-pattern-b's parse failure.

Fix: clear extraDestructiveWarnLogged whenever the cache key changes
(i.e. before the regex compile attempt). The warn-once-per-distinct-
pattern invariant now matches the per-key cache invariant.

Adds a same-process regression test via loadDirectHook() that spies on
process.stderr.write and asserts: same bad pattern warns once across
multiple invocations; switching to a different bad pattern emits a
second warning; switching to a valid regex emits zero warnings.
2026-06-07 13:01:30 +08:00
Gaurav Dubey a08445ad78
fix(suggest-compact): clean up old counter temp files (#2159)
* fix(suggest-compact): clean up old counter temp files

claude-tool-count-<sessionId> files were written into the OS temp dir
on every hook run and never removed, accumulating one orphan per
session indefinitely.

Sweep stale counter files at the top of main() before opening the
active counter. Retention is env-tunable via COMPACT_STATE_TTL_DAYS
(default 14 days); invalid values fall back to the default. The
active session's counter file is preserved unconditionally even if
its mtime is past the cutoff. Failures during the sweep are swallowed
to preserve the always-exit-0 hook contract.

Adds 7 regression tests covering the sweep, env-var validation, and
the always-exit-0 invariant under a populated temp dir.

Fixes #2156

* fix(suggest-compact): preserve counter files at the TTL cutoff boundary

The cleanup sweep used `mtimeMs > cutoffMs` to short-circuit, which
matched files whose mtime sits exactly on the cutoff boundary and
deleted them. The cleanupOldCounters docstring promises only files
*older than* retentionDays are removed; a file at age == retentionDays
is not older than retentionDays, so it must survive.

Switch the comparison to `>=` so only strictly older files fall
through to deletion. Add a regression test that pins boundary-aged
files (mtimeMs sitting just past the projected cutoff) are preserved.

Refs #2156
2026-06-07 13:01:27 +08:00
Chris Yau 898fd231ce
fix: guard two script edge cases (tolerant package.json parse, set -u empty array) (#2088)
* fix: guard two script edge cases

- scripts/harness-audit.js: getRepoChecks() parsed package.json with raw
  JSON.parse(readText(...)), while the rest of the file (lines 218, 822)
  uses the tolerant safeParseJson(safeRead(...)). In repo target mode a
  project lacking package.json — or with malformed JSON — threw an uncaught
  exception and crashed the audit instead of degrading. Match the existing
  convention so the audit tolerates a missing/invalid package.json.

- skills/frontend-slides/scripts/export-pdf.sh: `set -- "${POSITIONAL[@]}"`
  expands an empty array under `set -u` on bash 3.2 (the macOS system bash),
  aborting with "POSITIONAL[@]: unbound variable" instead of printing the
  usage message when invoked with no positional args. Guard the expansion
  with ${POSITIONAL[@]+"${POSITIONAL[@]}"} (no-op safe under bash 3.2 set -u).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: null-safe package.json access in getRepoChecks

Review follow-up (CodeRabbit + cubic): switching to safeParseJson at line
389 means packageJson can be null on a missing/malformed package.json, but
the quality-ci-validations check dereferenced packageJson.scripts before the
optional chaining could help — throwing TypeError instead of degrading.
Guard the base object with packageJson?.scripts?.test at the access site,
matching the file's existing convention (e.g. line 220 uses packageJson?.name).

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
2026-06-07 13:01:21 +08:00
Affaan Mustafa c8caf193c4
feat: worktree-lifecycle service (deterministic conflict prediction + safe GC) (#2164)
* feat: add worktree-lifecycle service (ecc.worktree-lifecycle.v1)

The "unowned moat" from the orchestrator landscape research: no existing
tool ships deterministic merge-conflict prediction or a safe worktree GC.

- scripts/lib/worktree-lifecycle/git.js: injectable, hermetic git layer.
  Predicts merge conflicts WITHOUT touching the working tree via
  `git merge-tree`. Strips inherited GIT_* env so it is safe inside hooks.
- scripts/lib/worktree-lifecycle/lifecycle.js: deterministic state machine
  (main/dirty/conflict/merge-ready/merged/stale/idle) + planCleanup that
  buckets worktrees into remove / salvage / keep. Only fully-merged trees
  are auto-removable; stale (unmerged+inactive) => salvage, never deleted.
- scripts/worktree-lifecycle.js: CLI (--json/--conflicts/--stale/
  --cleanup-plan/--base/--stale-days/--repo).
- tests/lib/worktree-lifecycle.test.js: 11 tests (fake-git + real-git).

Safety model mirrors the reference-arch salvage rule, validated by the
2026-06-05 MacBook->Mac Mini consolidation. Tests: 11/0.

* fix: hermetic git env in session adapters + mcp-inventory lint

- session adapters (codex-worktree, opencode): resolveGitBranch stripped
  no git env, so the "outside a repo" path returned the host branch when
  run inside a git hook (GIT_DIR set). Strip GIT_* before rev-parse.
- mcp-inventory: fix eslint no-unused-vars (signatures) and a stale
  eslint-disable directive in the merged code.

* test: run each test with inherited git env stripped (hermetic runner)

When the suite runs inside a git hook (pre-push), git sets GIT_DIR/
GIT_WORK_TREE, which hijack 'git -C <dir>' calls in tests that exercise
real git, making them operate on the host repo. Strip GIT_* before
spawning each test so the suite is isolated from ambient git state.

---------

Co-authored-by: ECC Test <ecc@example.test>
2026-06-07 13:00:08 +08:00
Affaan Mustafa 7113b5bf63
feat: MCP inventory (ecc.mcp.v1) — unified cross-harness MCP config view (#2146)
* feat: add MCP inventory (ecc.mcp.v1) across harnesses

Read-only MCP-gateway groundwork: discover MCP server configs across
every installed harness, normalize to a canonical ecc.mcp.v1 inventory,
redact secrets, and report which servers are configured in 2+ harnesses
(the configure-N-times pain). The read+dedup side of a unified gateway,
mirroring how the session-adapter layer started read-only.

Readers (per-harness config formats):
- claude-code: ~/.claude.json mcpServers + project .mcp.json
- codex: ~/.codex/config.toml [mcp_servers.*] TOML via @iarna/toml
- opencode: ~/.config/opencode/opencode.json mcp block (command ARRAY)

canonical-mcp.js:
- normalize transport labels (local=>stdio, remote=>http) to stdio/http/sse
- merge servers by name across harnesses; flag DRIFT when signatures differ
- fragmentation report + aggregates
- SECRET REDACTION: env values stripped to key names; secrets in args
  (--modelApiKey sk-ant-...), inline --flag=secret, and URL userinfo/token
  query params all redacted before storage AND before the dedup signature.

scripts/mcp-inventory.js: CLI (--json, --fragmented, --help).
tests/lib/mcp-inventory.test.js: 12 tests incl. a regression for the
real arg-carried-secret leak found while smoke-testing on live configs.

Tests: 12/0. Real-data smoke: 33 servers across 3 harnesses, 21
configured in 2+ harnesses (7 drift); secret-leak audit clean.

* test: cover reader error paths, collect skip-logic, and CLI main() for mcp-inventory

Lift global branch coverage past the 80% gate (was 79.86%). Adds 6
tests exercising: missing-file/malformed-JSON/missing-block reader
fallbacks, codex no-parser path, collect skipping non-function readers
and swallowing reader errors, CLI usage()/main() help+json+human paths,
and formatHumanReport no-fragmentation + fragmented-only branches.

Also scrub a real API-key fragment that had leaked into a test fixture;
all secret-like fixtures are now obviously-fake FAKE... tokens.

mcp-inventory.js branch 30%->93%, collect.js ->100%. Global branch 80.33%.
2026-06-06 03:55:17 +08:00
Affaan Mustafa ab5e17fea9
feat: extend session-adapter layer with codex-worktree + opencode adapters (#2145)
* feat: add codex-worktree session adapter

Adds the third session adapter (after dmux-tmux and claude-history),
normalizing Codex rollout sessions into the harness-neutral
ecc.session.v1 snapshot. Reads ~/.codex/sessions rollout JSONL,
derives objective (skipping the AGENTS.md preamble + leading message
UUID), model, originator, worktree cwd, and best-effort git branch.

This is step 1 of ECC-2.0-SESSION-ADAPTER-DISCOVERY (move the
abstraction beyond tmux + Claude-history) and supports the
wrap/adapt control-pane strategy: ECC reads sessions from any
harness rather than owning one UX.

- scripts/lib/session-adapters/codex-worktree.js: adapter + rollout parser
- canonical-session.js: normalizeCodexWorktreeSession
- registry.js: register adapter, codex/codex-worktree target types
- tests/lib/session-adapters-codex.test.js: 4 tests (unit + registry routing)

* feat: add opencode session adapter + allow empty intent objective

Adds the fourth session adapter (after dmux-tmux, claude-history,
codex-worktree), normalizing OpenCode sessions into ecc.session.v1.

Reads ~/.local/share/opencode/storage: session/<project>/ses_*.json
for metadata (id, directory, title, version, projectID, time) and
message/<session>/msg_*.json to extract the model (modelID/providerID
from the first assistant message). Derives objective from the session
title, treating the auto-generated "New session - <date>" title as no
objective. Recency-based active/recorded state.

Schema: relax intent.objective from non-empty to allow empty string
(ensureStringAllowEmpty). Sessions legitimately have no objective yet
(fresh/auto-titled), and claude-history already emitted "" via
metadata.title fallback. This fixes a latent over-strict validation.

- scripts/lib/session-adapters/opencode.js: adapter + storage parser
- canonical-session.js: normalizeOpencodeSession + ensureStringAllowEmpty
- registry.js: register adapter + opencode target type
- tests/lib/session-adapters-opencode.test.js: 5 tests

Tests: opencode 5/0, codex 4/0, session-adapters 14/0,
control-pane-state 10/0, session-inspect 8/0, control-pane 12/0.
Smoke-tested on a real OpenCode session (140 messages, gpt-5.3-codex).

* test: cover error/fallback branches for codex-worktree + opencode adapters

Lift global branch coverage past the 80% gate (was 79.53%). Adds error
and fallback path tests: missing-session/unknown-id throws, findRolloutById/
findSessionInfoById, direct file targets, objective truncation, model
fallbacks, corrupt-line skip, mtime activity fallback, and the real
resolveGitBranch path outside a repo.

codex-worktree.js branch 52.8%->78.3%; global branch 80.04%.
2026-06-06 03:55:00 +08:00
Affaan Mustafa bc8e12bb80
feat: add dynamic workflow team orchestration surface
Adds dynamic workflow/team orchestration skills, the content pack, and control-pane work-item/Kanban state DB support. Includes reviewer hardening for state-db CLI validation, optional state DB failure handling, and mergeStateStatus projection.
2026-06-04 21:45:13 +08:00
Affaan Mustafa 0f84c0e279
feat: add ECC2 local control pane (#2131)
* feat: add ECC2 local control pane

* fix: refresh control pane package locks

* test: harden control pane coverage

* test: allow portable control pane shutdown

* test: retry local control pane fetches

* fix: harden control pane error handling

* fix: wrap control pane metadata
2026-06-03 21:54:30 +08:00
Affaan Mustafa 64cd1ba248
fix: surface warn-only PreToolUse hooks (#2084) 2026-05-28 07:45:46 -04:00
Alexis Le Dain 04c68e483a
Add React language track with agents, skills, rules, and commands (#2024)
* feat(rules): add rules/react/ track

Five rule files mirroring per-language convention (coding-style,
hooks, patterns, security, testing). Each has `paths:` glob
frontmatter for auto-activation when editing matching files.

- coding-style.md: file extensions, naming, JSX, RSC boundary
- hooks.md: React hooks (NOT Claude Code hooks) — rules-of-hooks,
  dep arrays, cleanup, memoization, React 19 additions
- patterns.md: container/presentational split, state location
  decision tree, Suspense + error boundaries, forms, data fetching
- security.md: dangerouslySetInnerHTML, unsafe URL schemes,
  server-action validation, env-var leaks, CSP
- testing.md: RTL queries, userEvent, async, MSW, axe, anti-patterns

Each file extends typescript/* and common/* rules.

* feat(skills): add react-patterns, react-testing, react-performance

Three new skills under skills/ following the SKILL.md convention.

- react-patterns: React 18/19 idioms — hooks discipline, state
  location decision tree, server/client component boundary,
  Suspense + error boundaries, form actions (React 19), data
  fetching matrix, composition recipes, accessibility-first.
- react-testing: React Testing Library + Vitest/Jest, query
  priority order, userEvent, MSW network mocking, axe a11y
  assertions, RTL vs Playwright CT boundary, TDD workflow.
- react-performance: 70-rule performance ruleset adapted from
  Vercel Labs react-best-practices (MIT) across 8 priority
  categories — waterfalls, bundle size, server-side, client
  fetch, re-render, rendering, JS micro, advanced patterns.
  Includes Lighthouse / Web Vitals mapping and attribution to
  upstream.

Cross-links between the three skills and out to frontend-patterns,
accessibility, e2e-testing, tdd-workflow.

* feat(agents): add react-reviewer and react-build-resolver

Two new agents covering React-specific code review and build error
resolution, plus matching .kiro/ mirrors and a routing pointer
edit on typescript-reviewer.

- react-reviewer: slim React-only lanes (hooks rules,
  dangerouslySetInnerHTML, unsafe URL schemes, key prop, state
  mutation, derived-state-in-effect, server/client component
  boundary, accessibility, render performance, Server Action
  validation, env-var leaks). Explicitly delegates generic
  TypeScript/async/Node concerns to typescript-reviewer. Both
  agents should be invoked together on .tsx/.jsx PRs.
- react-build-resolver: React build/bundler/runtime hydration
  failures across Vite, webpack, Next.js, CRA, Parcel, esbuild,
  Bun, Rsbuild. Handles JSX/TSX compile errors, tsconfig fixes,
  Next.js App Router server/client boundary errors, hydration
  mismatches, duplicated React copies, Tailwind/PostCSS pipeline.
- .kiro/agents/react-reviewer.json + react-build-resolver.json:
  Kiro IDE format mirrors following the per-language precedent.
- typescript-reviewer: routing pointer added to its MEDIUM React
  block — defers to /react-review for React-specific concerns
  while keeping its block as fallback for repos that only invoke
  typescript-reviewer.

All agents carry the standard Prompt Defense Baseline stanza.

* feat(commands): add /react-review /react-build /react-test

Three new slash commands invoking the React agents.

- /react-review: invokes react-reviewer. Documents the routing
  rule with typescript-reviewer — both should run together on
  TSX/JSX PRs. Lists CRITICAL/HIGH/MEDIUM rule categories and
  the automated checks (eslint with react-hooks + jsx-a11y,
  tsc --noEmit, npm audit).
- /react-build: invokes react-build-resolver. Documents bundler
  detection, common failure patterns, fix strategy, and stop
  conditions.
- /react-test: enforces TDD with React Testing Library + Vitest
  or Jest, behavior-focused queries, userEvent + MSW patterns,
  axe accessibility assertions, coverage targets.

Each command file has the required description: frontmatter and
follows the per-language command convention (cpp-test, go-test,
kotlin-test, etc.).

* chore: wire react track into manifests and stack mappings

- agent.yaml: add react-patterns, react-performance, react-testing
  to the skills array; add react-build, react-review, react-test to
  the commands array (alphabetically inserted to satisfy the
  ci/agent-yaml-surface sync test).
- config/project-stack-mappings.json: extend the `react` stack
  entry — add "react" to rules array (was ["common","typescript",
  "web"]); add react-patterns, react-performance, react-testing,
  accessibility to the skills array.
- docs/COMMAND-REGISTRY.json: bump totalCommands 75 -> 78; add
  three new entries (react-build, react-review, react-test) with
  primaryAgents / allAgents / skills wiring. react-review's
  allAgents includes typescript-reviewer to reflect the dual-agent
  routing convention.
- CLAUDE.md: add Skills-table row mapping *.tsx / *.jsx /
  components/** to react-patterns + react-testing skills and
  the /react-review, /react-build, /react-test commands.

* chore(catalog): sync counts to 62 agents / 78 commands / 235 skills

Auto-generated via `node scripts/ci/catalog.js --write --text`
after the react track additions:

- 2 new agents: react-reviewer, react-build-resolver (60 -> 62)
- 3 new commands: react-build, react-review, react-test (75 -> 78)
- 3 new skills: react-patterns, react-performance, react-testing
  (232 -> 235)

Files updated by the catalog sync:
- .claude-plugin/plugin.json description string
- .claude-plugin/marketplace.json plugin description
- README.md quick-start summary, project tree, feature parity tables
- README.zh-CN.md quick-start summary
- AGENTS.md project structure summary
- docs/zh-CN/README.md parity table
- docs/zh-CN/AGENTS.md project structure summary

All counts now match the filesystem catalog (verified by
ci/catalog.test.js).

* feat(kiro): add react agent markdown companions to JSON entries

* feat(kiro): add react skills into manifests

* fix(ci): sync catalog counts, registry, and package files for react track

- .claude-plugin/{plugin,marketplace}.json: bump description counts to 62/235/78
- docs/COMMAND-REGISTRY.json: regenerate to include quality-gate and react commands
- package.json: add skills/react-{patterns,performance,testing}/ to files allowlist so npm-publish-surface aligns with install-modules manifest

* fix(react): address PR #2024 review feedback

Critical:
- Remove undefined/.claude/session-aliases.json containing __proto__ prototype-pollution
  fixture committed by accident in a7333c14

High:
- agents/react-build-resolver.md: replace brittle `test -o $(grep -l ...)` and
  `test -a -n $(grep ...)` detection with explicit `{ ... || grep -q ...; }` so
  bundler detection no longer breaks when grep returns empty
- agents/react-build-resolver.md: drop hardcoded `npm i react@^19 react-dom@^19`
  remediation; replace with version-agnostic pair-upgrade note that honors the
  project's installed major (17/18/19) — surgical fix principle
- commands/react-review.md: guard `tsc --noEmit -p tsconfig.json` with
  `[ -f tsconfig.json ] &&` so the review skips cleanly on JS-only projects

Medium:
- rules/react/security.md: correct the React-18-blocks-javascript-URL claim
  (React only warns in dev; production navigation is not blocked)
- rules/react/security.md: correct CRA env-var exposure row (CRA exposes
  REACT_APP_*, NODE_ENV, PUBLIC_URL — not 'all' variables)
- skills/react-testing/SKILL.md: instantiate QueryClient once outside the
  wrapper closure so React Query cache survives re-renders (flaky-test fix)
- skills/react-testing/SKILL.md: restore console.error spy with mockRestore()
  in a try/finally so the mock does not leak across tests
- commands/react-test.md: switch outer example-session fence to 4 backticks
  so the inner ```tsx/```bash blocks don't prematurely terminate it

* fix(kiro): mirror react-build-resolver react 19 conditional remediation

Discussion r3272907106 flagged the kiro json variant still carrying the hardcoded
'npm i react@^19 react-dom@^19' line that the .md companion already dropped.
Replace with the same conditional, version-agnostic guidance so both variants
stay in sync.

* fix(react): bump react-build example session fence to 4 backticks

Discussion r3272907144 flagged the same nested-fence issue in
commands/react-build.md that we fixed earlier in commands/react-test.md.
The outer triple-backtick text block was being prematurely terminated by
the inner bash/tsx fences inside the Example Session.

* fix(react): bump react-review example usage fence to 4 backticks

Discussion r3272907201 flagged the same nested-fence issue in
commands/react-review.md. The outer triple-backtick text block was
being prematurely terminated by the inner tsx/ts fences inside the
Example Usage transcript.

* fix(docs): clarify commands row as legacy shims in feature parity table

Discussion r3272912003: README comparison table said 'PASS: 78 commands'
while the install-section and quick-start prose use 'legacy command shims'.
Aligned the comparison-table cell to 'PASS: 78 commands (legacy shims)' so
the count word survives the catalog-validator regex while making the legacy
nature explicit.

Widened the catalog comparison-table commands regex to tolerate an optional
parenthetical after the count word, so both the existing 'X commands' and
the new 'X commands (legacy shims)' phrasings validate without breaking
older READMEs/translations.

* Update rules/react/security.md

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix(react): guard tsc in react-build-resolver diagnostic commands

Discussion r3288910205: the agent prompt instructed an unconditional
'tsc --noEmit -p tsconfig.json', which adds noise (or hard-fails) on
JavaScript-only projects with no tsconfig.json or no installed TypeScript.

Replaced with 'test -f tsconfig.json && npx --yes tsc --noEmit -p tsconfig.json'
in both variants:
- agents/react-build-resolver.md
- .kiro/agents/react-build-resolver.json (prompt string mirrored)

Mirrors the same guard already applied to commands/react-review.md in de135f61.

* fix(react): pin tsc resolution to local install in build resolver

Discussion r3289054157: previous fix used 'npx --yes tsc' which auto-installs
the latest TypeScript from npm when none is local, producing version drift
and non-reproducible typecheck results across machines.

Switched to 'npx --no-install tsc' in both variants so the diagnostic uses
only the project's pinned TypeScript and fails fast if it isn't installed:
- agents/react-build-resolver.md
- .kiro/agents/react-build-resolver.json (prompt string mirrored)

* feat(counts): resolve counts for agents, skills...

* fix(ci): regen command registry for golang-testing entry

Removes stale kotlin-patterns entry to satisfy command-registry:check.

* fix: keep local Claude settings out of React track PR

---------

Co-authored-by: AlexisLeDain <a.ledain@docoon.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Affaan Mustafa <affaan@dcube.ai>
2026-05-28 07:32:52 -04:00
Martin Klein 7fef1ddbeb
docs(i18n): add German localization scout (#2029)
Adds de-DE docs, installer wiring, and locale tests. Pre-validated on current main with install manifest checks, markdownlint, locale-install tests, and ECC 2.0 release-surface tests.
2026-05-25 14:12:01 -04:00
Robert Egoyan d243adbf8d
fix(hooks): prefer fresh harness cost cache (#2054)
Uses a fresh harness cost cache when available and keeps transcript pricing as the fallback. Focused cost-tracker tests passed locally before merge.
2026-05-25 14:08:11 -04:00
Gaurav Dubey ee9e5a19c4
fix(install-targets): validate compiled OpenCode plugin before install (#2041)
Fail fast when the OpenCode home install is attempted from a source checkout without the compiled .opencode/dist payload. PR had the full CI matrix green.
2026-05-25 14:07:52 -04:00
Affaan Mustafa 1e8c7e7994 docs: sync live native payments gate evidence 2026-05-19 23:25:38 -04:00
Affaan Mustafa 6e25458dbc
Sync billing gate env-file evidence (#2021) 2026-05-19 22:43:19 -04:00
Affaan Mustafa c2471fe5c5
docs: sync selected-target announcement gate (#2020) 2026-05-19 22:09:45 -04:00
Affaan Mustafa 30f60710d4
Sync Marketplace Pro readback release gate (#2019)
* docs: sync marketplace pro readback gate

* docs: refresh operator dashboard after readback sync

* docs: sanitize marketplace readback summary

* docs: refresh operator dashboard after marketplace readback
2026-05-19 21:39:03 -04:00
Affaan Mustafa b3c015c744 docs: sync late May 19 release roadmap state 2026-05-19 19:02:43 -04:00
Affaan Mustafa 9819626459 Add release approval gate 2026-05-19 18:18:54 -04:00
luyua9 14d88e517b fix(gateguard): preserve quoted git introspection args 2026-05-19 13:24:17 -04:00
Affaan Mustafa 8bf4de56b2 docs(release): refresh hypergrowth evidence 2026-05-19 13:17:23 -04:00
Mhd Ghaith Al Abtah 7004a66243 feat(install-targets): add claude-project (per-project Claude Code) adapter
Completes the install-target matrix for Claude Code. Until now, ECC's
Claude support was home-scope only (~/.claude/) via the `claude` target.
This adds a project-scope counterpart (./.claude/) via a new
`claude-project` target so teams can install ECC per-repo without
contaminating ~/.claude/ — matching the existing project-scope adapters
for Cursor, Antigravity, Gemini, CodeBuddy, Joycode, and Zed.

Symmetric with `claude`:
- Same namespace under rules/ecc and skills/ecc
- Same docs/<locale> handling for --locale
- Same hooks placeholder substitution for hooks.json
- Reuses claude-home's destination-mapping logic 1:1

Use cases:
- Monorepos with multiple Flow-managed projects
- Teams that want ECC scoped per-project without touching ~/.claude/
- Per-project skill/rule isolation when global install isn't desirable

No breaking change: existing --target claude continues to route to
claude-home (user-scope) unchanged. New target is opt-in.

Tests
-----
- 4 new tests in tests/lib/install-targets.test.js
  (root resolution, lookup-by-id, plan parity with claude, foreign-path filtering)
- All install-target regression guards (schema enum / SUPPORTED_INSTALL_TARGETS)
  still pass
- End-to-end smoke: `--target claude-project --profile minimal --dry-run`
  emits 359 ops with destinations rooted at <projectRoot>/.claude/ (parity
  with --target claude which emits 359 ops rooted at ~/.claude/)
2026-05-19 12:14:27 -04:00
Affaan Mustafa ac7434ea8f docs: sync may 19 linear readiness evidence 2026-05-19 11:06:56 -04:00
Affaan Mustafa c7d662c3c6 Track owner approval packet in dashboard 2026-05-19 10:40:31 -04:00
Affaan Mustafa 8148340ad1
chore: add release owner approval packet (#2001) 2026-05-19 10:18:22 -04:00
Affaan Mustafa e7a7b2aaa3
chore: refresh suite count evidence (#2000) 2026-05-19 09:58:53 -04:00
Affaan Mustafa 3304848beb
chore: refresh video dashboard evidence (#1999) 2026-05-19 09:39:10 -04:00
Affaan Mustafa b62f80750d chore: add release video visual qa 2026-05-19 09:16:35 -04:00
Affaan Mustafa 855e8c8336 chore: gate release video publish candidates 2026-05-19 08:54:50 -04:00
Affaan Mustafa f3cd006252 chore: add release video self-eval gate 2026-05-19 08:35:02 -04:00
Affaan Mustafa d135e03da0 docs: refresh May 19 operator dashboard 2026-05-19 08:13:26 -04:00
Affaan Mustafa c07276a347 docs: refresh May 19 publication evidence 2026-05-19 07:53:51 -04:00