Commit Graph
560 Commits
Author SHA1 Message Date
Affaan MustafaandGitHub bc774282e6 feat: connect ECC to canonical Ito compute CLI (#2558) 2026-07-23 19:28:55 -07:00
Affaan MustafaandGitHub 7b03a834b3 feat: add read-only Itō compute handoff (#2554)
* feat: add read-only Itō compute handoff

* fix(ito): keep handoff portable under CI

* test(ito): run npm welcome through Windows shell
2026-07-23 14:35:57 -07:00
a3130f9ebf feat(codex): add ECC navigation guide (#2525)
* feat(codex): add ECC navigation guide

* fix(codex): ship navigation guide references

---------

Co-authored-by: Haley Chen <2022hachen@gmail.com>
2026-07-22 20:44:37 -04:00
Haley Chen b6fe5a71e1 fix: harden plan canvas code scanning alerts 2026-07-22 16:28:07 -04:00
b6652335d3 fix(hooks): bound plugin shell probe timeouts (#2547)
Co-authored-by: Haley Chen <2022hachen@gmail.com>
2026-07-22 13:15:52 -04:00
KHandGitHub 8348fb5387 fix: detect Anthropic API keys (sk-ant-...) in pre-commit secret scan (#2529)
The existing OpenAI pattern sk-[a-zA-Z0-9]{20,} never matches real
Anthropic keys: their sk-ant-api03-... format contains hyphens, which
break the character class before reaching the 20-char threshold. Keys
from the fastest-growing Claude Code user base slipped through the scan.

Adds a dedicated sk-ant-[a-zA-Z0-9_-]{20,} pattern (checked before the
OpenAI one) and extends the staged-secrets test with a realistic
Anthropic key fixture.
2026-07-22 12:17:28 -04:00
Girish KanjiyaniandGitHub cd39df154c fix(suggest-compact): recognize large-window model families without a [1m] marker (#2468)
* fix(suggest-compact): recognize large-window model families without a [1m] marker

resolveContextWindowTokens() only detected a 1M window via the env
override, the [1m] model-id marker, or observed tokens already above
200k. Large-window models whose ids carry none of these (e.g.
claude-fable-5) were misclassified as 200k windows, overstating
context usage ~5x in the compact suggestion.

Add a known-model-family substring table (claude-fable-5,
claude-mythos-5) checked after the env override and [1m] marker and
before the token-count heuristic. Env overrides still win, and unknown
model ids still fall back to the 200k default.

Closes #2461

* fix(suggest-compact): anchor known-model-family match at a token boundary

Unanchored substring matching would misclassify a hypothetical smaller
tier sharing a known family prefix (e.g. claude-fable-5-mini) as a 1M
window. Require the family id to end at a token boundary: end of id, a
delimiter, or a dated/versioned suffix (-20260115). Alphanumeric
continuations and letter suffixes no longer match.

Addresses CodeRabbit/Greptile review on #2468
2026-07-22 12:17:11 -04:00
Emad DoughanandGitHub e7b3ba07bb fix(config-protection): match protected filenames case-insensitively (#2543)
On a case-insensitive filesystem (macOS APFS/HFS+, Windows NTFS) a write to
`.ESLINTRC.JS` lands on the exact same inode as `.eslintrc.js`, but the guard
looked the basename up in PROTECTED_FILES with a case-sensitive `Set.has`.
Every entry in that Set is lowercase, so any case-variant path missed the
branch entirely and returned exit 0 — a single Write silently overwrote a
live config while the hook reported success.

Reproduced on macOS APFS: `.eslintrc.js` and `.ESLINTRC.JS` share one inode,
yet the hook returned exit 2 for the former and exit 0 for the latter, and the
uppercase write replaced the real config's contents.

This is a one-step bypass of the whole guard and needs no shell access, unlike
the known delete-then-recreate route.

Fix: also test `basename.toLowerCase()`. All 32 PROTECTED_FILES entries are
already lowercase, so the fallback is exact. On a genuinely case-sensitive
filesystem this costs at most a false positive on a distinct file whose name
differs from a protected one by case alone.

Behaviour deliberately unchanged: first-time creation is still allowed (the
bootstrap affordance), non-config paths still pass through, and the existing
lstat/ENOENT fail-closed semantics are untouched.

Test: adds a case-variant case that asserts exit 2. It guards itself with an
inode comparison and skips on case-sensitive filesystems rather than asserting
something untrue there. Verified in both directions — it FAILS against the
unpatched hook (`Got 0; 0 !== 2`) and passes with the fix. Suite: 9/9.
2026-07-22 12:17:04 -04:00
Affaan MustafaandGitHub 96789caaf9 feat: add Itô compute sponsor routing and Phase 2 plan (#2546)
* feat: add Ito compute sponsor routing

* fix: harden Ito integration CI and framing
2026-07-22 03:07:45 -04:00
5deee34c93 fix(hooks): remove stray '?' that made every 'yarn <anything>' fire tmux reminder (#2517)
* fix(hooks): remove stray '?' that made every 'yarn <anything>' trigger tmux reminder

The tmux-reminder matcher uses one alternation per package manager. Each
branch requires a subcommand (install|test) — except yarn, whose subcommand
group carried a trailing `?`:

    yarn (install|test)?

That made the subcommand optional, so the branch degraded to "yarn " plus
anything: `yarn add foo`, `yarn build`, `yarn dev`, even `yarn --version`
all matched and spammed the "Consider running in tmux" hint into the
additional-context channel.

Drop the `?` so yarn matches parity with npm/pnpm/bun. Verified locally
against 14 cases (yarn install/test still fire; yarn add/build/dev/… no
longer do; npm/pnpm/bun/pytest behavior unchanged).

Fixes #2514

* test(hooks): add pre-bash-tmux-reminder regression tests

Add coverage for the tmux-reminder matcher following the auto-tmux-dev.test.js
structure — the regex-first hook now has direct regression tests for the yarn
branch fix in this PR (and for the sibling package managers, other matched
tools, TMUX bypass, and malformed input).

16 assertions total:
  - fires for: yarn install, yarn test, npm install, pnpm test, bun install,
               pytest tests/, cargo build
  - does NOT fire for: yarn add react, yarn build, yarn dev, yarn --version,
                       bare `yarn`, npm run dev
  - respects TMUX env var
  - tolerates invalid JSON and missing command field

Verified the tests actually catch the bug: reintroducing the buggy
`yarn (install|test)?` fails 4 of the 5 yarn non-match cases (the fifth,
bare `yarn`, stays passing because even the buggy branch requires a trailing
space after yarn).

Addresses CodeRabbit review on #2517.

* test(hooks): fail loudly on spawn errors, use destructuring, split runTests

Address three CodeRabbit review notes on tests/hooks/pre-bash-tmux-reminder.test.js:

- Fail loudly on spawnSync errors: raise instead of coercing
  `result.status || 0`, which would mask spawn errors, timeouts, or signal
  termination as a successful exit 0 (masks legitimate test failures).
- Use destructuring (`const { TMUX, ...env } = process.env`) instead of
  copy-then-`delete` so the base env is built immutably.
- Split `runTests` (was 66 lines) into small per-group helpers
  (runYarnTests, runSiblingPackageManagerTests, runOtherToolTests,
  runTmuxBypassTests, runEdgeCaseTests). `runTests` is now 18 lines and
  purely orchestrates.

16 assertions still pass; no coverage changes.

The 4th CodeRabbit note (avoid console.log in test files) is intentionally
not adopted here — every sibling hook test in this repo
(auto-tmux-dev.test.js, bash-hook-dispatcher.test.js, block-no-verify.test.js,
etc.) writes to console.log because the project's own test runner
(tests/run-all.js) is console-log based and there is no Jest/Mocha
dependency. Diverging from the established convention in a bugfix PR is
out of scope.

* test(hooks): trim tmux reminder regression coverage

---------

Co-authored-by: Haley Chen <2022hachen@gmail.com>
2026-07-20 16:21:03 -04:00
JongHyeok ParkandGitHub 0071fa5c3c refactor(hooks): consolidate PostToolUse hooks into sync/async dispatchers (#2494)
* refactor(hooks): consolidate PostToolUse hooks into sync/async dispatchers

Replace 10 individual PostToolUse entries in hooks.json with two
consolidated dispatcher entries (post:dispatcher:sync /
post:dispatcher:async). The dispatcher's internal registry preserves
every hook ID, matcher, and profile, so ECC_DISABLED_HOOKS and
ECC_HOOK_PROFILE gating behave exactly as before.

Performance (Edit event, actual hooks.json commands spawned in
parallel like the harness does, median of 7 runs):
- Blocking hook latency: 81ms -> 49ms (~40% faster; 7 blocking
  processes -> 1 sync dispatcher)
- Node processes per tool call: 10 -> 2 (7 blocking + 3 async
  -> 1 sync + 1 async)
- observe-runner now runs in-process (~370ms) inside the async
  dispatcher, which stays backgrounded (async: true, timeout 45s),
  so it adds no user-facing latency.

Also:
- dashboard-web lists dispatcher-managed child hooks so the hook
  inventory stays complete
- post-edit-console-warn refactored to export run() for in-process
  dispatch while keeping standalone stdin behavior
- dispatcher stdin reading is multi-byte safe (StringDecoder) and
  child hook exit codes propagate to the dispatcher exit code

* test(hooks): replace emoji literal with unicode escape for CI unicode safety check

* fix(hooks): adopt explicit cli() entrypoint and merge multi-hook stdout

Address Greptile review on #2494:

- Replace the non-standard 'require.main === undefined' guard with an
  explicit exported cli(). The hooks.json bootstraps now call
  require(s).cli(), so merely requiring the module (dashboard-web,
  test runners, Jest, worker threads) can never trigger dispatch,
  attach stdin listeners, or set process.exitCode.
- Replace last-writer-wins stdout with mergeHookStdout(): when several
  hooks emit additionalContext envelopes they merge into a single
  PostToolUse envelope; non-mergeable raw stdout keeps the last hook's
  output and emits a stderr warning naming the dropped hook IDs, so
  nothing is lost silently.

Also includes local formatter reformatting of the dispatcher and its
test file (no behavioral changes beyond the above).

* fix(hooks): keep post:bash:dispatcher phase reachable in minimal profile

The Greptile P1 premise was partially incorrect: sub-hooks without
explicit profiles default to standard,strict via parseProfiles()
(scripts/lib/hook-flags.js), so audit/cost logs never ran under the
minimal profile on main either — there is no user-visible regression.

However, main did spawn the bash dispatcher phase unconditionally and
let each sub-hook gate itself. Restore that semantic by opening the
outer registry gate to minimal,standard,strict so a future sub-hook
that opts into minimal is not silently blocked at the phase level.
Adds the previously missing minimal-profile async dry-run test.

* test(hooks): assert failing hook exit code propagates to real process status

Spawns the actual dispatcher subprocess with an injected failing hook
and asserts the OS-level exit status, stderr diagnostic, and suppressed
pass-through — closing the E2E gap CodeRabbit flagged on #2494.

* chore: retrigger CI (flaky windows powershell bootstrap test)
2026-07-19 15:47:10 -04:00
754b8dd76c fix: make the installer runtime pass strict supply-chain vetting (#2503)
* fix: make the installer runtime pass strict supply-chain vetting

Remediate the four enterprise supply-chain vetting blockers from
affaan-m/ECC#2502 so the installer runtime (package.json + manifests +
scripts/lib/**) passes strict exact-pin evidence policy:

1. Remove the package.json `postinstall` lifecycle script (it only echoed a
   post-install banner) and move that banner to an explicit opt-in
   `npm run welcome` command. No install-time lifecycle script remains.
2. Exact-pin every dependency in package.json (dependencies + devDependencies)
   to the versions already resolved in package-lock.json; no ^/~ ranges.
3. Replace non-ASCII characters on the installer runtime script/config surface:
   em-dashes (U+2014) in scripts/lib/{path-safety,install-executor,
   install/link-rewrite}.js comments and the two "Itô" (U+00F4) occurrences in
   manifests/{install-components,install-modules}.json descriptions become
   ASCII, so strict-surface Unicode scanners are clean.
4. Drop the bare `require("ajv")` from scripts/lib/install-state.js; the file
   already carries a complete hand-rolled validator enforcing the same
   schemas/install-state.schema.json (ecc.install.v1) constraints, so the
   installer closure is dependency-free (zero non-builtin bare requires).

Refs affaan-m/ECC#2502

* fix: avoid unpinned welcome invocations

Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>

* fix: validate translated skill frontmatter

Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>

* fix: repair skill frontmatter YAML

Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>

* fix: add MIT license to core skill manifests; pin verification-loop tsc invocation

* fix: preserve tsc/pyright exit status in verification-loop type-check (set -o pipefail)

* chore(deps): sync lockfiles with exact-pinned package.json

Regenerate package-lock.json and yarn.lock so the pinned dependency
specs are reflected in both lockfiles. npm ci and Yarn's --immutable
install now pass the sync check. The resolution tree is unchanged
(231 yarn resolutions, byte-identical set; zero npm transitive drift);
only the root descriptor strings move from ranges to the versions
already resolved in the committed lockfiles.

Addresses the Codex P1 on #2503.

---------

Signed-off-by: Samar Tomar <samar_tomar@hotmail.com>
Co-authored-by: Samarjeet Singh Tomar <samartomar@gmail.com>
2026-07-17 17:13:49 -04:00
黄云龙andGitHub 5da21c2b66 fix(project-detect): parse Python deps pinned with ~ and @ (was losing framework detection on compatible-release pins) (#2498)
* fix(project-detect): parse Python deps pinned with ~= and @ direct references

* test(project-detect): cover ~= compatible-release and @ direct-reference parsing

* test(project-detect): cover direct references

* fix(project-detect): skip bare VCS/URL requirement lines in getPythonDeps

A requirements.txt line like git+https://host/repo.git#egg=pkg carries
no leading package name, so the delimiter split recorded the whole URL
fragment as a dependency. Skip names that start with git+ or contain a
URL scheme, and tighten the test assertions so any leaked URL, scheme,
or @ delimiter fails loudly.
2026-07-17 17:11:09 -04:00
黄云龙andGitHub 5d68ef3617 fix(plan-canvas): stop dropping list items when a block's first item is over-indented (#2501)
* fix(plan-canvas): stop dropping list items when a block's first item is over-indented

* fix: address greptile findings for PR #2501 - list-type detection and outdent nesting

- plan-canvas markdown: fix nested list rendering where outdented runs (indent
  6→4) create duplicate sibling UL blocks instead of sharing parent (#2501)
- transcript-context: add LARGE_WINDOW_NATIVE_MODEL_IDS array for models whose
  default context window is 1M but do NOT carry the [1m] marker (fixes #2497)
- Add test coverage for transcript-context and shell-substitution modules
- Add test coverage for project-detect module (#2498)

* fix(plan-canvas): handle outdented list runs

* fix(plan-canvas): start a new list when marker type changes at the same indent

CommonMark treats a marker-type change (bullet to ordered or back) at
the same indentation as the start of a new list. buildList previously
absorbed the run into the current list, so mixed runs rendered under a
single wrong tag. Stop the run on a tag change and let buildListBlock
render the next run as a sibling list with its own tag.
2026-07-17 17:10:58 -04:00
JongHyeok ParkandGitHub ed38744605 fix(hooks): require successful shell probe in observe runner (#2403)
Require observe-runner shell detection to accept only candidates whose probe exits successfully, avoiding Windows WSL launcher false positives.
2026-07-13 21:31:12 -04:00
devin-ai-integration[bot]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>affaanDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
38a7ebbe32 fix(install): reference all curated skills in modules + reverse-coverage guard (#2431) (#2440)
* fix(install): reference all curated skills in modules + add reverse-coverage guard (#2431)

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

* test(install): normalize path separators in delivery-gate dry-run assertion (#2431)

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-08 17:14:52 -04:00
devin-ai-integration[bot]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>affaanDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
67537ea480 fix(repair): build opencode payload so repair clears doctor's opencode-plugin-not-built warning (#2414) (#2438)
* fix(repair): build opencode plugin payload so repair clears doctor's opencode-plugin-not-built warning (#2414)

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

* refactor(repair): narrow validation bypass to specific codes; fix test-artifact cleanup (#2414 review)

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-08 17:14:33 -04:00
a511395613 feat: Plan Canvas, a browser review canvas for plans (#2467)
* feat: add Plan Canvas - browser annotate-and-approve review for plan artifacts

- scripts/plan-canvas.js CLI (open/await/end/stop/server; bin ecc-plan-canvas)
- loopback server + ECC-styled chrome + annotation SDK + zero-dep markdown renderer
- Approve/Request-changes verdicts wired to the /plan confirmation gate
- plan-canvas skill, /plan-canvas command, SessionStart hook surfacing open reviews
- shared scripts/lib/loopback-guard.js extracted from control-pane (API re-exported)
- 121 new tests incl. full-workflow E2E; registered in manifests, catalog, registry

Inspired by lavish-axi (https://github.com/kunchenguid/lavish-axi) by @kunchenguid;
original ECC-native implementation, not a port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(plan-canvas): invoke via ecc-plan-canvas bin so the skill works from any project

Skill/command referenced a cwd-relative `node scripts/plan-canvas.js`, unusable
outside the ECC root. Switch to the ecc-plan-canvas bin (and $CLAUDE_PLUGIN_ROOT
fallback) and align CLI next_step hints so an agent can run it as a skill in any repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(plan-canvas): render Mermaid diagrams + ship Codex cross-harness surface

- markdown renderer emits <pre class="mermaid"> for ```mermaid blocks (source
  entity-escaped so the browser decodes it for the renderer while blocking injection)
- artifact template loads a pinned Mermaid build only when a diagram is present,
  themed to ECC dark, securityLevel strict, graceful offline fallback to source
  (ECC_PLAN_CANVAS_MERMAID_URL overrides for a local mirror)
- skill teaches Mermaid-for-diagrams and states the CLI+JSON loop is harness-agnostic
- add .agents/skills/plan-canvas (Codex) with agents/openai.yaml interface manifest
- register in install-modules workflow-quality paths; docs updated

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plan-canvas): add demo screenshot

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ci): sync yarn.lock with new bin; add contributor checklist

- yarn.lock records the ecc-plan-canvas bin so Yarn hardened-mode install no
  longer wants to modify the lockfile on public PRs
- PR template + CONTRIBUTING gain a pre-push checklist covering the lockfile
  trap and the full skill/command/CLI registration surfaces

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Haley Chen <2022hachen@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:12:48 -04:00
Affaan MustafaandGitHub 49128b5763 feat(install): add Kimi Code CLI install target (kimi-project adapter) (#2441)
Rebuild of #2154 on current main, following the hermes/openclaw (#2433)
adapter recipe: 10-line kimi-project adapter (project kind, ./.kimi root),
registry + helpers ownership, SUPPORTED_INSTALL_TARGETS + legacy-compat
modules, kimi target on the 5 shared modules + .kimi in platform-configs
paths, both schema enums, npm files allowlist, help text, README stub.

Credit to @MoYiC6 for the original adapter design in #2154.
2026-07-03 22:24:13 -07:00
c837139e6c feat: add Hermes and OpenClaw harness install targets (#2433)
* feat: add Hermes and OpenClaw harness install targets

- Add scripts/lib/install-targets/hermes-home.js and openclaw-home.js
- Register new adapters in registry.js and helpers.js
- Add hermes/openclaw to SUPPORTED_INSTALL_TARGETS, schema enum, and install help text
- Add .hermes/ and .openclaw/ platform source directories with READMEs
- Update install-modules.json so rules/agents/commands/platform-configs cover the new targets

Verification:
- npx ecc doctor reports all 13 targets OK (including hermes-home and openclaw-home)
- install-targets regression guard passes for new targets
- Catalog check passes

* fix(install): register hermes/openclaw in install-modules schema enum + npm files allowlist

---------

Co-authored-by: Lxcardoza993 <265670745+Lxcardoza993@users.noreply.github.com>
Co-authored-by: Affaan Mustafa <me@affaanmustafa.com>
2026-07-03 22:20:17 -07:00
devin-ai-integration[bot]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>affaanDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Affaan Mustafa
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 MustafaandGitHub 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
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 DubeyandGitHub 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 DubeyandGitHub 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 DubeyandGitHub 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]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>affaanDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>affaanDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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
JunandGitHub 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
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
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._.WORLDandGitHub 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
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
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
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
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 MustafaandGitHub 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