mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-12 03:03:23 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3f63dee4e |
@@ -135,24 +135,6 @@ As of 2026-05-13:
|
|||||||
check-run on the PR head SHA with ready/blocked hosted executor commands
|
check-run on the PR head SHA with ready/blocked hosted executor commands
|
||||||
and next action text, while keeping check-run publication best-effort so
|
and next action text, while keeping check-run publication best-effort so
|
||||||
bundle generation and analysis comments are not blocked.
|
bundle generation and analysis comments are not blocked.
|
||||||
- ECC-Tools PR #64 merged as `72020ef94db94840812977ea7ac37e9344036668`
|
|
||||||
and added PR-facing hosted job dispatch controls:
|
|
||||||
`/ecc-tools analyze --job ...` comments now queue hosted jobs against the
|
|
||||||
PR head SHA, execute them through the existing hosted readiness/evidence
|
|
||||||
gates, post artifacts/findings/next actions back to the PR, and scope
|
|
||||||
idempotency keys by job id so hosted jobs do not collide with bundle
|
|
||||||
analysis.
|
|
||||||
- ECC-Tools PR #65 merged as `bacd4adf6a3a629e8d403865456d15f127baaf4e`
|
|
||||||
and added hosted job result history/check-run summaries:
|
|
||||||
queued hosted jobs now cache both the latest result and immutable run records
|
|
||||||
for completed or blocked runs, then publish a non-blocking per-job check-run
|
|
||||||
on the PR head SHA with artifacts, findings, readiness blockers, and next
|
|
||||||
actions.
|
|
||||||
- ECC-Tools PR #66 merged as `4e1db48252d068ea5dcf4308b0bc11b0dfe0c9ce`
|
|
||||||
and added a read-only hosted status command:
|
|
||||||
`/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 next hosted job command, without queueing work or billing usage.
|
|
||||||
- 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
|
||||||
@@ -374,18 +356,6 @@ As of 2026-05-13:
|
|||||||
- ECC-Tools PR #63 publishes the hosted depth-plan check-run after queued PR
|
- ECC-Tools PR #63 publishes the hosted depth-plan check-run after queued PR
|
||||||
analysis completes, making the six hosted executor commands visible on the
|
analysis completes, making the six hosted executor commands visible on the
|
||||||
PR head SHA without turning the check into a merge blocker.
|
PR head SHA without turning the check into a merge blocker.
|
||||||
- ECC-Tools PR #64 wires those commands into the queue: maintainers can comment
|
|
||||||
`/ecc-tools analyze --job ci-diagnostics`, `security-evidence`,
|
|
||||||
`harness-compatibility`, `reference-set-evaluation`, `ai-routing-cost`, or
|
|
||||||
`team-backlog` on a PR and receive hosted job results in a PR comment.
|
|
||||||
- ECC-Tools PR #65 persists completed and blocked hosted job results to the
|
|
||||||
analysis cache for 30 days and publishes non-blocking `ECC Tools / Hosted
|
|
||||||
Job: ...` check-runs so maintainers can scan hosted outcomes from the PR
|
|
||||||
checks surface instead of rereading older comments.
|
|
||||||
- ECC-Tools PR #66 exposes the cached results from PR comments with
|
|
||||||
`/ecc-tools analyze --job status`, summarizing completed, blocked, and
|
|
||||||
not-yet-run hosted jobs for the PR head and recommending the next hosted job
|
|
||||||
command.
|
|
||||||
- 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.
|
||||||
@@ -439,10 +409,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 | 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 |
|
| 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 | PRs #26-#43 plus #53-#63 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`, and the `ECC Tools / Hosted Depth Plan` check-run | Hosted operator visibility complete; next work is hosted-job dispatch execution controls |
|
||||||
| 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 | Local corpus complete; hosted integration 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-#66, resolved queue/discussion counts, and Linear project status updates through ECC-Tools #66 | 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-#63, resolved queue/discussion counts, and Linear project status updates through ECC-Tools #63 | 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 |
|
||||||
@@ -463,7 +433,7 @@ repo evidence and merge commits.
|
|||||||
| 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 | Hosted retrieval/check-run automation 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, and #64 dispatches hosted jobs from PR comments; next work is hosted result history/check-run summaries | 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, and #63 publishes the hosted depth-plan check-run; next work is hosted-job dispatch execution controls | 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:
|
||||||
@@ -680,9 +650,10 @@ 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. Feed the #66 status surface back into hosted depth-plan recommendations so
|
2. Build hosted-job dispatch execution controls on top of the #63 hosted
|
||||||
queued analysis can suggest the next unrun or newly blocked hosted job from
|
depth-plan check-run so maintainers can queue the selected CI, security,
|
||||||
cached outcomes, not only static readiness.
|
harness, reference-set, AI-routing, or team-backlog job from the PR-facing
|
||||||
|
operator surface instead of manually calling the internal endpoints.
|
||||||
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.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ paths:
|
|||||||
Configure project-local hooks to prefer binstubs and checked-in tooling:
|
Configure project-local hooks to prefer binstubs and checked-in tooling:
|
||||||
|
|
||||||
- **RuboCop**: run `bundle exec rubocop -A <file>` or the project's safer formatter command after Ruby edits.
|
- **RuboCop**: run `bundle exec rubocop -A <file>` or the project's safer formatter command after Ruby edits.
|
||||||
- **Brakeman**: run `bundle exec brakeman --no-pager` after security-sensitive Rails changes.
|
- **Brakeman**: run `bundle exec brakeman --no-progress` after security-sensitive Rails changes.
|
||||||
- **Tests**: run the narrowest matching `bin/rails test ...` or `bundle exec rspec ...` command for touched files.
|
- **Tests**: run the narrowest matching `bin/rails test ...` or `bundle exec rspec ...` command for touched files.
|
||||||
- **Bundler audit**: run `bundle exec bundle-audit check --update` when `Gemfile` or `Gemfile.lock` changes and the project has bundler-audit installed.
|
- **Bundler audit**: run `bundle exec bundle-audit check --update` when `Gemfile` or `Gemfile.lock` changes and the project has bundler-audit installed.
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ Configure project-local hooks to prefer binstubs and checked-in tooling:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
bundle exec rubocop
|
bundle exec rubocop
|
||||||
bundle exec brakeman --no-pager
|
bundle exec brakeman --no-progress
|
||||||
bin/rails test
|
bin/rails test
|
||||||
bundle exec rspec
|
bundle exec rspec
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ paths:
|
|||||||
- Run dependency checks when the lockfile changes:
|
- Run dependency checks when the lockfile changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bundle audit check --update
|
bundle exec bundle-audit check --update
|
||||||
bundle exec brakeman --no-pager
|
bundle exec brakeman --no-progress
|
||||||
```
|
```
|
||||||
|
|
||||||
- Review new gems for maintainer activity, native extension risk, transitive dependencies, and whether the same behavior can be implemented with Rails core.
|
- Review new gems for maintainer activity, native extension risk, transitive dependencies, and whether the same behavior can be implemented with Rails core.
|
||||||
|
|||||||
@@ -4,10 +4,6 @@
|
|||||||
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',
|
||||||
@@ -127,8 +123,6 @@ 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);
|
||||||
@@ -160,55 +154,23 @@ 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 = collectCheckSegments(cmd);
|
const segments = splitShellSegments(cmd);
|
||||||
const hasBlockedDev = segments.some(isBlockedDevSegment);
|
const tmuxLauncher = /^\s*tmux\s+(new|new-session|new-window|split-window)\b/;
|
||||||
|
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');
|
||||||
|
|||||||
@@ -1,246 +0,0 @@
|
|||||||
'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 };
|
|
||||||
@@ -89,110 +89,6 @@ 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)', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user