sec-review-team
Run a multi-agent security review TEAM over a codebase or recent diff. Picks a specialist roster based on detected stack signals (webapp / desktop / iac / llm-agent / container / backend / mobile / etc.) from a library of 13 specialists. Each specialist writes structured JSONL + prose findings in parallel; input-validation-auditor and auth-authz-auditor run a loop-until-dry consensus fan-out (2-8 passes); every deduped finding is adversarially verified by a dedicated validator agent before it can appear in the report. Orchestrator consolidates into a single severity-ranked REPORT.md with deduped root issues, archived per-run under a known-findings.jsonl ledger that drives auto re-review. Distinct from the built-in `/security-review` (which is a single-pass branch review). Use when the user says "multi-agent security review", "security review team", "sec-review team", "audit this branch with a team", "pre-release hardening team pass", or similar. Review-only — no fixes.
- License 许可
- MIT
- 最近更新
- 2026-11-12
- Stars
- ★ 8
- Forks
- 4
01 前言 Front Matter
- description
- Run a multi-agent security review TEAM over a codebase or recent diff. Picks a specialist roster based on detected stack signals (webapp / desktop / iac / llm-agent / container / backend / mobile / etc.) from a library of 13 specialists. Each specialist writes structured JSONL + prose findings in parallel; input-validation-auditor and auth-authz-auditor run a loop-until-dry consensus fan-out (2-8 passes); every deduped finding is adversarially verified by a dedicated validator agent before it can appear in the report. Orchestrator consolidates into a single severity-ranked REPORT.md with deduped root issues, archived per-run under a known-findings.jsonl ledger that drives auto re-review. Distinct from the built-in `/security-review` (which is a single-pass branch review). Use when the user says "multi-agent security review", "security review team", "sec-review team", "audit this branch with a team", "pre-release hardening team pass", or similar. Review-only — no fixes.
- argument-hint
- `[target path] [scope: all | <subdir> | diff vs main] [--lite] [--yes]`
02 资源文件 85
- 参考文档docs/consolidation-template.md8.2 KB
- 参考文档docs/output-contract.md3.4 KB
- 参考文档docs/scanner-coverage.md5.6 KB
- 参考文档docs/tradeoffs.md5.7 KB
- 参考文档fixtures/cicd-github-actions-vulnerable/.github/workflows/ci.yml825 B
- 参考文档fixtures/cicd-github-actions-vulnerable/.github/workflows/deploy.yml570 B
- 其他fixtures/cicd-github-actions-vulnerable/expected-coverage.jsonl1.3 KB
- 其他fixtures/cicd-github-actions-vulnerable/expected-findings.jsonl3.7 KB
- 参考文档fixtures/cicd-github-actions-vulnerable/README-FIXTURE.md1.4 KB
- 脚本fixtures/concurrency-race-vulnerable/auth.js1.3 KB
- 其他fixtures/concurrency-race-vulnerable/expected-coverage.jsonl1.8 KB
- 其他fixtures/concurrency-race-vulnerable/expected-findings.jsonl3.6 KB
- 参考文档fixtures/concurrency-race-vulnerable/package.json281 B
- 参考文档fixtures/concurrency-race-vulnerable/README-FIXTURE.md1.2 KB
- 脚本fixtures/concurrency-race-vulnerable/session.js1.0 KB
- 脚本fixtures/concurrency-race-vulnerable/wallet.js840 B
- 参考文档fixtures/container-docker-vulnerable/docker-compose.yml516 B
- 其他fixtures/container-docker-vulnerable/Dockerfile484 B
- 其他fixtures/container-docker-vulnerable/expected-coverage.jsonl1.3 KB
- 其他fixtures/container-docker-vulnerable/expected-findings.jsonl4.2 KB
- 参考文档fixtures/container-docker-vulnerable/README-FIXTURE.md1.5 KB
- 其他fixtures/desktop-tauri-vulnerable/expected-coverage.jsonl719 B
- 其他fixtures/desktop-tauri-vulnerable/expected-findings.jsonl2.7 KB
- 参考文档fixtures/desktop-tauri-vulnerable/README-FIXTURE.md1.2 KB
- 参考文档fixtures/desktop-tauri-vulnerable/src-tauri/capabilities/default.json266 B
- 其他fixtures/desktop-tauri-vulnerable/src-tauri/Cargo.toml577 B
- 其他fixtures/desktop-tauri-vulnerable/src-tauri/src/db/schema.sql188 B
- 脚本fixtures/desktop-tauri-vulnerable/src-tauri/src/lib.rs758 B
- 参考文档fixtures/desktop-tauri-vulnerable/src-tauri/tauri.conf.json337 B
- 其他fixtures/iac-terraform-vulnerable/expected-coverage.jsonl1.9 KB
- 其他fixtures/iac-terraform-vulnerable/expected-findings.jsonl4.2 KB
- 其他fixtures/iac-terraform-vulnerable/iam.tf783 B
- 其他fixtures/iac-terraform-vulnerable/main.tf939 B
- 其他fixtures/iac-terraform-vulnerable/network.tf659 B
- 参考文档fixtures/iac-terraform-vulnerable/README-FIXTURE.md1.5 KB
- 其他fixtures/iac-terraform-vulnerable/terraform.tfvars164 B
- 其他fixtures/iac-terraform-vulnerable/variables.tf236 B
- 脚本fixtures/llm-agent-python-vulnerable/agent.py1.7 KB
- 其他fixtures/llm-agent-python-vulnerable/expected-coverage.jsonl1.7 KB
- 其他fixtures/llm-agent-python-vulnerable/expected-findings.jsonl5.2 KB
- 脚本fixtures/llm-agent-python-vulnerable/rag.py715 B
- 参考文档fixtures/llm-agent-python-vulnerable/README-FIXTURE.md1.5 KB
- 参考文档fixtures/llm-agent-python-vulnerable/requirements.txt35 B
- 脚本fixtures/privacy-telemetry-vulnerable/app.js1.2 KB
- 其他fixtures/privacy-telemetry-vulnerable/expected-coverage.jsonl2.2 KB
- 其他fixtures/privacy-telemetry-vulnerable/expected-findings.jsonl3.5 KB
- 参考文档fixtures/privacy-telemetry-vulnerable/index.html2.1 KB
- 参考文档fixtures/privacy-telemetry-vulnerable/package.json314 B
- 参考文档fixtures/privacy-telemetry-vulnerable/README-FIXTURE.md1.4 KB
- 其他fixtures/python-django-vulnerable/expected-coverage.jsonl843 B
- 其他fixtures/python-django-vulnerable/expected-findings.jsonl4.2 KB
- 脚本fixtures/python-django-vulnerable/myapp/views.py1.1 KB
- 脚本fixtures/python-django-vulnerable/myproject/settings.py1.2 KB
- 参考文档fixtures/python-django-vulnerable/README-FIXTURE.md1.4 KB
- 参考文档fixtures/python-django-vulnerable/requirements.txt186 B
- 参考文档fixtures/README.md3.6 KB
- 脚本fixtures/run-harness.sh4.5 KB
- 其他fixtures/webapp-express-vulnerable/expected-coverage.jsonl1.1 KB
- 其他fixtures/webapp-express-vulnerable/expected-findings.jsonl3.9 KB
- 参考文档fixtures/webapp-express-vulnerable/package.json392 B
- 参考文档fixtures/webapp-express-vulnerable/README-FIXTURE.md1.8 KB
- 脚本fixtures/webapp-express-vulnerable/src/app.js1.4 KB
- 脚本fixtures/webapp-express-vulnerable/src/config.js308 B
- 脚本fixtures/webapp-express-vulnerable/src/routes/user.js878 B
- 其他fixtures/webapp-frontend-vulnerable/expected-coverage.jsonl1.4 KB
- 其他fixtures/webapp-frontend-vulnerable/expected-findings.jsonl3.8 KB
- 参考文档fixtures/webapp-frontend-vulnerable/index.html772 B
- 参考文档fixtures/webapp-frontend-vulnerable/README-FIXTURE.md1.4 KB
- 脚本fixtures/webapp-frontend-vulnerable/server.js710 B
- 参考文档schema/coverage.schema.json2.1 KB
- 参考文档schema/finding.schema.json3.5 KB
- 参考文档specialists/auth-authz-auditor.md4.2 KB
- 参考文档specialists/ci-cd-security-auditor.md2.0 KB
- 参考文档specialists/concurrency-race-auditor.md2.2 KB
- 参考文档specialists/container-runtime-auditor.md2.0 KB
- 参考文档specialists/data-exposure-auditor.md2.3 KB
- 参考文档specialists/dependency-supplychain-auditor.md2.5 KB
- 参考文档specialists/frontend-security-auditor.md2.0 KB
- 参考文档specialists/iac-auditor.md2.2 KB
- 参考文档specialists/input-validation-auditor.md3.9 KB
- 参考文档specialists/privacy-telemetry-auditor.md2.4 KB
- 参考文档specialists/prompt-injection-auditor.md2.3 KB
- 参考文档specialists/secrets-crypto-auditor.md3.2 KB
- 参考文档specialists/silent-failure-hunter.md2.6 KB
- 参考文档stack-signals.md4.5 KB
03 SKILL.md 正文
Multi-Agent Security Review Team — Orchestration
What this optimizes for: two complementary guarantees, in this order of priority. First, coverage-of-absence — a "clean" verdict on any category must cite the searches that back it, never a bare assertion. Second, verification-of-positives — a finding only reaches the report after surviving independent adversarial validation (Step 4.5) against a category-appropriate proof standard, with self-reported specialist confidence no longer sufficient on its own. "Done" means every roster specialist's owned categories are accounted for (checked-clean, checked-issues-found, or explicitly not-checked with a reason), and every surviving finding in the report is validator-confirmed, not merely raised.
Follow these steps. Review-only. Never apply fixes.
Asking the user
This rule covers every question this skill asks the user: a confirmation, a choice, a missing value, a numbered decision list, or a stop-and-ask. It applies even where a later step says "ask the user" or "ask exactly once". The user may watch the decisions pane rather than the chat, so a question that only goes to chat can go unanswered.
- When
mcp__decision-tracker__decisionis in your tool list (loaded, or named as a deferred tool; load a deferred one withToolSearchfirst), log each question before you ask it:- Call it with
action: "open", the question (ending in?), andoptionswhen the answers are a closed set. Setrequired: falsewhen the work can go on without an answer. A numbered list of questions opens one decision per item. Keep each returnedid. - Ask the question in chat as this skill says. The open call does not replace the chat question.
- When the user answers, in chat or as a
Decision Dn: <answer>message from the plugin, call the tool withaction: "close", theidand the answer. - When the change the answer asked for is in place, call it with
action: "implement", theidand apath:line.
- Call it with
- When the tool is absent, ask in chat only.
- An
AskUserQuestioncall is logged on its own. Do not open it again. - This rule changes where a question is logged. It does not change when this skill asks, or how many times.
Step 0: Pre-run confirmation
The skill's largest user-facing safety gate. Never spawn specialists without showing this block (unless --yes / auto-approve is passed).
0.1 Parse arguments
- Target path — absolute path. If missing, ask: "What repo should I review? (absolute path)" Verify it exists and is readable.
- Scope — one of:
all— full tree<subdir>— a subdirectory, e.g.src/api/diff vs main(ordiff vs <branch>) — only files changed on current branch vs target branch If missing, ask: "What scope? Full tree, a subdirectory, or a diff?"
- Flags:
--yes/-y— skip confirmation entirely. Default is to confirm.--lite/-l— run only the baseline 4 (secrets-crypto-auditor,dependency-supplychain-auditor,silent-failure-hunter,data-exposure-auditor) plusauth-authz-auditorandinput-validation-auditorwhen the stack roster would otherwise include them (5–6 agent subset). Auto-activated when scope is a diff with ≤25 changed files, same threshold ascode-review-team.--full— force a fresh baseline scan, ignoring any prior run history for this target (see Step 0.1a). Without this flag, a target with prior history runs in re-review mode automatically.
0.1a Detect prior run (re-review mode)
Every run writes its artifacts under a run-scoped directory, RUN_DIR = <TARGET>/.planning/security-review/runs/<run_id>/ (run_id = an ISO-timestamp-derived slug), so a later run never overwrites an earlier one. Runs accumulate a top-level ledger at <TARGET>/.planning/security-review/known-findings.jsonl — one record per validator-confirmed root_issue ever reported for this target (root_issue, title, severity, first_seen_run_id, first_seen_date, file, evidence_snippet — the latter two exist solely so a later run can cheaply re-verify the issue is still actually present before reporting it as "still open"; see Step 4.5's ledger-append and re-verification logic).
- Check whether
<TARGET>/.planning/security-review/known-findings.jsonlexists. - Exists, and
--fullwas not passed →re_review_mode = true. This run skipsroot_issues already in the ledger and suppresses alllow/info-severity findings from the report (Step 4.5's re-review filter,re_review_modebranch — sec-review-team's severity floor for "nit-equivalent" islow/info, mirroringcode-review-team's nit suppression). Announce this in the Step 0.7 confirmation block. - Missing, or
--fullwas passed →re_review_mode = false. This is a baseline scan; every validator-confirmed finding is eligible for the report, and the ledger is (re)built from this run's results.
All RUN_DIR-relative paths referenced in Steps 0.9 through 5 below resolve against the RUN_DIR computed here — the ledger itself is the one exception, always written at the top-level <TARGET>/.planning/security-review/ regardless of run_id.
0.2 Enumerate scope
Use git-aware commands (fall back to find if not a git repo):
# File count + line count (scoped)
cd "<TARGET>"
if [[ "<SCOPE>" == "all" ]]; then
FILE_COUNT=$(git ls-files | wc -l)
LINE_COUNT=$(git ls-files | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')
elif [[ "<SCOPE>" == diff* ]]; then
BASE="${SCOPE#diff vs }"; BASE="${BASE:-main}"
FILES=$(git diff --name-only "$BASE"...HEAD)
FILE_COUNT=$(echo "$FILES" | wc -l)
LINE_COUNT=$(echo "$FILES" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')
else
FILE_COUNT=$(git ls-files "<SCOPE>" | wc -l)
LINE_COUNT=$(git ls-files "<SCOPE>" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}')
fi
Cap LINE_COUNT at 500k (anything larger: warn and suggest scope narrowing).
0.3 Detect stack signals
Read the highest-signal manifests first (cheapest): README.md top 50 lines, package.json, Cargo.toml, pyproject.toml, go.mod, Gemfile, tauri.conf.json, Dockerfile, .github/workflows/ (existence only), requirements.txt. Two or three file-reads is enough.
Apply each rule in stack-signals.md against what you observe. Collect matching signals (multiple can match). Example: a Tauri note app emits desktop + desktop-with-webview.
0.4 Pick default roster
Per stack-signals.md: union the specialist sets across all matching stack signals. Always include the baseline 4 (secrets-crypto-auditor, dependency-supplychain-auditor, silent-failure-hunter, data-exposure-auditor).
--lite mode (explicit flag or diff ≤25 files, per 0.1): restrict the roster to the baseline 4 plus auth-authz-auditor and input-validation-auditor — but only if the stack-signal union would have included them anyway. Do not add either specialist in lite mode if the full-roster resolution wouldn't have included it (e.g. a pure iac-only target stays baseline-only in lite mode, not baseline+auth+input-validation). All other stack-specific additions (iac-auditor, container-runtime-auditor, frontend-security-auditor, etc.) are dropped in lite mode. --lite never drops input-validation-auditor or auth-authz-auditor when they'd otherwise be in roster — mirroring code-review-team's decision to keep security-reviewer mandatory in its lite roster, on the same reasoning: a missed injection or authz bug outweighs the tokens saved by cutting these two.
0.5 Detect scanner availability
For each scanner in Step 2.5's pre-pass list, run command -v <name>. Flag available vs missing. Missing scanners don't block — they just reduce pre-pass coverage; dependent specialists will work in manual mode.
0.6 Estimate runtime + tokens + cost
Historical baseline (from the CBP-060 reference run against recall, executed on Opus 4.7 — kept as a labeled historical data point, not a current default):
- Input tokens per specialist:
~min(40k, 30 + 0.02 × LINE_COUNT)k tokens — small repos float near 30k overhead; large repos grow linearly with source lines but the agent self-caps reads. - Output tokens per specialist:
~5–10kregardless of repo size (findings + prose). - Total tokens:
input-validation-auditorandauth-authz-auditoreach run as a loop-until-dry consensus fan-out when in roster (Step 3a,MIN_PASSES = 2..MAX_PASSES = 8), not a single pass. Since the actual pass count (N) isn't known until the loop terminates, estimate withNat its expected midpoint (~4, i.e.N-1 = 3extra specialist-equivalents) for a planning estimate, and note the true range spansMIN_PASSEStoMAX_PASSES:Total tokens ≈ (N_specialists + 3 × N_consensus_specialists_in_roster) × (input + output), worst case(N_specialists + 7 × N_consensus_specialists_in_roster) × (input + output)if every consensus specialist runs the full 8-pass cap. The Step 4.5 validator adds roughly one more agent call per surviving finding group after consolidation — this can't be sized until findings exist, so note it as "+validator passes (post-consolidation, ~1 call per finding group)" rather than folding it into this estimate. - Runtime: longest-path specialist
≈ 3 minbaseline; add0.6 sec / file read. Dependency auditor is typically slowest (~20–30 minon a repo with a 700-packagenode_modules). Report estimate as a range:longest × 1.1tolongest × 1.5. The two consensus specialists run their first 2 passes concurrently, then any further passes one at a time until dry or capped — so wall-clock adds roughly one to a few specialists' worth of time depending on how quickly they dry up, not a fixed multiple — plus a consolidation/tally step afterward (~30s each). - Cost (compute at run time, not from the historical baseline above): look up the current model tier's per-MTok input/output pricing (e.g. via the
claude-apiskill's model catalog or the Models API) for whatever model the orchestrator is actually running on, then compute(input_tokens × price_in + output_tokens × price_out) / 1_000_000, using the consensus-adjusted token total above plus the validator caveat. Print$X.XXalong with the pricing assumption used (model name + rate) so the estimate doesn't silently rot next generation.
0.7 Render confirmation block
╭─ sec-review-team ──────────────────────────────────────────╮
│ │
│ Target: <absolute path> │
│ Scope: <scope> │
│ <N> files, <N> lines │
│ │
│ Stack: <detected signals, comma-separated> │
│ Model: <current model ID> │
│ Mode: <full | lite> │
│ Run mode: <baseline | re-review (vs run <prior_run_id>)>│
│ │
│ Specialists (<N> in roster): │
│ ✓ secrets-crypto-auditor │
│ ✓ dependency-supplychain-auditor │
│ ✓ silent-failure-hunter │
│ ✓ data-exposure-auditor │
│ ✓ auth-authz-auditor (loop-until-dry consensus) │
│ ✓ input-validation-auditor (loop-until-dry consensus) │
│ ✓ <stack-specific additions> (full mode only) │
│ │
│ Pre-pass scanners: │
│ ✓ gitleaks (found on PATH) │
│ ✗ trufflehog (missing — skip) │
│ ✓ semgrep │
│ ✓ npm audit │
│ ✗ cargo audit (missing — skip) │
│ │
│ Estimated: │
│ Tokens: ~<N>k input / ~<N>k output │
│ Runtime: <N>–<N> min (wall-clock) │
│ Cost: ~$<N.NN> (<model pricing tier>) │
│ + validator passes after consolidation (~1 call/finding)│
│ │
│ Output: <TARGET>/.planning/security-review/runs/<run_id>/│
│ (+ known-findings.jsonl ledger, one level up) │
│ │
╰────────────────────────────────────────────────────────────╯
Proceed? [y / lite / narrow / add-specialist / drop-specialist / abort]
0.8 Handle response
- y / yes / (
--yesflag) → continue to Step 1. - lite → switch to lite roster (per 0.4), re-render 0.7.
- narrow → prompt: "Narrow to which subdirectory or diff?" — re-run 0.2–0.7 with the new scope.
- add-specialist
<name>→ append the named specialist if present inspecialists/, reject if unknown, re-render 0.7. - drop-specialist
<name>→ remove from roster, warn if dropping a baseline specialist ("secrets-crypto is in the baseline. Drop anyway?"), re-render 0.7. - abort / n / no / empty response after 60 s (if interactive) → exit cleanly. Do NOT create
.planning/security-review/. Do NOT spawn any agent.
0.9 Run-metadata capture
After confirmation, before Step 1, write <RUN_DIR>/run-metadata.json (<RUN_DIR> from Step 0.1a):
{
"run_id": "<ISO timestamp>",
"target": "<absolute path>",
"scope": "<scope>",
"stack_signals": ["..."],
"roster": ["..."],
"mode": "full | lite",
"run_mode": "baseline | re-review",
"prior_run_id": "<run_id of the most recent prior run, or null in baseline mode>",
"scanners_available": ["..."],
"scanners_missing": ["..."],
"model": "<model ID>",
"estimated_tokens_input": <N>,
"estimated_tokens_output": <N>,
"estimated_cost_usd": <N.NN>,
"confirmed_at": "<ISO timestamp>"
}
This is consumed later by the consolidation step (fills in REPORT.md header), by re-run comparison, and by telemetry if enabled.
Step 1: Final roster
Load only the specialist brief files from specialists/ that match the confirmed roster. Do not load briefs for specialists not in the roster.
Step 2: Prepare findings directory
Create <RUN_DIR> (<TARGET>/.planning/security-review/runs/<run_id>/) if absent — this also creates the parent <TARGET>/.planning/security-review/ on first run. Every specialist/validator/consolidation output for this run is written under RUN_DIR; only known-findings.jsonl lives one level up, at <TARGET>/.planning/security-review/.
Pollution check: run git check-ignore <TARGET>/.planning/ (or grep .gitignore). If .planning/ is NOT ignored, warn the user:
"⚠
.planning/is not in the target's .gitignore. Findings land inside the repo. Options: (a) add to .gitignore, (b) write to /tmp/sec-review-<timestamp>/ instead, (c) proceed."
Respect choice. Default (no response) is (c).
Step 2.5: Scanner pre-pass
Deterministic scanners run before the LLM specialists. This is the biggest accuracy-per-token lever in the skill — specialists spend tokens on signal tools can't catch (architectural, multi-step, context-dependent), not re-deriving secret-detection regexes.
2.5.1 Scanner inventory
| Scanner | Detects | Command | Feeds specialist |
|---|---|---|---|
gitleaks |
secrets at rest | gitleaks detect --source <TARGET> --report-format json --report-path - |
secrets-crypto-auditor |
trufflehog |
secrets (git history + fs) | trufflehog filesystem <TARGET> --json |
secrets-crypto-auditor |
semgrep |
pattern-based SAST | semgrep scan --config auto --json --output - |
multi: input-validation, data-exposure, silent-failure (by rule category) |
npm audit |
npm CVEs | cd <TARGET> && npm audit --json |
dependency-supplychain-auditor |
cargo audit |
crate CVEs | cd <TARGET> && cargo audit --json |
dependency-supplychain-auditor |
pip-audit |
Python CVEs | pip-audit -r <TARGET>/requirements.txt -f json |
dependency-supplychain-auditor |
osv-scanner |
multi-ecosystem CVEs | osv-scanner -r <TARGET> --format json |
dependency-supplychain-auditor |
bandit |
Python SAST | bandit -r <TARGET> -f json |
input-validation-auditor (Python targets) |
hadolint |
Dockerfile lint | hadolint <file> -f json |
container-runtime-auditor |
trivy image |
base image CVEs | trivy image --format json <image> |
container-runtime-auditor |
actionlint |
GHA syntax + security | actionlint -format '{{json .}}' |
ci-cd-security-auditor |
tfsec |
Terraform security | tfsec <TARGET> --format json |
iac-auditor |
checkov |
IaC security | checkov -d <TARGET> --output json |
iac-auditor |
retire.js |
known-vulnerable JS libs | retire --path <TARGET> --outputformat json |
frontend-security-auditor |
Full mapping: see docs/scanner-coverage.md.
2.5.2 Detection + invocation
For each scanner in the inventory:
if command -v <scanner> >/dev/null 2>&1; then
run the scanner, capture stdout
parse JSON output
normalize to finding.schema.json format (see 2.5.3)
append to <RUN_DIR>/scanner-prepass.jsonl
else
log "scanner <name> not available — skipping"
fi
Run scanners in parallel where safe (no shared-state conflicts). Wall-clock ceiling per scanner: 120 s (kill if exceeded; log partial results).
2.5.3 Normalization
Every scanner emits different JSON. Normalize each finding to finding.schema.json with:
id—scanner-<name>-<index>(e.g.scanner-gitleaks-001)specialist— the specialist this finding is routed to (see inventory above). Even scanner findings get routed; specialists triage them.source—"scanner-<name>"(so provenance is visible)severity— mapped from scanner's severity (HIGH→high,MEDIUM→medium, etc.)confidence— scanners getcertainfor deterministic rule hits,likelyfor heuristic rulestitle,root_issue— from scanner output, slug-normalizedfile,line_range,evidence— from scanner outputexploit,fix— scanners often omit these; leave""and let the specialist agent fill during triage
Save to <RUN_DIR>/scanner-prepass.jsonl.
2.5.4 Feed specialists
When spawning each specialist in Step 3, include a "Pre-pass context" section in the brief:
Pre-pass scanner findings relevant to your specialist (from `<RUN_DIR>/scanner-prepass.jsonl`):
[N findings from gitleaks]
[M findings from semgrep for your category]
Triage rules:
1. For each scanner-sourced finding, decide: true-positive / false-positive / needs-investigation.
Emit a finding entry in your JSONL with `source: "scanner-<name>-confirmed"` (for true-positive) or
`source: "scanner-<name>-false-positive"` (for false-positive triaged out).
2. After triage, find what the scanner missed via architectural / cross-file analysis — these go in your
JSONL with `source: "specialist"` as usual.
Specialists are not obligated to re-derive secret regexes. Their job is triage + judgment.
2.5.5 REPORT.md provenance
The consolidation step (Step 4) renders REPORT.md with a "Source" column for each finding so the reader sees which scanner (if any) caught it and whether a specialist confirmed or overturned the triage. Scanner-only confirmations (no specialist review) are flagged with ⚠ unreviewed by specialist — this happens when a specialist errored before triaging the scanner pre-pass.
2.5.6 Scanner unavailability
If zero scanners are available in the environment, the run continues — specialists do the full job themselves, which is the CBP-060 baseline mode. Log a tooling caveat in REPORT.md so the reader knows coverage is LLM-only.
Step 3: Spawn specialists in parallel
Issue one Agent tool call per specialist in a single assistant message — this is what makes them run concurrently.
input-validation-auditor and auth-authz-auditor are excluded from this step when they're in roster — they run via Step 3a's loop-until-dry consensus fan-out instead. Spawn every other roster specialist here as usual (if neither is in roster for this run's stack signals, this step just spawns the full roster as before).
For each specialist (other than input-validation-auditor and auth-authz-auditor):
- Read its brief file (
specialists/<name>.md). - Substitute
<TARGET>,<SCOPE>,<STACK CONTEXT>,<THREAT_MODEL>. - Pass
allowed-toolsper the specialist's## Allowed toolssection (hard read-only; Write scoped to the four output-file paths only). - Use
preferred_subagent_typefrom frontmatter; fall back tofallback_subagent_typeif unregistered. - Read the
effortfrontmatter field (high/medium/low; specialists without the field default tomedium) and tune the spawn accordingly:high(currentlyauth-authz-auditor,input-validation-auditor— also applied per-pass in Step 3a) gets the model's extended-thinking / max-effort mode where the runtime exposes one, and no early-stop encouragement in the brief;low(currentlyprivacy-telemetry-auditor,ci-cd-security-auditor— narrower, more mechanical scopes) gets standard effort with an added brief line: "This scope is narrow — a thorough single pass is sufficient, don't over-invest reasoning budget beyond what the category list requires.";medium(everything else) gets no effort-specific brief addition. This is a token/latency lever, not a quality floor — every specialist still runs the full hard-rules/flag-everything/validator pipeline regardless of effort tier.
Print to the user before the parallel batch returns:
Spawning <N> specialists in parallel against <TARGET> (scope: <SCOPE>, mode: <mode>).
Each specialist writes to <RUN_DIR>/<name>.{md, findings.jsonl, coverage.jsonl, status.json}.
Live progress (run in a separate terminal):
watch -n 5 'jq -s "sort_by(.agent) | map({agent, status, files_read, findings_written})" <RUN_DIR>/*.status.json'
Or:
tail -f <RUN_DIR>/*.status.json
Spawning now. Interim status lines will appear as each specialist returns.
3.1 Heartbeat contract (per specialist)
Every specialist brief instructs the agent to write <name>.status.json at spawn time and to update it as progress happens. Format:
{
"agent": "<specialist-name>",
"status": "starting | scanning | writing-findings | completed | errored",
"started_at": "<ISO>",
"finished_at": "<ISO|null>",
"files_read": <int>,
"findings_written": <int>,
"current_file": "<path|null>",
"severity_counts": { "critical": 0, "high": 0, "medium": 0, "low": 0 },
"error": "<message|null>"
}
- On spawn: write with
status: "starting",started_at, counters at 0. - Every ~5 file reads: update
files_read,current_file, setstatus: "scanning". - On each finding: increment
findings_written, updateseverity_counts. - On completion: set
status: "completed",finished_at, final counts. - On error: set
status: "errored",finished_at, populateerror.
The agent must flush .status.json to disk before returning from its Agent tool call, so the orchestrator sees the final state once the subagent completes.
3.2 Interim reports (orchestrator-side)
Batched Agent calls in a single message run concurrently, but the orchestrator receives their results serialized — one completion at a time. As each specialist returns:
- Read the specialist's
status.jsonfile to confirm final state. - Print one line to the user:
✓ auth-authz-auditor done (0C / 2H / 2M / 1L) [4m 22s] 12 files read
✓ input-validation-auditor done (0C / 0H / 0M / 3L) [3m 15s] 8 files read
✗ dependency-supplychain-auditor errored after 18m — node_modules walk timed out. Partial findings preserved.
✓ silent-failure-hunter done (0C / 2H / 3M / 3L) [5m 03s] 14 files read
...
-
Errored specialists don't block the run. Keep processing completions from the remaining specialists. In Step 4 (consolidate), errored specialists are flagged in REPORT.md with their last-known
status.jsonstate. -
After the last specialist returns, print:
All <N> specialists completed (<X> done, <Y> errored). Consolidating...
3.3 Output files per specialist
Each specialist writes four files to <RUN_DIR>:
<name>.md— prose findings (human-readable)<name>.findings.jsonl— perschema/finding.schema.json<name>.coverage.jsonl— perschema/coverage.schema.json<name>.status.json— heartbeat / final state
Step 3a: Multi-pass consensus fan-out (input-validation-auditor, auth-authz-auditor)
input-validation-auditor and auth-authz-auditor own the two highest-variance tasks in the roster — tracing every external input to its sink, and mapping every endpoint/IPC command to its auth requirement, are both enumerate-everything jobs a single pass under-samples, finding a different random subset of the real issues (and missing others) each run. When either is in the confirmed roster (per Step 0.4 / Step 1), run it through a loop-until-dry fan-out — independent passes with the file scope reordered per pass, keeping only findings that reproduce across at least 2 passes, and stopping once a pass stops contributing anything new rather than always running a fixed count — the same design code-review-team uses for correctness-reviewer/performance-reviewer (TBS-044), modeled on Cursor Bugbot's multi-pass agreement design, adapted so effort scales with how much variance the target is actually producing.
MAX_PASSES = 8 — the hard ceiling regardless of whether passes are still finding new issues. MIN_PASSES = 2 — the floor, since hit_count >= 2 can never have a survivor with fewer than 2 passes.
If neither specialist is in roster for this run (e.g. a pure iac-only target), skip this step entirely — Step 3 already spawned the full roster as single-pass.
3a.1 File-order rotation
Enumerate the scope's file list once (same enumeration as Step 0.2). For pass i (1-indexed, 1..MAX_PASSES), rotate the list by i × floor(len / MAX_PASSES) positions (wrap-around) before handing it to the specialist brief as an explicit ordered file list — rotating against the fixed ceiling (not however many passes end up running) keeps every pass's ordering distinct regardless of when the loop stops. This produces a materially different read order per pass without depending on random-number generation, which a markdown-driven orchestration can't reliably produce.
3a.2 Spawn passes — loop until a round is dry, capped at MAX_PASSES
For each of input-validation-auditor and auth-authz-auditor that's in roster, independently:
- Run passes 1 and 2 (
MIN_PASSES) concurrently as 2Agentcalls in a single assistant message — the minimum needed for anyhit_count >= 2survivor to exist at all. - After every pass (or batch) completes, tally (3a.3) and compare this round's distinct
root_issueset against the union of everyroot_issueseen in all prior rounds for this specialist:- This round introduced at least one
root_issuenot seen in any earlier round → continue: spawn one more pass (i+1). - This round introduced zero new
root_issues → stop. The pass is "dry" — further passes are unlikely to surface anything new. - Regardless of dryness, stop unconditionally once pass
MAX_PASSES(8) completes.
- This round introduced at least one
- Passes after the initial 2 are spawned one at a time (each pass's file rotation depends only on its own fixed index, not on prior results — only the stopping decision is sequential).
Each pass uses the specialist's normal brief (substituting <TARGET>, <SCOPE>, <STACK CONTEXT>, pre-pass scanner findings as usual), with the rotated file order substituted for <SCOPE>'s file enumeration, plus an added line: "This is pass <i> of up to MAX_PASSES independent review passes over the same scope, each in a different file order. Review thoroughly as if this were the only pass — do not assume another pass covers what you skip."
Each pass writes to <RUN_DIR>/<name>.pass<i>.findings.jsonl and <name>.pass<i>.status.json — not the canonical <name>.findings.jsonl / <name>.status.json. The canonical files are written by the tally step below, once the loop terminates.
3a.3 Tally and threshold
Once the loop in 3a.2 terminates (a dry round, or the MAX_PASSES cap) with N total passes run for a specialist (MIN_PASSES <= N <= MAX_PASSES):
- Read all
N<name>.pass<i>.findings.jsonlfiles. - Group findings by
root_issueacross theNpasses. - For each group, compute
hit_count= number of distinct passes it appeared in (1–N). - Keep only groups with
hit_count >= 2— drop the rest as one-off, unreproduced findings (sampling noise or hallucination). - For each surviving group, take the representative finding with the highest
confidence(ties: earliest pass), tag it withhit_count, and write it to the canonical<name>.findings.jsonl, ranked byhit_countdescending. Union thecoverage.jsonlentries across allNpasses into the canonical<name>.coverage.jsonl(a category ischecked-cleanonly if allNpasses checked it clean). - Write the canonical
<name>.status.jsonascompleted, withseverity_countsreflecting only the survivors. - Print one line per specialist:
✓ input-validation-auditor consensus: <kept>/<total distinct root_issues> root_issues survived (hit_count >= 2 of N passes, stopped <on a dry round after pass N | at the MAX_PASSES cap>).
Step 4's consolidation reads <name>.findings.jsonl and <name>.coverage.jsonl exactly as it does for every other specialist — it has no knowledge that these came from a variable-length tally rather than a single pass.
Step 4: Consolidate
Read all *.findings.jsonl and *.coverage.jsonl from <RUN_DIR>. Apply docs/consolidation-template.md:
- Group findings by
root_issue(dedupe across specialists). - For each group:
max(severity),max(confidence),distinct(specialist) → raised_by,hit_count(carried through if present). - Build coverage matrix (category × specialist) from coverage.jsonl. Completeness-score denominator: the union of every roster specialist's static "Coverage categories this specialist owns" list (from its brief), not
count(coverage.jsonl records actually written)— an errored specialist that wrote zero coverage records must not shrink the denominator by having its owned categories silently disappear from the count, which would let a less-complete run score higher than a more-complete one. Missing categories score asnot-checked(shown as ✗ in the matrix), which drags the score down as it should.
Every deduped group — regardless of confidence — proceeds to Step 4.5. There is no confidence-based pre-filter here; specialists are instructed to flag everything they notice (Step 3's briefs), so the validator step is what decides what's real.
If any specialist failed schema validation on its JSONL, fall back to prose parse for that specialist and log a warning in REPORT.md.
As each specialist returns, print an interim-status line: ✓ <name> done (<counts>) [<runtime>] or ✗ <name> errored: <msg>. Don't block on errored specialists.
Step 4.5: Verify
For every group produced by Step 4, spawn one validator Agent to try to refute it. Batch validator calls into as few concurrent assistant messages as practical (all in one message, unless the finding count is large enough to need splitting for tool-call limits).
Validator prompt (substitute the group's consolidated fields):
Here is a claimed security finding:
<title>—<one-line summary combining evidence + fix from the group>. Open<file>:<line_range>, read the surrounding code and its callers/entry points. Can you PROVE it's real — with afile:linecitation AND a working exploit path (the concrete chain from an attacker-reachable entry point to the vulnerable sink, since security findings often need a reachability chain rather than a single failing input value)?
- Yes → keep, attach the exploit path.
- No / can't confirm from the actual code, or the "vulnerable" sink is unreachable / already guarded → drop it. Cite
file:line. Do not infer from naming or pattern-matching alone — verify against the actual code you read, including what calls into and guards the sink.
Validator subagent constraints: read-only (Read, Grep, Glob, safe Bash allowlist matching the specialist briefs' allowlist); no Write access to findings files — it returns its verdict as its final message, which the orchestrator parses.
Handle the verdict:
- Confirmed — set
validator_confirmed: trueon the group, attach the citedexploit_path. The group proceeds to the report. - Rejected — set
validator_confirmed: false. Drop the group fromREPORT.mdentirely. Append it (with the validator's rejection reason) to<RUN_DIR>/rejected-by-validator.jsonlfor audit/debugging — this file is never surfaced in the report body, only mentioned by count in Tooling Caveats. - Validator itself errors/times out — treat as unconfirmed (drop from report, log to
<RUN_DIR>/rejected-by-validator.jsonlwitherror: true) rather than blocking the run.
After this step, actionable = [g for g in groups if g.validator_confirmed] — this replaces the old confidence-based certain|likely split that used to gate inclusion here. Everything that survives the validator is actionable by definition; everything that doesn't is dropped, not demoted.
Re-review filter (only when re_review_mode is true, from Step 0.1a): after computing actionable, load <TARGET>/.planning/security-review/known-findings.jsonl and split every non-low/info ledger entry into exactly one of two disjoint sets relative to this run:
reconfirmed_known— the entry'sroot_issuealso appears in this run'sactionable(an independent specialist re-found it and the validator re-confirmed it, this run). Drop these groups fromactionablebefore rendering the report — already reported in a prior run, don't repeat it — and recordlen(reconfirmed_known)for Step 5's Tooling Caveats. Areconfirmed_knownentry needs no lightweight re-verification below: this run's fresh validator confirmation is stronger evidence than any storedfile/evidence_snippetgrep could provide.carried_over_known— the entry'sroot_issuedoes not appear in this run'sactionable(not independently rediscovered this run — its file may be entirely outside this run's<SCOPE>, or in scope but simply missed by every specialist this pass).<SCOPE>never gates membership here — a diff-scoped run's ledger entries for files outside the diff still entercarried_over_knownexactly like any other not-rediscovered entry;<SCOPE>only ever governed which files specialists actively looked at, not which known findings this run is allowed to report status on. The lightweight check below (not scope) is what keeps an unverified claim from being asserted about a file this run never touched. Before running that check, first look up this run's<RUN_DIR>/rejected-by-validator.jsonl: if the entry'sroot_issueappears there (a specialist rediscovered it this run and the validator explicitly rejected it as fixed/unreachable/already-guarded), treat that rejection as authoritative and exclude the entry from "still open" without running the grep check below — a same-run validator verdict is stronger evidence either way than a stale snippet match.
Then drop every remaining low/info-severity group from actionable outright (sec-review-team's floor tier is never carried forward into the report, mirroring code-review-team's nit suppression) — low/info-severity ledger entries are excluded from both sets above for the same reason: the floor tier is never carried forward whether it resurfaces in actionable or only survives in the ledger. Record the floor-tier-suppressed count — count(actionable groups dropped as low/info this run) specifically, not ledger-only low/info entries, which were never part of either set and were never counted or reported to begin with — for Step 5's Tooling Caveats. Baseline runs (re_review_mode false) skip this filter entirely — reconfirmed_known and carried_over_known are both empty, and there is no still-open bookkeeping to do.
Example: ledger has three non-floor-tier entries —
S1(file inside this run's diff),S2(file outside this run's diff),S3(file touched and re-examined this pass). This re-review run's specialists rediscover and the validator reconfirmsS3this pass →S3 ∈ reconfirmed_known(suppressed from the report, no lightweight check needed).S1andS2are not rediscovered by any specialist this run → both land incarried_over_knownregardless of scope —S2is not dropped just because it's outside the diff.
Update the ledger: append every validator-confirmed root_issue from this run that isn't already in <TARGET>/.planning/security-review/known-findings.jsonl (including ones suppressed from this run's report because they matched the re-review filter — the ledger tracks everything ever confirmed, not just what's newly shown) — with first_seen_run_id set to this run's run_id, file set to the finding's file, and evidence_snippet set to the finding's evidence field trimmed to its first line only, up to ~80 characters, whichever is shorter — never spanning a newline, since the re-verification check below matches it line-by-line — for genuinely new entries; first_seen_run_id/first_seen_date on existing entries are left unchanged (they record provenance, not current state). Refresh file/evidence_snippet on every reconfirmation: whenever a root_issue lands in reconfirmed_known this run (validator-confirmed, and already had a ledger entry), overwrite that entry's file/evidence_snippet with this run's finding, regardless of whether the fields were already populated. This is deliberate, not just a legacy-entry backfill: code legitimately moves (refactors, renames), and an entry frozen at its first-confirmed location would eventually cause the lightweight check below to grep a stale, possibly-nonexistent path and wrongly conclude a still-present issue is "no longer open." first_seen_run_id/first_seen_date still never change on this refresh — only the location/evidence fields track the most recent confirmation.
Re-verify "still open" claims before reporting them (do not assert by absence alone): for every root_issue in carried_over_known not already excluded by this run's rejected-by-validator.jsonl (above), don't just count it as "still open" because it's still on the ledger — a carried_over_known entry may have been fixed since it was first confirmed, and the ledger by itself can't tell you that. Instead:
- Look up that
root_issue's ledger record'sfileandevidence_snippet. - Legacy entries — no
file/evidence_snippeton record: these two fields didn't exist in the ledger schema before this re-verification mechanism was added, so anyknown-findings.jsonlwritten by an earlier version of this skill will have entries missing them. For those, the mechanical check in step 4 below is impossible to run — there's nothing to grep for. Don't treat "can't check" the same as "can't confirm" (step 4's exclusion rule): fall back to counting the entry toward "still open" as before, but tag it in Step 5's caveats as<N> previously-reported issues counted as still open without re-verification (ledger entry predates file/evidence tracking)— this keeps the count from silently shrinking just because older entries lack the new fields, while being honest that those specific counts are unconfirmed. Once any of theseroot_issues is independently reconfirmed by a specialist in a future run, it moves to that run'sreconfirmed_knownand the ledger-append step refreshes itsfile/evidence_snippet, so this caveat category shrinks over time and eventually empties out. - Path containment check: resolve
fileagainst<TARGET>. If it resolves outside<TARGET>'s tree (via..traversal, an absolute path pointing elsewhere, or any other escape) — a ledger value should never do this, but the ledger is ordinary repo-tracked JSON an external contributor could edit — treat it the same as a legacy entry (step 2): can't verify, count with the unverified caveat, and do not read or grep the out-of-tree path. This matters more here than in a general code-quality tool: a false "no longer open" on a security ledger entry is a much sharper failure than an unverified path. - In-tree entries with
file/evidence_snippeton record: check file existence, then run the grep as a single fixed-string, argument-safe invocation — native argument passing with an explicit--separator (e.g.grep -qF -- "<evidence_snippet>" "<resolved_file_path>", never a shell string concatenation), or equivalently theGreptool in literal mode — using the same restricted, read-only tool scope a specialist would use, never an unconstrained shell. Iffileno longer exists at that path, or the grep returns no match, the finding can no longer be confirmed present at that location.- Critical/high severity: a miss here is not treated as "confirmed absent" — treat it as unverified, same bucket as a legacy entry (step 2), and tag it separately in Step 5 as
<N> critical/high previously-reported issues could not be re-verified this run — status unknown, recommend manual check. A false "no longer open" on a still-exploitable vulnerability is the costliest failure mode this whole mechanism exists to avoid — it must never be folded silently into the same exclusion path as a low-severity finding. - Medium/low/info severity (below
critical/high, and non-floor-tier): a miss excludes it from the "still open" count for this run's report, as before (it may have been fixed, refactored away, or moved somewhere a plain grep won't find).
- Critical/high severity: a miss here is not treated as "confirmed absent" — treat it as unverified, same bucket as a legacy entry (step 2), and tag it separately in Step 5 as
- Only
root_issues whoseevidence_snippetstill greps-clean infile(step 4) count toward<P>in Step 5's "previously-reported issues still open" line; legacy entries (step 2), out-of-tree entries (step 3), and critical/high unverified misses (step 4) all count toward<P>too, but are called out separately in the caveats as unverified. This is a cheap, mechanical check for the entries that support it, not a full re-review — it catches the common case (the file/snippet is simply gone) without re-running a specialist. Perform it as a single loop over all ofcarried_over_known(one script/Bashinvocation), not one tool call per entry. - This check only affects what gets reported as still open this run — it never edits or removes entries from
known-findings.jsonlitself, which remains a historical record of everything ever confirmed.
Example (multi-run backfill trace): Run N (pre-fix baseline, before this ledger schema existed) confirms
root_issue: S4and appends it to the ledger with nofile/evidence_snippet(legacy shape). Run N+1 (re-review; no specialist re-findsS4this pass):S4 ∉ actionable→S4 ∈ carried_over_known; its ledger record has nofile/evidence_snippet→ step 2's legacy fallback fires →S4counts toward<P>, tagged "counted without re-verification." Run N+2 (re-review; a specialist independently re-findsS4and the validator reconfirms it this pass):S4 ∈ actionableand already in the ledger →S4 ∈ reconfirmed_known→ suppressed from the report, and the ledger-append step's refresh clause (above) now setsS4'sfile/evidence_snippetfrom this run's finding. Run N+3 (re-review;S4not re-found again, and its file hasn't moved since N+2):S4 ∉ actionable→ back incarried_over_known, its ledger record hasfile/evidence_snippetfrom N+2 → step 4's grep check runs andS4counts toward<P>as verified, no caveat. (HadS4's code instead moved to a new file between N+2 and N+3 without being rediscovered, step 4's existence check on the stale N+2 path would fail — for a medium/low finding this excludes it as before; for a critical/high finding it's flagged unverified rather than silently dropped, per step 4's severity branch above.)
Render <RUN_DIR>/REPORT.md from the consolidation template using only the final actionable groups — <P> (the still-open count from the re-verification above) is now available for the template.
Step 5: Deliver
Tell the user:
- Path to
<RUN_DIR>/REPORT.mdand the per-specialist files each (four, or up to nine for the two consensus specialists — canonical + up to 8 per-pass files) - Headline severity counts (deduped, validator-confirmed, post-re-review-filter)
- Top 2–3 fixes by impact
- Validator summary:
<N> findings confirmed, <M> rejected by the validator(rejected count only — not the rejected findings themselves, seerejected-by-validator.jsonl) - If
re_review_mode:<P> previously-reported issues still open (not repeated above — see known-findings.jsonl), <Q> low/info findings suppressed—<P>is the re-verifiedcarried_over_knowncount (Step 4.5's "Re-verify still open claims"), not a raw ledger-match count; if any of<P>are legacy entries counted per that step's step 2 (predatingfile/evidence_snippettracking), append(<L> of these counted without re-verification — ledger entry predates file/evidence tracking); if any critical/high entries hit step 4's unverified branch, append a separate line:<M> critical/high previously-reported issues could not be re-verified this run — status unknown, recommend manual check - Tooling caveats (scanners unavailable, fallbacks taken, specialists errored)
- Coverage completeness score
Do NOT apply fixes. Fix workflow → companion skill /fsad-harness:sec-review-fixes (CBP-070).
Related
- Specialist library:
specialists/(13 briefs) - Stack signals:
stack-signals.md - Schemas:
schema/finding.schema.json,schema/coverage.schema.json - Shared output-format contract:
docs/output-contract.md - Consolidation template:
docs/consolidation-template.md - Design tradeoffs:
docs/tradeoffs.md - Canonical prompt source:
markdown/design/Team_of_security_agents.md - Embedded playbook section:
fsad-playbook.html→#practices/security-review - Opus 4.7 validation (historical):
markdown/research/security-review-opus-4.7-validation.md - Improvement recommendations:
markdown/research/sec-review-recommendations.md
04 同仓库更多 Skills
sync
Pre-flight sync check for registered projects. Fetches from remote, reports uncommitted changes, and handles four states — up-to-date, ahead, behind (warns + pulls), or diverged (warns, no pull). Supports single-project (cwd detection), named-project (fsad-harness:sync key1 key2), and full-sync (fsad-harness:sync ALL) modes.
spec-review
Run a detailed, adversarial multi-agent review of a written proposal or specification (spec.md, PRD, RFC, design doc, architecture proposal, business case) — reviews the document, not code. Runs a steelman/inventory pass, then up to 10 specialist lenses (logic, assumptions, evidence, completeness, feasibility, alternatives, consistency, stakeholders, testability, risk), then adversarial validation that defaults to refuting each finding unless it survives, then a loop-until-dry completeness pass. Writes a single severity-ranked SPEC-REVIEW.md next to the document. Use when the user says "review this spec", "adversarial review", "poke holes in this proposal", "red team this PRD", "critique this design doc", "what's wrong with this RFC", "stress test this plan", or similar. Review-only — never edits the document.
ship
Short alias for /fsad-harness:ship-it. Wrap up finished work, cut a version, commit, push, and open a PR.
ship-it
Wrap up a finished round of work — verify README and CHANGELOG are current, cut a version if warranted, move completed task files into the completed/ folder, then create a feature branch if needed, commit, push, and open a PR. Run after `/fsad-harness:do-task` has marked one or more tasks done. Takes no arguments.
set-context
Gather codebase and initiative context before planning starts — runs a graphify build or query when graphify is available locally, falls back to a manual repo sweep otherwise, then asks targeted questions and writes a single context.md. Invocable standalone or as fsad-harness:plan's mandatory Phase 0. Use when the user says "set context", "gather context", or as the first step of fsad-harness:plan.
sec-review-fixes
Companion to `sec-review-team`. Takes the `REPORT.md` or `findings.jsonl` from a security review run and produces fix PRs (one per finding or one consolidated) with interactive approval. Each candidate finding becomes a proposed diff + a regression test that would have caught it + a commit/PR citing the finding ID. Filters by default to `severity IN (critical, high) AND confidence IN (certain, likely)`. Modes: interactive (default), `--dry-run` (produces a fix-plan document with no code changes), `--re-verify` (re-runs sec-review-team against a specific set of finding IDs and reports fixed / still-present / inconclusive). Safeguards: branch-only commits (never main), no force-push, clean-working-tree required, regression-test must pass before commit lands. Use when the user says "fix the security findings", "apply sec-review fixes", "open PRs for the security report", "re-verify the security fixes", or similar.