Compare commits

..

7 Commits

Author SHA1 Message Date
Jamkris
85d33748e0 test(hooks): regression coverage for round 1 review fixes
9 new cases locking in the behavior added by the previous two
commits. Each was verified to fail before the fix and pass after.

Greptile — quote-aware depth counting:
  - blocks $(echo ")"; (npm run dev))
  - blocks (echo ")"; npm run dev)
  - allows $(echo "(npm run dev)") — () inside double-quoted body is literal

Greptile — brace groups:
  - blocks { npm run dev; }
  - blocks echo hi && { npm run dev; }
  - allows {npm run dev} — bash brace-group syntax requires a space after {

CodeRabbit — missing package-manager variants:
  - blocks yarn run dev (yarn 1.x convention)
  - blocks bun dev (bun bare form)

CodeRabbit nitpick — symmetric quote test:
  - blocks echo "$(npm run dev)" — double-quoted substitution still substitutes

The `{npm run dev}` allow case is intentional: bash treats `{` as
a reserved word only when followed by whitespace. The pre-fix code
already passed this through, but until now we never asserted it,
so a future change to brace handling could silently start blocking
literal `{npm` tokens.
2026-05-14 12:24:45 +09:00
Jamkris
e2eaf4ac2f fix(hooks): cover brace groups + yarn-run/bun-bare dev variants
Two false-negatives surfaced in PR #1889 review:

1. Brace-group bypass (Greptile).
   `{ npm run dev; }` evaluates the dev command in the *current*
   shell — semantically distinct from `( ... )` but with the same
   effect for this hook. `splitShellSegments` correctly cleaves the
   group at `;` into `["{ npm run dev", "}"]`, but the first segment's
   leading token under `readToken` is the bare `{`, which was not in
   `DEV_COMMAND_WORDS`, so the dev-pattern check was skipped.

   Fix: treat `{` and `}` as no-op tokens in `getLeadingCommandWord`
   so we keep walking to the real command word. Matches how shell
   itself parses brace groups (the braces are reserved words, not
   commands). Bash requires a space after `{` and a terminator before
   `}` for an actual group, so `{npm run dev}` correctly remains
   allowed (single token `{npm`, not in `DEV_COMMAND_WORDS`).

2. Missing yarn-run / bun-bare variants (CodeRabbit).
   Both `yarn dev` *and* `yarn run dev` are valid (the latter is what
   `package.json` actually wires `dev` to under yarn 1.x). The same
   `(run )?` symmetry applies to bun. The previous `DEV_PATTERN` only
   matched `yarn\s+dev` and `bun\s+run\s+dev`, allowing the cross
   forms to pass through silently.

   Fix: `yarn(?:\s+run)?\s+dev` and `bun(?:\s+run)?\s+dev` — same
   shape `pnpm(?:\s+run)?\s+dev` was already using.

Verified after this commit (every form now exits 2):

  { npm run dev; }
  { npm run dev ; }
  echo hi && { npm run dev; }
  ({ npm run dev; })
  $( { npm run dev; } )
  yarn run dev
  bun dev

Verified still allowed (no regression):

  echo "{ npm run dev; }"   # literal inside double quotes
  {npm run dev}             # not a brace group per bash syntax
2026-05-14 12:23:55 +09:00
Jamkris
70b86d81c4 fix(lib): track quote state inside command-substitution depth counters
Greptile flagged a bypass in PR #1889: `$(echo ")"; (npm run dev))`
threaded the depth-counting loops in `extractCommandSubstitutions`
and `extractSubshellGroups` to terminate early, because a literal `)`
inside double quotes was treated as a real closing paren. The
truncated body then ended in a dangling `"` that toggled `inDouble`
in the outer scan, masking the subsequent `(npm run dev)` group from
extraction.

Reproduced (before this commit) by piping the synthetic PreToolUse
payload `{"tool_input":{"command":"$(echo \")\"; (npm run dev))"}}`
into `scripts/hooks/pre-bash-dev-server-block.js` and observing
exit 0 (allow) where the dev pattern is clearly present.

Fix: each `$(...)` and `(...)` body loop now tracks its own
single/double quote state and only treats `(` / `)` as depth
delimiters when outside quotes. The quoted `)` no longer closes
the group early, the body now extends to the real closing paren,
and the outer scan's quote state remains untouched.

After this commit:
  $ echo '{"tool_input":{"command":"$(echo \")\"; (npm run dev))"}}' \
      | node scripts/hooks/pre-bash-dev-server-block.js; echo $?
  2

The symmetric form `$(echo "(npm run dev)")` correctly remains
allowed (bash does not honor `(...)` inside double quotes).
2026-05-14 12:22:22 +09:00
Jamkris
4f4654bf21 test(hooks): regression coverage for dev-server-block subshell bypass
Lock in the behavior added by the previous commit. Each new case was
verified to fail before the fix and pass after.

Bypasses now blocked (exit 2):
- \$(npm run dev)              command substitution
- \`npm run dev\`              backtick substitution
- echo \$(npm run dev)         substitution inside an argument
- (npm run dev)               plain subshell group
- \$(echo a; npm run dev)      substitution containing a sequenced segment
- (pnpm dev)                  plain subshell group, alt package manager

Allow cases — explicitly proven NOT to regress so the fix doesn't
over-block legitimate uses:
- (tmux new-session -d -s dev "npm run dev")   tmux launcher inside ()
- git commit -m '(npm run dev)'                literal in single quotes
- echo "(npm run dev)"                         literal in double quotes
  (bash does NOT subshell () inside double quotes)
- git commit -m '\$(npm run dev) fix'          literal in single quotes

Single- and double-quote allow cases are important: they distinguish a
real subshell construct from one that's just text inside a string,
which is what `extractSubshellGroups` / `extractCommandSubstitutions`
quote-awareness is for.
2026-05-14 11:22:44 +09:00
Jamkris
a7e51e8046 fix(hooks): close subshell bypass in pre-bash-dev-server-block
Before this commit the dev-server-block hook ran the leading-command
and dev-pattern check only against the top-level segments returned by
`splitShellSegments`, which doesn't split on `$(...)`, backticks, or
plain `(...)`. That left the policy bypassable by wrapping a dev
command in any of those constructs:

  $(npm run dev)
  `npm run dev`
  echo $(npm run dev)
  (npm run dev)

Each verified by piping a synthetic PreToolUse payload into the hook
on this branch: every form above returned exit 0 (allow) where a plain
`npm run dev` correctly returned exit 2 (block).

Fix: expand the check space before running the leading-command rule.
A small BFS walks the raw command, harvesting bodies from
`extractCommandSubstitutions` (`$(...)` and backticks) and from
`extractSubshellGroups` (plain `(...)`), then splits each harvested
body through `splitShellSegments` and feeds the result into the
existing `isBlockedDevSegment` check.

This preserves every existing allow case (`tmux new-session -d -s dev
"npm run dev"`, quoted-string mentions like `git commit -m "npm run
dev fix"`, `echo hi`) because the leading-command rule is unchanged —
only the set of segments it runs against grew.

Known limitation, not fixed here: `eval "$(echo npm run dev)"` still
slips through because the substitution body's leading command is
`echo`, and statically modeling echo's output to recover the executed
command is out of scope. The same class affects `gateguard-fact-force`
(via `eval "$(echo rm -rf /)"` etc.) and is best addressed in both
hooks together as a follow-up rather than as a one-off here.
2026-05-14 11:21:41 +09:00
Jamkris
04dc03c3af feat(lib): add extractSubshellGroups for plain (...) subshells
`extractCommandSubstitutions` only walks `$(...)` and backticks — the two
shell constructs whose bodies are captured as strings. Bash also has
plain `(...)` subshells (e.g. `(npm run dev)`), where the body executes
in a child shell but is not value-captured. Our PreToolUse hooks need
to peer inside those too, because a `(...)` group bypasses the
top-level segment splitter just like `$(...)` does.

This commit adds a sibling extractor with the same conventions as
`extractCommandSubstitutions`:

- single quotes literal — `'(npm run dev)'` is a string, ignored
- double quotes literal for parens — `"(npm run dev)"` is a string
  (bash only honors `$(...)`, not bare `(...)`, inside double quotes)
- skips `$(...)` and backtick spans so we don't double-extract
  bodies the other helper already handles
- recurses into its own bodies for nested groups

No consumer yet; the next commit wires both extractors into
`scripts/hooks/pre-bash-dev-server-block.js` to close the subshell
bypass surface.
2026-05-14 11:17:46 +09:00
Jamkris
0a380c3e85 feat(lib): extract shell command-substitution parser to shared lib
Extract the `extractCommandSubstitutions` function originally
introduced in scripts/hooks/gateguard-fact-force.js (PR #1853
round 2) into scripts/lib/shell-substitution.js so other PreToolUse
hooks can reuse the same single-quote-aware, double-quote-aware,
nested-subshell-aware parser without duplicating it.

No behavior change in this commit — the function body is copied
verbatim and exposed via `module.exports`. The next commit wires it
into scripts/hooks/pre-bash-dev-server-block.js to close that hook's
own subshell-bypass holes.

gateguard-fact-force.js still defines its own private copy of the
function; consolidating both call sites onto this shared lib is a
follow-up worth doing once this PR lands, but is intentionally out
of scope here to keep the diff focused on the dev-server-block fix.
2026-05-14 11:10:40 +09:00
12 changed files with 410 additions and 1190 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +0,0 @@
{
"sessionId": "d43d7ca3-7fe6-40a0-8bf6-92c3848903b8",
"eventCount": 13,
"lastAnalysisTimestamp": null,
"lastAnalysisEventCount": 0
}

View File

@@ -149,21 +149,6 @@ jobs:
env: env:
CLAUDE_CODE_PACKAGE_MANAGER: ${{ inputs.package-manager }} CLAUDE_CODE_PACKAGE_MANAGER: ${{ inputs.package-manager }}
- name: Run coverage
run: npm run coverage
continue-on-error: true
env:
CLAUDE_CODE_PACKAGE_MANAGER: ${{ inputs.package-manager }}
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-${{ inputs.os }}-node${{ inputs.node-version }}-${{ inputs.package-manager }}
path: |
coverage/
*.lcov
- name: Upload test artifacts - name: Upload test artifacts
if: failure() if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1

View File

@@ -89,7 +89,7 @@ This repo is the raw code only. The guides explain everything.
### v2.0.0-rc.1 — Surface Refresh, Operator Workflows, and ECC 2.0 Alpha (Apr 2026) ### v2.0.0-rc.1 — Surface Refresh, Operator Workflows, and ECC 2.0 Alpha (Apr 2026)
- **Dashboard GUI** — New Tkinter-based desktop application (`ecc_dashboard.py` or `npm run dashboard`) with dark/light theme toggle, font customization, and project logo in header and taskbar. - **Dashboard GUI** — New Tkinter-based desktop application (`ecc_dashboard.py` or `npm run dashboard`) with dark/light theme toggle, font customization, and project logo in header and taskbar.
- **Public surface synced to the live repo** — metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: 60 agents, 228 skills, and 75 legacy command shims. - **Public surface synced to the live repo** — metadata, catalog counts, plugin manifests, and install-facing docs now match the actual OSS surface: 55 agents, 208 skills, and 72 legacy command shims.
- **Operator and outbound workflow expansion** — `brand-voice`, `social-graph-ranker`, `connections-optimizer`, `customer-billing-ops`, `ecc-tools-cost-audit`, `google-workspace-ops`, `project-flow-ops`, and `workspace-surface-audit` round out the operator lane. - **Operator and outbound workflow expansion** — `brand-voice`, `social-graph-ranker`, `connections-optimizer`, `customer-billing-ops`, `ecc-tools-cost-audit`, `google-workspace-ops`, `project-flow-ops`, and `workspace-surface-audit` round out the operator lane.
- **Media and launch tooling** — `manim-video`, `remotion-video-creation`, and upgraded social publishing surfaces make technical explainers and launch content part of the same system. - **Media and launch tooling** — `manim-video`, `remotion-video-creation`, and upgraded social publishing surfaces make technical explainers and launch content part of the same system.
- **Framework and product surface growth** — `nestjs-patterns`, richer Codex/OpenCode install surfaces, and expanded cross-harness packaging keep the repo usable beyond Claude Code alone. - **Framework and product surface growth** — `nestjs-patterns`, richer Codex/OpenCode install surfaces, and expanded cross-harness packaging keep the repo usable beyond Claude Code alone.

View File

@@ -1,798 +0,0 @@
{
"generated": "2026-05-14T18:36:38.210Z",
"totalCommands": 75,
"commands": [
{
"command": "aside",
"description": "Aside Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/aside.md"
},
{
"command": "auto-update",
"description": "Auto Update",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/auto-update.md"
},
{
"command": "build-fix",
"description": "Build and Fix",
"type": "refactoring",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/build-fix.md"
},
{
"command": "checkpoint",
"description": "Checkpoint Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/checkpoint.md"
},
{
"command": "code-review",
"description": "Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/code-review.md"
},
{
"command": "cost-report",
"description": "Cost Report",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/cost-report.md"
},
{
"command": "cpp-build",
"description": "C++ Build and Fix",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"cpp-coding-standards"
],
"path": "commands/cpp-build.md"
},
{
"command": "cpp-review",
"description": "C++ Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"cpp-coding-standards",
"cpp-testing"
],
"path": "commands/cpp-review.md"
},
{
"command": "cpp-test",
"description": "C++ TDD Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"cpp-testing",
"tdd-workflow"
],
"path": "commands/cpp-test.md"
},
{
"command": "ecc-guide",
"description": "/ecc-guide",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [
"ecc-guide",
"security-scan"
],
"path": "commands/ecc-guide.md"
},
{
"command": "evolve",
"description": "Analyze instincts and suggest or generate evolved structures",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"continuous-learning-v2"
],
"path": "commands/evolve.md"
},
{
"command": "fastapi-review",
"description": "FastAPI Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/fastapi-review.md"
},
{
"command": "feature-dev",
"description": "",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/feature-dev.md"
},
{
"command": "flutter-build",
"description": "Flutter Build and Fix",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"flutter-dart-code-review"
],
"path": "commands/flutter-build.md"
},
{
"command": "flutter-review",
"description": "Flutter Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"flutter-dart-code-review"
],
"path": "commands/flutter-review.md"
},
{
"command": "flutter-test",
"description": "Flutter Test",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"flutter-dart-code-review"
],
"path": "commands/flutter-test.md"
},
{
"command": "gan-build",
"description": "",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/gan-build.md"
},
{
"command": "gan-design",
"description": "",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/gan-design.md"
},
{
"command": "go-build",
"description": "Go Build and Fix",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"golang-patterns"
],
"path": "commands/go-build.md"
},
{
"command": "go-review",
"description": "Go Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"golang-patterns",
"golang-testing"
],
"path": "commands/go-review.md"
},
{
"command": "go-test",
"description": "Go TDD Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"golang-testing",
"tdd-workflow"
],
"path": "commands/go-test.md"
},
{
"command": "gradle-build",
"description": "Gradle Build Fix",
"type": "build",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/gradle-build.md"
},
{
"command": "harness-audit",
"description": "Harness Audit Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/harness-audit.md"
},
{
"command": "hookify-configure",
"description": "",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/hookify-configure.md"
},
{
"command": "hookify-help",
"description": "",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/hookify-help.md"
},
{
"command": "hookify-list",
"description": "",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/hookify-list.md"
},
{
"command": "hookify",
"description": "",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/hookify.md"
},
{
"command": "instinct-export",
"description": "Export instincts from project/global scope to a file",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/instinct-export.md"
},
{
"command": "instinct-import",
"description": "Import instincts from file or URL into project/global scope",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"continuous-learning-v2"
],
"path": "commands/instinct-import.md"
},
{
"command": "instinct-status",
"description": "Show learned instincts (project + global) with confidence",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [
"continuous-learning-v2"
],
"path": "commands/instinct-status.md"
},
{
"command": "jira",
"description": "Jira Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"jira-integration"
],
"path": "commands/jira.md"
},
{
"command": "kotlin-build",
"description": "Kotlin Build and Fix",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"kotlin-patterns"
],
"path": "commands/kotlin-build.md"
},
{
"command": "kotlin-review",
"description": "Kotlin Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"kotlin-patterns",
"kotlin-testing"
],
"path": "commands/kotlin-review.md"
},
{
"command": "kotlin-test",
"description": "Kotlin TDD Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"kotlin-testing",
"tdd-workflow"
],
"path": "commands/kotlin-test.md"
},
{
"command": "learn-eval",
"description": "\"Under 130 characters\"",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/learn-eval.md"
},
{
"command": "learn",
"description": "/learn - Extract Reusable Patterns",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/learn.md"
},
{
"command": "loop-start",
"description": "Loop Start Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/loop-start.md"
},
{
"command": "loop-status",
"description": "Loop Status Command",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/loop-status.md"
},
{
"command": "model-route",
"description": "Model Route Command",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/model-route.md"
},
{
"command": "multi-backend",
"description": "Backend - Backend-Focused Development",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/multi-backend.md"
},
{
"command": "multi-execute",
"description": "Execute - Multi-Model Collaborative Execution",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/multi-execute.md"
},
{
"command": "multi-frontend",
"description": "Frontend - Frontend-Focused Development",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/multi-frontend.md"
},
{
"command": "multi-plan",
"description": "Plan - Multi-Model Collaborative Planning",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"accessibility"
],
"path": "commands/multi-plan.md"
},
{
"command": "multi-workflow",
"description": "Workflow - Multi-Model Collaborative Development",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/multi-workflow.md"
},
{
"command": "plan-prd",
"description": "PRD Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/plan-prd.md"
},
{
"command": "plan",
"description": "Plan Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/plan.md"
},
{
"command": "pm2",
"description": "PM2 Init",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/pm2.md"
},
{
"command": "pr",
"description": "Create Pull Request",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/pr.md"
},
{
"command": "project-init",
"description": "/project-init",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"ecc-guide"
],
"path": "commands/project-init.md"
},
{
"command": "projects",
"description": "List known projects and their instinct statistics",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [
"continuous-learning-v2"
],
"path": "commands/projects.md"
},
{
"command": "promote",
"description": "Promote project-scoped instincts to global scope",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [
"continuous-learning-v2"
],
"path": "commands/promote.md"
},
{
"command": "prp-commit",
"description": "Smart Commit",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/prp-commit.md"
},
{
"command": "prp-implement",
"description": "PRP Implement",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/prp-implement.md"
},
{
"command": "prp-plan",
"description": "PRP Plan",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/prp-plan.md"
},
{
"command": "prp-pr",
"description": "Create Pull Request",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/prp-pr.md"
},
{
"command": "prp-prd",
"description": "Product Requirements Document Generator",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/prp-prd.md"
},
{
"command": "prune",
"description": "Delete pending instincts older than 30 days that were never promoted",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [
"continuous-learning-v2"
],
"path": "commands/prune.md"
},
{
"command": "python-review",
"description": "Python Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"python-patterns",
"python-testing"
],
"path": "commands/python-review.md"
},
{
"command": "quality-gate",
"description": "Quality Gate Command",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/quality-gate.md"
},
{
"command": "refactor-clean",
"description": "Refactor Clean",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/refactor-clean.md"
},
{
"command": "resume-session",
"description": "Resume Session Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/resume-session.md"
},
{
"command": "review-pr",
"description": "",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/review-pr.md"
},
{
"command": "rust-build",
"description": "Rust Build and Fix",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"rust-patterns"
],
"path": "commands/rust-build.md"
},
{
"command": "rust-review",
"description": "Rust Code Review",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"rust-patterns",
"rust-testing"
],
"path": "commands/rust-review.md"
},
{
"command": "rust-test",
"description": "Rust TDD Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [
"rust-patterns",
"rust-testing"
],
"path": "commands/rust-test.md"
},
{
"command": "santa-loop",
"description": "Santa Loop",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/santa-loop.md"
},
{
"command": "save-session",
"description": "Save Session Command",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/save-session.md"
},
{
"command": "security-scan",
"description": "Security Scan Command",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [
"security-scan"
],
"path": "commands/security-scan.md"
},
{
"command": "sessions",
"description": "Sessions Command",
"type": "general",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/sessions.md"
},
{
"command": "setup-pm",
"description": "Package Manager Setup",
"type": "build",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/setup-pm.md"
},
{
"command": "skill-create",
"description": "Analyze local git history to extract coding patterns and generate SKILL.md files. Local version of the Skill Creator GitHub App.",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/skill-create.md"
},
{
"command": "skill-health",
"description": "Show skill portfolio health dashboard with charts and analytics",
"type": "review",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/skill-health.md"
},
{
"command": "test-coverage",
"description": "Test Coverage",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/test-coverage.md"
},
{
"command": "update-codemaps",
"description": "Update Codemaps",
"type": "planning",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/update-codemaps.md"
},
{
"command": "update-docs",
"description": "Update Documentation",
"type": "testing",
"primaryAgents": [],
"allAgents": [],
"skills": [],
"path": "commands/update-docs.md"
}
],
"statistics": {
"byType": {
"testing": 52,
"refactoring": 1,
"review": 11,
"build": 2,
"general": 8,
"planning": 1
},
"topAgents": [],
"topSkills": [
{
"skill": "continuous-learning-v2",
"count": 6
},
{
"skill": "tdd-workflow",
"count": 3
},
{
"skill": "flutter-dart-code-review",
"count": 3
},
{
"skill": "rust-patterns",
"count": 3
},
{
"skill": "cpp-coding-standards",
"count": 2
},
{
"skill": "cpp-testing",
"count": 2
},
{
"skill": "ecc-guide",
"count": 2
},
{
"skill": "security-scan",
"count": 2
},
{
"skill": "golang-patterns",
"count": 2
},
{
"skill": "golang-testing",
"count": 2
}
]
}
}

View File

@@ -153,24 +153,6 @@ As of 2026-05-13:
`/ecc-tools analyze --job status` now reads the #65 latest-result cache for `/ecc-tools analyze --job status` now reads the #65 latest-result cache for
the current PR head and posts a compact completed/blocked/not-run table with the current PR head and posts a compact completed/blocked/not-run table with
the next hosted job command, without queueing work or billing usage. the next hosted job command, without queueing work or billing usage.
- ECC-Tools PR #67 merged as `f20e6bec2b0bf49e4cc36e08b7285c795973b73d`
and made the hosted depth-plan check-run status-aware:
queued PR analysis now reads the #65/#66 latest-result cache when publishing
`ECC Tools / Hosted Depth Plan`, includes the latest hosted run status in
the plan table, and recommends the next unrun ready job before reruns.
- ECC-Tools PR #68 merged as `2cde524b5ef8f34ab7bb1af973248fe4be4359f8`
and added deterministic hosted promotion readiness:
opened/synchronized PRs now publish a non-blocking
`ECC Tools / Hosted Promotion Readiness` check-run that compares changed
files against the checked-in evaluator/RAG corpus, warns on missing
hosted-job promotion evidence, and can be disabled with
`PR_HOSTED_PROMOTION_READINESS_CHECK_MODE=off`.
- ECC-Tools PR #69 merged as `d0112dac7cef807ae27def41f057682ef0772cce`
and extended hosted promotion readiness with deterministic output scoring:
the check now reads cached completed hosted job results for the current PR
head, scores their artifacts and findings against evaluator/RAG corpus
expectations, and treats matching hosted artifacts as promotion evidence
before reporting a gap.
- Handoff `ecc-supply-chain-audit-20260513-0645.md` under - Handoff `ecc-supply-chain-audit-20260513-0645.md` under
`~/.cluster-swarm/handoffs/` `~/.cluster-swarm/handoffs/`
records the May 13 supply-chain sweep: no active lockfile/manifest hit for records the May 13 supply-chain sweep: no active lockfile/manifest hit for
@@ -404,16 +386,6 @@ As of 2026-05-13:
`/ecc-tools analyze --job status`, summarizing completed, blocked, and `/ecc-tools analyze --job status`, summarizing completed, blocked, and
not-yet-run hosted jobs for the PR head and recommending the next hosted job not-yet-run hosted jobs for the PR head and recommending the next hosted job
command. command.
- ECC-Tools PR #67 feeds those cached results back into the hosted depth-plan
check-run so queued analysis recommends the next unrun ready hosted job from
cache state instead of repeating the static readiness order.
- ECC-Tools PR #68 adds the first evaluator-backed hosted promotion gate:
opened/synchronized PRs get a non-blocking Hosted Promotion Readiness
check-run that turns the evaluator/RAG corpus into warnings when changed
files match fixture scenarios without their expected evidence artifacts.
- ECC-Tools PR #69 extends that gate to score cached completed hosted job
outputs for the current PR head, so hosted artifacts can satisfy corpus
evidence expectations before the check reports a promotion gap.
- ECC PR #1803 landed the contributor Quarkus handling branch after maintainer - ECC PR #1803 landed the contributor Quarkus handling branch after maintainer
cleanup, current-`main` alignment, full local validation, and preservation of cleanup, current-`main` alignment, full local validation, and preservation of
the author's removal of incomplete ja-JP and zh-CN Quarkus translations. the author's removal of incomplete ja-JP and zh-CN Quarkus translations.
@@ -467,10 +439,10 @@ is not complete unless the evidence column exists and has been freshly verified.
| Claude and Codex plugin publication | Contact/submission path with required artifacts and status | Publication readiness, naming matrix, and May 12 dry-run evidence document plugin validation, clean-checkout Claude tag/install smoke, and Codex marketplace CLI shape | Needs explicit approval for real tag/push and marketplace submission | | Claude and Codex plugin publication | Contact/submission path with required artifacts and status | Publication readiness, naming matrix, and May 12 dry-run evidence document plugin validation, clean-checkout Claude tag/install smoke, and Codex marketplace CLI shape | Needs explicit approval for real tag/push and marketplace submission |
| Articles, tweets, and announcements | X thread, LinkedIn copy, GitHub release copy, push checklist | Draft launch collateral exists under rc.1 release docs | Needs URL-backed refresh | | Articles, tweets, and announcements | X thread, LinkedIn copy, GitHub release copy, push checklist | Draft launch collateral exists under rc.1 release docs | Needs URL-backed refresh |
| AgentShield enterprise iteration | Policy gates, SARIF, packs, provenance, corpus, HTML reports, exception lifecycle audit, baseline drift Action/CLI surfaces, evidence-pack redaction, harness adapter registry, enterprise research roadmap, supply-chain hardened release path, CI-safe baseline fingerprints, corpus accuracy recommendations, remediation workflow phases, env proxy hijack corpus coverage | PRs #53, #55-#64, #67-#69, and #78-#82 landed with test evidence; native PDF export deferred in favor of self-contained HTML plus print-to-PDF until explicit enterprise demand appears; `docs/architecture/agentshield-enterprise-research-roadmap.md` now has baseline drift, evidence-pack bundle, redaction, adapter-registry, supply-chain hardening, hashed baseline fingerprints, corpus accuracy recommendation, remediation workflow, and env proxy hijack corpus slices landed | Next hosted evidence-pack workflow depth | | AgentShield enterprise iteration | Policy gates, SARIF, packs, provenance, corpus, HTML reports, exception lifecycle audit, baseline drift Action/CLI surfaces, evidence-pack redaction, harness adapter registry, enterprise research roadmap, supply-chain hardened release path, CI-safe baseline fingerprints, corpus accuracy recommendations, remediation workflow phases, env proxy hijack corpus coverage | PRs #53, #55-#64, #67-#69, and #78-#82 landed with test evidence; native PDF export deferred in favor of self-contained HTML plus print-to-PDF until explicit enterprise demand appears; `docs/architecture/agentshield-enterprise-research-roadmap.md` now has baseline drift, evidence-pack bundle, redaction, adapter-registry, supply-chain hardening, hashed baseline fingerprints, corpus accuracy recommendation, remediation workflow, and env proxy hijack corpus slices landed | Next hosted evidence-pack workflow depth |
| ECC Tools next-level app | Billing audit, PR checks, deep analyzer, sync backlog, evaluator/RAG corpus, analysis-depth readiness, hosted execution planning, hosted CI diagnostics, hosted security evidence review, hosted harness compatibility audit, hosted reference-set evaluation, hosted AI routing/cost review, hosted team backlog routing, hosted depth-plan check-run, PR-comment hosted job dispatch, hosted job result history/check-runs, hosted result status command, status-aware depth-plan recommendations, hosted promotion readiness, hosted promotion output scoring | PRs #26-#43 plus #53-#69 landed with test evidence, including AgentShield evidence-pack gap routing, canonical bundle recognition, supply-chain signature gates, PR draft follow-up Linear tracking, evidence-backed/deep-ready repository classification, the `/api/analysis/depth-plan` hosted job plan, `/api/analysis/jobs/ci-diagnostics`, `/api/analysis/jobs/security-evidence-review`, `/api/analysis/jobs/harness-compatibility-audit`, `/api/analysis/jobs/reference-set-evaluation`, `/api/analysis/jobs/ai-routing-cost-review`, `/api/analysis/jobs/team-backlog-routing`, the `ECC Tools / Hosted Depth Plan` check-run, `/ecc-tools analyze --job ...` PR-comment dispatch, non-blocking per-hosted-job result check-runs backed by 30-day result cache records, `/ecc-tools analyze --job status` cache lookup, cache-aware next-job recommendations in the depth-plan check-run, the `ECC Tools / Hosted Promotion Readiness` corpus-backed PR check-run, and deterministic hosted-output scoring against cached completed job artifacts/findings | Next work is retrieval/model-backed hosted promotion after deterministic output scoring | | ECC Tools next-level app | Billing audit, PR checks, deep analyzer, sync backlog, evaluator/RAG corpus, analysis-depth readiness, hosted execution planning, hosted CI diagnostics, hosted security evidence review, hosted harness compatibility audit, hosted reference-set evaluation, hosted AI routing/cost review, hosted team backlog routing, hosted depth-plan check-run, PR-comment hosted job dispatch, hosted job result history/check-runs, hosted result status command | PRs #26-#43 plus #53-#66 landed with test evidence, including AgentShield evidence-pack gap routing, canonical bundle recognition, supply-chain signature gates, PR draft follow-up Linear tracking, evidence-backed/deep-ready repository classification, the `/api/analysis/depth-plan` hosted job plan, `/api/analysis/jobs/ci-diagnostics`, `/api/analysis/jobs/security-evidence-review`, `/api/analysis/jobs/harness-compatibility-audit`, `/api/analysis/jobs/reference-set-evaluation`, `/api/analysis/jobs/ai-routing-cost-review`, `/api/analysis/jobs/team-backlog-routing`, the `ECC Tools / Hosted Depth Plan` check-run, `/ecc-tools analyze --job ...` PR-comment dispatch, non-blocking per-hosted-job result check-runs backed by 30-day result cache records, and `/ecc-tools analyze --job status` cache lookup | Next work is evaluator-backed hosted promotion and status-aware depth-plan recommendations |
| GitGuardian/Dependabot/CodeRabbit-style checks | Non-blocking taxonomy, deterministic follow-up checks, and local supply-chain gates | ECC-Tools risk taxonomy check plus follow-up signals landed, including Skill Quality, Deep Analyzer Evidence, Analyzer Corpus Evidence, RAG/Evaluator Evidence, PR Review/Salvage Evidence, and AgentShield evidence-pack evidence; #1846 added npm registry signature gates; #1848 added the supply-chain incident-response playbook and `pull_request_target` cache-poisoning validator guard; #1851 added the privileged checkout credential-persistence guard; AgentShield #78, JARVIS #13, and ECC-Tools #53 applied the same hardening outside trunk | Current supply-chain gate complete; deeper hosted review features remain future | | GitGuardian/Dependabot/CodeRabbit-style checks | Non-blocking taxonomy, deterministic follow-up checks, and local supply-chain gates | ECC-Tools risk taxonomy check plus follow-up signals landed, including Skill Quality, Deep Analyzer Evidence, Analyzer Corpus Evidence, RAG/Evaluator Evidence, PR Review/Salvage Evidence, and AgentShield evidence-pack evidence; #1846 added npm registry signature gates; #1848 added the supply-chain incident-response playbook and `pull_request_target` cache-poisoning validator guard; #1851 added the privileged checkout credential-persistence guard; AgentShield #78, JARVIS #13, and ECC-Tools #53 applied the same hardening outside trunk | Current supply-chain gate complete; deeper hosted review features remain future |
| Harness-agnostic learning system | Audit, adapter matrix, observability, traces, promotion loop | Audit/adapters/observability gates plus `docs/architecture/evaluator-rag-prototype.md`, `examples/evaluator-rag-prototype/`, and ECC-Tools PR #40 define read-only stale-salvage, billing-readiness, CI-failure-diagnosis, harness-config-quality, AgentShield policy-exception, skill-quality evidence, deep-analyzer evidence, and RAG/evaluator comparison scenarios with trace, report, playbook, verifier, and predictive-check artifacts; ECC-Tools PRs #68/#69 now turn that corpus into a deterministic PR check-run gate with cached hosted-output scoring | Deterministic hosted PR check and cached output scoring integrated; hosted retrieval remains future | | Harness-agnostic learning system | Audit, adapter matrix, observability, traces, promotion loop | Audit/adapters/observability gates plus `docs/architecture/evaluator-rag-prototype.md`, `examples/evaluator-rag-prototype/`, and ECC-Tools PR #40 define read-only stale-salvage, billing-readiness, CI-failure-diagnosis, harness-config-quality, AgentShield policy-exception, skill-quality evidence, deep-analyzer evidence, and RAG/evaluator comparison scenarios with trace, report, playbook, verifier, and predictive-check artifacts | Local corpus complete; hosted integration remains future |
| Linear roadmap is detailed | Linear project status plus repo mirror | Repo mirror exists; issue creation was retried on 2026-05-12 and remains blocked by the workspace free issue limit; this May 13 sync adds ECC #1860, AgentShield #78-#82, JARVIS #13, ECC-Tools #53-#69, resolved queue/discussion counts, and Linear project status updates through ECC-Tools #69 | Needs recurring status updates after each merge batch | | Linear roadmap is detailed | Linear project status plus repo mirror | Repo mirror exists; issue creation was retried on 2026-05-12 and remains blocked by the workspace free issue limit; this May 13 sync adds ECC #1860, AgentShield #78-#82, JARVIS #13, ECC-Tools #53-#66, resolved queue/discussion counts, and Linear project status updates through ECC-Tools #66 | Needs recurring status updates after each merge batch |
| Flow separation and progress tracking | Flow lanes with owner artifacts and update cadence | This roadmap defines lanes below and `docs/architecture/progress-sync-contract.md` makes GitHub/Linear/handoff/roadmap sync part of the readiness gate | Active | | Flow separation and progress tracking | Flow lanes with owner artifacts and update cadence | This roadmap defines lanes below and `docs/architecture/progress-sync-contract.md` makes GitHub/Linear/handoff/roadmap sync part of the readiness gate | Active |
| Realtime Linear sync | Project updates while issue limit is blocked; issues later | ECC-Tools #39 implements opt-in Linear API sync for deferred follow-up backlog items, and ECC-Tools #54 adds copy-ready PR drafts to that backlog when draft PR shells are not opened; `docs/architecture/progress-sync-contract.md` defines the local file-backed realtime boundary while issue capacity is blocked | Needs workspace capacity/config rollout | | Realtime Linear sync | Project updates while issue limit is blocked; issues later | ECC-Tools #39 implements opt-in Linear API sync for deferred follow-up backlog items, and ECC-Tools #54 adds copy-ready PR drafts to that backlog when draft PR shells are not opened; `docs/architecture/progress-sync-contract.md` defines the local file-backed realtime boundary while issue capacity is blocked | Needs workspace capacity/config rollout |
| Observability for self-use | Local readiness gate, traces, status snapshots, HUD/status contract, risk ledger, progress-sync contract | `npm run observability:ready` reports 21/21 | Complete for local gate | | Observability for self-use | Local readiness gate, traces, status snapshots, HUD/status contract, risk ledger, progress-sync contract | `npm run observability:ready` reports 21/21 | Complete for local gate |
@@ -489,9 +461,9 @@ repo evidence and merge commits.
| Queue hygiene and salvage | GitHub PR/issue state, salvage ledger | Append ledger entries for any future stale closures | Every cleanup batch | | Queue hygiene and salvage | GitHub PR/issue state, salvage ledger | Append ledger entries for any future stale closures | Every cleanup batch |
| Release and publication | rc.1 release docs, publication readiness doc | Naming matrix and plugin submission/contact checklist | Before any tag | | Release and publication | rc.1 release docs, publication readiness doc | Naming matrix and plugin submission/contact checklist | Before any tag |
| Harness OS core | Audit, adapter matrix, observability docs, `ecc2/` | HUD/session-control acceptance spec | Weekly until GA | | Harness OS core | Audit, adapter matrix, observability docs, `ecc2/` | HUD/session-control acceptance spec | Weekly until GA |
| Evaluation and RAG | Reference-set validation, harness audit, traces, ECC-Tools corpus | Read-only evaluator/RAG prototype plus stale-salvage, billing-readiness, CI-failure-diagnosis, harness-config-quality, AgentShield policy-exception, skill-quality evidence, deep-analyzer evidence, and RAG/evaluator comparison fixtures; ECC-Tools #68 publishes the corpus as a hosted promotion readiness check-run, and #69 scores cached hosted job outputs against the same corpus | Hosted retrieval/model-backed promotion plan | | Evaluation and RAG | Reference-set validation, harness audit, traces, ECC-Tools corpus | Read-only evaluator/RAG prototype plus stale-salvage, billing-readiness, CI-failure-diagnosis, harness-config-quality, AgentShield policy-exception, skill-quality evidence, deep-analyzer evidence, and RAG/evaluator comparison fixtures | Hosted retrieval/check-run automation plan |
| AgentShield enterprise | AgentShield PR evidence and roadmap notes | Remediation workflow depth or corpus expansion follow-up | Next implementation batch | | AgentShield enterprise | AgentShield PR evidence and roadmap notes | Remediation workflow depth or corpus expansion follow-up | Next implementation batch |
| ECC Tools app | ECC-Tools PR evidence, billing audit, risk taxonomy, evaluator/RAG corpus | ECC-Tools #53 published the supply-chain workflow hardening branch, #54 tracks copy-ready PR drafts in the Linear/project backlog, #55 classifies analysis-depth readiness, #56 exposes the hosted execution plan, #57 executes the first hosted CI diagnostics job, #58 executes the hosted security evidence review job, #59 executes the hosted harness compatibility audit, #60 executes the hosted reference-set evaluation, #61 executes the hosted AI routing/cost review, #62 executes hosted team backlog routing, #63 publishes the hosted depth-plan check-run, #64 dispatches hosted jobs from PR comments, #65 persists hosted result history/check-runs, #66 exposes hosted job status from PR comments, #67 makes depth-plan recommendations cache-aware, #68 publishes hosted promotion readiness from the evaluator/RAG corpus, and #69 scores cached hosted job outputs against that corpus; next work is retrieval/model-backed hosted promotion | Next implementation batch | | ECC Tools app | ECC-Tools PR evidence, billing audit, risk taxonomy, evaluator/RAG corpus | ECC-Tools #53 published the supply-chain workflow hardening branch, #54 tracks copy-ready PR drafts in the Linear/project backlog, #55 classifies analysis-depth readiness, #56 exposes the hosted execution plan, #57 executes the first hosted CI diagnostics job, #58 executes the hosted security evidence review job, #59 executes the hosted harness compatibility audit, #60 executes the hosted reference-set evaluation, #61 executes the hosted AI routing/cost review, #62 executes hosted team backlog routing, #63 publishes the hosted depth-plan check-run, and #64 dispatches hosted jobs from PR comments; next work is hosted result history/check-run summaries | Next implementation batch |
| Linear progress | Linear project status updates, `docs/architecture/progress-sync-contract.md`, and this mirror | Status update with queue/evidence/missing gates | Every significant merge batch | | Linear progress | Linear project status updates, `docs/architecture/progress-sync-contract.md`, and this mirror | Status update with queue/evidence/missing gates | Every significant merge batch |
The project status update should always include: The project status update should always include:
@@ -708,9 +680,9 @@ Acceptance:
PR #82 expanded corpus coverage for env proxy hijacks and out-of-band PR #82 expanded corpus coverage for env proxy hijacks and out-of-band
exfiltration; and ECC-Tools PRs #42/#43 now route and recognize evidence exfiltration; and ECC-Tools PRs #42/#43 now route and recognize evidence
packs. The next slice is hosted evidence-pack workflow depth. packs. The next slice is hosted evidence-pack workflow depth.
2. Plan retrieval/model-backed hosted promotion on top of the #69 deterministic 2. Feed the #66 status surface back into hosted depth-plan recommendations so
hosted output scoring contract, keeping vector/model judgment behind fixture queued analysis can suggest the next unrun or newly blocked hosted job from
evaluation until the retrieval contract is stable. cached outcomes, not only static readiness.
3. Enable/configure the merged Linear backlog sync path after workspace issue 3. Enable/configure the merged Linear backlog sync path after workspace issue
capacity clears or the Linear workspace is upgraded, then verify PR-draft capacity clears or the Linear workspace is upgraded, then verify PR-draft
salvage items land in the expected project. salvage items land in the expected project.

View File

@@ -285,8 +285,6 @@
"postinstall": "echo '\\n ecc-universal installed!\\n Run: npx ecc typescript\\n Compat: npx ecc-install typescript\\n Docs: https://github.com/affaan-m/everything-claude-code\\n'", "postinstall": "echo '\\n ecc-universal installed!\\n Run: npx ecc typescript\\n Compat: npx ecc-install typescript\\n Docs: https://github.com/affaan-m/everything-claude-code\\n'",
"catalog:check": "node scripts/ci/catalog.js --text", "catalog:check": "node scripts/ci/catalog.js --text",
"catalog:sync": "node scripts/ci/catalog.js --write --text", "catalog:sync": "node scripts/ci/catalog.js --write --text",
"command-registry:generate": "node scripts/ci/generate-command-registry.js",
"command-registry:write": "node scripts/ci/generate-command-registry.js --write",
"lint": "eslint . && markdownlint '**/*.md' --ignore node_modules", "lint": "eslint . && markdownlint '**/*.md' --ignore node_modules",
"harness:adapters": "node scripts/harness-adapter-compliance.js", "harness:adapters": "node scripts/harness-adapter-compliance.js",
"harness:audit": "node scripts/harness-audit.js", "harness:audit": "node scripts/harness-audit.js",

View File

@@ -1,249 +0,0 @@
#!/usr/bin/env node
/**
* Generate Command → Agent/Skill Registry
*
* Scans all command markdown files and extracts:
* - Command name/description
* - Primary agent(s) referenced
* - Skills referenced
* - Command type (workflow, testing, review, etc.)
*
* Usage:
* node scripts/ci/generate-command-registry.js
* node scripts/ci/generate-command-registry.js --json
* node scripts/ci/generate-command-registry.js --write
*/
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '../..');
const COMMANDS_DIR = path.join(ROOT, 'commands');
const AGENTS_DIR = path.join(ROOT, 'agents');
const OUTPUT_PATH = path.join(ROOT, 'docs', 'COMMAND-REGISTRY.json');
const WRITE_MODE = process.argv.includes('--write');
const OUTPUT_JSON = process.argv.includes('--json');
const KNOWN_AGENTS = new Set();
const KNOWN_SKILLS = new Set();
// Scan agents directory for known agents
function scanKnownAgents() {
if (!fs.existsSync(AGENTS_DIR)) return;
const files = fs.readdirSync(AGENTS_DIR, { withFileTypes: true });
files.forEach(entry => {
if (entry.isFile() && entry.name.endsWith('.md')) {
const agentName = entry.name.replace('.md', '');
KNOWN_AGENTS.add(agentName);
}
});
}
// Scan skills directory for known skills
function scanKnownSkills() {
const skillsDir = path.join(ROOT, 'skills');
if (!fs.existsSync(skillsDir)) return;
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
entries.forEach(entry => {
if (entry.isDirectory() && fs.existsSync(path.join(skillsDir, entry.name, 'SKILL.md'))) {
KNOWN_SKILLS.add(entry.name);
}
});
}
// Extract agents and skills from markdown content
function extractReferences(content) {
const agents = new Set();
const skills = new Set();
// Pattern: @agent-name or agent-name in code blocks, lists, or descriptions
const agentPatterns = [
/@([a-z][a-z0-9-]*)/gi, // @agent-name
/agent:\s*([a-z][a-z0-9-]*)/gi, // agent: name
/subagent(?:_type)?:\s*["']?([a-z][a-z0-9-]*)/gi, // subagent_type: "name"
];
// Pattern: /skill-name or /command-name
const skillPatterns = [
/\/([a-z][a-z0-9-]*)/gi, // /skill-name
/skill:\s*\/?([a-z][a-z0-9-]*)/gi, // skill: /name or skill: name
];
// Extract agents
agentPatterns.forEach(pattern => {
const matches = content.matchAll(pattern);
for (const match of matches) {
const ref = match[1];
if (KNOWN_AGENTS.has(ref)) {
agents.add(ref);
}
}
});
// Extract skills
skillPatterns.forEach(pattern => {
const matches = content.matchAll(pattern);
for (const match of matches) {
const ref = match[1];
if (KNOWN_SKILLS.has(ref)) {
skills.add(ref);
}
}
});
return {
agents: Array.from(agents).sort(),
skills: Array.from(skills).sort()
};
}
// Infer command type from content
function inferCommandType(content, filename) {
const lower = content.toLowerCase();
if (lower.includes('test') || lower.includes('tdd') || lower.includes('coverage')) {
return 'testing';
}
if (lower.includes('review') || lower.includes('audit') || lower.includes('quality')) {
return 'review';
}
if (lower.includes('plan') || lower.includes('design') || lower.includes('architecture')) {
return 'planning';
}
if (lower.includes('refactor') || lower.includes('clean') || lower.includes('simplify')) {
return 'refactoring';
}
if (lower.includes('build') || lower.includes('compile') || lower.includes('setup')) {
return 'build';
}
if (filename.startsWith('multi-')) {
return 'orchestration';
}
return 'general';
}
// Process single command file
function processCommandFile(filename) {
const filePath = path.join(COMMANDS_DIR, filename);
const content = fs.readFileSync(filePath, 'utf8');
// Extract description from frontmatter or first heading
let description = '';
const frontmatterMatch = content.match(/^---\n[\s\S]*?\ndescription:\s*(.+?)\n[\s\S]*?^---/m);
if (frontmatterMatch) {
description = frontmatterMatch[1].trim();
} else {
const headingMatch = content.match(/^#\s+(.+)$/m);
if (headingMatch) {
description = headingMatch[1].trim();
}
}
const commandName = filename.replace('.md', '');
const references = extractReferences(content);
const type = inferCommandType(content, filename);
return {
command: commandName,
description,
type,
primaryAgents: references.agents.slice(0, 3), // Top 3 agents
allAgents: references.agents,
skills: references.skills,
path: `commands/${filename}`
};
}
// Generate full registry
function generateRegistry() {
scanKnownAgents();
scanKnownSkills();
if (!fs.existsSync(COMMANDS_DIR)) {
console.error('commands/ directory not found');
process.exit(1);
}
const files = fs.readdirSync(COMMANDS_DIR, { withFileTypes: true })
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
.map(entry => entry.name)
.sort();
const registry = {
generated: new Date().toISOString(),
totalCommands: files.length,
commands: files.map(processCommandFile)
};
// Add statistics
const typeCounts = {};
const agentUsage = {};
const skillUsage = {};
registry.commands.forEach(cmd => {
typeCounts[cmd.type] = (typeCounts[cmd.type] || 0) + 1;
cmd.allAgents.forEach(agent => {
agentUsage[agent] = (agentUsage[agent] || 0) + 1;
});
cmd.skills.forEach(skill => {
skillUsage[skill] = (skillUsage[skill] || 0) + 1;
});
});
registry.statistics = {
byType: typeCounts,
topAgents: Object.entries(agentUsage)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([agent, count]) => ({ agent, count })),
topSkills: Object.entries(skillUsage)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([skill, count]) => ({ skill, count }))
};
return registry;
}
// Main execution
function main() {
const registry = generateRegistry();
if (OUTPUT_JSON) {
console.log(JSON.stringify(registry, null, 2));
} else {
console.log('\n📊 Command Registry Statistics\n');
console.log(`Total commands: ${registry.totalCommands}`);
console.log('\nBy type:');
Object.entries(registry.statistics.byType)
.sort((a, b) => b[1] - a[1])
.forEach(([type, count]) => {
console.log(` ${type}: ${count}`);
});
console.log('\nTop 10 agents:');
registry.statistics.topAgents.forEach(({ agent, count }) => {
console.log(` ${agent}: ${count} commands`);
});
console.log('\nTop 10 skills:');
registry.statistics.topSkills.forEach(({ skill, count }) => {
console.log(` ${skill}: ${count} commands`);
});
console.log(`\n📄 Generated: ${registry.generated}`);
}
if (WRITE_MODE) {
fs.mkdirSync(path.dirname(OUTPUT_PATH), { recursive: true });
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(registry, null, 2) + '\n');
console.log(`\n✅ Registry written to: ${OUTPUT_PATH}`);
}
}
main();

View File

@@ -4,6 +4,10 @@
const MAX_STDIN = 1024 * 1024; const MAX_STDIN = 1024 * 1024;
const path = require('path'); const path = require('path');
const { splitShellSegments } = require('../lib/shell-split'); const { splitShellSegments } = require('../lib/shell-split');
const {
extractCommandSubstitutions,
extractSubshellGroups
} = require('../lib/shell-substitution');
const DEV_COMMAND_WORDS = new Set([ const DEV_COMMAND_WORDS = new Set([
'npm', 'npm',
@@ -123,6 +127,8 @@ function getLeadingCommandWord(segment) {
continue; continue;
} }
if (token === '{' || token === '}') continue;
if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token)) continue; if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token)) continue;
const normalizedToken = normalizeCommandWord(token); const normalizedToken = normalizeCommandWord(token);
@@ -154,23 +160,55 @@ process.stdin.on('data', chunk => {
} }
}); });
const TMUX_LAUNCHER = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/;
const DEV_PATTERN = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn(?:\s+run)?\s+dev|bun(?:\s+run)?\s+dev)\b/;
/**
* Collect every command-line segment we should evaluate. Returns the top-level
* segments first, then segments harvested from `$(...)` / backtick command
* substitutions and plain `(...)` subshell groups, recursively.
*
* Without this expansion the leading-command and dev-pattern check below only
* sees the outermost command, so wrappers like `$(npm run dev)` and
* `(npm run dev)` (which still spawn a dev server) sneak past.
*/
function collectCheckSegments(cmd) {
const segments = [...splitShellSegments(cmd)];
const queue = [cmd];
const seen = new Set();
while (queue.length) {
const current = queue.shift();
if (seen.has(current)) continue;
seen.add(current);
for (const body of extractCommandSubstitutions(current)) {
for (const seg of splitShellSegments(body)) segments.push(seg);
queue.push(body);
}
for (const body of extractSubshellGroups(current)) {
for (const seg of splitShellSegments(body)) segments.push(seg);
queue.push(body);
}
}
return segments;
}
function isBlockedDevSegment(segment) {
const commandWord = getLeadingCommandWord(segment);
if (!commandWord || !DEV_COMMAND_WORDS.has(commandWord)) return false;
return DEV_PATTERN.test(segment) && !TMUX_LAUNCHER.test(segment);
}
process.stdin.on('end', () => { process.stdin.on('end', () => {
try { try {
const input = JSON.parse(raw); const input = JSON.parse(raw);
const cmd = String(input.tool_input?.command || ''); const cmd = String(input.tool_input?.command || '');
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
const segments = splitShellSegments(cmd); const segments = collectCheckSegments(cmd);
const tmuxLauncher = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/; const hasBlockedDev = segments.some(isBlockedDevSegment);
const devPattern = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn\s+dev|bun\s+run\s+dev)\b/;
const hasBlockedDev = segments.some(segment => {
const commandWord = getLeadingCommandWord(segment);
if (!commandWord || !DEV_COMMAND_WORDS.has(commandWord)) {
return false;
}
return devPattern.test(segment) && !tmuxLauncher.test(segment);
});
if (hasBlockedDev) { if (hasBlockedDev) {
console.error('[Hook] BLOCKED: Dev server must run in tmux for log access'); console.error('[Hook] BLOCKED: Dev server must run in tmux for log access');

View File

@@ -0,0 +1,246 @@
'use strict';
/**
* Extract executable command-substitution bodies from a shell line.
*
* Single quotes are literal, so substitutions inside them are ignored;
* double quotes still permit substitutions, so those bodies are scanned
* before quoted text is stripped. Returns each substitution body plus
* any nested substitutions discovered recursively.
*
* Originally introduced in scripts/hooks/gateguard-fact-force.js
* (PR #1853 round 2). Extracted to a shared lib so other PreToolUse
* hooks that need the same "scan inside `$(...)` and backticks"
* behavior can reuse it without duplicating the parser.
*
* @param {string} input
* @returns {string[]}
*/
function extractCommandSubstitutions(input) {
const source = String(input || '');
const substitutions = [];
let inSingle = false;
let inDouble = false;
for (let i = 0; i < source.length; i++) {
const ch = source[i];
const prev = source[i - 1];
if (ch === '\\' && !inSingle) {
i += 1;
continue;
}
if (ch === "'" && !inDouble && prev !== '\\') {
inSingle = !inSingle;
continue;
}
if (ch === '"' && !inSingle && prev !== '\\') {
inDouble = !inDouble;
continue;
}
if (inSingle) {
continue;
}
if (ch === '`') {
let body = '';
i += 1;
while (i < source.length) {
const inner = source[i];
if (inner === '\\') {
body += inner;
if (i + 1 < source.length) {
body += source[i + 1];
i += 2;
continue;
}
}
if (inner === '`') {
break;
}
body += inner;
i += 1;
}
if (body.trim()) {
substitutions.push(body);
substitutions.push(...extractCommandSubstitutions(body));
}
continue;
}
if (ch === '$' && source[i + 1] === '(') {
let depth = 1;
let body = '';
let bodyInSingle = false;
let bodyInDouble = false;
i += 2;
while (i < source.length && depth > 0) {
const inner = source[i];
const innerPrev = source[i - 1];
if (inner === '\\' && !bodyInSingle) {
body += inner;
if (i + 1 < source.length) {
body += source[i + 1];
i += 2;
continue;
}
}
if (inner === "'" && !bodyInDouble && innerPrev !== '\\') {
bodyInSingle = !bodyInSingle;
} else if (inner === '"' && !bodyInSingle && innerPrev !== '\\') {
bodyInDouble = !bodyInDouble;
} else if (!bodyInSingle && !bodyInDouble) {
if (inner === '(') {
depth += 1;
} else if (inner === ')') {
depth -= 1;
if (depth === 0) {
break;
}
}
}
body += inner;
i += 1;
}
if (body.trim()) {
substitutions.push(body);
substitutions.push(...extractCommandSubstitutions(body));
}
}
}
return substitutions;
}
/**
* Extract bodies of plain `(...)` subshell groups.
*
* Bash treats `(npm run dev)` as a subshell that executes its contents, but
* the regex-light segment splitters used by our PreToolUse hooks don't peer
* inside those parens. This helper finds top-level `(...)` groups (skipping
* `$(...)` command substitutions and backticks, which `extractCommandSubstitutions`
* already covers) and returns each body, recursing for nested groups.
*
* Quote semantics:
* - Single quotes are literal: `'( ... )'` is a string, not a subshell.
* - Double quotes are literal *for parens*: `"( ... )"` is a string too —
* bash only honors `$( )` inside double quotes, not bare `( )`.
*
* @param {string} input
* @returns {string[]}
*/
function extractSubshellGroups(input) {
const source = String(input || '');
const groups = [];
let inSingle = false;
let inDouble = false;
for (let i = 0; i < source.length; i++) {
const ch = source[i];
const prev = source[i - 1];
if (ch === '\\' && !inSingle) {
i += 1;
continue;
}
if (ch === "'" && !inDouble && prev !== '\\') {
inSingle = !inSingle;
continue;
}
if (ch === '"' && !inSingle && prev !== '\\') {
inDouble = !inDouble;
continue;
}
if (inSingle || inDouble) {
continue;
}
if (ch === '$' && source[i + 1] === '(') {
let depth = 1;
let skipInSingle = false;
let skipInDouble = false;
i += 2;
while (i < source.length && depth > 0) {
const inner = source[i];
const innerPrev = source[i - 1];
if (inner === '\\' && !skipInSingle) {
i += 2;
continue;
}
if (inner === "'" && !skipInDouble && innerPrev !== '\\') {
skipInSingle = !skipInSingle;
} else if (inner === '"' && !skipInSingle && innerPrev !== '\\') {
skipInDouble = !skipInDouble;
} else if (!skipInSingle && !skipInDouble) {
if (inner === '(') depth += 1;
else if (inner === ')') depth -= 1;
}
i += 1;
}
i -= 1;
continue;
}
if (ch === '`') {
i += 1;
while (i < source.length && source[i] !== '`') {
if (source[i] === '\\' && i + 1 < source.length) {
i += 2;
continue;
}
i += 1;
}
continue;
}
if (ch === '(') {
let depth = 1;
let body = '';
let bodyInSingle = false;
let bodyInDouble = false;
i += 1;
while (i < source.length && depth > 0) {
const inner = source[i];
const innerPrev = source[i - 1];
if (inner === '\\' && !bodyInSingle) {
body += inner;
if (i + 1 < source.length) {
body += source[i + 1];
i += 2;
continue;
}
}
if (inner === "'" && !bodyInDouble && innerPrev !== '\\') {
bodyInSingle = !bodyInSingle;
} else if (inner === '"' && !bodyInSingle && innerPrev !== '\\') {
bodyInDouble = !bodyInDouble;
} else if (!bodyInSingle && !bodyInDouble) {
if (inner === '(') {
depth += 1;
} else if (inner === ')') {
depth -= 1;
if (depth === 0) {
break;
}
}
}
body += inner;
i += 1;
}
if (body.trim()) {
groups.push(body);
groups.push(...extractSubshellGroups(body));
}
}
}
return groups;
}
module.exports = { extractCommandSubstitutions, extractSubshellGroups };

View File

@@ -130,12 +130,12 @@ test('candidate playbook preserves stale-salvage operating rules', () => {
} }
}); });
test('roadmap points to the evaluator RAG prototype and hosted PR check', () => { test('roadmap points to the evaluator RAG prototype and keeps hosted integration open', () => {
const roadmap = read('docs/ECC-2.0-GA-ROADMAP.md'); const roadmap = read('docs/ECC-2.0-GA-ROADMAP.md');
assert.ok(roadmap.includes('docs/architecture/evaluator-rag-prototype.md')); assert.ok(roadmap.includes('docs/architecture/evaluator-rag-prototype.md'));
assert.ok(roadmap.includes('examples/evaluator-rag-prototype/')); assert.ok(roadmap.includes('examples/evaluator-rag-prototype/'));
assert.ok(roadmap.includes('Deterministic hosted PR check and cached output scoring integrated; hosted retrieval remains future')); assert.ok(roadmap.includes('Local corpus complete; hosted integration remains future'));
}); });
test('billing readiness scenario rejects launch copy overclaims', () => { test('billing readiness scenario rejects launch copy overclaims', () => {

View File

@@ -89,6 +89,110 @@ function runTests() {
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`); assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++); }) ? passed++ : failed++);
// --- Subshell bypass regression (issue: dev server slipped past via $(), ``, ()) ---
if (!isWindows) {
(test('blocks $(npm run dev) — command substitution', () => {
const result = runScript('$(npm run dev)');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
assert.ok(result.stderr.includes('BLOCKED'), 'expected BLOCKED in stderr');
}) ? passed++ : failed++);
(test('blocks `npm run dev` — backtick substitution', () => {
const result = runScript('`npm run dev`');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks echo $(npm run dev) — substitution nested in argument', () => {
const result = runScript('echo $(npm run dev)');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks (npm run dev) — plain subshell group', () => {
const result = runScript('(npm run dev)');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks $(echo a; npm run dev) — substitution with sequenced segments', () => {
const result = runScript('$(echo a; npm run dev)');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks (pnpm dev) — plain subshell group with pnpm', () => {
const result = runScript('(pnpm dev)');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('allows tmux launcher inside subshell wrapping (exit code 0)', () => {
const result = runScript('(tmux new-session -d -s dev "npm run dev")');
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++);
(test('allows single-quoted "(npm run dev)" — literal string, not a subshell', () => {
const result = runScript("git commit -m '(npm run dev)'");
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++);
(test('allows double-quoted "(npm run dev)" — literal in double quotes (bash does not subshell)', () => {
const result = runScript('echo "(npm run dev)"');
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++);
(test("allows single-quoted '$(npm run dev)' — literal string, no substitution", () => {
const result = runScript("git commit -m '$(npm run dev) fix'");
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++);
}
// --- Round 1 review fixes (Greptile + CodeRabbit on PR #1889) ---
if (!isWindows) {
(test('blocks $(echo ")"; (npm run dev)) — quoted ) does not terminate $() early', () => {
const result = runScript('$(echo ")"; (npm run dev))');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks (echo ")"; npm run dev) — quoted ) does not terminate (...) early', () => {
const result = runScript('(echo ")"; npm run dev)');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('allows $(echo "(npm run dev)") — () inside double-quoted substitution body is literal', () => {
const result = runScript('$(echo "(npm run dev)")');
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks { npm run dev; } — brace group runs in current shell', () => {
const result = runScript('{ npm run dev; }');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks echo hi && { npm run dev; } — brace group after &&', () => {
const result = runScript('echo hi && { npm run dev; }');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('allows {npm run dev} — bash requires space after { to form a group', () => {
const result = runScript('{npm run dev}');
assert.strictEqual(result.code, 0, `Expected exit code 0, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks yarn run dev — yarn 1.x convention', () => {
const result = runScript('yarn run dev');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks bun dev — bun bare form', () => {
const result = runScript('bun dev');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
(test('blocks "$(npm run dev)" — double-quoted substitution still substitutes', () => {
const result = runScript('echo "$(npm run dev)"');
assert.strictEqual(result.code, 2, `Expected exit code 2, got ${result.code}`);
}) ? passed++ : failed++);
}
// --- Edge cases --- // --- Edge cases ---
(test('empty/invalid input passes through (exit code 0)', () => { (test('empty/invalid input passes through (exit code 0)', () => {