Commit Graph

646 Commits

Author SHA1 Message Date
Affaan Mustafa
8bf4de56b2 docs(release): refresh hypergrowth evidence 2026-05-19 13:17:23 -04:00
Affaan Mustafa
bc519e5b8e fix(learning): add project registry maintenance 2026-05-19 12:51:18 -04:00
Mhd Ghaith Al Abtah
b2c2616ab4 test(install-targets): add positive rules assertion to claude-project foreign-path test
Addresses CodeRabbit review: the negative-only assertions could have
passed on an empty plan. Add a positive assertion that the non-foreign
'rules' path is still planned under .claude/rules/ecc so regression to
zero ops would fail loudly.
2026-05-19 12:14:27 -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
27e4036075 Fix release supply-chain evidence gate 2026-05-19 11:59:42 -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
Affaan Mustafa
7a0645ed47 docs: add ECC 2 growth outreach pack (#1993) 2026-05-19 07:33:41 -04:00
Affaan Mustafa
e209afc8c1 chore: gate ECC release video suite (#1992) 2026-05-19 07:13:52 -04:00
Affaan Mustafa
8141f6904f chore: gate canonical ECC release identity (#1991) 2026-05-19 06:42:17 -04:00
Affaan Mustafa
af9b2c1c4c feat: extend harness audit integration scoring (#1990)
Salvages the useful harness-audit scoring work from #1989 while preserving the current hook registry and newer plugin install detection. Adds GitHub integration checks, conditional deploy-provider categories, dynamic applicable category metadata, and CODEOWNERS coverage.
2026-05-19 06:20:54 -04:00
Affaan Mustafa
9ee1e15564 docs: define ECC 2.0 hypergrowth release lane
Refresh the active 2.0 release surface for the affaan-m/ECC repo identity, update package/plugin/workflow launch metadata, and add an operator command center for release video, partner, sponsor, consulting, and social launch execution.
2026-05-19 05:42:38 -04:00
Affaan Mustafa
6cb194a3c6 fix(hooks): avoid escaped quotes in plugin bootstrap
Generate the inline hook root resolver with single-quoted JavaScript literals so Windows Git Bash does not choke on nested escaped double quotes before Node starts. Refresh hooks.json and add regression coverage for parsed hook commands and installed hook manifests.
2026-05-19 05:15:42 -04:00
Jamkris
d904edc615 test(lib): make concurrent-write test actually concurrent + use regex matcher for assert.throws
Two round-1 review findings in `tests/lib/session-bridge.test.js`,
both about test correctness rather than the underlying fix:

1. **greptile P1 + coderabbitai Major + cubic P2 (all three): concurrent-write test ran sequentially.**

   The test spawned two child processes with two consecutive
   `spawnSync` calls. Because `spawnSync` blocks until the child
   exits, the second writer started *after* the first finished —
   the two writers never overlapped, so the rename race the fix
   targets was never actually exercised. The test would have passed
   with the old broken `${target}.tmp` suffix.

   Fix: introduce a one-off "race runner" helper that runs inside
   its own subprocess and uses async `spawn` to start both writers
   simultaneously. The runner waits for both to exit (the event
   loop is local to the runner subprocess, so this stays compatible
   with the synchronous test harness used elsewhere in this file)
   and reports both exit codes plus stderrs on stdout. The test
   then calls the runner via `spawnSync` and parses the result.
   Both writer children now overlap for the duration of their 200
   `writeBridgeAtomic` calls each, which is enough wall time to
   reliably trigger the rename race against the pre-fix code.

   Verified: with the fixed `${target}.${pid}.${nonce}.tmp` suffix,
   the test passes; with the old fixed `${target}.tmp` suffix
   reintroduced, it fails as expected (one writer hits ENOENT on
   roughly half its rename calls).

2. **greptile P2 + cubic P3: `assert.throws` used a string as the second argument.**

   Node deprecated passing a string as the second argument to
   `assert.throws` years ago: the string is silently treated as
   the assertion failure message (what to print when the function
   does *not* throw) rather than as an error matcher. The check
   passed for any thrown error, not just the rename failure.

   Fix: pass a regex matcher as the second arg and keep the
   explanatory text as the third. The regex matches `EISDIR`,
   `EPERM`, `ENOTDIR`, or `ENOENT` because `renameSync` of a
   regular tmp file onto an existing directory raises different
   codes on Linux / macOS / BSD — making the matcher portable
   across CI runners.

Test count unchanged at 14; `npm test` green; `npm run lint` clean.

The two helper files (`tests/__tmp_bridge_writer.js`,
`tests/__tmp_bridge_race_runner.js`) are written and unlinked
inside the test's try/finally so they never persist beyond the
test run.
2026-05-19 04:57:10 -04:00
Jamkris
5acb01a276 test(lib): concurrent writeBridgeAtomic + tmp-cleanup regression
Two regression tests pin down the previous two commits' atomic-rename
fixes:

1. **concurrent writes don't throw ENOENT or corrupt the file** —
   spawns two child Node processes (`tests/__tmp_bridge_writer.js`
   created in-test, cleaned up in finally) that each call
   `writeBridgeAtomic(sid, …)` 200 times against the same session
   ID with independent payloads. Asserts both subprocesses exit 0
   (the previous implementation produced ENOENT on roughly 50% of
   rename calls, all swallowed by the in-test catch) and the final
   bridge file is parseable JSON belonging to one of the two writers
   (last-writer-wins is fine; the contract is *no corruption* and
   *no rename ENOENT*, not data preservation).

2. **tmp file cleanup on rename failure** — pre-creates a directory
   at the target bridge path so `renameSync(tmp, target)` fails,
   calls `writeBridgeAtomic`, asserts the call throws AND that no
   tmp file with the writer's `pid.<nonce>.tmp` prefix is left
   behind in `os.tmpdir()`. The previous code had no cleanup; the
   fix's `try/catch + unlinkSync` keeps tmpdir from accumulating
   orphan files across repeated rename failures.

The first test deliberately writes independent payloads from each
subprocess so this regression doesn't try to claim a property the
fix doesn't actually deliver (read-modify-write race in the caller
is a separate issue and out of scope per PR body).

Test count: 12 → 14 in `tests/lib/session-bridge.test.js`;
`npm test` green; `npm run lint` clean.
2026-05-19 04:57:10 -04:00
Jamkris
33ed494adf test(ci): regression coverage for newly-covered invisible code points
9 new test cases pin down the two previous commits' denylist
extensions. Each verifies both detection (validator exit non-zero +
the expected `dangerous-invisible U+<HEX>` line on stderr) and,
where applicable, `--write` sanitization.

Coverage:

Tag block (commit 1):
- U+E0041 TAG LATIN CAPITAL LETTER A — the range's printable ASCII
  shadow; this is the byte sequence demonstrated in published ASCII
  smuggling proofs of concept.
- U+E007F CANCEL TAG — the range end.

Other invisibles (commit 2):
- U+180E MONGOLIAN VOWEL SEPARATOR
- U+115F HANGUL CHOSEONG FILLER
- U+1160 HANGUL JUNGSEONG FILLER
- U+2061 FUNCTION APPLICATION (range start)
- U+2064 INVISIBLE PLUS (range end)
- U+3164 HANGUL FILLER

Detection table is data-driven (one loop, one assertion per row) so
adding the next invisible to the denylist also gets a paired
regression test by simply appending to NEWLY_COVERED_RANGES.

Plus a `--write` integration test:
- writes a markdown file containing both Tag block (5 chars) and
  U+180E, runs `--write`, asserts both removed and surrounding text
  preserved character-for-character ('# Title\n\nBenigntext.\n').
- re-runs the validator without `--write` and asserts exit 0,
  confirming the sanitizer's output is idempotent under the
  extended denylist.

Test count: 5 → 14 in this file; full `yarn test` green; `yarn lint`
clean.
2026-05-18 21:20:36 -04:00
Affaan Mustafa
4470e2e670 docs: refresh rc1 publication evidence 2026-05-18 16:12:37 -04:00
Affaan Mustafa
0f1775e30b docs: refresh release blockers evidence 2026-05-18 15:23:48 -04:00
Affaan Mustafa
12ac22e674 docs: add discussion response playbook 2026-05-18 14:39:11 -04:00
Affaan Mustafa
97567a91e7 test: normalize release workflow line endings 2026-05-18 13:53:26 -04:00
Affaan Mustafa
7911af4a39 security: scope release oidc publishing 2026-05-18 13:41:10 -04:00
Affaan Mustafa
386326df8e fix: treat MCP HTTP 406 probes as reachable 2026-05-18 12:48:52 -04:00
Affaan Mustafa
b41e6fb3d0 docs: refresh publication readiness gate 2026-05-18 10:49:49 -04:00
Affaan Mustafa
680aeff0fb test: enforce release publication checklist in readiness gates 2026-05-18 09:10:51 -04:00
Affaan Mustafa
6c0fbfb6c5 docs: add release plugin publication checklist 2026-05-18 08:56:17 -04:00
Affaan Mustafa
cdc92de42a docs: finish owner queue cleanup 2026-05-18 06:35:44 -04:00
Affaan Mustafa
08807e7fd6 docs: record owner-wide queue cleanup 2026-05-18 06:16:45 -04:00
Affaan Mustafa
ff3eaff137 docs: refresh billing readback gate evidence 2026-05-18 04:30:09 -04:00
Affaan Mustafa
bf17737969 test: stabilize repair lifecycle on Windows 2026-05-18 03:48:51 -04:00
Affaan Mustafa
fb4b0c8dce docs: mirror target billing readback gate 2026-05-18 03:27:42 -04:00
Affaan Mustafa
04d4d81938 fix: ignore defensive ioc deny rules 2026-05-18 02:29:59 -04:00
Affaan Mustafa
e53933de1b docs: refine billing readback dashboard blocker 2026-05-18 02:03:37 -04:00
Affaan Mustafa
80f6c27957 Merge PR #1976 provider response guards 2026-05-18 01:05:37 -04:00
Affaan Mustafa
eb0d893948 fix: harden openai-compatible provider responses 2026-05-18 01:04:28 -04:00
Affaan Mustafa
044d1863d0 test: skip insaits monitor subprocesses without python 2026-05-18 00:47:05 -04:00
Affaan Mustafa
c276639bc7 docs: mirror marketplace billing provenance gate 2026-05-18 00:36:01 -04:00
Affaan Mustafa
d14191bed8 test: align dashboard fixture with May 18 evidence 2026-05-17 22:29:06 -04:00
Affaan Mustafa
5475db4f97 test: point platform audit at May 18 evidence 2026-05-17 22:22:02 -04:00
Affaan Mustafa
9b1d891870 fix(hooks): persist metrics warning dedup 2026-05-17 21:41:24 -04:00
Affaan Mustafa
4cafdb8304 fix(hooks): suppress repeated metrics warning breadcrumbs 2026-05-17 21:41:24 -04:00
Jamkris
086e44c964 fix(hooks): log fail-open breadcrumb on parse/read errors in metrics bridge
coderabbitai flagged: the two `catch` blocks in `readSessionCost`
silently swallowed every failure mode. A malformed `costs.jsonl`
row, a permission error opening the file, or any other unexpected
I/O failure would silently return zero cost — masking real
problems and feeding stale or zero numbers into
`ecc-context-monitor.js` (which then injects them as
`additionalContext` into the live model turn).

Fix two things, both fail-open-preserving:

1. **Inner JSON.parse catch** — count malformed lines and write
   one aggregated breadcrumb per call:

     [ecc-metrics-bridge] skipped N malformed line(s) in <path>

   Aggregating (rather than per-line) keeps a log-flooded
   `costs.jsonl` diagnosable without overwhelming stderr.

2. **Outer fs.readFileSync catch** — write a breadcrumb on real
   errors, but stay silent on `ENOENT`. The "no costs.jsonl yet"
   case is genuinely normal (no Stop event has fired this session)
   and producing noise on every PreToolUse before the first Stop
   would be reviewer-visible spam. All other error codes
   (`EACCES`, `EISDIR`, `EMFILE`, …) get:

     [ecc-metrics-bridge] failing open after <name> reading <path>: <msg>

In both cases the function still returns the zero-cost fallback
so the bridge never breaks tool execution — only the
diagnosability changes.

Two new regression tests in
`tests/hooks/ecc-metrics-bridge.test.js`:

  ✓ readSessionCost writes a stderr breadcrumb when malformed
    lines are skipped — feeds 4 rows (2 valid, 2 malformed),
    asserts the last valid row still wins AND captured stderr
    contains "skipped 2 malformed line(s)".

  ✓ readSessionCost stays silent when costs.jsonl does not exist
    (ENOENT) — uses a fresh tmp HOME with no metrics dir, asserts
    zero return AND empty stderr.

Test count: 16 → 18; `npm test` green; `yarn lint` clean.
2026-05-17 21:41:24 -04:00
Jamkris
63c9788f50 fix(hooks): scan full costs.jsonl when locating session row
`readSessionCost` read only the trailing 8 KiB of
`~/.claude/metrics/costs.jsonl` to "avoid scanning entire file".
That ceiling is the opposite-sign sibling of the double-count bug
fixed in the previous commit: once a session's most recent
cumulative row gets pushed past the 8 KiB window by newer rows
from other sessions, the bridge silently reports `totalCost: 0`,
`totalIn: 0`, `totalOut: 0` for that session — same false signal
to `ecc-context-monitor.js`, same wrong number injected into the
live model turn as `additionalContext`.

`cost-tracker.js` has no rotation policy, so on any non-trivial
workstation costs.jsonl grows past 8 KiB within minutes of normal
use. For users who keep multiple concurrent sessions, this means
the second-and-later sessions silently report zero almost
immediately.

Reproduced before this commit:

  $ HOME=/tmp/eccc node -e '
      const fs = require("fs");
      const m = require("./scripts/hooks/ecc-metrics-bridge.js");
      // S1 row at file start, then 200 rows of OTHER-session noise (~16 KiB).
      // S1 is the row we want, but it sits past the 8 KiB tail.
      const s1 = `{"session_id":"S1","estimated_cost_usd":0.5,"input_tokens":500,"output_tokens":250}`;
      const other = `{"session_id":"OTHER","estimated_cost_usd":1,"input_tokens":100,"output_tokens":50}`;
      fs.mkdirSync("/tmp/eccc/.claude/metrics", { recursive: true });
      fs.writeFileSync("/tmp/eccc/.claude/metrics/costs.jsonl",
        [s1, ...Array(200).fill(other)].join("\\n") + "\\n");
      console.log(JSON.stringify(m.readSessionCost("S1")));'
  {"totalCost":0,"totalIn":0,"totalOut":0}

Expected: `{"totalCost":0.5, "totalIn":500, "totalOut":250}` (the
S1 row that exists in the file).
Actual: zero — the row is past the 8 KiB tail.

Fix: drop the `fs.openSync` + bounded `fs.readSync` + position
arithmetic in favour of `fs.readFileSync(costsPath, 'utf8')` and
iterate every line. Each row is ~150 bytes; even 100k rows is
~15 MB and a single sync read on PreToolUse is in the low ms.
If file rotation lands in `cost-tracker.js` later, this scan
becomes proportionally cheaper.

After this commit the reproduction above returns
`{"totalCost":0.5, "totalIn":500, "totalOut":250}`.

Regression test in `tests/hooks/ecc-metrics-bridge.test.js`:
`readSessionCost finds session row beyond the old 8 KiB tail
boundary`. The test asserts the costs.jsonl fixture is > 8 KiB
before reading so any reintroduction of a bounded tail would
re-fail the test (i.e. the assertion is the contract, not the
specific number 8192).

Together with the previous commit, both directions of the
metrics-bridge cost-reporting bug are closed.
2026-05-17 21:41:24 -04:00