Commit Graph

1866 Commits

Author SHA1 Message Date
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
Jamkris
4f21ed2acf fix(hooks): use last cumulative row for session cost in metrics bridge
`ecc-metrics-bridge.js#readSessionCost` summed the
`estimated_cost_usd`, `input_tokens`, and `output_tokens` of
every matching row in `~/.claude/metrics/costs.jsonl`. That breaks
the documented contract of `scripts/hooks/cost-tracker.js`, which
explicitly states (in its module docblock):

  Cumulative behavior: Stop fires per assistant response, not
  per session. Each row therefore represents the cumulative
  session total up to that point. To get per-session cost, take
  the last row per session_id.

Summing N cumulative rows over-counts by roughly (N+1)/2 ×. For a
session with 3 rows at 0.01, 0.02, 0.03 USD (true running total
0.03), the bridge today reports 0.06 USD. The over-counted value
feeds `ecc-context-monitor.js`, which then trips its
COST_NOTICE_USD / COST_WARNING_USD / COST_CRITICAL_USD thresholds
on phantom spend AND injects the inflated number as
`additionalContext` into the live model turn — so the agent
itself is told a wrong cost.

Reproduced on `main` before this commit:

  $ cat > /tmp/eccc/.claude/metrics/costs.jsonl <<EOF
  {"session_id":"S1","estimated_cost_usd":0.01,"input_tokens":333,"output_tokens":166}
  {"session_id":"S1","estimated_cost_usd":0.02,"input_tokens":666,"output_tokens":333}
  {"session_id":"S1","estimated_cost_usd":0.03,"input_tokens":1000,"output_tokens":500}
  EOF

  $ HOME=/tmp/eccc node -e 'const m = require("./scripts/hooks/ecc-metrics-bridge.js"); \
      console.log(JSON.stringify(m.readSessionCost("S1")))'
  {"totalCost":0.06,"totalIn":1999,"totalOut":999}

Expected: `{"totalCost":0.03,"totalIn":1000,"totalOut":500}` (the
last cumulative row).
Actual: 2× over-count.

Fix: replace `+=` with `=` in the matching branch so the assigned
values reflect the most recent row encountered. The iteration
order is file order, which is also event time order, so the last
assignment wins — exactly the contract cost-tracker writes
against.

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

Regression test in `tests/hooks/ecc-metrics-bridge.test.js`:
`readSessionCost returns the LAST cumulative row, not the sum
(cost-tracker contract)`. The existing
`readSessionCost does not include unrelated default-session rows`
test happened to pass even with the bug because it only had one
target-session row — single-row sessions are coincidentally
correct under both formulas. The new test uses three rows so the
two formulas diverge.

A second issue in the same function — the 8 KiB tail-only read
silently drops older rows once a session's recent cumulative
totals scroll past that window — is fixed in the next commit.
2026-05-17 21:41:24 -04:00
Jamkris
7bb3172041 test(ci): coverage for round-1 fixes (quoted write-all, dedup, lifecycle scope)
Three test changes in response to the round-1 review:

1. **Add quoted-write-all coverage** (cubic P0 follow-up).
   Two new cases assert the regex now matches the double-quoted and
   single-quoted YAML forms of `permissions: "write-all"`:
     - `rejects double-quoted permissions: "write-all"`
     - `rejects single-quoted permissions: 'write-all'`
   Both fixtures trigger only the persist-credentials gate, so they
   exercise the WRITE_ALL_PATTERN OR-clause in isolation.

2. **Add expression+ref dedup coverage** (greptile P2 follow-up).
   `emits a single violation when both expressionPattern and refPattern
   match the same step` — uses `refs/pull/${{ … head.sha }}/merge` as
   the fixture (which matches both patterns) and counts ERROR lines for
   the `pull_request_target` rule, asserting exactly one. Re-introducing
   the duplicate-push bug would re-fail this test immediately.

3. **Drop the `npm ci without --ignore-scripts under write-all` test**
   (greptile P2). That test happened to pass under the previous
   `--ignore-scripts` regex, but `UNSAFE_INSTALL_PATTERNS` (added in
   `f7035b56`) fires unconditionally for every workflow regardless of
   permissions. So the test was exercising a pre-existing code path
   that has nothing to do with WRITE_ALL_PATTERN. Reviewer flagged this
   could mislead future contributors into thinking lifecycle-script
   enforcement is gated on write permissions.

   Replaced by the surrounding `rejects checkout credential persistence
   in workflows with permissions: write-all` test (already present) and
   the new quoted-form tests above, which all exercise the actual
   persist-credentials gate that the WRITE_ALL_PATTERN clause newly
   activates.

Test count: 22 → 24 (added 3 new, dropped 1). All green; `yarn lint`
clean.

The cohort comment above the write-all block was also tightened to
explicitly note that "the lifecycle-script gate already fires
unconditionally for every workflow" so the next reader sees the
distinction up front.
2026-05-17 21:19:29 -04:00
Jamkris
e06d038257 fix(ci): match quoted write-all + dedupe duplicate checkout violations
Two round-1 review findings, fixed together because they touch the
same regex/loop region of `findViolations`:

1. **cubic P0 — quoted write-all bypass**.
   `WRITE_ALL_PATTERN` was `/^\s*permissions:\s*write-all\b/m`, which
   does not match the perfectly valid YAML forms
   `permissions: "write-all"` and `permissions: 'write-all'`. A
   workflow that quoted the shorthand slipped right through the
   persist-credentials gate the previous commit was supposed to close.

   Reproduced before this commit:
     $ cat /tmp/q.yml
     name: bad
     on: [push]
     permissions: "write-all"
     jobs:
       do:
         runs-on: ubuntu-latest
         steps:
           - uses: actions/checkout@v4
     $ ECC_WORKFLOWS_DIR=/tmp node scripts/ci/validate-workflow-security.js
     Validated workflow security for 1 workflow files
     exit=0

   Fix: tighten the regex to
     /^\s*permissions:\s*["']?write-all["']?\s*$/m
   which accepts the bare, double-quoted, and single-quoted YAML forms
   while still anchoring on the `permissions:` key. The trailing `\s*$`
   prevents accidentally matching keys whose value happens to start
   with `write-all` (e.g. some future literal `write-all-something`).

2. **greptile P2 — duplicate violation when both patterns match**.
   A `ref: refs/pull/${{ github.event.pull_request.head.sha }}/merge`
   value matches both the `pull_request_target` rule's
   `expressionPattern` (the `head.sha` interpolation) and its
   `refPattern` (the `refs/pull/` literal). Each push generates an
   ERROR line with the same description and just a different
   `expression:` echo, so the reviewer sees the same violation twice.

   Fix: track `stepFlagged` inside the per-step loop and skip the
   `refPattern` fallback once any `expressionPattern` match has already
   produced a violation for this step. The `refPattern` is a fallback
   for ref-only forms (`refs/pull/123/head`, `${{ env.X }}` whose
   resolved value is a PR ref); when the more specific expression
   already fires, the fallback is redundant by definition.

After both fixes, the round-1 reproductions resolve cleanly:

  $ # quoted form now blocks
  $ ECC_WORKFLOWS_DIR=/tmp/q1/.github/workflows node scripts/ci/validate-workflow-security.js
  ERROR: quoted.yml:8 - workflows with write permissions must disable checkout credential persistence
  exit=1

  $ # combined head.sha + refs/pull now prints one ERROR, not two
  $ ECC_WORKFLOWS_DIR=/tmp/q2/.github/workflows node scripts/ci/validate-workflow-security.js
  ERROR: dup.yml:10 - pull_request_target must not checkout an untrusted pull_request head ref/repository
    Unsafe expression: ${{ github.event.pull_request.head.sha }}
  exit=1

Test additions land in the next commit.
2026-05-17 21:19:29 -04:00
Jamkris
cdbc925d89 fix(ci): flag refs/pull checkouts under pull_request_target
The `pull_request_target` rule's `expressionPattern` matches only
the canonical `github.event.pull_request.head.{ref,sha,repo.full_name}`
interpolations. It does not match the second canonical form of
the same exploit — fetching `refs/pull/<N>/{head,merge}` directly:

  - uses: actions/checkout@v4
    with:
      ref: refs/pull/${{ github.event.pull_request.number }}/merge

The merge-ref variant is what GitHub's own security guidance calls
out as the highest-severity privilege-escalation pattern under
`pull_request_target`: it materialises the PR's merge commit
(attacker code spliced with base), executes inside a workflow that
has full repo-scoped tokens, and gives the attacker the chance to
exfiltrate secrets or push to default branches. `refs/pull/N/head`
is functionally equivalent — same source, same trust boundary.

Reproduced on `main` before this commit:

  $ cat /tmp/bad.yml
  name: bad
  on: { pull_request_target: { types: [opened] } }
  permissions: { contents: read }
  jobs:
    do:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
          with:
            ref: refs/pull/${{ github.event.pull_request.number }}/merge
            persist-credentials: false
        - run: npm ci --ignore-scripts

  $ ECC_WORKFLOWS_DIR=/tmp node scripts/ci/validate-workflow-security.js
  Validated workflow security for 1 workflow files
  $ echo $?
  0

Expected: violation flagging the refs/pull checkout under pull_request_target.
Actual: passes silently.

Fix: add a `refPattern` to the `pull_request_target` rule:

    /^\s*ref:\s*['"]?[^'"\n]*refs\/(?:remotes\/)?pull\/[^'"\n\s]+/m

and apply it per checkout step inside the existing
event-gated loop. The pattern matches the ref VALUE so it catches
all interpolation shapes — `refs/pull/123/head`,
`refs/pull/${{ github.event.pull_request.number }}/merge`,
`${{ env.FOO }}/refs/pull/N/head` — without enumerating the
possible interpolations themselves.

Scoping: the rule is already gated on the workflow containing
`pull_request_target:`, so non-privileged `pull_request` workflows
that legitimately check out a PR ref are not affected.

After this commit the reproduction above exits 1 with:

  ERROR: bad.yml:10 - pull_request_target must not checkout an untrusted pull_request head ref/repository

Three new regression tests in `tests/ci/validate-workflow-security.test.js`:
  - rejects pull_request_target + refs/pull/<N>/merge
  - rejects pull_request_target + hardcoded refs/pull/<N>/head
  - allows pull_request_target with no `with.ref:` (base-ref checkout —
    the safe pattern from GitHub's own guidance)

Test count: 17 → 20 in this file; full `yarn test` still green.

Together with the previous commit, this closes the two
independent `validate-workflow-security.js` bypasses I found.
2026-05-17 21:19:29 -04:00
Jamkris
7f971b7e6f fix(ci): treat 'permissions: write-all' as a write-permission gate
`WRITE_PERMISSION_PATTERN` in `validate-workflow-security.js`
enumerates named GitHub Actions scopes (`contents: write`,
`issues: write`, etc.) to decide whether a workflow needs to:
  - disable `persist-credentials` on `actions/checkout`
  - pass `--ignore-scripts` to `npm ci`

The pattern misses the top-level shorthand `permissions:
write-all`, which is the strictly broader form — it grants every
named scope write access in a single line. As a result, a
workflow that opts into write-all currently slips both gates.

Reproduced on `main` before this commit:

  $ cat /tmp/bad.yml
  name: bad
  on: [push]
  permissions: write-all
  jobs:
    do:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
        - run: npm ci

  $ ECC_WORKFLOWS_DIR=/tmp node scripts/ci/validate-workflow-security.js
  Validated workflow security for 1 workflow files
  $ echo $?
  0

Expected: at least two violations (missing `persist-credentials:
false`, missing `--ignore-scripts`).
Actual: passes silently.

Fix: add a sibling pattern `WRITE_ALL_PATTERN` that matches
`^\s*permissions:\s*write-all\b` and OR it with
`WRITE_PERMISSION_PATTERN` at the single gate. Both top-level
and job-level `permissions:` blocks satisfy the `^\s*` prefix.

After this commit the reproduction above exits 1 with:

  ERROR: bad.yml:8 - workflows with write permissions must disable checkout credential persistence
  ERROR: bad.yml:9 - workflows with write permissions must install npm dependencies with --ignore-scripts

Three new regression tests in `tests/ci/validate-workflow-security.test.js`:
  - rejects write-all + credential-persisting checkout
  - rejects write-all + `npm ci` without `--ignore-scripts`
  - allows write-all when both gates are satisfied (no over-block)

Test count: 14 → 17 in this file; full `yarn test` still green.

A separate `refs/pull/N/merge` bypass under `pull_request_target`
exists in the same validator and is fixed in the next commit.
2026-05-17 21:19:29 -04:00
Affaan Mustafa
f318e91b23 docs: refresh rc1 operator readiness dashboard 2026-05-17 21:06:55 -04:00
Affaan Mustafa
666b4e2261 fix(installer): harden locale docs install 2026-05-17 20:46:04 -04:00
Claude
71aedad889 feat(installer): add --locale flag for translated docs installation
Adds `--locale <code>` support to the ECC installer so users can install
localized reference docs (agents, commands, skills, rules) into
`~/.claude/docs/<locale>/` alongside the existing English installation.

Changes:
- manifests/install-modules.json: add 8 locale doc modules (docs-ja-JP,
  docs-zh-CN, docs-ko-KR, docs-pt-BR, docs-ru, docs-tr, docs-vi-VN,
  docs-zh-TW), each with kind="docs" and defaultInstall=false
- manifests/install-components.json: add 8 locale: components mapping to
  the new modules
- scripts/lib/install-manifests.js: add locale: family prefix,
  SUPPORTED_LOCALES, LOCALE_ALIAS_TO_COMPONENT_ID (with aliases like
  ja=ja-JP, zh=zh-CN, ko=ko-KR), and listSupportedLocales()
- scripts/lib/install/request.js: add --locale flag to parseInstallArgs(),
  resolve locale alias → component ID in normalizeInstallRequest(), throw
  on unsupported locale codes
- scripts/lib/install-targets/claude-home.js: map docs/<locale>/ source
  paths to ~/.claude/docs/<locale>/ destination (side-by-side, no overwrite
  of English files)
- scripts/install-apply.js: import listSupportedLocales, add --locale
  usage line and available locales list to --help output

Usage examples:
  ./install.sh --locale ja                    # Japanese docs only
  ./install.sh --profile core --locale zh-CN  # core profile + zh-CN docs
  ./install.sh typescript --locale ja         # legacy + locale (errors)
2026-05-17 20:32:52 -04:00
Affaan Mustafa
519c592a12 fix: skip disabled discussion queries in platform audit 2026-05-17 20:32:09 -04:00
Affaan Mustafa
b113edac4b docs: remove personal paths from rc1 evidence 2026-05-17 18:02:23 -04:00
Affaan Mustafa
a9c8c3ed76 docs: refresh rc1 evidence after security recheck 2026-05-17 17:59:17 -04:00
Affaan Mustafa
e6c16b40b8 docs: refresh rc1 dashboard after security hardening 2026-05-17 17:57:37 -04:00
Affaan Mustafa
36d390aa7d security: cover gh-token-monitor token persistence 2026-05-17 17:46:35 -04:00
Affaan Mustafa
6b282aaa43 docs(th): address README review nits 2026-05-17 17:28:06 -04:00
Roongroj P
989559a728 docs(th): add Thai (th) README translation
Adds docs/th/README.md with a concise onboarding-style Thai
translation mirroring the docs/vi-VN format. Updates the language
switchers in the English, Simplified Chinese, Traditional Chinese,
Japanese, Korean, Portuguese (BR), Russian, Turkish, Vietnamese,
and Simplified Chinese docs READMEs to link to the new Thai page.

The English README remains the canonical source of truth; the Thai
page links back to it for full content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:28:06 -04:00
Affaan Mustafa
3539bdbef6 Refresh rc1 launch readiness copy 2026-05-17 16:43:04 -04:00
Affaan Mustafa
27dc2918a2 Regenerate preview pack readiness dashboard after lint fix 2026-05-17 15:41:19 -04:00
Affaan Mustafa
822ed726a8 Fix preview pack smoke lint 2026-05-17 15:40:29 -04:00
Affaan Mustafa
fd7c7cf47f Regenerate preview pack readiness dashboard 2026-05-17 15:36:41 -04:00
Affaan Mustafa
3215e655ef Add preview pack smoke gate 2026-05-17 15:35:23 -04:00
Affaan Mustafa
1a384dc533 Regenerate Linear progress readiness dashboard 2026-05-17 15:14:40 -04:00
Affaan Mustafa
355c4f12cf Refresh Linear progress readiness detection 2026-05-17 15:13:42 -04:00
Affaan Mustafa
5c135fb846 Regenerate legacy readiness dashboard 2026-05-17 14:49:05 -04:00
Affaan Mustafa
f397216aa0 Track legacy localization tail in readiness dashboard 2026-05-17 14:47:29 -04:00
Affaan Mustafa
7b2f0125bb Regenerate operator readiness dashboard 2026-05-17 14:29:55 -04:00
Affaan Mustafa
f9bf94b246 Refresh operator dashboard readiness markers 2026-05-17 14:28:16 -04:00
Affaan Mustafa
ffcde01e4b docs: record marketplace readback state 2026-05-17 14:09:48 -04:00
Affaan Mustafa
4ca31057c6 docs: record billing announcement preflight 2026-05-17 13:53:20 -04:00
Affaan Mustafa
fa7f8e2287 docs: record hosted promotion judge audit traces 2026-05-17 13:37:47 -04:00
Affaan Mustafa
3aab0a67f4 docs: record policy promotion operator telemetry 2026-05-17 13:13:53 -04:00
Affaan Mustafa
ddc1e45f2a docs: record policy promotion hosted telemetry 2026-05-17 12:52:30 -04:00
Affaan Mustafa
c8a66e13d4 docs: record AgentShield promotion action outputs 2026-05-17 12:12:18 -04:00
Affaan Mustafa
3dc884acf2 docs: record AgentShield hardening action outputs 2026-05-17 11:07:27 -04:00
Affaan Mustafa
c40b6c0cf5 docs: refresh rc1 readiness evidence 2026-05-17 07:42:26 -04:00
Affaan Mustafa
744f416997 test: normalize zed install path assertion 2026-05-17 07:27:05 -04:00
Affaan Mustafa
2371a3cf05 feat: add zed install target 2026-05-17 07:06:49 -04:00
Affaan Mustafa
fb6d4a7104 fix: tighten supply-chain ioc markers 2026-05-17 06:48:01 -04:00
Affaan Mustafa
98592ab6b8 docs: mirror AgentShield npm age-gate correction 2026-05-17 05:14:10 -04:00
Affaan Mustafa
1b9ecb9004 docs: mirror AgentShield hardening evidence 2026-05-17 04:49:56 -04:00
Affaan Mustafa
bf1ccb0a65 docs: record AgentShield promotion review progress 2026-05-17 03:58:31 -04:00
Affaan Mustafa
0dd78387c6 docs: refresh rc1 preview pack pointers 2026-05-17 03:02:46 -04:00
Affaan Mustafa
a9edd20462 docs: sync May 17 roadmap evidence 2026-05-17 02:59:21 -04:00
Affaan Mustafa
99dd6ac0db docs: refresh May 17 release readiness evidence 2026-05-17 02:44:14 -04:00
Affaan Mustafa
afe0ae8d72 fix(ja-JP): remove broken autonomous loop anchors 2026-05-17 02:35:32 -04:00
Affaan Mustafa
9495b109e2 fix(ja-JP): repair localized docs links 2026-05-17 02:31:40 -04:00
Affaan Mustafa
b98f007a51 fix(ja-JP): repair localized security links 2026-05-17 02:31:40 -04:00
Claude
6b59276d76 fix(ja-JP): translate frontmatter description to Japanese in 3 skills
- skill-scout: translate description field
- tinystruct-patterns: translate description field
- ui-to-vue: translate description field
2026-05-17 02:31:40 -04:00
Claude
fabb4d0c11 fix(ja-JP): address review feedback and add 5 missing skills
- Fix Chinese term '提炼' → '蒸留' in commands/rules-distill.md
- Fix '重大な所見' (Critical→重大) in agents/opensource-sanitizer.md
- Fix non-transactional persistence in swift-actor-persistence/SKILL.md:
  add rollback logic so cache stays consistent if disk write fails
- Clarify anti-pattern wording: 'configurable file URL' → 'externally
  mutable after init' to remove internal inconsistency (P2)
- Fix broken relative link in videodb/reference/api-reference.md:
  ../../../../../skills/... → ./editor.md
- Add 5 previously missing SKILL.md translations:
  skill-scout, tinystruct-patterns, ui-to-vue, vite-patterns,
  windows-desktop-e2e
2026-05-17 02:31:40 -04:00
Claude
d66b5fa480 docs: fix zh-CN parity — add 44 missing files to ja-JP
Add files present in zh-CN but missing from ja-JP:
- commands: claw, context-budget, devfleet, docs, projects, prompt-optimize, rules-distill (7 files)
- skills: regex-vs-llm-structured-text, remotion-video-creation, repo-scan, research-ops,
  returns-reverse-logistics, rules-distill, rust-patterns, rust-testing, skill-comply,
  skill-stocktake, social-graph-ranker, swift-actor-persistence, swift-concurrency-6-2,
  swift-protocol-di-testing, swiftui-patterns, team-builder, terminal-ops, token-budget-advisor,
  ui-demo, unified-notifications-ops, video-editing, videodb (+reference/*), visa-doc-translate,
  workspace-surface-audit, x-api (37 files)

Result: ja-JP now has 517 files vs zh-CN 412 files.
zh-CN parity: 0 missing files (complete parity achieved).
2026-05-17 02:31:40 -04:00