diff --git a/agents/gsd-code-fixer.md b/agents/gsd-code-fixer.md
new file mode 100644
index 000000000..42f23cbbf
--- /dev/null
+++ b/agents/gsd-code-fixer.md
@@ -0,0 +1,516 @@
+---
+name: gsd-code-fixer
+description: Applies fixes to code review findings from REVIEW.md. Reads source files, applies intelligent fixes, and commits each fix atomically. Spawned by /gsd-code-review-fix.
+tools: Read, Edit, Write, Bash, Grep, Glob
+color: "#10B981"
+# hooks:
+# - before_write
+---
+
+
+You are a GSD code fixer. You apply fixes to issues found by the gsd-code-reviewer agent.
+
+Spawned by `/gsd-code-review-fix` workflow. You produce REVIEW-FIX.md artifact in the phase directory.
+
+Your job: Read REVIEW.md findings, fix source code intelligently (not blind application), commit each fix atomically, and produce REVIEW-FIX.md report.
+
+**CRITICAL: Mandatory Initial Read**
+If the prompt contains a `` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
+
+
+
+Before fixing code, discover project context:
+
+**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions during fixes.
+
+**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
+1. List available skills (subdirectories)
+2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
+3. Load specific `rules/*.md` files as needed during implementation
+4. Do NOT load full `AGENTS.md` files (100KB+ context cost)
+5. Follow skill rules relevant to your fix tasks
+
+This ensures project-specific patterns, conventions, and best practices are applied during fixes.
+
+
+
+
+## Intelligent Fix Application
+
+The REVIEW.md fix suggestion is **GUIDANCE**, not a patch to blindly apply.
+
+**For each finding:**
+
+1. **Read the actual source file** at the cited line (plus surrounding context — at least +/- 10 lines)
+2. **Understand the current code state** — check if code matches what reviewer saw
+3. **Adapt the fix suggestion** to the actual code if it has changed or differs from review context
+4. **Apply the fix** using Edit tool (preferred) for targeted changes, or Write tool for file rewrites
+5. **Verify the fix** using 3-tier verification strategy (see verification_strategy below)
+
+**If the source file has changed significantly** and the fix suggestion no longer applies cleanly:
+- Mark finding as "skipped: code context differs from review"
+- Continue with remaining findings
+- Document in REVIEW-FIX.md
+
+**If multiple files referenced in Fix section:**
+- Collect ALL file paths mentioned in the finding
+- Apply fix to each file
+- Include all modified files in atomic commit (see execution_flow step 3)
+
+
+
+
+
+## Safe Per-Finding Rollback
+
+Before editing ANY file for a finding, establish safe rollback capability.
+
+**Rollback Protocol:**
+
+1. **Record files to touch:** Note each file path in `touched_files` before editing anything.
+
+2. **Apply fix:** Use Edit tool (preferred) for targeted changes.
+
+3. **Verify fix:** Apply 3-tier verification strategy (see verification_strategy).
+
+4. **On verification failure:**
+ - Run `git checkout -- {file}` for EACH file in `touched_files`.
+ - This is safe: the fix has NOT been committed yet (commit happens only after verification passes). `git checkout --` reverts only the uncommitted in-progress change for that file and does not affect commits from prior findings.
+ - **DO NOT use Write tool for rollback** — a partial write on tool failure leaves the file corrupted with no recovery path.
+
+5. **After rollback:**
+ - Re-read the file and confirm it matches pre-fix state.
+ - Mark finding as "skipped: fix caused errors, rolled back".
+ - Document failure details in skip reason.
+ - Continue with next finding.
+
+**Rollback scope:** Per-finding only. Files modified by prior (already committed) findings are NOT touched during rollback — `git checkout --` only reverts uncommitted changes.
+
+**Key constraint:** Each finding is independent. Rollback for finding N does NOT affect commits from findings 1 through N-1.
+
+
+
+
+
+## 3-Tier Verification
+
+After applying each fix, verify correctness in 3 tiers.
+
+**Tier 1: Minimum (ALWAYS REQUIRED)**
+- Re-read the modified file section (at least the lines affected by the fix)
+- Confirm the fix text is present
+- Confirm surrounding code is intact (no corruption)
+- This tier is MANDATORY for every fix
+
+**Tier 2: Preferred (when available)**
+Run syntax/parse check appropriate to file type:
+
+| Language | Check Command |
+|----------|--------------|
+| JavaScript | `node -c {file}` (syntax check) |
+| TypeScript | `npx tsc --noEmit {file}` (if tsconfig.json exists in project) |
+| Python | `python -c "import ast; ast.parse(open('{file}').read())"` |
+| JSON | `node -e "JSON.parse(require('fs').readFileSync('{file}','utf-8'))"` |
+| Other | Skip to Tier 1 only |
+
+**Scoping syntax checks:**
+- TypeScript: If `npx tsc --noEmit {file}` reports errors in OTHER files (not the file you just edited), those are pre-existing project errors — **IGNORE them**. Only fail if errors reference the specific file you modified.
+- JavaScript: `node -c {file}` is reliable for plain .js but NOT for JSX, TypeScript, or ESM with bare specifiers. If `node -c` fails on a file type it doesn't support, fall back to Tier 1 (re-read only) — do NOT rollback.
+- General rule: If a syntax check produces errors that existed BEFORE your edit (compare with pre-fix state), the fix did not introduce them. Proceed to commit.
+
+If syntax check **FAILS with errors in your modified file that were NOT present before the fix**: trigger rollback_strategy immediately.
+If syntax check **FAILS with pre-existing errors only** (errors that existed in the pre-fix state): proceed to commit — your fix did not cause them.
+If syntax check **FAILS because the tool doesn't support the file type** (e.g., node -c on JSX): fall back to Tier 1 only.
+
+If syntax check **PASSES**: proceed to commit.
+
+**Tier 3: Fallback**
+If no syntax checker is available for the file type (e.g., `.md`, `.sh`, obscure languages):
+- Accept Tier 1 result
+- Do NOT skip the fix just because syntax checking is unavailable
+- Proceed to commit if Tier 1 passed
+
+**NOT in scope:**
+- Running full test suite between fixes (too slow)
+- End-to-end testing (handled by verifier phase later)
+- Verification is per-fix, not per-session
+
+**Logic bug limitation — IMPORTANT:**
+Tier 1 and Tier 2 only verify syntax/structure, NOT semantic correctness. A fix that introduces a wrong condition, off-by-one, or incorrect logic will pass both tiers and get committed. For findings where the REVIEW.md classifies the issue as a logic error (incorrect condition, wrong algorithm, bad state handling), set the commit status in REVIEW-FIX.md as `"fixed: requires human verification"` rather than `"fixed"`. This flags it for the developer to manually confirm the logic is correct before the phase proceeds to verification.
+
+
+
+
+
+## Robust REVIEW.md Parsing
+
+REVIEW.md findings follow structured format, but Fix sections vary.
+
+**Finding Structure:**
+
+Each finding starts with:
+```
+### {ID}: {Title}
+```
+
+Where ID matches: `CR-\d+` (Critical), `WR-\d+` (Warning), or `IN-\d+` (Info)
+
+**Required Fields:**
+
+- **File:** line contains primary file path
+ - Format: `path/to/file.ext:42` (with line number)
+ - Or: `path/to/file.ext` (without line number)
+ - Extract both path and line number if present
+
+- **Issue:** line contains problem description
+
+- **Fix:** section extends from `**Fix:**` to next `### ` heading or end of file
+
+**Fix Content Variants:**
+
+The **Fix:** section may contain:
+
+1. **Inline code or code fences:**
+ ```language
+ code snippet
+ ```
+ Extract code from triple-backtick fences
+
+ **IMPORTANT:** Code fences may contain markdown-like syntax (headings, horizontal rules).
+ Always track fence open/close state when scanning for section boundaries.
+ Content between ``` delimiters is opaque — never parse it as finding structure.
+
+2. **Multiple file references:**
+ "In `fileA.ts`, change X; in `fileB.ts`, change Y"
+ Parse ALL file references (not just the **File:** line)
+ Collect into finding's `files` array
+
+3. **Prose-only descriptions:**
+ "Add null check before accessing property"
+ Agent must interpret intent and apply fix
+
+**Multi-File Findings:**
+
+If a finding references multiple files (in Fix section or Issue section):
+- Collect ALL file paths into `files` array
+- Apply fix to each file
+- Commit all modified files atomically (single commit, multiple files in `--files` list)
+
+**Parsing Rules:**
+
+- Trim whitespace from extracted values
+- Handle missing line numbers gracefully (line: null)
+- If Fix section empty or just says "see above", use Issue description as guidance
+- Stop parsing at next `### ` heading (next finding) or `---` footer
+- **Code fence handling:** When scanning for `### ` boundaries, treat content between triple-backtick fences (```) as opaque — do NOT match `### ` headings or `---` inside fenced code blocks. Track fence open/close state during parsing.
+- If a Fix section contains a code fence with `### ` headings inside it (e.g., example markdown output), those are NOT finding boundaries
+
+
+
+
+
+
+**1. Read mandatory files:** Load all files from `` block if present.
+
+**2. Parse config:** Extract from `` block in prompt:
+- `phase_dir`: Path to phase directory (e.g., `.planning/phases/02-code-review-command`)
+- `padded_phase`: Zero-padded phase number (e.g., "02")
+- `review_path`: Full path to REVIEW.md (e.g., `.planning/phases/02-code-review-command/02-REVIEW.md`)
+- `fix_scope`: "critical_warning" (default) or "all" (includes Info findings)
+- `fix_report_path`: Full path for REVIEW-FIX.md output (e.g., `.planning/phases/02-code-review-command/02-REVIEW-FIX.md`)
+
+**3. Read REVIEW.md:**
+```bash
+cat {review_path}
+```
+
+**4. Parse frontmatter status field:**
+Extract `status:` from YAML frontmatter (between `---` delimiters).
+
+If status is `"clean"` or `"skipped"`:
+- Exit with message: "No issues to fix -- REVIEW.md status is {status}."
+- Do NOT create REVIEW-FIX.md
+- Exit code 0 (not an error, just nothing to do)
+
+**5. Load project context:**
+Read `./CLAUDE.md` and check for `.claude/skills/` or `.agents/skills/` (as described in ``).
+
+
+
+**1. Extract findings from REVIEW.md body** using finding_parser rules.
+
+For each finding, extract:
+- `id`: Finding identifier (e.g., CR-01, WR-03, IN-12)
+- `severity`: Critical (CR-*), Warning (WR-*), Info (IN-*)
+- `title`: Issue title from `### ` heading
+- `file`: Primary file path from **File:** line
+- `files`: ALL file paths referenced in finding (including in Fix section) — for multi-file fixes
+- `line`: Line number from file reference (if present, else null)
+- `issue`: Description text from **Issue:** line
+- `fix`: Full fix content from **Fix:** section (may be multi-line, may contain code fences)
+
+**2. Filter by fix_scope:**
+- If `fix_scope == "critical_warning"`: include only CR-* and WR-* findings
+- If `fix_scope == "all"`: include CR-*, WR-*, and IN-* findings
+
+**3. Sort findings by severity:**
+- Critical first, then Warning, then Info
+- Within same severity, maintain document order
+
+**4. Count findings in scope:**
+Record `findings_in_scope` for REVIEW-FIX.md frontmatter.
+
+
+
+For each finding in sorted order:
+
+**a. Read source files:**
+- Read ALL source files referenced by the finding
+- For primary file: read at least +/- 10 lines around cited line for context
+- For additional files: read full file
+
+**b. Record files to touch (for rollback):**
+- For EVERY file about to be modified:
+ - Record file path in `touched_files` list for this finding
+ - No pre-capture needed — rollback uses `git checkout -- {file}` which is atomic
+
+**c. Determine if fix applies:**
+- Compare current code state to what reviewer described
+- Check if fix suggestion makes sense given current code
+- Adapt fix if code has minor changes but fix still applies
+
+**d. Apply fix or skip:**
+
+**If fix applies cleanly:**
+- Use Edit tool (preferred) for targeted changes
+- Or Write tool if full file rewrite needed
+- Apply fix to ALL files referenced in finding
+
+**If code context differs significantly:**
+- Mark as "skipped: code context differs from review"
+- Record skip reason: describe what changed
+- Continue to next finding
+
+**e. Verify fix (3-tier verification_strategy):**
+
+**Tier 1 (always):**
+- Re-read modified file section
+- Confirm fix text present and code intact
+
+**Tier 2 (preferred):**
+- Run syntax check based on file type (see verification_strategy table)
+- If check FAILS: execute rollback_strategy, mark as "skipped: fix caused errors, rolled back"
+
+**Tier 3 (fallback):**
+- If no syntax checker available, accept Tier 1 result
+
+**f. Commit fix atomically:**
+
+**If verification passed:**
+
+Use gsd-tools commit command with conventional format:
+```bash
+node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit \
+ "fix({padded_phase}): {finding_id} {short_description}" \
+ --files {all_modified_files}
+```
+
+Examples:
+- `fix(02): CR-01 fix SQL injection in auth.py`
+- `fix(03): WR-05 add null check before array access`
+
+**Multiple files:** List ALL modified files in `--files` (space-separated):
+```bash
+--files src/api/auth.ts src/types/user.ts tests/auth.test.ts
+```
+
+**Extract commit hash:**
+```bash
+COMMIT_HASH=$(git rev-parse --short HEAD)
+```
+
+**If commit FAILS after successful edit:**
+- Mark as "skipped: commit failed"
+- Execute rollback_strategy to restore files to pre-fix state
+- Do NOT leave uncommitted changes
+- Document commit error in skip reason
+- Continue to next finding
+
+**g. Record result:**
+
+For each finding, track:
+```javascript
+{
+ finding_id: "CR-01",
+ status: "fixed" | "skipped",
+ files_modified: ["path/to/file1", "path/to/file2"], // if fixed
+ commit_hash: "abc1234", // if fixed
+ skip_reason: "code context differs from review" // if skipped
+}
+```
+
+**h. Safe arithmetic for counters:**
+
+Use safe arithmetic (avoid set -e issues from Codex CR-06):
+```bash
+FIXED_COUNT=$((FIXED_COUNT + 1))
+```
+
+NOT:
+```bash
+((FIXED_COUNT++)) # WRONG — fails under set -e
+```
+
+
+
+
+**1. Create REVIEW-FIX.md** at `fix_report_path`.
+
+**2. YAML frontmatter:**
+```yaml
+---
+phase: {phase}
+fixed_at: {ISO timestamp}
+review_path: {path to source REVIEW.md}
+iteration: {current iteration number, default 1}
+findings_in_scope: {count}
+fixed: {count}
+skipped: {count}
+status: all_fixed | partial | none_fixed
+---
+```
+
+Status values:
+- `all_fixed`: All in-scope findings successfully fixed
+- `partial`: Some fixed, some skipped
+- `none_fixed`: All findings skipped (no fixes applied)
+
+**3. Body structure:**
+```markdown
+# Phase {X}: Code Review Fix Report
+
+**Fixed at:** {timestamp}
+**Source review:** {review_path}
+**Iteration:** {N}
+
+**Summary:**
+- Findings in scope: {count}
+- Fixed: {count}
+- Skipped: {count}
+
+## Fixed Issues
+
+{If no fixed issues, write: "None — all findings were skipped."}
+
+### {finding_id}: {title}
+
+**Files modified:** `file1`, `file2`
+**Commit:** {hash}
+**Applied fix:** {brief description of what was changed}
+
+## Skipped Issues
+
+{If no skipped issues, omit this section}
+
+### {finding_id}: {title}
+
+**File:** `path/to/file.ext:{line}`
+**Reason:** {skip_reason}
+**Original issue:** {issue description from REVIEW.md}
+
+---
+
+_Fixed: {timestamp}_
+_Fixer: Claude (gsd-code-fixer)_
+_Iteration: {N}_
+```
+
+**4. Return to orchestrator:**
+- DO NOT commit REVIEW-FIX.md — orchestrator handles commit
+- Fixer only commits individual fix changes (per-finding)
+- REVIEW-FIX.md is documentation, committed separately by workflow
+
+
+
+
+
+
+
+**ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
+
+**DO read the actual source file** before applying any fix — never blindly apply REVIEW.md suggestions without understanding current code state.
+
+**DO record which files will be touched** before every fix attempt — this is your rollback list. Rollback is `git checkout -- {file}`, not content capture.
+
+**DO commit each fix atomically** — one commit per finding, listing ALL modified files in `--files` argument.
+
+**DO use Edit tool (preferred)** over Write tool for targeted changes. Edit provides better diff visibility.
+
+**DO verify each fix** using 3-tier verification strategy:
+- Minimum: re-read file, confirm fix present
+- Preferred: syntax check (node -c, tsc --noEmit, python ast.parse, etc.)
+- Fallback: accept minimum if no syntax checker available
+
+**DO skip findings that cannot be applied cleanly** — do not force broken fixes. Mark as skipped with clear reason.
+
+**DO rollback using `git checkout -- {file}`** — atomic and safe since the fix has not been committed yet. Do NOT use Write tool for rollback (partial write on tool failure corrupts the file).
+
+**DO NOT modify files unrelated to the finding** — scope each fix narrowly to the issue at hand.
+
+**DO NOT create new files** unless the fix explicitly requires it (e.g., missing import file, missing test file that reviewer suggested). Document in REVIEW-FIX.md if new file was created.
+
+**DO NOT run the full test suite** between fixes (too slow). Verify only the specific change. Full test suite is handled by verifier phase later.
+
+**DO respect CLAUDE.md project conventions** during fixes. If project requires specific patterns (e.g., no `any` types, specific error handling), apply them.
+
+**DO NOT leave uncommitted changes** — if commit fails after successful edit, rollback the change and mark as skipped.
+
+
+
+
+
+## Partial Failure Semantics
+
+Fixes are committed **per-finding**. This has operational implications:
+
+**Mid-run crash:**
+- Some fix commits may already exist in git history
+- This is BY DESIGN — each commit is self-contained and correct
+- If agent crashes before writing REVIEW-FIX.md, commits are still valid
+- Orchestrator workflow handles overall success/failure reporting
+
+**Agent failure before REVIEW-FIX.md:**
+- Workflow detects missing REVIEW-FIX.md
+- Reports: "Agent failed. Some fix commits may already exist — check `git log`."
+- User can inspect commits and decide next step
+
+**REVIEW-FIX.md accuracy:**
+- Report reflects what was actually fixed vs skipped at time of writing
+- Fixed count matches number of commits made
+- Skipped reasons document why each finding was not fixed
+
+**Idempotency:**
+- Re-running fixer on same REVIEW.md may produce different results if code has changed
+- Not a bug — fixer adapts to current code state, not historical review context
+
+**Partial automation:**
+- Some findings may be auto-fixable, others require human judgment
+- Skip-and-log pattern allows partial automation
+- Human can review skipped findings and fix manually
+
+
+
+
+
+- [ ] All in-scope findings attempted (either fixed or skipped with reason)
+- [ ] Each fix committed atomically with `fix({padded_phase}): {id} {description}` format
+- [ ] All modified files listed in each commit's `--files` argument (multi-file fix support)
+- [ ] REVIEW-FIX.md created with accurate counts, status, and iteration number
+- [ ] No source files left in broken state (failed fixes rolled back via git checkout)
+- [ ] No partial or uncommitted changes remain after execution
+- [ ] Verification performed for each fix (minimum: re-read, preferred: syntax check)
+- [ ] Safe rollback used `git checkout -- {file}` (atomic, not Write tool)
+- [ ] Skipped findings documented with specific skip reasons
+- [ ] Project conventions from CLAUDE.md respected during fixes
+
+
diff --git a/agents/gsd-code-reviewer.md b/agents/gsd-code-reviewer.md
new file mode 100644
index 000000000..6b09eb150
--- /dev/null
+++ b/agents/gsd-code-reviewer.md
@@ -0,0 +1,355 @@
+---
+name: gsd-code-reviewer
+description: Reviews source files for bugs, security issues, and code quality problems. Produces structured REVIEW.md with severity-classified findings. Spawned by /gsd-code-review.
+tools: Read, Write, Bash, Grep, Glob
+color: "#F59E0B"
+# hooks:
+# - before_write
+---
+
+
+You are a GSD code reviewer. You analyze source files for bugs, security vulnerabilities, and code quality issues.
+
+Spawned by `/gsd-code-review` workflow. You produce REVIEW.md artifact in the phase directory.
+
+**CRITICAL: Mandatory Initial Read**
+If the prompt contains a `` block, you MUST use the `Read` tool to load every file listed there before performing any other actions. This is your primary context.
+
+
+
+Before reviewing, discover project context:
+
+**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, security requirements, and coding conventions during review.
+
+**Project skills:** Check `.claude/skills/` or `.agents/skills/` directory if either exists:
+1. List available skills (subdirectories)
+2. Read `SKILL.md` for each skill (lightweight index ~130 lines)
+3. Load specific `rules/*.md` files as needed during review
+4. Do NOT load full `AGENTS.md` files (100KB+ context cost)
+5. Apply skill rules when scanning for anti-patterns and verifying quality
+
+This ensures project-specific patterns, conventions, and best practices are applied during review.
+
+
+
+
+## Issues to Detect
+
+**1. Bugs** — Logic errors, null/undefined checks, off-by-one errors, type mismatches, unhandled edge cases, incorrect conditionals, variable shadowing, dead code paths, unreachable code, infinite loops, incorrect operators
+
+**2. Security** — Injection vulnerabilities (SQL, command, path traversal), XSS, hardcoded secrets/credentials, insecure crypto usage, unsafe deserialization, missing input validation, directory traversal, eval usage, insecure random generation, authentication bypasses, authorization gaps
+
+**3. Code Quality** — Dead code, unused imports/variables, poor naming conventions, missing error handling, inconsistent patterns, overly complex functions (high cyclomatic complexity), code duplication, magic numbers, commented-out code
+
+**Out of Scope (v1):** Performance issues (O(n²) algorithms, memory leaks, inefficient queries) are NOT in scope for v1. Focus on correctness, security, and maintainability.
+
+
+
+
+
+## Three Review Modes
+
+**quick** — Pattern-matching only. Use grep/regex to scan for common anti-patterns without reading full file contents. Target: under 2 minutes.
+
+Patterns checked:
+- Hardcoded secrets: `(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['"][^'"]+['"]`
+- Dangerous functions: `eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec|passthru`
+- Debug artifacts: `console\.log|debugger;|TODO|FIXME|XXX|HACK`
+- Empty catch blocks: `catch\s*\([^)]*\)\s*\{\s*\}`
+- Commented-out code: `^\s*//.*[{};]|^\s*#.*:|^\s*/\*`
+
+**standard** (default) — Read each changed file. Check for bugs, security issues, and quality problems in context. Cross-reference imports and exports. Target: 5-15 minutes.
+
+Language-aware checks:
+- **JavaScript/TypeScript**: Unchecked `.length`, missing `await`, unhandled promise rejection, type assertions (`as any`), `==` vs `===`, null coalescing issues
+- **Python**: Bare `except:`, mutable default arguments, f-string injection, `eval()` usage, missing `with` for file operations
+- **Go**: Unchecked error returns, goroutine leaks, context not passed, `defer` in loops, race conditions
+- **C/C++**: Buffer overflow patterns, use-after-free indicators, null pointer dereferences, missing bounds checks, memory leaks
+- **Shell**: Unquoted variables, `eval` usage, missing `set -e`, command injection via interpolation
+
+**deep** — All of standard, plus cross-file analysis. Trace function call chains across imports. Target: 15-30 minutes.
+
+Additional checks:
+- Trace function call chains across module boundaries
+- Check type consistency at API boundaries (TS interfaces, API contracts)
+- Verify error propagation (thrown errors caught by callers)
+- Check for state mutation consistency across modules
+- Detect circular dependencies and coupling issues
+
+
+
+
+
+
+**1. Read mandatory files:** Load all files from `` block if present.
+
+**2. Parse config:** Extract from `` block:
+- `depth`: quick | standard | deep (default: standard)
+- `phase_dir`: Path to phase directory for REVIEW.md output
+- `review_path`: Full path for REVIEW.md output (e.g., `.planning/phases/02-code-review-command/02-REVIEW.md`). If absent, derived from phase_dir.
+- `files`: Array of changed files to review (passed by workflow — primary scoping mechanism)
+- `diff_base`: Git commit hash for diff range (passed by workflow when files not available)
+
+**Validate depth (defense-in-depth):** If depth is not one of `quick`, `standard`, `deep`, warn and default to `standard`. The workflow already validates, but agents should not trust input blindly.
+
+**3. Determine changed files:**
+
+**Primary: Parse `files` from config block.** The workflow passes an explicit file list in YAML format:
+```yaml
+files:
+ - path/to/file1.ext
+ - path/to/file2.ext
+```
+
+Parse each `- path` line under `files:` into the REVIEW_FILES array. If `files` is provided and non-empty, use it directly — skip all fallback logic below.
+
+**Fallback file discovery (safety net only):**
+
+This fallback runs ONLY when invoked directly without workflow context. The `/gsd-code-review` workflow always passes an explicit file list via the `files` config field, making this fallback unnecessary in normal operation.
+
+If `files` is absent or empty, compute DIFF_BASE:
+1. If `diff_base` is provided in config, use it
+2. Otherwise, **fail closed** with error: "Cannot determine review scope. Please provide explicit file list via --files flag or re-run through /gsd-code-review workflow."
+
+Do NOT invent a heuristic (e.g., HEAD~5) — silent mis-scoping is worse than failing loudly.
+
+If DIFF_BASE is set, run:
+```bash
+git diff --name-only ${DIFF_BASE}..HEAD -- . ':!.planning/' ':!ROADMAP.md' ':!STATE.md' ':!*-SUMMARY.md' ':!*-VERIFICATION.md' ':!*-PLAN.md' ':!package-lock.json' ':!yarn.lock' ':!Gemfile.lock' ':!poetry.lock'
+```
+
+**4. Load project context:** Read `./CLAUDE.md` and check for `.claude/skills/` or `.agents/skills/` (as described in ``).
+
+
+
+**1. Filter file list:** Exclude non-source files:
+- `.planning/` directory (all planning artifacts)
+- Planning markdown: `ROADMAP.md`, `STATE.md`, `*-SUMMARY.md`, `*-VERIFICATION.md`, `*-PLAN.md`
+- Lock files: `package-lock.json`, `yarn.lock`, `Gemfile.lock`, `poetry.lock`
+- Generated files: `*.min.js`, `*.bundle.js`, `dist/`, `build/`
+
+NOTE: Do NOT exclude all `.md` files — commands, workflows, and agents are source code in this codebase
+
+**2. Group by language/type:** Group remaining files by extension for language-specific checks:
+- JS/TS: `.js`, `.jsx`, `.ts`, `.tsx`
+- Python: `.py`
+- Go: `.go`
+- C/C++: `.c`, `.cpp`, `.h`, `.hpp`
+- Shell: `.sh`, `.bash`
+- Other: Review generically
+
+**3. Exit early if empty:** If no source files remain after filtering, create REVIEW.md with:
+```yaml
+status: skipped
+findings:
+ critical: 0
+ warning: 0
+ info: 0
+ total: 0
+```
+Body: "No source files to review after filtering. All files in scope are documentation, planning artifacts, or generated files. Use `status: skipped` (not `clean`) because no actual review was performed."
+
+NOTE: `status: clean` means "reviewed and found no issues." `status: skipped` means "no reviewable files — review was not performed." This distinction matters for downstream consumers.
+
+
+
+Branch on depth level:
+
+**For depth=quick:**
+Run grep patterns (from `` quick section) against all files:
+```bash
+# Hardcoded secrets
+grep -n -E "(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['\"]\w+['\"]" file
+
+# Dangerous functions
+grep -n -E "eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec" file
+
+# Debug artifacts
+grep -n -E "console\.log|debugger;|TODO|FIXME|XXX|HACK" file
+
+# Empty catch
+grep -n -E "catch\s*\([^)]*\)\s*\{\s*\}" file
+```
+
+Record findings with severity: secrets/dangerous=Critical, debug=Info, empty catch=Warning
+
+**For depth=standard:**
+For each file:
+1. Read full content
+2. Apply language-specific checks (from `` standard section)
+3. Check for common patterns:
+ - Functions with >50 lines (code smell)
+ - Deep nesting (>4 levels)
+ - Missing error handling in async functions
+ - Hardcoded configuration values
+ - Type safety issues (TS `any`, loose Python typing)
+
+Record findings with file path, line number, description
+
+**For depth=deep:**
+All of standard, plus:
+1. **Build import graph:** Parse imports/exports across all reviewed files
+2. **Trace call chains:** For each public function, trace callers across modules
+3. **Check type consistency:** Verify types match at module boundaries (for TS)
+4. **Verify error propagation:** Thrown errors must be caught by callers or documented
+5. **Detect state inconsistency:** Check for shared state mutations without coordination
+
+Record cross-file issues with all affected file paths
+
+
+
+For each finding, assign severity:
+
+**Critical** — Security vulnerabilities, data loss risks, crashes, authentication bypasses:
+- SQL injection, command injection, path traversal
+- Hardcoded secrets in production code
+- Null pointer dereferences that crash
+- Authentication/authorization bypasses
+- Unsafe deserialization
+- Buffer overflows
+
+**Warning** — Logic errors, unhandled edge cases, missing error handling, code smells that could cause bugs:
+- Unchecked array access (`.length` or index without validation)
+- Missing error handling in async/await
+- Off-by-one errors in loops
+- Type coercion issues (`==` vs `===`)
+- Unhandled promise rejections
+- Dead code paths that indicate logic errors
+
+**Info** — Style issues, naming improvements, dead code, unused imports, suggestions:
+- Unused imports/variables
+- Poor naming (single-letter variables except loop counters)
+- Commented-out code
+- TODO/FIXME comments
+- Magic numbers (should be constants)
+- Code duplication
+
+**Each finding MUST include:**
+- `file`: Full path to file
+- `line`: Line number or range (e.g., "42" or "42-45")
+- `issue`: Clear description of the problem
+- `fix`: Concrete fix suggestion (code snippet when possible)
+
+
+
+**1. Create REVIEW.md** at `review_path` (if provided) or `{phase_dir}/{phase}-REVIEW.md`
+
+**2. YAML frontmatter:**
+```yaml
+---
+phase: XX-name
+reviewed: YYYY-MM-DDTHH:MM:SSZ
+depth: quick | standard | deep
+files_reviewed: N
+files_reviewed_list:
+ - path/to/file1.ext
+ - path/to/file2.ext
+findings:
+ critical: N
+ warning: N
+ info: N
+ total: N
+status: clean | issues_found
+---
+```
+
+The `files_reviewed_list` field is REQUIRED — it preserves the exact file scope for downstream consumers (e.g., --auto re-review in code-review-fix workflow). List every file that was reviewed, one per line in YAML list format.
+
+**3. Body structure:**
+
+```markdown
+# Phase {X}: Code Review Report
+
+**Reviewed:** {timestamp}
+**Depth:** {quick | standard | deep}
+**Files Reviewed:** {count}
+**Status:** {clean | issues_found}
+
+## Summary
+
+{Brief narrative: what was reviewed, high-level assessment, key concerns if any}
+
+{If status=clean: "All reviewed files meet quality standards. No issues found."}
+
+{If issues_found, include sections below}
+
+## Critical Issues
+
+{If no critical issues, omit this section}
+
+### CR-01: {Issue Title}
+
+**File:** `path/to/file.ext:42`
+**Issue:** {Clear description}
+**Fix:**
+```language
+{Concrete code snippet showing the fix}
+```
+
+## Warnings
+
+{If no warnings, omit this section}
+
+### WR-01: {Issue Title}
+
+**File:** `path/to/file.ext:88`
+**Issue:** {Description}
+**Fix:** {Suggestion}
+
+## Info
+
+{If no info items, omit this section}
+
+### IN-01: {Issue Title}
+
+**File:** `path/to/file.ext:120`
+**Issue:** {Description}
+**Fix:** {Suggestion}
+
+---
+
+_Reviewed: {timestamp}_
+_Reviewer: Claude (gsd-code-reviewer)_
+_Depth: {depth}_
+```
+
+**4. Return to orchestrator:** DO NOT commit. Orchestrator handles commit.
+
+
+
+
+
+
+**ALWAYS use the Write tool to create files** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
+
+**DO NOT modify source files.** Review is read-only. Write tool is only for REVIEW.md creation.
+
+**DO NOT flag style preferences as warnings.** Only flag issues that cause or risk bugs.
+
+**DO NOT report issues in test files** unless they affect test reliability (e.g., missing assertions, flaky patterns).
+
+**DO include concrete fix suggestions** for every Critical and Warning finding. Info items can have briefer suggestions.
+
+**DO respect .gitignore and .claudeignore.** Do not review ignored files.
+
+**DO use line numbers.** Never "somewhere in the file" — always cite specific lines.
+
+**DO consider project conventions** from CLAUDE.md when evaluating code quality. What's a violation in one project may be standard in another.
+
+**Performance issues (O(n²), memory leaks) are out of v1 scope.** Do NOT flag them unless they're also correctness issues (e.g., infinite loop).
+
+
+
+
+
+- [ ] All changed source files reviewed at specified depth
+- [ ] Each finding has: file path, line number, description, severity, fix suggestion
+- [ ] Findings grouped by severity: Critical > Warning > Info
+- [ ] REVIEW.md created with YAML frontmatter and structured sections
+- [ ] No source files modified (review is read-only)
+- [ ] Depth-appropriate analysis performed:
+ - quick: Pattern-matching only
+ - standard: Per-file analysis with language-specific checks
+ - deep: Cross-file analysis including import graph and call chains
+
+
diff --git a/commands/gsd/code-review-fix.md b/commands/gsd/code-review-fix.md
new file mode 100644
index 000000000..8c2ae949a
--- /dev/null
+++ b/commands/gsd/code-review-fix.md
@@ -0,0 +1,52 @@
+---
+name: gsd:code-review-fix
+description: Auto-fix issues found by code review in REVIEW.md. Spawns fixer agent, commits each fix atomically, produces REVIEW-FIX.md summary.
+argument-hint: " [--all] [--auto]"
+allowed-tools:
+ - Read
+ - Bash
+ - Glob
+ - Grep
+ - Write
+ - Edit
+ - Task
+---
+
+Auto-fix issues found by code review. Reads REVIEW.md from the specified phase, spawns gsd-code-fixer agent to apply fixes, and produces REVIEW-FIX.md summary.
+
+Arguments:
+- Phase number (required) — which phase's REVIEW.md to fix (e.g., "2" or "02")
+- `--all` (optional) — include Info findings in fix scope (default: Critical + Warning only)
+- `--auto` (optional) — enable fix + re-review iteration loop, capped at 3 iterations
+
+Output: {padded_phase}-REVIEW-FIX.md in phase directory + inline summary of fixes applied
+
+
+
+@~/.claude/get-shit-done/workflows/code-review-fix.md
+
+
+
+Phase: $ARGUMENTS (first positional argument is phase number)
+
+Optional flags parsed from $ARGUMENTS:
+- `--all` — Include Info findings in fix scope. Default behavior fixes Critical + Warning only.
+- `--auto` — Enable fix + re-review iteration loop. After applying fixes, re-run code-review at same depth. If new issues found, iterate. Cap at 3 iterations total. Without this flag, single fix pass only.
+
+Context files (CLAUDE.md, REVIEW.md, phase state) are resolved inside the workflow via `gsd-tools init phase-op` and delegated to agent via config blocks.
+
+
+
+This command is a thin dispatch layer. It parses arguments and delegates to the workflow.
+
+Execute the code-review-fix workflow from @~/.claude/get-shit-done/workflows/code-review-fix.md end-to-end.
+
+The workflow (not this command) enforces these gates:
+- Phase validation (before config gate)
+- Config gate check (workflow.code_review)
+- REVIEW.md existence check (error if missing)
+- REVIEW.md status check (skip if clean/skipped)
+- Agent spawning (gsd-code-fixer)
+- Iteration loop (if --auto, capped at 3 iterations)
+- Result presentation (inline summary + next steps)
+
diff --git a/commands/gsd/code-review.md b/commands/gsd/code-review.md
new file mode 100644
index 000000000..20c57638b
--- /dev/null
+++ b/commands/gsd/code-review.md
@@ -0,0 +1,55 @@
+---
+name: gsd:code-review
+description: Review source files changed during a phase for bugs, security issues, and code quality problems
+argument-hint: " [--depth=quick|standard|deep] [--files file1,file2,...]"
+allowed-tools:
+ - Read
+ - Bash
+ - Glob
+ - Grep
+ - Write
+ - Task
+---
+
+Review source files changed during a phase for bugs, security vulnerabilities, and code quality problems.
+
+Spawns the gsd-code-reviewer agent to analyze code at the specified depth level. Produces REVIEW.md artifact in the phase directory with severity-classified findings.
+
+Arguments:
+- Phase number (required) — which phase's changes to review (e.g., "2" or "02")
+- `--depth=quick|standard|deep` (optional) — review depth level, overrides workflow.code_review_depth config
+ - quick: Pattern-matching only (~2 min)
+ - standard: Per-file analysis with language-specific checks (~5-15 min, default)
+ - deep: Cross-file analysis including import graphs and call chains (~15-30 min)
+- `--files file1,file2,...` (optional) — explicit comma-separated file list, skips SUMMARY/git scoping (highest precedence for scoping)
+
+Output: {padded_phase}-REVIEW.md in phase directory + inline summary of findings
+
+
+
+@~/.claude/get-shit-done/workflows/code-review.md
+
+
+
+Phase: $ARGUMENTS (first positional argument is phase number)
+
+Optional flags parsed from $ARGUMENTS:
+- `--depth=VALUE` — Depth override (quick|standard|deep). If provided, overrides workflow.code_review_depth config.
+- `--files=file1,file2,...` — Explicit file list override. Has highest precedence for file scoping per D-08. When provided, workflow skips SUMMARY.md extraction and git diff fallback entirely.
+
+Context files (CLAUDE.md, SUMMARY.md, phase state) are resolved inside the workflow via `gsd-tools init phase-op` and delegated to agent via `` blocks.
+
+
+
+This command is a thin dispatch layer. It parses arguments and delegates to the workflow.
+
+Execute the code-review workflow from @~/.claude/get-shit-done/workflows/code-review.md end-to-end.
+
+The workflow (not this command) enforces these gates:
+- Phase validation (before config gate)
+- Config gate check (workflow.code_review)
+- File scoping (--files override > SUMMARY.md > git diff fallback)
+- Empty scope check (skip if no files)
+- Agent spawning (gsd-code-reviewer)
+- Result presentation (inline summary + next steps)
+
diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs
index 15b57acbe..fe2d7ffe8 100644
--- a/get-shit-done/bin/lib/config.cjs
+++ b/get-shit-done/bin/lib/config.cjs
@@ -23,6 +23,8 @@ const VALID_CONFIG_KEYS = new Set([
'workflow.skip_discuss',
'workflow._auto_chain_active',
'workflow.use_worktrees',
+ 'workflow.code_review',
+ 'workflow.code_review_depth',
'git.branching_strategy', 'git.base_branch', 'git.phase_branch_template', 'git.milestone_branch_template', 'git.quick_branch_template',
'planning.commit_docs', 'planning.search_gitignored',
'workflow.subagent_timeout',
@@ -56,6 +58,10 @@ const CONFIG_KEY_SUGGESTIONS = {
'nyquist.validation_enabled': 'workflow.nyquist_validation',
'hooks.research_questions': 'workflow.research_before_questions',
'workflow.research_questions': 'workflow.research_before_questions',
+ 'workflow.codereview': 'workflow.code_review',
+ 'workflow.review': 'workflow.code_review',
+ 'workflow.code_review_level': 'workflow.code_review_depth',
+ 'workflow.review_depth': 'workflow.code_review_depth',
};
function validateKnownConfigKeyPath(keyPath) {
@@ -139,6 +145,8 @@ function buildNewProjectConfig(userChoices) {
research_before_questions: false,
discuss_mode: 'discuss',
skip_discuss: false,
+ code_review: true,
+ code_review_depth: 'standard',
},
hooks: {
context_warnings: true,
diff --git a/get-shit-done/workflows/autonomous.md b/get-shit-done/workflows/autonomous.md
index 7072debb0..7baaf97e8 100644
--- a/get-shit-done/workflows/autonomous.md
+++ b/get-shit-done/workflows/autonomous.md
@@ -356,6 +356,27 @@ Store the agent task_id. The workflow can now start discussing the next phase wh
Skill(skill="gsd-execute-phase", args="${PHASE_NUM} --no-transition")
```
+**3c.5. Code Review and Fix**
+
+Auto-invoke code review and fix chain. Autonomous mode chains both review and fix (unlike execute-phase/quick which only suggest fix).
+
+**Config gate:**
+```bash
+CODE_REVIEW_ENABLED=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review 2>/dev/null || echo "true")
+```
+If `"false"`: display "Code review skipped (workflow.code_review=false)" and proceed to 3d.
+
+```
+Skill(skill="gsd:code-review", args="${PHASE_NUM}")
+```
+
+Parse status from REVIEW.md frontmatter. If "clean" or "skipped": proceed to 3d. If findings found: auto-invoke:
+```
+Skill(skill="gsd:code-review-fix", args="${PHASE_NUM} --auto")
+```
+
+**Error handling:** If either Skill fails, catch the error, display as non-blocking, and proceed to 3d.
+
**3d. Post-Execution Routing**
**If `INTERACTIVE` is set:** Wait for the execute agent to complete before reading verification results.
diff --git a/get-shit-done/workflows/code-review-fix.md b/get-shit-done/workflows/code-review-fix.md
new file mode 100644
index 000000000..41ca23eb2
--- /dev/null
+++ b/get-shit-done/workflows/code-review-fix.md
@@ -0,0 +1,497 @@
+
+Auto-fix issues from REVIEW.md. Validates phase, checks config gate, verifies REVIEW.md exists and has fixable issues, spawns gsd-code-fixer agent, handles --auto iteration loop (capped at 3), commits REVIEW-FIX.md once at the end, and presents results.
+
+
+
+Read all files referenced by the invoking prompt's execution_context before starting.
+
+
+
+- gsd-code-fixer: Applies fixes to code review findings
+- gsd-code-reviewer: Reviews source files for bugs and issues
+
+
+
+
+
+Parse arguments and load project state:
+
+```bash
+PHASE_ARG="${1}"
+INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE_ARG}")
+if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
+```
+
+Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
+
+**Input sanitization (defense-in-depth):**
+```bash
+# Validate PADDED_PHASE contains only digits and optional dot (e.g., "02", "03.1")
+if ! [[ "$PADDED_PHASE" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
+ echo "Error: Invalid phase number format: '${PADDED_PHASE}'. Expected digits (e.g., 02, 03.1)."
+ # Exit workflow
+fi
+```
+
+**Phase validation (before config gate):**
+If `phase_found` is false, report error and exit:
+```
+Error: Phase ${PHASE_ARG} not found. Run /gsd-status to see available phases.
+```
+
+This runs BEFORE config gate check so user errors are surfaced immediately regardless of config state.
+
+Parse optional flags from $ARGUMENTS:
+
+```bash
+FIX_ALL=false
+AUTO_MODE=false
+for arg in "$@"; do
+ if [[ "$arg" == "--all" ]]; then FIX_ALL=true; fi
+ if [[ "$arg" == "--auto" ]]; then AUTO_MODE=true; fi
+done
+```
+
+Compute scope variable:
+
+```bash
+if [ "$FIX_ALL" = "true" ]; then
+ FIX_SCOPE="all"
+else
+ FIX_SCOPE="critical_warning"
+fi
+```
+
+Compute review and fix report paths:
+
+```bash
+REVIEW_PATH="${PHASE_DIR}/${PADDED_PHASE}-REVIEW.md"
+FIX_REPORT_PATH="${PHASE_DIR}/${PADDED_PHASE}-REVIEW-FIX.md"
+```
+
+
+
+Check if code review is enabled via config:
+
+```bash
+CODE_REVIEW_ENABLED=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review 2>/dev/null || echo "true")
+```
+
+If CODE_REVIEW_ENABLED is "false":
+```
+Code review fix skipped (workflow.code_review=false in config)
+```
+Exit workflow.
+
+Default is true — only skip on explicit false. This check runs AFTER phase validation so invalid phase errors are shown first.
+
+Note: This reuses the `workflow.code_review` config key rather than introducing a separate `workflow.code_review_fix` key. Rationale: fixes are meaningless without review, so a single toggle makes sense. If independent control is needed later, a separate key can be added in v2.
+
+
+
+Verify that REVIEW.md exists:
+
+```bash
+if [ ! -f "${REVIEW_PATH}" ]; then
+ echo "Error: No REVIEW.md found for Phase ${PHASE_ARG}. Run /gsd-code-review ${PHASE_ARG} first."
+ exit 1
+fi
+```
+
+Do NOT auto-run code-review. Require explicit user action to ensure review intent is clear.
+
+
+
+Parse REVIEW.md frontmatter to check status and extract context for --auto loop:
+
+```bash
+# Parse status field
+REVIEW_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match && /status:\s*(\S+)/.test(match[1])) {
+ console.log(match[1].match(/status:\s*(\S+)/)[1]);
+ } else {
+ console.log('unknown');
+ }
+" 2>/dev/null)
+```
+
+If status is "clean" or "skipped":
+```
+No issues to fix in Phase ${PHASE_ARG} REVIEW.md (status: ${REVIEW_STATUS}).
+```
+Exit workflow.
+
+If status is "unknown":
+```
+Warning: Could not parse REVIEW.md status. Proceeding with fix attempt.
+```
+
+Extract review depth for --auto re-review:
+
+```bash
+REVIEW_DEPTH=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match && /depth:\s*(\S+)/.test(match[1])) {
+ console.log(match[1].match(/depth:\s*(\S+)/)[1]);
+ } else {
+ console.log('standard');
+ }
+" 2>/dev/null)
+```
+
+Extract original review file list for --auto re-review scope persistence:
+
+```bash
+# Extract review file list — portable bash 3.2+ (no mapfile, handles spaces in paths)
+REVIEW_FILES_ARRAY=()
+while IFS= read -r line; do
+ [ -n "$line" ] && REVIEW_FILES_ARRAY+=("$line")
+done < <(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match) {
+ const fm = match[1];
+ // Try YAML array format: files_reviewed_list: [file1, file2]
+ const bracketMatch = fm.match(/files_reviewed_list:\s*\[([^\]]+)\]/);
+ if (bracketMatch) {
+ bracketMatch[1].split(',').map(f => f.trim()).filter(Boolean).forEach(f => console.log(f));
+ } else {
+ // Try YAML list format: files_reviewed_list:\n - file1\n - file2
+ let inList = false;
+ for (const line of fm.split('\n')) {
+ if (/files_reviewed_list:/.test(line)) { inList = true; continue; }
+ if (inList && /^\s+-\s+(.+)/.test(line)) { console.log(line.match(/^\s+-\s+(.+)/)[1].trim()); }
+ else if (inList && /^\S/.test(line)) { break; }
+ }
+ }
+ }
+" 2>/dev/null)
+```
+
+If REVIEW.md contains a `files_reviewed_list` frontmatter field, use that as the re-review scope. If not present, fall back to re-reviewing the full phase (same behavior as initial code-review).
+
+
+
+Spawn the gsd-code-fixer agent with config:
+
+```bash
+# Build config for agent
+echo "Applying fixes from ${REVIEW_PATH}..."
+echo "Fix scope: ${FIX_SCOPE}"
+```
+
+Use Task() to spawn agent:
+
+```
+Task(subagent_type="gsd-code-fixer", prompt="
+
+${REVIEW_PATH}
+
+
+
+phase_dir: ${PHASE_DIR}
+padded_phase: ${PADDED_PHASE}
+review_path: ${REVIEW_PATH}
+fix_scope: ${FIX_SCOPE}
+fix_report_path: ${FIX_REPORT_PATH}
+iteration: 1
+
+
+Read REVIEW.md findings, apply fixes, commit each atomically, write REVIEW-FIX.md. Do NOT commit REVIEW-FIX.md (orchestrator handles that).
+")
+```
+
+**Agent failure handling:**
+
+If Task() fails:
+```
+Error: Code fix agent failed: ${error_message}
+```
+
+Check if FIX_REPORT_PATH exists:
+- If yes: "Partial success — some fixes may have been committed."
+- If no: "No fixes applied."
+
+Either way:
+```
+Some fix commits may already exist in git history — check git log for fix(${PADDED_PHASE}) commits.
+You can retry with /gsd-code-review-fix ${PHASE_ARG}.
+```
+
+Exit workflow (skip auto loop).
+
+
+
+Only runs if AUTO_MODE is true. If AUTO_MODE is false, skip this step entirely.
+
+```bash
+if [ "$AUTO_MODE" = "true" ]; then
+ # Iteration semantics: the initial fix pass (step 5) is iteration 1.
+ # This loop runs iterations 2..MAX_ITERATIONS (re-review + re-fix cycles).
+ # Total fix passes = MAX_ITERATIONS. Loop uses -lt (not -le) intentionally.
+ ITERATION=1
+ MAX_ITERATIONS=3
+
+ while [ $ITERATION -lt $MAX_ITERATIONS ]; do
+ ITERATION=$((ITERATION + 1))
+
+ echo ""
+ echo "═══════════════════════════════════════════════════════"
+ echo " --auto: Starting iteration ${ITERATION}/${MAX_ITERATIONS}"
+ echo "═══════════════════════════════════════════════════════"
+ echo ""
+
+ # Re-review using same depth and file scope as original review
+ echo "Re-reviewing phase ${PHASE_ARG} at ${REVIEW_DEPTH} depth..."
+
+ # Backup previous REVIEW.md and REVIEW-FIX.md before overwriting
+ if [ -f "${REVIEW_PATH}" ]; then
+ cp "${REVIEW_PATH}" "${REVIEW_PATH%.md}.iter${ITERATION}.md" 2>/dev/null || true
+ fi
+ if [ -f "${FIX_REPORT_PATH}" ]; then
+ cp "${FIX_REPORT_PATH}" "${FIX_REPORT_PATH%.md}.iter${ITERATION}.md" 2>/dev/null || true
+ fi
+
+ # If original review had explicit file list, pass it safely to re-review agent
+ FILES_CONFIG=""
+ if [ ${#REVIEW_FILES_ARRAY[@]} -gt 0 ]; then
+ FILES_CONFIG="files:"
+ for f in "${REVIEW_FILES_ARRAY[@]}"; do
+ FILES_CONFIG="${FILES_CONFIG}
+ - ${f}"
+ done
+ fi
+
+ # Spawn gsd-code-reviewer agent to re-review
+ # (This overwrites REVIEW_PATH with latest review state)
+ Task(subagent_type="gsd-code-reviewer", prompt="
+
+depth: ${REVIEW_DEPTH}
+phase_dir: ${PHASE_DIR}
+review_path: ${REVIEW_PATH}
+${FILES_CONFIG}
+
+
+Re-review the phase at ${REVIEW_DEPTH} depth. Write findings to ${REVIEW_PATH}.
+Do NOT commit the output — the orchestrator handles that.
+")
+
+ # Check new REVIEW.md status
+ NEW_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match && /status:\s*(\S+)/.test(match[1])) {
+ console.log(match[1].match(/status:\s*(\S+)/)[1]);
+ } else {
+ console.log('unknown');
+ }
+ " 2>/dev/null)
+
+ if [ "$NEW_STATUS" = "clean" ]; then
+ echo ""
+ echo "✓ All issues resolved after iteration ${ITERATION}."
+ break
+ fi
+
+ # Still has issues — spawn fixer again
+ echo "Issues remain. Applying fixes for iteration ${ITERATION}..."
+
+ Task(subagent_type="gsd-code-fixer", prompt="
+
+${REVIEW_PATH}
+
+
+
+phase_dir: ${PHASE_DIR}
+padded_phase: ${PADDED_PHASE}
+review_path: ${REVIEW_PATH}
+fix_scope: ${FIX_SCOPE}
+fix_report_path: ${FIX_REPORT_PATH}
+iteration: ${ITERATION}
+
+
+Read REVIEW.md findings, apply fixes, commit each atomically, write REVIEW-FIX.md (overwrite previous). Do NOT commit REVIEW-FIX.md.
+")
+
+ # Check if fixer succeeded
+ if [ ! -f "${FIX_REPORT_PATH}" ]; then
+ echo "Warning: Iteration ${ITERATION} fixer failed to produce fix report. Stopping auto-loop."
+ break
+ fi
+ done
+
+ # After loop completes
+ if [ $ITERATION -ge $MAX_ITERATIONS ]; then
+ echo ""
+ echo "⚠ Reached maximum iterations (${MAX_ITERATIONS}). Remaining issues documented in REVIEW-FIX.md."
+ fi
+fi
+```
+
+Key design decisions for --auto (addresses ALL review HIGH concerns):
+1. **Re-review scope**: Uses REVIEW_FILES_ARRAY from original REVIEW.md frontmatter, falling back to full phase scope. Scope is NOT lost between iterations. Uses portable while-read loop (bash 3.2+ compatible, handles spaces in paths).
+2. **Artifact semantics**: REVIEW.md is overwritten by each re-review (latest review state). REVIEW-FIX.md is overwritten by each fixer iteration (latest fix state with iteration count). There is ONE final version of each artifact, not per-iteration copies.
+ Backup files (.iterN.md) preserve history for post-mortem analysis if iterations degrade.
+3. **Commit timing**: Fix commits happen per-finding inside the agent. REVIEW-FIX.md is NOT committed until step 7 (after ALL iterations complete). Only ONE docs commit for REVIEW-FIX.md, not one per iteration.
+
+
+
+After ALL iterations complete (or single pass in non-auto mode), validate and commit REVIEW-FIX.md:
+
+```bash
+if [ -f "${FIX_REPORT_PATH}" ]; then
+ # Validate REVIEW-FIX.md has valid YAML frontmatter with status field
+ HAS_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.FIX_REPORT_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match && /status:/.test(match[1])) { console.log('valid'); } else { console.log('invalid'); }
+ " 2>/dev/null)
+
+ if [ "$HAS_STATUS" = "valid" ]; then
+ echo "REVIEW-FIX.md created at ${FIX_REPORT_PATH}"
+
+ if [ "$COMMIT_DOCS" = "true" ]; then
+ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit \
+ "docs(${PADDED_PHASE}): add code review fix report" \
+ --files "${FIX_REPORT_PATH}"
+ fi
+ else
+ echo "Warning: REVIEW-FIX.md has invalid frontmatter (no status field). Not committing."
+ echo "Agent may have produced malformed output. Review manually: ${FIX_REPORT_PATH}"
+ fi
+else
+ echo "Warning: REVIEW-FIX.md not found at ${FIX_REPORT_PATH}."
+ echo "Agent may have failed before writing report."
+ echo "Check git log for any fix(${PADDED_PHASE}) commits that were applied."
+fi
+```
+
+This commit happens ONCE at the end of the workflow, after all iterations (if --auto) complete. Not per-iteration.
+
+
+
+Parse REVIEW-FIX.md frontmatter and present formatted summary to user.
+
+First check if fix report exists:
+
+```bash
+if [ ! -f "${FIX_REPORT_PATH}" ]; then
+ echo ""
+ echo "═══════════════════════════════════════════════════════════════"
+ echo ""
+ echo " ⚠ No fix report generated"
+ echo ""
+ echo "───────────────────────────────────────────────────────────────"
+ echo ""
+ echo "The fixer agent may have failed before completing."
+ echo "Check git log for any fix(${PADDED_PHASE}) commits."
+ echo ""
+ echo "Retry: /gsd-code-review-fix ${PHASE_ARG}"
+ echo ""
+ echo "═══════════════════════════════════════════════════════════════"
+ exit 1
+fi
+```
+
+Extract frontmatter fields:
+
+```bash
+# Extract only the YAML frontmatter block (between first two --- lines)
+FIX_FRONTMATTER=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.FIX_REPORT_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match) process.stdout.write(match[1]);
+" 2>/dev/null)
+
+# Parse fields from frontmatter only (not full file)
+FIX_STATUS=$(echo "$FIX_FRONTMATTER" | grep "^status:" | cut -d: -f2 | xargs)
+FINDINGS_IN_SCOPE=$(echo "$FIX_FRONTMATTER" | grep "^findings_in_scope:" | cut -d: -f2 | xargs)
+FIXED_COUNT=$(echo "$FIX_FRONTMATTER" | grep "^fixed:" | cut -d: -f2 | xargs)
+SKIPPED_COUNT=$(echo "$FIX_FRONTMATTER" | grep "^skipped:" | cut -d: -f2 | xargs)
+ITERATION_COUNT=$(echo "$FIX_FRONTMATTER" | grep "^iteration:" | cut -d: -f2 | xargs)
+```
+
+Display formatted inline summary:
+
+```bash
+echo ""
+echo "═══════════════════════════════════════════════════════════════"
+echo ""
+echo " Code Review Fix Complete: Phase ${PHASE_NUMBER} (${PHASE_NAME})"
+echo ""
+echo "───────────────────────────────────────────────────────────────"
+echo ""
+echo " Fix Scope: ${FIX_SCOPE}"
+echo " Findings: ${FINDINGS_IN_SCOPE}"
+echo " Fixed: ${FIXED_COUNT}"
+echo " Skipped: ${SKIPPED_COUNT}"
+if [ "$AUTO_MODE" = "true" ]; then
+ echo " Iterations: ${ITERATION_COUNT}"
+fi
+echo " Status: ${FIX_STATUS}"
+echo ""
+echo "───────────────────────────────────────────────────────────────"
+echo ""
+```
+
+If status is "all_fixed":
+```bash
+if [ "$FIX_STATUS" = "all_fixed" ]; then
+ echo "✓ All issues resolved."
+ echo ""
+ echo "Full report: ${FIX_REPORT_PATH}"
+ echo ""
+ echo "Next step:"
+ echo " /gsd-verify-work — Verify phase completion"
+ echo ""
+fi
+```
+
+If status is "partial" or "none_fixed":
+```bash
+if [ "$FIX_STATUS" = "partial" ] || [ "$FIX_STATUS" = "none_fixed" ]; then
+ echo "⚠ Some issues could not be fixed automatically."
+ echo ""
+ echo "Full report: ${FIX_REPORT_PATH}"
+ echo ""
+ echo "Next steps:"
+ echo " cat ${FIX_REPORT_PATH} — View fix report"
+ echo " /gsd-code-review ${PHASE_NUMBER} — Re-review code"
+ echo " /gsd-verify-work — Verify phase completion"
+ echo ""
+fi
+```
+
+```bash
+echo "═══════════════════════════════════════════════════════════════"
+```
+
+
+
+
+
+**Windows:** This workflow uses bash features (arrays, variable expansion, while loops). On Windows, it requires Git Bash or WSL. Native PowerShell is not supported. The CI matrix (Ubuntu/macOS/Windows) runs under Git Bash on Windows runners, which provides bash compatibility.
+
+
+
+- [ ] Phase validated before config gate check
+- [ ] Config gate checked (workflow.code_review)
+- [ ] REVIEW.md existence verified (error if missing)
+- [ ] REVIEW.md status checked (skip if clean/skipped)
+- [ ] Agent spawned with correct config (review_path, fix_scope, fix_report_path)
+- [ ] Agent failure handled with partial-success awareness (some fix commits may exist)
+- [ ] --auto iteration loop respects 3-iteration cap
+- [ ] --auto re-review uses persisted file scope (not lost between iterations)
+- [ ] REVIEW-FIX.md committed ONCE after all iterations (not per-iteration)
+- [ ] Missing fix report handled with explicit error message in present_results
+- [ ] Results presented inline with next step suggestion
+
diff --git a/get-shit-done/workflows/code-review.md b/get-shit-done/workflows/code-review.md
new file mode 100644
index 000000000..d464bbdd1
--- /dev/null
+++ b/get-shit-done/workflows/code-review.md
@@ -0,0 +1,515 @@
+
+Review source files changed during a phase for bugs, security issues, and code quality problems. Computes file scope (--files override > SUMMARY.md > git diff fallback), checks config gate, spawns gsd-code-reviewer agent, commits REVIEW.md, and presents results to user.
+
+
+
+Read all files referenced by the invoking prompt's execution_context before starting.
+
+
+
+- gsd-code-reviewer: Reviews source files for bugs and quality issues
+
+
+
+
+
+Parse arguments and load project state:
+
+```bash
+PHASE_ARG="${1}"
+INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE_ARG}")
+if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
+```
+
+Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
+
+**Input sanitization (defense-in-depth):**
+```bash
+# Validate PADDED_PHASE contains only digits and optional dot (e.g., "02", "03.1")
+if ! [[ "$PADDED_PHASE" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
+ echo "Error: Invalid phase number format: '${PADDED_PHASE}'. Expected digits (e.g., 02, 03.1)."
+ # Exit workflow
+fi
+```
+
+**Phase validation (before config gate):**
+If `phase_found` is false, report error and exit:
+```
+Error: Phase ${PHASE_ARG} not found. Run /gsd-status to see available phases.
+```
+
+This runs BEFORE config gate check so user errors are surfaced immediately regardless of config state.
+
+Parse optional flags from $ARGUMENTS:
+
+**--depth flag:**
+```bash
+DEPTH_OVERRIDE=""
+for arg in "$@"; do
+ if [[ "$arg" == --depth=* ]]; then
+ DEPTH_OVERRIDE="${arg#--depth=}"
+ fi
+done
+```
+
+**--files flag:**
+```bash
+FILES_OVERRIDE=""
+for arg in "$@"; do
+ if [[ "$arg" == --files=* ]]; then
+ FILES_OVERRIDE="${arg#--files=}"
+ fi
+done
+```
+
+If FILES_OVERRIDE is set, split by comma into array:
+```bash
+if [ -n "$FILES_OVERRIDE" ]; then
+ IFS=',' read -ra FILES_ARRAY <<< "$FILES_OVERRIDE"
+fi
+```
+
+
+
+Check if code review is enabled via config:
+
+```bash
+CODE_REVIEW_ENABLED=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review 2>/dev/null || echo "true")
+```
+
+If CODE_REVIEW_ENABLED is "false":
+```
+Code review skipped (workflow.code_review=false in config)
+```
+Exit workflow.
+
+Default is true — only skip on explicit false. This check runs AFTER phase validation so invalid phase errors are shown first.
+
+
+
+Determine review depth with priority order:
+
+1. DEPTH_OVERRIDE from --depth flag (highest priority)
+2. Config value: `node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review_depth 2>/dev/null`
+3. Default: "standard"
+
+```bash
+if [ -n "$DEPTH_OVERRIDE" ]; then
+ REVIEW_DEPTH="$DEPTH_OVERRIDE"
+else
+ CONFIG_DEPTH=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review_depth 2>/dev/null || echo "")
+ REVIEW_DEPTH="${CONFIG_DEPTH:-standard}"
+fi
+```
+
+**Validate depth value:**
+```bash
+case "$REVIEW_DEPTH" in
+ quick|standard|deep)
+ # Valid
+ ;;
+ *)
+ echo "Warning: Invalid depth '${REVIEW_DEPTH}'. Valid values: quick, standard, deep. Using 'standard'."
+ REVIEW_DEPTH="standard"
+ ;;
+esac
+```
+
+
+
+Three-tier scoping with explicit precedence:
+
+**Tier 1 — --files override (highest precedence per D-08):**
+
+If FILES_OVERRIDE is set (from --files flag):
+```bash
+if [ -n "$FILES_OVERRIDE" ]; then
+ REVIEW_FILES=()
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
+
+ for file_path in "${FILES_ARRAY[@]}"; do
+ # Security: validate path is within repository (prevent path traversal)
+ ABS_PATH=$(realpath -m "${file_path}" 2>/dev/null || echo "${file_path}")
+ if [[ "$ABS_PATH" != "$REPO_ROOT"* ]]; then
+ echo "Error: File path outside repository, skipping: ${file_path}"
+ continue
+ fi
+
+ # Validate path exists (relative to repo root)
+ if [ -f "${REPO_ROOT}/${file_path}" ] || [ -f "${file_path}" ]; then
+ REVIEW_FILES+=("$file_path")
+ else
+ echo "Warning: File not found, skipping: ${file_path}"
+ fi
+ done
+
+ echo "File scope: ${#REVIEW_FILES[@]} files from --files override"
+fi
+```
+
+Skip SUMMARY/git scoping entirely when --files is provided.
+
+**Tier 2 — SUMMARY.md extraction (primary per D-01):**
+
+If --files NOT provided:
+```bash
+if [ -z "$FILES_OVERRIDE" ]; then
+ SUMMARIES=$(ls "${PHASE_DIR}"/*-SUMMARY.md 2>/dev/null)
+ REVIEW_FILES=()
+
+ if [ -n "$SUMMARIES" ]; then
+ for summary in $SUMMARIES; do
+ # Extract key_files.created and key_files.modified using node for reliable YAML parsing
+ # This avoids fragile awk parsing that breaks on indentation differences
+ EXTRACTED=$(node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync('$summary', 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (!match) { process.exit(0); }
+ const yaml = match[1];
+ const files = [];
+ let inSection = null;
+ for (const line of yaml.split('\n')) {
+ if (/^\s+created:/.test(line)) { inSection = 'created'; continue; }
+ if (/^\s+modified:/.test(line)) { inSection = 'modified'; continue; }
+ if (/^\s+\w+:/.test(line) && !/^\s+-/.test(line)) { inSection = null; continue; }
+ if (inSection && /^\s+-\s+(.+)/.test(line)) {
+ files.push(line.match(/^\s+-\s+(.+)/)[1].trim());
+ }
+ }
+ if (files.length) console.log(files.join('\n'));
+ " 2>/dev/null)
+
+ # Add extracted files to REVIEW_FILES array
+ if [ -n "$EXTRACTED" ]; then
+ while IFS= read -r file; do
+ if [ -n "$file" ]; then
+ REVIEW_FILES+=("$file")
+ fi
+ done <<< "$EXTRACTED"
+ fi
+ done
+
+ if [ ${#REVIEW_FILES[@]} -eq 0 ]; then
+ echo "Warning: SUMMARY artifacts found but contained no file paths. Falling back to git diff."
+ fi
+ fi
+fi
+```
+
+**Tier 3 — Git diff fallback (per D-02):**
+
+If no SUMMARY.md files found OR no files extracted from them:
+```bash
+if [ ${#REVIEW_FILES[@]} -eq 0 ]; then
+ # Compute diff base from phase commits — fail closed if no reliable base found
+ PHASE_COMMITS=$(git log --oneline --all --grep="${PADDED_PHASE}" --format="%H" 2>/dev/null)
+
+ if [ -n "$PHASE_COMMITS" ]; then
+ DIFF_BASE=$(echo "$PHASE_COMMITS" | tail -1)^
+
+ # Verify the parent commit exists (first commit in repo has no parent)
+ if ! git rev-parse "${DIFF_BASE}" >/dev/null 2>&1; then
+ DIFF_BASE=$(echo "$PHASE_COMMITS" | tail -1)
+ fi
+
+ # Run git diff with specific exclusions (per D-03)
+ DIFF_FILES=$(git diff --name-only "${DIFF_BASE}..HEAD" -- . \
+ ':!.planning/' ':!ROADMAP.md' ':!STATE.md' \
+ ':!*-SUMMARY.md' ':!*-VERIFICATION.md' ':!*-PLAN.md' \
+ ':!package-lock.json' ':!yarn.lock' ':!Gemfile.lock' ':!poetry.lock' 2>/dev/null)
+
+ while IFS= read -r file; do
+ [ -n "$file" ] && REVIEW_FILES+=("$file")
+ done <<< "$DIFF_FILES"
+
+ echo "File scope: ${#REVIEW_FILES[@]} files from git diff (base: ${DIFF_BASE})"
+ else
+ # Fail closed — no reliable diff base found. Do not use arbitrary HEAD~N.
+ echo "Warning: No phase commits found for '${PADDED_PHASE}'. Cannot determine reliable diff scope."
+ echo "Use --files flag to specify files explicitly: /gsd-code-review ${PHASE_ARG} --files=file1,file2,..."
+ fi
+fi
+```
+
+**Post-processing (all tiers):**
+
+1. **Apply exclusions (per D-03):** Remove paths matching planning artifacts
+```bash
+FILTERED_FILES=()
+for file in "${REVIEW_FILES[@]}"; do
+ # Skip planning directory and specific artifacts
+ if [[ "$file" == .planning/* ]] || \
+ [[ "$file" == ROADMAP.md ]] || \
+ [[ "$file" == STATE.md ]] || \
+ [[ "$file" == *-SUMMARY.md ]] || \
+ [[ "$file" == *-VERIFICATION.md ]] || \
+ [[ "$file" == *-PLAN.md ]]; then
+ continue
+ fi
+ FILTERED_FILES+=("$file")
+done
+REVIEW_FILES=("${FILTERED_FILES[@]}")
+```
+
+2. **Filter deleted files:** Remove paths that don't exist on disk
+```bash
+EXISTING_FILES=()
+DELETED_COUNT=0
+for file in "${REVIEW_FILES[@]}"; do
+ if [ -f "$file" ]; then
+ EXISTING_FILES+=("$file")
+ else
+ DELETED_COUNT=$((DELETED_COUNT + 1))
+ fi
+done
+REVIEW_FILES=("${EXISTING_FILES[@]}")
+
+if [ $DELETED_COUNT -gt 0 ]; then
+ echo "Filtered $DELETED_COUNT deleted files from review scope"
+fi
+```
+
+3. **Deduplicate:** Remove duplicate paths (portable — bash 3.2+ compatible, handles spaces in paths)
+```bash
+DEDUPED=()
+while IFS= read -r line; do
+ [ -n "$line" ] && DEDUPED+=("$line")
+done < <(printf '%s\n' "${REVIEW_FILES[@]}" | sort -u)
+REVIEW_FILES=("${DEDUPED[@]}")
+```
+
+4. **Sort:** Alphabetical sort for reproducible agent input (already sorted by sort -u above)
+
+**Log final scope and warn if large:**
+```bash
+if [ -n "$FILES_OVERRIDE" ]; then
+ TIER="--files override"
+elif [ -n "$SUMMARIES" ] && [ ${#REVIEW_FILES[@]} -gt 0 ]; then
+ TIER="SUMMARY.md"
+else
+ TIER="git diff"
+fi
+echo "File scope: ${#REVIEW_FILES[@]} files from ${TIER}"
+
+# Warn if file count is very large — may exceed agent context or produce superficial review
+if [ ${#REVIEW_FILES[@]} -gt 50 ]; then
+ echo "Warning: ${#REVIEW_FILES[@]} files is a large review scope."
+ echo "Consider using --files to narrow scope, or --depth=quick for a faster pass."
+ if [ "$REVIEW_DEPTH" = "deep" ]; then
+ echo "Switching from deep to standard depth for large file count."
+ REVIEW_DEPTH="standard"
+ fi
+fi
+```
+
+
+
+If REVIEW_FILES is empty:
+```
+No source files changed in phase ${PHASE_ARG}. Skipping review.
+```
+Exit workflow. Do NOT spawn agent or create REVIEW.md.
+
+
+
+Compute the review output path:
+```bash
+REVIEW_PATH="${PHASE_DIR}/${PADDED_PHASE}-REVIEW.md"
+```
+
+Compute DIFF_BASE for agent context (in case agent needs it):
+```bash
+PHASE_COMMITS=$(git log --oneline --all --grep="${PADDED_PHASE}" --format="%H" 2>/dev/null)
+if [ -n "$PHASE_COMMITS" ]; then
+ DIFF_BASE=$(echo "$PHASE_COMMITS" | tail -1)^
+else
+ DIFF_BASE=""
+fi
+```
+
+Build files_to_read block for agent:
+```bash
+FILES_TO_READ=""
+for file in "${REVIEW_FILES[@]}"; do
+ FILES_TO_READ+="- ${file}\n"
+done
+```
+
+Build config block for agent:
+```bash
+CONFIG_FILES=""
+for file in "${REVIEW_FILES[@]}"; do
+ CONFIG_FILES+=" - ${file}\n"
+done
+```
+
+Spawn the gsd-code-reviewer agent:
+
+```
+Task(subagent_type="gsd-code-reviewer", prompt="
+
+${FILES_TO_READ}
+
+
+
+depth: ${REVIEW_DEPTH}
+phase_dir: ${PHASE_DIR}
+review_path: ${REVIEW_PATH}
+${DIFF_BASE:+diff_base: ${DIFF_BASE}}
+files:
+${CONFIG_FILES}
+
+
+Review the listed source files at ${REVIEW_DEPTH} depth. Write findings to ${REVIEW_PATH}.
+Do NOT commit the output — the orchestrator handles that.
+")
+```
+
+**Agent failure handling:**
+
+If the Task() call fails (agent error, timeout, or exception):
+```
+Error: Code review agent failed: ${error_message}
+
+No REVIEW.md created. You can retry with /gsd-code-review ${PHASE_ARG} or check agent logs.
+```
+
+Do NOT proceed to commit_review step. Do NOT create a partial or empty REVIEW.md. Exit workflow.
+
+
+
+After agent completes successfully, verify REVIEW.md was created and has valid structure:
+
+```bash
+if [ -f "${REVIEW_PATH}" ]; then
+ # Validate REVIEW.md has valid YAML frontmatter with status field
+ HAS_STATUS=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match && /status:/.test(match[1])) { console.log('valid'); } else { console.log('invalid'); }
+ " 2>/dev/null)
+
+ if [ "$HAS_STATUS" = "valid" ]; then
+ echo "REVIEW.md created at ${REVIEW_PATH}"
+
+ if [ "$COMMIT_DOCS" = "true" ]; then
+ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit \
+ "docs(${PADDED_PHASE}): add code review report" \
+ --files "${REVIEW_PATH}"
+ fi
+ else
+ echo "Warning: REVIEW.md exists but has invalid or missing frontmatter (no status field)."
+ echo "Agent may have produced malformed output. Not committing. Review manually: ${REVIEW_PATH}"
+ fi
+else
+ echo "Warning: Agent completed but REVIEW.md not found at ${REVIEW_PATH}. This may indicate an agent issue."
+ echo "No REVIEW.md to commit. Please retry with /gsd-code-review ${PHASE_ARG}"
+fi
+```
+
+
+
+Read the REVIEW.md YAML frontmatter to extract finding counts.
+
+Extract frontmatter between `---` delimiters first to avoid matching values in the review body:
+
+```bash
+# Extract only the YAML frontmatter block (between first two --- lines)
+FRONTMATTER=$(REVIEW_PATH="${REVIEW_PATH}" node -e "
+ const fs = require('fs');
+ const content = fs.readFileSync(process.env.REVIEW_PATH, 'utf-8');
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
+ if (match) process.stdout.write(match[1]);
+" 2>/dev/null)
+
+# Parse fields from frontmatter only (not full file)
+STATUS=$(echo "$FRONTMATTER" | grep "^status:" | cut -d: -f2 | xargs)
+FILES_REVIEWED=$(echo "$FRONTMATTER" | grep "^files_reviewed:" | cut -d: -f2 | xargs)
+CRITICAL=$(echo "$FRONTMATTER" | grep "critical:" | head -1 | cut -d: -f2 | xargs)
+WARNING=$(echo "$FRONTMATTER" | grep "warning:" | head -1 | cut -d: -f2 | xargs)
+INFO=$(echo "$FRONTMATTER" | grep "info:" | head -1 | cut -d: -f2 | xargs)
+TOTAL=$(echo "$FRONTMATTER" | grep "total:" | head -1 | cut -d: -f2 | xargs)
+```
+
+Display inline summary to user:
+
+```
+═══════════════════════════════════════════════════════════════
+
+ Code Review Complete: Phase ${PHASE_NUMBER} (${PHASE_NAME})
+
+───────────────────────────────────────────────────────────────
+
+ Depth: ${REVIEW_DEPTH}
+ Files Reviewed: ${FILES_REVIEWED}
+
+ Findings:
+ Critical: ${CRITICAL}
+ Warning: ${WARNING}
+ Info: ${INFO}
+ ──────────
+ Total: ${TOTAL}
+
+───────────────────────────────────────────────────────────────
+```
+
+If status is "clean":
+```
+✓ No issues found. All ${FILES_REVIEWED} files pass review at ${REVIEW_DEPTH} depth.
+
+Full report: ${REVIEW_PATH}
+```
+
+If total findings > 0:
+```
+⚠ Issues found. Review the report for details.
+
+Full report: ${REVIEW_PATH}
+
+Next steps:
+ /gsd-code-review-fix ${PHASE_NUMBER} — Auto-fix issues
+ cat ${REVIEW_PATH} — View full report
+```
+
+If critical > 0 or warning > 0, list top 3 issues inline:
+```bash
+echo "Top issues:"
+grep -A 3 "^### CR-\|^### WR-" "${REVIEW_PATH}" | head -n 12
+```
+
+**Note on tests:** Automated tests for this command and workflow are planned for Phase 4 (Pipeline Integration & Testing, requirement INFR-03). Phase 2 focuses on correct implementation; Phase 4 adds regression coverage across platforms.
+
+═══════════════════════════════════════════════════════════════
+
+
+
+
+
+**Windows:** This workflow uses bash features (arrays, process substitution). On Windows, it requires
+Git Bash or WSL. Native PowerShell is not supported. The CI matrix (Ubuntu/macOS/Windows)
+runs under Git Bash on Windows runners, which provides bash compatibility.
+
+**macOS:** macOS ships with bash 3.2 (GPL licensing). This workflow does NOT use `mapfile` (bash 4+
+only) — all array construction uses portable `while IFS= read -r` loops compatible with bash 3.2.
+The `--files` path validation uses `realpath -m` which requires GNU coreutils (install via
+`brew install coreutils`). Without coreutils, the path guard falls back to fail-closed behavior
+(rejects paths it cannot verify), so security is maintained but valid relative paths may be rejected.
+If `--files` validation fails unexpectedly on macOS, install coreutils or use absolute paths.
+
+
+
+- [ ] Phase validated before config gate check
+- [ ] Config gate checked (workflow.code_review)
+- [ ] Depth resolved with validation (quick|standard|deep)
+- [ ] File scope computed with 3 tiers: --files > SUMMARY.md > git diff
+- [ ] Malformed/missing SUMMARY.md handled gracefully with fallback
+- [ ] Deleted files filtered from scope
+- [ ] Files deduplicated and sorted
+- [ ] Empty scope results in skip (no agent spawn)
+- [ ] Agent spawned with explicit file list, depth, review_path, diff_base
+- [ ] Agent failure handled without partial commits
+- [ ] REVIEW.md committed if created
+- [ ] Results presented inline with next step suggestion
+
diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md
index 784e3a994..db77efda6 100644
--- a/get-shit-done/workflows/execute-phase.md
+++ b/get-shit-done/workflows/execute-phase.md
@@ -740,6 +740,39 @@ Selected wave finished successfully. This phase still has incomplete plans, so p
- this means the selected wave happened to be the last remaining work in the phase
+
+**This step is REQUIRED and must not be skipped.** Auto-invoke code review on the phase's source changes. Advisory only — never blocks execution flow.
+
+**Config gate:**
+```bash
+CODE_REVIEW_ENABLED=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review 2>/dev/null || echo "true")
+```
+
+If `CODE_REVIEW_ENABLED` is `"false"`: display "Code review skipped (workflow.code_review=false)" and proceed to next step.
+
+**Invoke review:**
+```
+Skill(skill="gsd:code-review", args="${PHASE_NUMBER}")
+```
+
+**Check results using deterministic path (not glob):**
+```bash
+PADDED=$(printf "%02d" "${PHASE_NUMBER}")
+REVIEW_FILE="${PHASE_DIR}/${PADDED}-REVIEW.md"
+REVIEW_STATUS=$(sed -n '/^---$/,/^---$/p' "$REVIEW_FILE" | grep "^status:" | head -1 | cut -d: -f2 | tr -d ' ')
+```
+
+If REVIEW_STATUS is not "clean" and not "skipped" and not empty, display:
+```
+Code review found issues. Consider running:
+/gsd-code-review-fix ${PHASE_NUMBER}
+```
+
+**Error handling:** If the Skill invocation fails or throws, catch the error, display "Code review encountered an error (non-blocking): {error}" and proceed to next step. Review failures must never block execution.
+
+Regardless of review result, ALWAYS proceed to close_parent_artifacts → regression_gate → verify_phase_goal.
+
+
**For decimal/polish phases only (X.Y pattern):** Close the feedback loop by resolving parent UAT and debug artifacts.
diff --git a/get-shit-done/workflows/quick.md b/get-shit-done/workflows/quick.md
index 0aef0995e..6ca5ab287 100644
--- a/get-shit-done/workflows/quick.md
+++ b/get-shit-done/workflows/quick.md
@@ -23,6 +23,7 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
- gsd-plan-checker — Reviews plan quality before execution
- gsd-executor — Executes plan tasks, commits, creates SUMMARY.md
- gsd-verifier — Verifies phase completion, checks quality gates
+- gsd-code-reviewer — Reviews source files for bugs, security issues, and code quality
@@ -659,6 +660,55 @@ Note: For quick tasks producing multiple plans (rare), spawn executors in parall
---
+**Step 6.25: Code review (auto)**
+
+Skip this step entirely if `$FULL_MODE` is false.
+
+**Config gate:**
+```bash
+CODE_REVIEW_ENABLED=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.code_review 2>/dev/null || echo "true")
+```
+If `"false"`, skip with message "Code review skipped (workflow.code_review=false)".
+
+**Scope files from executor's commits:**
+```bash
+# Find the diff base: last commit before quick task started
+# Use git log to find commits referencing the quick task id, then take the parent of the oldest
+QUICK_COMMITS=$(git log --oneline --format="%H" --grep="${quick_id}" 2>/dev/null)
+if [ -n "$QUICK_COMMITS" ]; then
+ DIFF_BASE=$(echo "$QUICK_COMMITS" | tail -1)^
+ # Verify parent exists (guard against first commit in repo)
+ git rev-parse "${DIFF_BASE}" >/dev/null 2>&1 || DIFF_BASE=$(echo "$QUICK_COMMITS" | tail -1)
+else
+ # No commits found for this quick task — skip review
+ DIFF_BASE=""
+fi
+
+if [ -n "$DIFF_BASE" ]; then
+ CHANGED_FILES=$(git diff --name-only "${DIFF_BASE}..HEAD" -- . ':!.planning' 2>/dev/null | tr '\n' ' ')
+else
+ CHANGED_FILES=""
+fi
+```
+
+If `CHANGED_FILES` is empty, skip with "No source files changed — skipping code review."
+
+**Invoke review:**
+```
+Task(
+ prompt="Review these files for bugs, security issues, and code quality.
+ Files: ${CHANGED_FILES}
+ Output: ${QUICK_DIR}/${quick_id}-REVIEW.md
+ Depth: quick",
+ subagent_type="gsd-code-reviewer",
+ model="{executor_model}"
+)
+```
+
+If review produces findings, display advisory message. **Error handling:** Failures are non-blocking — catch and proceed.
+
+---
+
**Step 6.5: Verification (only when `$VALIDATE_MODE`)**
Skip this step entirely if NOT `$VALIDATE_MODE`.
diff --git a/tests/code-review.test.cjs b/tests/code-review.test.cjs
new file mode 100644
index 000000000..df2b82536
--- /dev/null
+++ b/tests/code-review.test.cjs
@@ -0,0 +1,401 @@
+/**
+ * GSD Code Review Tests
+ *
+ * Validates all code review artifacts from Phases 1-4:
+ * - Agent frontmatter (gsd-code-reviewer, gsd-code-fixer)
+ * - Command structure (code-review.md, code-review-fix.md)
+ * - Workflow structure (code-review.md, code-review-fix.md)
+ * - Config key registration (workflow.code_review, workflow.code_review_depth)
+ * - Workflow integration points (execute-phase, quick, autonomous)
+ *
+ * Test structure:
+ * - CR-AGENT: Hermetic agent tests (repo files only)
+ * - CR-CMD: Hermetic command tests (repo files only)
+ * - CR-WORKFLOW: Hermetic workflow tests (repo files only)
+ * - CR-CONFIG: Hermetic config tests (repo files only)
+ * - CR-INTEGRATION: Conditional integration tests (skip if plugin dir absent)
+ */
+
+const { test, describe } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
+
+// --- Test Environment Setup ---
+
+const AGENTS_DIR = path.join(__dirname, '..', 'agents');
+const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
+const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
+const CONFIG_PATH = path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'config.cjs');
+
+// Plugin directory resolution (cross-platform safe)
+const PLUGIN_WORKFLOWS_DIR = process.env.GSD_PLUGIN_ROOT || path.join(os.homedir(), '.claude', 'get-shit-done', 'workflows');
+const PLUGIN_AVAILABLE = fs.existsSync(PLUGIN_WORKFLOWS_DIR);
+
+// --- CR-AGENT: code review agent frontmatter ---
+
+describe('CR-AGENT: code review agent frontmatter', () => {
+ test('gsd-code-reviewer.md has required frontmatter fields', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('name:'), 'gsd-code-reviewer missing name:');
+ assert.ok(frontmatter.includes('description:'), 'gsd-code-reviewer missing description:');
+ assert.ok(frontmatter.includes('tools:'), 'gsd-code-reviewer missing tools:');
+ assert.ok(frontmatter.includes('color:'), 'gsd-code-reviewer missing color:');
+ });
+
+ test('gsd-code-fixer.md has required frontmatter fields', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('name:'), 'gsd-code-fixer missing name:');
+ assert.ok(frontmatter.includes('description:'), 'gsd-code-fixer missing description:');
+ assert.ok(frontmatter.includes('tools:'), 'gsd-code-fixer missing tools:');
+ assert.ok(frontmatter.includes('color:'), 'gsd-code-fixer missing color:');
+ });
+
+ test('gsd-code-reviewer.md has Read, Bash, Glob, Grep, Write tools', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('Read'), 'gsd-code-reviewer missing Read tool');
+ assert.ok(frontmatter.includes('Bash'), 'gsd-code-reviewer missing Bash tool');
+ assert.ok(frontmatter.includes('Glob'), 'gsd-code-reviewer missing Glob tool');
+ assert.ok(frontmatter.includes('Grep'), 'gsd-code-reviewer missing Grep tool');
+ assert.ok(frontmatter.includes('Write'), 'gsd-code-reviewer missing Write tool');
+ });
+
+ test('gsd-code-fixer.md has Read, Edit, Write, Bash, Grep, Glob tools', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('Read'), 'gsd-code-fixer missing Read tool');
+ assert.ok(frontmatter.includes('Edit'), 'gsd-code-fixer missing Edit tool');
+ assert.ok(frontmatter.includes('Write'), 'gsd-code-fixer missing Write tool');
+ assert.ok(frontmatter.includes('Bash'), 'gsd-code-fixer missing Bash tool');
+ });
+
+ test('gsd-code-reviewer.md does not have skills: in frontmatter', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(!frontmatter.includes('skills:'),
+ 'gsd-code-reviewer has skills: in frontmatter — breaks Gemini CLI');
+ });
+
+ test('gsd-code-fixer.md does not have skills: in frontmatter', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(!frontmatter.includes('skills:'),
+ 'gsd-code-fixer has skills: in frontmatter — breaks Gemini CLI');
+ });
+
+ test('gsd-code-fixer.md rollback uses git checkout (not Write tool)', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
+ assert.ok(content.includes('git checkout --'),
+ 'gsd-code-fixer rollback should use git checkout -- {file} for atomic rollback');
+ assert.ok(!content.includes('PRE_FIX_CONTENT'),
+ 'gsd-code-fixer should not use PRE_FIX_CONTENT in-memory capture (use git checkout instead)');
+ });
+
+ test('gsd-code-fixer.md success_criteria consistent with rollback strategy (git checkout)', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
+ const successCriteria = content.match(/([\s\S]*?)<\/success_criteria>/)?.[1] || '';
+ assert.ok(successCriteria.includes('git checkout'),
+ 'gsd-code-fixer success_criteria must reference git checkout rollback');
+ assert.ok(!successCriteria.includes('Write tool with captured'),
+ 'gsd-code-fixer success_criteria must not say Write tool for rollback');
+ });
+
+ test('gsd-code-fixer.md flags logic-bug fixes for human review', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-fixer.md'), 'utf-8');
+ assert.ok(content.includes('requires human verification'),
+ 'gsd-code-fixer should flag logic-bug fixes as requiring human verification');
+ });
+
+ test('gsd-code-reviewer.md REVIEW.md spec includes files_reviewed_list field', () => {
+ const content = fs.readFileSync(path.join(AGENTS_DIR, 'gsd-code-reviewer.md'), 'utf-8');
+ assert.ok(content.includes('files_reviewed_list'),
+ 'gsd-code-reviewer REVIEW.md frontmatter spec must include files_reviewed_list for --auto scope persistence');
+ });
+});
+
+// --- CR-CMD: code review command structure ---
+
+describe('CR-CMD: code review command structure', () => {
+ test('code-review.md has correct frontmatter name: gsd:code-review', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('name: gsd:code-review'),
+ 'code-review.md missing correct name in frontmatter');
+ });
+
+ test('code-review-fix.md has correct frontmatter name: gsd:code-review-fix', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review-fix.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('name: gsd:code-review-fix'),
+ 'code-review-fix.md missing correct name in frontmatter');
+ });
+
+ test('code-review.md references workflow: code-review.md', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review.md'), 'utf-8');
+
+ assert.ok(content.includes('code-review.md'),
+ 'code-review.md does not reference its workflow');
+ });
+
+ test('code-review-fix.md references workflow: code-review-fix.md', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review-fix.md'), 'utf-8');
+
+ assert.ok(content.includes('code-review-fix.md'),
+ 'code-review-fix.md does not reference its workflow');
+ });
+
+ test('code-review.md has argument-hint in frontmatter', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('argument-hint:'),
+ 'code-review.md missing argument-hint');
+ });
+
+ test('code-review-fix.md has argument-hint in frontmatter', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review-fix.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('argument-hint:'),
+ 'code-review-fix.md missing argument-hint');
+ });
+
+ test('code-review.md has allowed-tools in frontmatter', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('allowed-tools:'),
+ 'code-review.md missing allowed-tools');
+ });
+
+ test('code-review-fix.md has allowed-tools in frontmatter', () => {
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, 'code-review-fix.md'), 'utf-8');
+ const frontmatter = content.split('---')[1] || '';
+
+ assert.ok(frontmatter.includes('allowed-tools:'),
+ 'code-review-fix.md missing allowed-tools');
+ });
+});
+
+// --- CR-WORKFLOW: code review workflow structure ---
+
+describe('CR-WORKFLOW: code review workflow structure', () => {
+ test('code-review.md workflow has ', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review.md'), 'utf-8');
+
+ assert.ok(content.includes(''),
+ 'code-review.md workflow missing initialize step');
+ });
+
+ test('code-review.md workflow has ', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review.md'), 'utf-8');
+
+ assert.ok(content.includes(''),
+ 'code-review.md workflow missing check_config_gate step');
+ });
+
+ test('code-review.md workflow references gsd-code-reviewer agent', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review.md'), 'utf-8');
+
+ assert.ok(content.includes('gsd-code-reviewer'),
+ 'code-review.md workflow does not reference gsd-code-reviewer agent');
+ });
+
+ test('code-review-fix.md workflow has ', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review-fix.md'), 'utf-8');
+
+ assert.ok(content.includes(''),
+ 'code-review-fix.md workflow missing initialize step');
+ });
+
+ test('code-review-fix.md workflow references gsd-code-fixer agent', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review-fix.md'), 'utf-8');
+
+ assert.ok(content.includes('gsd-code-fixer'),
+ 'code-review-fix.md workflow does not reference gsd-code-fixer agent');
+ });
+
+ test('code-review-fix.md workflow has iteration cap', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review-fix.md'), 'utf-8');
+
+ // Check for iteration logic with cap
+ assert.ok(content.includes('MAX_ITERATIONS') || (content.includes('3') && content.includes('iteration')),
+ 'code-review-fix.md workflow missing iteration cap logic');
+ });
+
+ test('code-review.md --files path traversal guard rejects paths outside repo', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review.md'), 'utf-8');
+ // Guard must resolve and compare against REPO_ROOT
+ assert.ok(content.includes('REPO_ROOT') && content.includes('realpath'),
+ 'code-review.md missing path traversal guard (realpath + REPO_ROOT check)');
+ assert.ok(content.includes('File path outside repository'),
+ 'code-review.md missing rejection message for paths outside repo');
+ });
+
+ test('code-review.md uses portable while-read loop for array dedup (not mapfile)', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review.md'), 'utf-8');
+ // mapfile is bash 4+ only; macOS ships bash 3.2. Dedup must use portable while-read.
+ // Note: 'mapfile' may appear in platform_notes documentation — check bash code blocks only
+ const codeBlocks = content.match(/```bash[\s\S]*?```/g) || [];
+ const hasMapfileInCode = codeBlocks.some(block => block.includes('mapfile -t'));
+ assert.ok(!hasMapfileInCode,
+ 'code-review.md bash code blocks use mapfile which is bash 4+ only — breaks macOS default bash 3.2');
+ assert.ok(content.includes('while IFS= read -r'),
+ 'code-review.md should use portable while-read loop instead of mapfile');
+ });
+
+ test('code-review-fix.md uses portable while-read loop for array construction (not mapfile)', () => {
+ const content = fs.readFileSync(path.join(WORKFLOWS_DIR, 'code-review-fix.md'), 'utf-8');
+ const codeBlocks = content.match(/```bash[\s\S]*?```/g) || [];
+ const hasMapfileInCode = codeBlocks.some(block => block.includes('mapfile -t'));
+ assert.ok(!hasMapfileInCode,
+ 'code-review-fix.md bash code blocks use mapfile which is bash 4+ only — breaks macOS default bash 3.2');
+ assert.ok(content.includes('while IFS= read -r'),
+ 'code-review-fix.md should use portable while-read loop instead of mapfile');
+ });
+});
+
+// --- CR-CONFIG: config key registration ---
+
+describe('CR-CONFIG: config key registration', () => {
+ test('config.cjs contains workflow.code_review key', () => {
+ const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
+
+ assert.ok(content.includes('workflow.code_review'),
+ 'config.cjs missing workflow.code_review key registration');
+ });
+
+ test('config.cjs contains workflow.code_review_depth key', () => {
+ const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
+
+ assert.ok(content.includes('workflow.code_review_depth'),
+ 'config.cjs missing workflow.code_review_depth key registration');
+ });
+
+ test('gsd-tools config-get workflow.code_review succeeds', () => {
+ const tmpDir = createTempProject();
+
+ try {
+ // Initialize config with code_review key
+ const configPath = path.join(tmpDir, '.planning', 'config.json');
+ fs.writeFileSync(configPath, JSON.stringify({
+ workflow: {
+ code_review: true,
+ code_review_depth: 'standard'
+ }
+ }, null, 2), 'utf-8');
+
+ const result = runGsdTools(['config-get', 'workflow.code_review'], tmpDir);
+
+ assert.ok(result.success,
+ 'config-get workflow.code_review failed — key not recognized');
+ assert.strictEqual(result.output, 'true',
+ 'workflow.code_review should return "true"');
+ } finally {
+ cleanup(tmpDir);
+ }
+ });
+
+ test('gsd-tools config-get workflow.code_review_depth succeeds', () => {
+ const tmpDir = createTempProject();
+
+ try {
+ // Initialize config with code_review_depth key
+ const configPath = path.join(tmpDir, '.planning', 'config.json');
+ fs.writeFileSync(configPath, JSON.stringify({
+ workflow: {
+ code_review: true,
+ code_review_depth: 'standard'
+ }
+ }, null, 2), 'utf-8');
+
+ const result = runGsdTools(['config-get', 'workflow.code_review_depth'], tmpDir);
+
+ assert.ok(result.success,
+ 'config-get workflow.code_review_depth failed — key not recognized');
+ // Output may include quotes from JSON serialization
+ assert.ok(result.output === 'standard' || result.output === '"standard"',
+ `workflow.code_review_depth should return "standard", got ${result.output}`);
+ } finally {
+ cleanup(tmpDir);
+ }
+ });
+});
+
+// --- CR-INTEGRATION: workflow integration points ---
+
+describe('CR-INTEGRATION: workflow integration points', () => {
+ test('execute-phase.md contains code_review_gate step', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'execute-phase.md'), 'utf-8');
+
+ assert.ok(content.includes('code_review_gate'),
+ 'execute-phase.md missing code_review_gate step name');
+ });
+
+ test('execute-phase.md contains config-get workflow.code_review', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'execute-phase.md'), 'utf-8');
+
+ assert.match(content, /config-get\s+workflow\.code_review/,
+ 'execute-phase.md missing config-get workflow.code_review call');
+ });
+
+ test('execute-phase.md does NOT contain ls.*REVIEW.md.*head pattern', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'execute-phase.md'), 'utf-8');
+
+ // Extract code_review_gate section to check
+ const gateMatch = content.match(/([\s\S]*?)<\/step>/);
+ if (gateMatch) {
+ const gateContent = gateMatch[1];
+ assert.ok(!gateContent.match(/ls.*REVIEW\.md.*head/),
+ 'execute-phase.md code_review_gate uses non-deterministic glob pattern (ls | head)');
+ }
+ });
+
+ test('quick.md contains code-review invocation', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'quick.md'), 'utf-8');
+
+ assert.ok(content.includes('code-review') || content.includes('code_review'),
+ 'quick.md missing code-review invocation');
+ });
+
+ test('quick.md contains config-get workflow.code_review', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'quick.md'), 'utf-8');
+
+ assert.match(content, /config-get\s+workflow\.code_review/,
+ 'quick.md missing config-get workflow.code_review call');
+ });
+
+ test('autonomous.md contains gsd:code-review skill invocation', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'autonomous.md'), 'utf-8');
+
+ assert.ok(content.includes('gsd:code-review'),
+ 'autonomous.md missing gsd:code-review skill invocation');
+ });
+
+ test('autonomous.md contains gsd:code-review-fix skill invocation', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'autonomous.md'), 'utf-8');
+
+ assert.ok(content.includes('gsd:code-review-fix'),
+ 'autonomous.md missing gsd:code-review-fix skill invocation');
+ });
+
+ test('autonomous.md contains --auto flag for code-review-fix', { skip: !PLUGIN_AVAILABLE ? 'Plugin dir not installed' : false }, () => {
+ const content = fs.readFileSync(path.join(PLUGIN_WORKFLOWS_DIR, 'autonomous.md'), 'utf-8');
+
+ assert.ok(content.includes('--auto'),
+ 'autonomous.md missing --auto flag for code-review-fix iteration');
+ });
+});
diff --git a/tests/copilot-install.test.cjs b/tests/copilot-install.test.cjs
index 16e7d1e36..91787d2d7 100644
--- a/tests/copilot-install.test.cjs
+++ b/tests/copilot-install.test.cjs
@@ -1181,6 +1181,8 @@ describe('E2E: Copilot full install verification', () => {
const expected = [
'gsd-advisor-researcher.agent.md',
'gsd-assumptions-analyzer.agent.md',
+ 'gsd-code-fixer.agent.md',
+ 'gsd-code-reviewer.agent.md',
'gsd-codebase-mapper.agent.md',
'gsd-debugger.agent.md',
'gsd-doc-verifier.agent.md',