| name | cold-review |
| description | Blind security audit for any codebase. Derives system structure from code only — no project documentation read. Parallel 20-category Sonnet subagents + Opus synthesis. Counter-evidence required for CRITICAL/HIGH/MEDIUM. Resumable via STATE.json. |
Cold Review
You are PM for a broad, infrequently-run review of the entire codebase. The review is based on the principle that nothing in the project's documentation may influence the assessment — the reviewer derives the system's structure, trust boundaries, and business rules from the code itself.
The skill runs in two modes: launcher (sets up a fresh worktree and hands off) and runner (executes the full discovery + synthesis flow inside that worktree). Determine which mode applies before doing anything else.
Known limitation: CC harness injection of CLAUDE.md
The Claude Code harness automatically injects the project's CLAUDE.md (and certain other project .md files) as <system-reminder> in the context window for all sessions, including subagents. This happens regardless of whether the agent actively calls Read against the file. The content is therefore available for anchoring even without an active read.
Consequence: cold-review's core value ("no .md anchors the review") is not fully achievable on the CC platform in its current form. The 2-source anti-cheating declaration (active Read calls + passive injection) in the discovery and synth prompts attempts to mitigate through transparency, not elimination. The PM check verifies that the declaration is complete — not that injection has not occurred.
Practical implication: cold-review delivers somewhat less value over /security-audit than the design spec promises — estimated 50–70% of the ideal case. Accept this when interpreting final reports. The subagent's own assessment of whether content "was used" is best-effort. A long-term fix requires a platform change (CC config that suppresses system-reminders for specific sessions) that is not available in current tooling.
Discipline for you as PM
Your own discipline is everything. If you cheat (reading CLAUDE.md for "context" beyond injection, anchoring on what you believe to be true, accepting subagent reports without spot-checking) the entire exercise becomes theater. Follow the checklist point by point. Do not stop early.
PM output discipline
While the audit runs, the user mostly does not care about your individual tool calls — they want to know where in the flow we are and whether anything went wrong. Apply this discipline to your text output:
- Status-line format only. Each user-visible message MUST be a single line in the form:
[HH:MM:SS] <event>. Example: [21:00:03] ✓ 04-cryptographic-failures done — 0C/0H/1M/3L (4/16).
- One status line per phase transition. Emit at: phase start, batch dispatch, each subagent return, re-dispatch decisions, final report. Do not narrate intermediate tool calls.
- No commentary. Do not write paragraphs of reasoning between tools. Do not summarize what you are about to do next. Do not duplicate file content in chat. Trust that the user can see the artifacts on disk.
- Errors are loud. When something fails (timeout, re-dispatch, format error): emit a clearly-marked
[HH:MM:SS] ⚠ <category> re-dispatch — reason: <one-line cause>.
- At the end (and only at the end), emit the full result summary as described in section "3. Report to user (English)".
This is a rigid rule, not a guideline. If you feel the urge to write a paragraph explaining what you are doing — stop. The user can read the status lines.
Timestamps must come from $(date +%H:%M:%S) at emit time. Do not pre-compute or batch.
Two run modes
This skill runs in one of two modes depending on where the PM session is invoked:
- Launcher mode — invoked from project main. Sets up a fresh worktree+branch
feature/cold-review-YYYYMMDD, copies a kickoff command to clipboard, and attempts to open a new terminal tab. Then stops. The audit itself does NOT run in this session.
- Runner mode — invoked from inside a cold-review worktree (
.claude/worktrees/cold-review-* on branch feature/cold-review-*). Executes the full discovery + synthesis flow there.
The rationale for the worktree split: the cold-review run produces local files (.audits/, the synthesis report) and a commit. Doing this in main pollutes the user's main checkout and risks interleaving with other work. A fresh worktree on a fresh branch from main gives the same code state as main (at create-time) with full isolation.
When the skill starts — flow
1. Detect mode
Run this bash to determine where we are:
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null)
BRANCH=$(git branch --show-current 2>/dev/null)
PWD_REL=$(pwd)
if [ -z "$GIT_DIR" ]; then
echo "Not in a git repository."
exit 1
fi
if [ "$GIT_DIR" = "$GIT_COMMON" ]; then
MODE="launcher"
elif [[ "$BRANCH" == feature/cold-review-* ]] && [[ "$PWD_REL" == *.claude/worktrees/cold-review-* ]]; then
MODE="runner"
else
echo "Not in main directory or a cold-review worktree (branch=$BRANCH)."
echo "cold-review must be started from main."
echo "Run: git worktree list # for overview"
exit 1
fi
echo "Mode: $MODE"
In main, --git-dir and --git-common-dir are the same path. In worktrees, --git-dir is .git/worktrees/<name> while --git-common-dir is the main .git. The branch-name + path check filters cold-review worktrees specifically.
Branch on $MODE:
launcher → continue to section 2 ("Launcher mode")
runner → skip section 2, jump to section 3 ("Runner mode")
2. Launcher mode — set up worktree + tab + clipboard
Runs only when we are in project main.
Step 2-pre: MCP server pre-flight check
Before setting up the worktree, verify that the two MCP servers the skill relies on are installed: Context7 (library/framework documentation) and sequential-thinking (multi-step reasoning under uncertainty). The skill is degraded but not broken without them — discovery agents will fall back to training-data reasoning and WebSearch for CVE lookups — so this check warns but does not block.
CC_USER_SETTINGS="$HOME/.claude/settings.json"
CC_USER_JSON="$HOME/.claude.json"
CC_PLUGINS_CACHE="$HOME/.claude/plugins/cache"
found_context7=0
found_sequential=0
for f in "$CC_USER_SETTINGS" "$CC_USER_JSON"; do
if [ -f "$f" ]; then
if grep -qi "context7" "$f" 2>/dev/null; then
found_context7=1
fi
if grep -qi "sequential-thinking\|sequentialthinking" "$f" 2>/dev/null; then
found_sequential=1
fi
fi
done
if [ -d "$CC_PLUGINS_CACHE" ]; then
if find "$CC_PLUGINS_CACHE" -type d -iname '*context7*' 2>/dev/null | grep -q .; then
found_context7=1
fi
if find "$CC_PLUGINS_CACHE" -type d -iname '*sequential*thinking*' 2>/dev/null | grep -q .; then
found_sequential=1
fi
fi
missing=""
[ "$found_context7" -eq 0 ] && missing="$missing - Context7 (library docs lookups)\n Install: https://github.com/upstash/context7\n"
[ "$found_sequential" -eq 0 ] && missing="$missing - sequential-thinking (multi-step reasoning)\n Install: https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking\n"
if [ -n "$missing" ]; then
printf "⚠ Cold-review uses two MCP servers that appear NOT to be installed:\n\n"
printf "%b" "$missing"
printf "\nThe skill will still run, but discovery agents will fall back to training-data\n"
printf "reasoning and WebSearch for CVE lookups. Findings quality may be slightly\n"
printf "lower for framework-specific semantics and ambiguous-evidence reasoning.\n\n"
printf "Continue anyway? (y/n): "
read -r answer
case "$answer" in
y|Y|yes|YES) echo "Continuing without missing MCP servers." ;;
*) echo "Aborted. Install the listed servers and re-run /cold-review."; exit 0 ;;
esac
else
echo "✓ MCP pre-flight: Context7 + sequential-thinking detected"
fi
If the user is non-interactive (e.g. dispatched from another skill), default to "continue with warning" rather than block.
Step 2-pre-2: Upstream reference-material sync check
After the MCP check, verify whether the local checklists/ reference material is in sync with the upstream repository it was cloned from. The skill is intentionally based on Claude Security Audit and keeps a local copy of its references/ folder. Upstream releases new attack vectors, framework checks, and compliance entries periodically.
Trust model: The default sync policy is trust upstream main blindly — when upstream has new commits, the sync prompt offers to apply them after classifying per file (safe-update / local-only-mod / CONFLICT). This is a deliberate trade-off: we get maximally fresh checks at the cost of trusting the upstream maintainer's main branch. See the README "Upstream sync" section for the supply-chain risk discussion. Users who want stricter behaviour can opt out by setting COLD_REVIEW_SKIP_UPSTREAM_SYNC=1 in their environment.
The 3-way classification per file:
| Local vs baseline SHA | Upstream vs baseline SHA | Class | Default action |
|---|
| equal | equal | unchanged | skip |
| equal | different | safe-update | overwrite local with upstream |
| different | equal | local-only-mod | keep local |
| different | different | CONFLICT | keep local + warn |
Baseline SHA-1s are stored in checklists/.upstream-sync.json. The last_sync_commit field marks the upstream commit those baselines were captured from.
if [ -n "${COLD_REVIEW_SKIP_UPSTREAM_SYNC:-}" ]; then
echo "ⓘ Upstream sync skipped (COLD_REVIEW_SKIP_UPSTREAM_SYNC set)"
else
SYNC_FILE=~/.claude/skills/cold-review/checklists/.upstream-sync.json
if [ ! -f "$SYNC_FILE" ]; then
echo "⚠ .upstream-sync.json missing — cannot check for upstream updates. Skipping sync."
else
UPSTREAM_REPO=$(jq -r '.upstream_repo' "$SYNC_FILE")
LAST_SYNC_SHA=$(jq -r '.last_sync_commit' "$SYNC_FILE")
CURRENT_UPSTREAM_SHA=$(git ls-remote "$UPSTREAM_REPO" HEAD 2>/dev/null | awk '{print $1}')
if [ -z "$CURRENT_UPSTREAM_SHA" ]; then
echo "⚠ Cannot reach upstream ($UPSTREAM_REPO). Skipping sync check — running with current local checklists."
elif [ "$CURRENT_UPSTREAM_SHA" = "$LAST_SYNC_SHA" ]; then
echo "✓ Upstream sync: local checklists are at the latest upstream commit ($LAST_SYNC_SHA)"
else
echo "⚠ Upstream has new commits since last sync"
echo " Last synced: $LAST_SYNC_SHA"
echo " Current: $CURRENT_UPSTREAM_SHA"
TMP_UP=$(mktemp -d)
curl -sL "https://codeload.github.com/${UPSTREAM_REPO#https://github.com/}/tar.gz/$CURRENT_UPSTREAM_SHA" \
-o "$TMP_UP/upstream.tar.gz"
tar -xzf "$TMP_UP/upstream.tar.gz" -C "$TMP_UP/"
UPSTREAM_DIR=$(ls -d "$TMP_UP"/*/ | head -1)
UPSTREAM_REF="${UPSTREAM_DIR}$(jq -r '.upstream_path_prefix' "$SYNC_FILE")"
safe_updates=()
local_mods=()
conflicts=()
new_in_upstream=()
while IFS= read -r rel_path; do
baseline_sha=$(jq -r ".baseline_shas[\"$rel_path\"]" "$SYNC_FILE")
local_file=~/.claude/skills/cold-review/checklists/"$rel_path"
upstream_file="$UPSTREAM_REF$rel_path"
local_sha=""
upstream_sha=""
[ -f "$local_file" ] && local_sha=$(shasum -a 1 "$local_file" | awk '{print $1}')
[ -f "$upstream_file" ] && upstream_sha=$(shasum -a 1 "$upstream_file" | awk '{print $1}')
if [ "$local_sha" = "$baseline_sha" ] && [ "$upstream_sha" = "$baseline_sha" ]; then
:
elif [ "$local_sha" = "$baseline_sha" ] && [ "$upstream_sha" != "$baseline_sha" ]; then
safe_updates+=("$rel_path")
elif [ "$local_sha" != "$baseline_sha" ] && [ "$upstream_sha" = "$baseline_sha" ]; then
local_mods+=("$rel_path")
else
conflicts+=("$rel_path")
fi
done < <(jq -r '.baseline_shas | keys[]' "$SYNC_FILE")
EXCLUDE_UP=$(jq -r '.sync_exclude_upstream[]?' "$SYNC_FILE" 2>/dev/null | tr '\n' '|' | sed 's/|$//')
find "$UPSTREAM_REF" -type f -name "*.md" | while read upf; do
rp="${upf#$UPSTREAM_REF}"
[ -n "$EXCLUDE_UP" ] && echo "$rp" | grep -qE "^($EXCLUDE_UP)$" && continue
if [ "$(jq -r ".baseline_shas[\"$rp\"]" "$SYNC_FILE")" = "null" ]; then
echo " NEW: $rp (added upstream since last sync)"
fi
done
echo ""
echo "Summary:"
echo " Safe updates (local unchanged, upstream updated): ${#safe_updates[@]}"
echo " Local-only modifications (will be kept): ${#local_mods[@]}"
echo " Conflicts (both changed — needs decision): ${#conflicts[@]}"
echo ""
echo "How to proceed?"
echo " (1) Apply safe updates only, keep local mods, skip conflicts [recommended]"
echo " (2) Show conflict diffs and decide per-file"
echo " (3) Skip sync, run with current checklists"
echo " (4) Cancel"
printf "Choice (1-4): "
read -r choice
case "$choice" in
1)
for f in "${safe_updates[@]}"; do
cp "$UPSTREAM_REF$f" ~/.claude/skills/cold-review/checklists/"$f"
echo " Updated: $f"
done
jq --arg sha "$CURRENT_UPSTREAM_SHA" --arg date "$(date +%Y-%m-%d)" \
'.last_sync_commit = $sha | .last_sync_date = $date' \
"$SYNC_FILE" > "$SYNC_FILE.tmp" && mv "$SYNC_FILE.tmp" "$SYNC_FILE"
echo "✓ Sync applied. ${#safe_updates[@]} files updated."
;;
2)
for f in "${conflicts[@]}"; do
echo ""
echo "=== CONFLICT: $f ==="
echo "Local vs upstream diff:"
diff -u ~/.claude/skills/cold-review/checklists/"$f" "$UPSTREAM_REF$f" | head -40
echo "Action: (k)eep local / (t)ake upstream / (s)kip [default k]: "
read -r per_file_choice
case "$per_file_choice" in
t|T) cp "$UPSTREAM_REF$f" ~/.claude/skills/cold-review/checklists/"$f"; echo " Took upstream" ;;
*) echo " Kept local" ;;
esac
done
;;
3) echo "ⓘ Skipping sync — using current local checklists" ;;
*) echo "Cancelled."; exit 0 ;;
esac
rm -rf "$TMP_UP"
fi
fi
fi
Implementation note: the full re-baselining and per-file conflict resolution loop above is a reference implementation. Edge cases (network failure mid-download, malformed upstream tarball, jq missing) should be handled with graceful degradation — never block the audit if sync fails; print warning and continue.
Step 2a: Compute topic + branch + path, with collision handling
DATE=$(date +%Y%m%d)
TOPIC="cold-review-$DATE"
BRANCH="feature/$TOPIC"
WORKTREE=".claude/worktrees/$TOPIC"
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1 || [ -e "$WORKTREE" ]; then
SUFFIX=$(date +%H%M)
TOPIC="cold-review-$DATE-$SUFFIX"
BRANCH="feature/$TOPIC"
WORKTREE=".claude/worktrees/$TOPIC"
fi
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1 || [ -e "$WORKTREE" ]; then
echo "Collision even after HHMM-suffix. Investigate manually:"
echo " git branch --list 'feature/cold-review-*'"
echo " ls -la .claude/worktrees/ | grep cold-review"
exit 1
fi
echo "Topic: $TOPIC"
echo "Branch: $BRANCH"
echo "Worktree: $WORKTREE"
Step 2b: Fetch latest main and create the worktree
git fetch origin main 2>&1
git worktree add "$WORKTREE" -b "$BRANCH" main
WORKTREE_ABS=$(cd "$WORKTREE" && pwd -P)
echo "Worktree created at $WORKTREE_ABS"
If git worktree add fails: stop and report the git error to the user — no further setup is meaningful.
Step 2c: Build the kickoff prompt
The prompt is short. The new session will invoke this same skill and detect runner-mode on its own.
Cold-review run (run-ID: <TOPIC>).
You are in an isolated worktree on branch feature/<TOPIC>, freshly created from main.
Invoke `Skill(skill="cold-review")` to start the audit. The skill will detect runner mode automatically and run the full flow:
- Resume check (if a previous run exists in this worktree)
- STATE.json init + scope verification
- Phase 1 Reconnaissance (1 Opus agent)
- Phase 2 Discovery (3 batches of parallel Sonnet agents, 20 categories total)
- Phase 3 Hotspots + Smells (1 Opus agent)
- Phase 4 Synthesis (1 Opus agent)
- Final report (synthesis.md + synthesis.json)
Progress output is printed continuously so you can see the skill making progress.
Do not propose scope changes — run the full flow per the skill.
Substitute <TOPIC> with the actual topic string computed in Step 2a before building the final command.
Step 2d: Build the full shell command + put it in clipboard + open terminal tab
WORKTREE_ABS=$(cd "$WORKTREE" && pwd -P)
FULL_CMD="cd \"$WORKTREE_ABS\" && claude --dangerously-skip-permissions \"$KICKOFF\""
if command -v pbcopy >/dev/null 2>&1; then
echo "$FULL_CMD" | pbcopy
CLIP="pbcopy (macOS)"
elif command -v wl-copy >/dev/null 2>&1; then
echo "$FULL_CMD" | wl-copy
CLIP="wl-copy (Wayland)"
elif command -v xclip >/dev/null 2>&1; then
echo "$FULL_CMD" | xclip -selection clipboard
CLIP="xclip (X11)"
elif command -v clip.exe >/dev/null 2>&1; then
echo "$FULL_CMD" | clip.exe
CLIP="clip.exe (Git Bash on Windows)"
else
echo "No clipboard tool found. Copy this command manually and run in a new terminal in $WORKTREE_ABS:"
echo ""
echo "$FULL_CMD"
echo ""
CLIP="none"
fi
if [ "$(uname -s)" = "Darwin" ] && command -v open >/dev/null 2>&1; then
open "warp://action/new_tab?path=$WORKTREE_ABS" 2>/dev/null || true
TAB="Warp tab opened (best-effort)"
else
TAB="Open a new terminal tab in $WORKTREE_ABS manually"
fi
The --dangerously-skip-permissions flag is included by default (YOLO mode) so the audit runs without tool-call prompts. If you want permission prompts instead, remove --dangerously-skip-permissions from the command before pasting.
Step 2e: Report to user and STOP
Report exactly this to the user (substituting actual values for <TOPIC>, <CLIP>, and <TAB>):
✓ Worktree created: .claude/worktrees/<TOPIC>
Branch: feature/<TOPIC>
✓ Kickoff command (with YOLO default) copied to clipboard via <CLIP>
✓ <TAB>
Paste into the new terminal tab and press Enter to start the audit.
The audit is read-only — it will not modify your code, run tests, or push.
This launcher session is done; the full cold-review flow runs in the new tab.
To opt out of YOLO: before pasting, remove "--dangerously-skip-permissions" from the
command in your clipboard. The skill will then prompt for permission on each tool call.
Do not continue with discovery/synth in this session. The launcher's job ends here.
3. Runner mode — execute the audit
Runs only when we are inside a cold-review worktree on the matching branch.
3a. Resume detection
Scan .audits/ (in the current worktree) for existing runs:
ls .audits/cold-review-*/STATE.json 2>/dev/null
If one or more STATE.json files are found, read each one. For each run where phases.synthesis.status != "done" OR any phase != "done", consider it resumable and present to the user:
Found ongoing run:
.audits/cold-review-YYYY-MM-DD-HHMM/
Started: YYYY-MM-DD HH:MM
Status: recon=done, discovery=4/20 categories done, 1 in_progress (unreliable), 15 pending
Synthesis: pending
Would you like to:
(1) Continue this run (in_progress categories treated as unreliable → re-dispatched)
(2) Start a new run (old directory is kept for reference)
(3) Cancel
Wait for explicit user choice before proceeding.
Categories with status: "in_progress" are considered unreliable (PM may have died mid-dispatch) and are re-dispatched when resuming.
3b. For a new run — initialization
Create the run directory and subdirectories:
RUN_TS=$(date +%Y-%m-%d-%H%M)
RUN_DIR=".audits/cold-review-$RUN_TS"
mkdir -p "$RUN_DIR/prompts" "$RUN_DIR/reports"
Write initial STATE.json to $RUN_DIR/STATE.json:
{
"started": "<ISO 8601 with local TZ offset>",
"timing": {
"started_at": "<same ISO 8601 timestamp>",
"completed_at": null,
"accumulated_active_seconds": 0,
"current_session_started_at": "<same ISO 8601 timestamp>"
},
"phases": {
"recon": { "status": "pending" },
"discovery": {
"status": "pending",
"categories": {
"01-broken-access-control": { "status": "pending" },
"02-security-misconfiguration": { "status": "pending" },
"03-supply-chain-failures": { "status": "pending" },
"04-cryptographic-failures": { "status": "pending" },
"05-injection": { "status": "pending" },
"06-insecure-design": { "status": "pending" },
"07-authentication-failures": { "status": "pending" },
"08-software-data-integrity-failures": { "status": "pending" },
"09-logging-alerting-failures": { "status": "pending" },
"10-mishandling-exceptional-conditions": { "status": "pending" },
"11-xss": { "status": "pending" },
"12-csrf": { "status": "pending" },
"13-file-upload": { "status": "pending" },
"14-api-security": { "status": "pending" },
"15-business-logic-flaws": { "status": "pending" },
"16-infrastructure-devops": { "status": "pending" },
"17-ai-llm-security": { "status": "pending" },
"18-websocket-security": { "status": "pending" },
"19-grpc-security": { "status": "pending" },
"20-serverless-cloud-native": { "status": "pending" }
}
},
"hotspots_smells": { "status": "pending" },
"synthesis": { "status": "pending" }
}
}
Timer model — read carefully.
The timing object measures active runtime only — wall-clock time excluding pauses caused by token-exhaustion, PM death, terminal close, /clear, or any other interruption.
Implementation contract:
started_at — set once at run init. Never changes.
current_session_started_at — start time of the CURRENT PM session. Updated on every resume (when STATE.json detects an interrupted run).
accumulated_active_seconds — sum of (now - current_session_started_at) from all PRIOR sessions. Set to 0 at run init.
completed_at — set when synthesis is done. Never set during interrupted state.
On every status-line emit AND on every phase transition AND when synthesis completes:
jq --arg now "$(date -Iseconds)" '
.timing.last_active_at = $now
' "$RUN_DIR/STATE.json" > "$RUN_DIR/STATE.json.tmp" && mv "$RUN_DIR/STATE.json.tmp" "$RUN_DIR/STATE.json"
On resume detection (section 3a) — BEFORE asking the user resume-or-new:
When you read STATE.json from a prior run, compute the pause and roll forward:
PRIOR_SESSION_START=$(jq -r '.timing.current_session_started_at' "$RUN_DIR/STATE.json")
PRIOR_ACCUM=$(jq -r '.timing.accumulated_active_seconds' "$RUN_DIR/STATE.json")
LAST_ACTIVE=$(jq -r '.timing.last_active_at // .timing.current_session_started_at' "$RUN_DIR/STATE.json")
PRIOR_SECONDS=$(( $(date -d "$LAST_ACTIVE" +%s 2>/dev/null || gdate -d "$LAST_ACTIVE" +%s) - $(date -d "$PRIOR_SESSION_START" +%s 2>/dev/null || gdate -d "$PRIOR_SESSION_START" +%s) ))
NEW_ACCUM=$(( PRIOR_ACCUM + PRIOR_SECONDS ))
jq --arg now "$(date -Iseconds)" --argjson accum "$NEW_ACCUM" '
.timing.current_session_started_at = $now |
.timing.accumulated_active_seconds = $accum
' "$RUN_DIR/STATE.json" > "$RUN_DIR/STATE.json.tmp" && mv "$RUN_DIR/STATE.json.tmp" "$RUN_DIR/STATE.json"
The gap between last_active_at and now is the pause — it is NOT counted in active runtime.
At synthesis completion:
NOW=$(date -Iseconds)
SESSION_SECS=$(( $(date -d "$NOW" +%s 2>/dev/null || gdate -d "$NOW" +%s) - $(date -d "$(jq -r .timing.current_session_started_at $RUN_DIR/STATE.json)" +%s 2>/dev/null || gdate -d "$(jq -r .timing.current_session_started_at $RUN_DIR/STATE.json)" +%s) ))
TOTAL_ACTIVE=$(( $(jq -r .timing.accumulated_active_seconds $RUN_DIR/STATE.json) + SESSION_SECS ))
jq --arg now "$NOW" --argjson total "$TOTAL_ACTIVE" '
.timing.completed_at = $now |
.timing.accumulated_active_seconds = $total
' "$RUN_DIR/STATE.json" > "$RUN_DIR/STATE.json.tmp" && mv "$RUN_DIR/STATE.json.tmp" "$RUN_DIR/STATE.json"
Render in synthesis.md and synthesis.json. When passing run-metadata to the synth subagent (section 3f), include total_active_seconds and a human-readable form like 1h 47m. The synth template writes these into both output files.
Render in final report to user. The Report to user section (3) MUST include Active runtime: Xh Ym (excludes pauses).
Per category, the PM may write a findings field when the category reaches done:
"01-broken-access-control": {
"status": "done",
"started_at": "...",
"completed_at": "...",
"findings": { "critical": 0, "high": 1, "medium": 2, "low": 0, "info": 0 }
}
3c. Phase 1 — Reconnaissance dispatch
- Read
~/.claude/skills/cold-review/templates/recon-prompt.md.
- Substitute placeholders:
{{RUN_DIR}} → absolute path to $RUN_DIR
{{PROJECT_ROOT}} → absolute path to the audited project's root ($(git rev-parse --show-toplevel) from within the worktree)
- Write the resolved prompt to
$RUN_DIR/prompts/recon.md.
- Set
STATE.json → phases.recon.status = "in_progress".
- Emit progress line:
🚀 Phase 1 recon dispatched (Opus)
- Dispatch via Agent tool:
subagent_type: general-purpose, model: opus, prompt = content of $RUN_DIR/prompts/recon.md.
When recon returns:
Verify that $RUN_DIR/recon-report.md exists and contains all four required sections:
## Stack
## Entry points
## Scan plan
## Anti-cheating declaration
If valid:
If the report is missing or any section is absent: re-dispatch (max 2 attempts total). On the second failure, stop and ask the user to inspect $RUN_DIR/prompts/recon.md and the subagent output before retrying manually.
3d. Phase 2 — Discovery in 3 batches
Read ~/.claude/skills/cold-review/templates/discovery-prompt.md once. For each of the 20 categories that recon marked as applicable (or all 20 if recon did not explicitly exclude any), build a per-category prompt by substituting the following variables:
{{CATEGORY_ID}} — e.g. 01-broken-access-control
{{CATEGORY_NAME}} — e.g. Broken Access Control (A01:2025)
{{SCOPE_FILES}} — file list from the recon scan-plan for this category
{{FRAMEWORK_REFS}} — absolute paths to applicable framework checklist files under ~/.claude/skills/cold-review/checklists/frameworks/, as determined by the recon report
{{RUN_DIR}} — absolute path to the run directory
{{PROJECT_ROOT}} — absolute path to the audited project's root ($(git rev-parse --show-toplevel) from within the worktree)
Write each resolved prompt to $RUN_DIR/prompts/{{CATEGORY_ID}}.md.
Batch structure:
- Batch 1 (7 agents):
01-broken-access-control, 02-security-misconfiguration, 03-supply-chain-failures, 04-cryptographic-failures, 05-injection, 06-insecure-design, 07-authentication-failures
- Batch 2 (7 agents):
08-software-data-integrity-failures, 09-logging-alerting-failures, 10-mishandling-exceptional-conditions, 11-xss, 12-csrf, 13-file-upload, 14-api-security
- Batch 3 (6 agents):
15-business-logic-flaws, 16-infrastructure-devops, 17-ai-llm-security, 18-websocket-security, 19-grpc-security, 20-serverless-cloud-native
Skip any categories that recon explicitly marked as non-applicable (e.g., 18-websocket-security on a project with no WebSocket routes, or 19-grpc-security on a project with no gRPC). Record non-applicable categories in STATE.json with "status": "n/a".
Budget assumption: A full run consumes roughly 3 M tokens. The skill does not pre-check rate-limit budget before each batch (there is no portable API for that across Claude Code installations). It assumes you have started the run with a full 5-hour budget window. If you run out mid-flow, the skill relies on the resume mechanic — STATE.json and any completed reports survive on disk, and re-invoking /cold-review in the same worktree picks up where it stopped.
Dispatch all agents in a batch via parallel Agent calls in the same message. Model: sonnet.
Mark each category "in_progress" in STATE.json immediately before dispatch (so a PM crash mid-batch is detectable on resume).
Emit before the parallel calls:
🚀 Batch N dispatched: <category-ids> (parallel)
When batch returns — for each subagent:
- Verify
$RUN_DIR/reports/{{CATEGORY_ID}}.md exists on disk.
- Run the PM verification checklist (see below).
- If OK:
- If report missing or verification fails:
- Emit:
⚠ <category-id> re-dispatch (reason: <one-line cause>)
- Re-dispatch Sonnet (max 2 Sonnet attempts). On the third attempt use Opus. On a fourth failure: mark
status: "failed" with reason.
After all agents in a batch are verified, emit:
✓ Batch N verified (N/20 categories done)
Then proceed to the next batch.
3e. Phase 3 — Hotspots + Smells dispatch
After all 20 discovery categories are done (or failed/n/a), dispatch one Opus agent that reads all completed discovery reports as input.
- Read
~/.claude/skills/cold-review/templates/hotspots-smells-prompt.md.
- Build
{{REPORT_PATHS}} as a newline-separated list of all completed discovery report paths.
- Substitute placeholders:
{{REPORT_PATHS}} → newline-separated path list
{{RUN_DIR}} → absolute path to $RUN_DIR
{{PROJECT_ROOT}} → absolute path to the audited project's root
- Write the resolved prompt to
$RUN_DIR/prompts/hotspots-smells.md.
- Dispatch via Agent tool:
subagent_type: general-purpose, model: opus, prompt = content of $RUN_DIR/prompts/hotspots-smells.md.
The agent identifies:
- Security hotspots — zones not currently vulnerable but fragile to careless future modification (e.g., inconsistent validation pattern, mixed authentication paths, hand-rolled crypto wrapper used in 1 of 3 places, shared private helpers called from routes with different auth levels)
- Code smells affecting security — god classes, duplicated security logic, swallowed errors, fail-open exception handling, inconsistent use of framework security primitives
Output: $RUN_DIR/hotspots-smells.md.
Enumeration discipline (enforced by the template): Every pattern instance must be listed individually with file:line. The template forbids aggregated counts like "10+ sites" — instances are enumerated. The PM verifies this on return: if the hotspots report contains aggregated phrases like "N+ sites", "many", "etc." without an underlying enumerated list, re-dispatch with a sharper prompt.
Set STATE.json → phases.hotspots_smells.status = "done" on success.
Progress lines:
[HH:MM:SS] 🚀 Phase 3 hotspots+smells dispatched (Opus) — reading N discovery reports
[HH:MM:SS] ✓ Phase 3 hotspots+smells done — H hotspots (M instances) / S smells (L instances)
3f. Phase 4 — Synthesis dispatch
- Read
~/.claude/skills/cold-review/templates/synth-prompt.md.
- Build
{{REPORT_PATHS}} as a newline-separated list of all completed discovery report paths + $RUN_DIR/hotspots-smells.md.
- Build
{{RUN_METADATA}} from STATE.json — extract started, applicable categories count, resume history if any, and a snapshot of the current active runtime computed as accumulated_active_seconds + (now - current_session_started_at). Serialize as a small JSON snippet for embedding in the prompt — including total_active_seconds_so_far and total_active_human (e.g. "1h 47m").
- Substitute placeholders:
{{REPORT_PATHS}} → newline-separated path list
{{RUN_DIR}} → absolute path to $RUN_DIR
{{RUN_METADATA}} → JSON snippet (includes timing snapshot for synth to render in synthesis.md + synthesis.json)
{{PROJECT_ROOT}} → absolute path to the audited project's root
- Write the resolved prompt to
$RUN_DIR/prompts/synthesis.md.
- Set
STATE.json → phases.synthesis.status = "in_progress".
- Emit:
[HH:MM:SS] 🚀 Phase 4 synthesis dispatched (Opus) — analyzing N reports
- Dispatch via Agent tool:
subagent_type: general-purpose, model: opus, prompt = content of $RUN_DIR/prompts/synthesis.md.
When synthesis returns:
On all checks passing:
- Set
STATE.json → phases.synthesis.status = "done".
PM verification checklist per subagent
After every subagent returns, run this 6-point check on its report before marking the category done. Do this concretely — do not just read the summary.
If any check fails: re-dispatch with a sharper prompt citing the specific gap. Re-dispatch counts toward the 2-attempt cap.
Resume via STATE.json
STATE.json is written to disk and survives PM death, rate-limit interruption, /clear, and terminal close. On a new session in the same worktree:
- Scan
.audits/cold-review-*/STATE.json (section 3a above).
- For each phase that is not
"done": re-dispatch.
- For discovery categories with
"status": "in_progress": treat as unreliable — re-dispatch from scratch.
- For discovery categories with
"status": "done": skip (do not re-dispatch).
- Phase 3 (hotspots+smells): only dispatch when ALL applicable discovery categories are
"done" or "failed" or "n/a".
- Phase 4 (synthesis): only dispatches when ALL of discovery + hotspots_smells are in a terminal state (
done, failed, or n/a).
This means a PM crash at any point loses at most the categories that were in_progress at crash time — all done categories are preserved.
Error handling
| Scenario | Handling |
|---|
Launcher: git worktree add fails | Stop and report the git error to the user. Do not fall back silently — the HHMM-suffix collision step already ran; a second failure indicates a real problem requiring manual intervention. |
| Launcher: clipboard tool absent | Continue. Print the full command to stdout so the user can copy manually. Do not block. |
| Runner: subagent crashes or times out | reports/{{CATEGORY_ID}}.md is missing on disk. Emit ⚠ <id> re-dispatch (reason: report missing). Re-dispatch on Sonnet (max 2 Sonnet attempts). On the third attempt use Opus. On the fourth failure: mark status: "failed". |
| Subagent lies about anti-cheating | The 2-source declaration check catches the discrepancy. Re-dispatch with a sharper prompt citing the forbidden paths. On 2 failures: escalate to Opus. |
| Subagent "no major findings" on large scope | Deception signal. Re-dispatch with Opus. |
| Rate-limit hard rejection mid-batch | STATE.json already has the affected categories as "in_progress". On next run → resume flow re-dispatches them. The skill does not pre-check budget — assume a full 5-hour window at run start. |
| Synth crashes | Re-dispatch synth only (all discovery reports remain on disk). |
STATE.json shows synthesis=done but not all phases done | Inconsistent state. Show full state to user, ask: ignore (accept synthesis as done) or rebuild missing phases + redo synth. |
| STATE.json is not valid JSON | Rename to STATE.json.broken-TIMESTAMP, warn user, start a new run. Existing reports on disk under reports/ can be manually rescued to the new run's reports/ directory. |
PM session dies (terminal closed, /clear) | Disk state survives. New session in same worktree → /cold-review → runner-mode detection → resume flow. If the worktree itself has been removed, the run is lost — start a new one from main. |
| Subagent fails 2 Sonnet + 1 Opus + 1 escalation attempt | Hard cap reached (4 total attempts per category). Mark status: "failed" in STATE.json with reason. Synthesis continues without that category. List under "What cold-review did NOT examine" in the final report. |
Writing to .audits/ fails | Print a clear error to stdout with diagnostics (df -h, ls -la .audits/). Exit without corrupting STATE.json. Manual intervention required before resume can work. |
| Report missing mandatory sections | Format failure. Emit ⚠ <id> re-dispatch (reason: report format). Re-dispatch (counts toward 2-attempt cap) with a sharper prompt citing the missing sections. |
Final steps
1. Copy outputs to docs/audits/
Copy both the markdown report and JSON to the worktree's docs/audits/ directory (NOT the skill repo):
RUN_DATE=$(date +%Y-%m-%d)
mkdir -p docs/audits
cp "$RUN_DIR/synthesis.md" "docs/audits/cold-review-$RUN_DATE.md"
cp "$RUN_DIR/synthesis.json" "docs/audits/cold-review-$RUN_DATE.json"
2. Commit to worktree branch
git add docs/audits/
git commit -m "audit(cold-review): report $RUN_DATE
X CRITICAL, Y HIGH, Z MEDIUM, W LOW.
Run directory: .audits/cold-review-$RUN_TS/ (gitignored)"
Substitute actual finding counts from synthesis.json. The commit goes on the worktree's branch (feature/cold-review-YYYYMMDD). When triage is complete, the worktree is merged to main.
3. Report to user (English)
This is the only place during runner mode where you may break the status-line-only output discipline.
✓ Cold review done.
Run: .audits/cold-review-YYYY-MM-DD-HHMM/
Final report: docs/audits/cold-review-YYYY-MM-DD.md
JSON output: docs/audits/cold-review-YYYY-MM-DD.json
Active runtime: Xh Ym (excludes pauses)
Summary:
- X CRITICAL — fix today, full-stop
- Y HIGH — fix before merge this week
- Z MEDIUM — open backlog ticket
- W LOW — observations
- N cross-cutting findings
- M categories with no findings (possible blind spots)
Top findings (CRITICAL + HIGH):
1. [CRITICAL] <title> — src/.../file:NN
2. [HIGH] <title> — src/.../file:NN
Next steps:
1. Read through the final report.
2. For task-tracker integration: see README "Integrating with task trackers" section.
3. Optional: run a severity re-review pass in a fresh session — see README "Severity re-review" or the auto-generated kickoff at `.audits/cold-review-YYYY-MM-DD-HHMM/severity-review-kickoff.md`.
4. When triage done: merge the worktree to main.
The Active runtime field reads from STATE.json.timing.accumulated_active_seconds, rendered as Xh Ym (or Ym if under one hour). This is the time the PM and subagents were actively working — pauses from token-exhaustion, PM death, terminal close, /clear, or any other interruption are excluded.
No task-tracker integration. No automatic task creation. The skill produces structured output; user owns triage.
Hard rule for you as PM
Your own discipline is everything. The audit's value depends entirely on you not anchoring on the project's own documentation.
You MAY read:
~/.claude/skills/cold-review/SKILL.md (this file)
~/.claude/skills/cold-review/templates/*.md
~/.claude/skills/cold-review/checklists/**/*.md
You do NOT read:
- The audited project's
CLAUDE.md, AGENTS.md, DECISIONS.md, WORKLOG.md, README.md, or any other project .md
- The project's
docs/, tests/, or .claude/ directory (except this skill's own folder under ~/.claude/skills/)
- Any project configuration, spec, or plan file
If you need library semantics: use Context7 MCP. If you need CVE or vulnerability data: use WebSearch.
If you cheat on this the entire exercise becomes meaningless.