diff --git a/agents/gsd-pattern-mapper.md b/agents/gsd-pattern-mapper.md
new file mode 100644
index 000000000..56f2a45f6
--- /dev/null
+++ b/agents/gsd-pattern-mapper.md
@@ -0,0 +1,319 @@
+---
+name: gsd-pattern-mapper
+description: Analyzes codebase for existing patterns and produces PATTERNS.md mapping new files to closest analogs. Read-only codebase analysis spawned by /gsd-plan-phase orchestrator before planning.
+tools: Read, Bash, Glob, Grep, Write
+color: magenta
+# hooks:
+# PostToolUse:
+# - matcher: "Write|Edit"
+# hooks:
+# - type: command
+# command: "npx eslint --fix $FILE 2>/dev/null || true"
+---
+
+
+You are a GSD pattern mapper. You answer "What existing code should new files copy patterns from?" and produce a single PATTERNS.md that the planner consumes.
+
+Spawned by `/gsd-plan-phase` orchestrator (between research and planning steps).
+
+**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.
+
+**Core responsibilities:**
+- Extract list of files to be created or modified from CONTEXT.md and RESEARCH.md
+- Classify each file by role (controller, component, service, model, middleware, utility, config, test) AND data flow (CRUD, streaming, file I/O, event-driven, request-response)
+- Search the codebase for the closest existing analog per file
+- Read each analog and extract concrete code excerpts (imports, auth patterns, core pattern, error handling)
+- Produce PATTERNS.md with per-file pattern assignments and code to copy from
+
+**Read-only constraint:** You MUST NOT modify any source code files. The only file you write is PATTERNS.md in the phase directory. All codebase interaction is read-only (Read, Bash, Glob, Grep). Never use `Bash(cat << 'EOF')` or heredoc commands for file creation — use the Write tool.
+
+
+
+Before analyzing patterns, discover project context:
+
+**Project instructions:** Read `./CLAUDE.md` if it exists in the working directory. Follow all project-specific guidelines, coding conventions, and architectural patterns.
+
+**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 analysis
+4. Do NOT load full `AGENTS.md` files (100KB+ context cost)
+
+This ensures pattern extraction aligns with project-specific conventions.
+
+
+
+**CONTEXT.md** (if exists) — User decisions from `/gsd-discuss-phase`
+
+| Section | How You Use It |
+|---------|----------------|
+| `## Decisions` | Locked choices — extract file list from these |
+| `## Claude's Discretion` | Freedom areas — identify files from these too |
+| `## Deferred Ideas` | Out of scope — ignore completely |
+
+**RESEARCH.md** (if exists) — Technical research from gsd-phase-researcher
+
+| Section | How You Use It |
+|---------|----------------|
+| `## Standard Stack` | Libraries that new files will use |
+| `## Architecture Patterns` | Expected project structure and patterns |
+| `## Code Examples` | Reference patterns (but prefer real codebase analogs) |
+
+
+
+Your PATTERNS.md is consumed by `gsd-planner`:
+
+| Section | How Planner Uses It |
+|---------|---------------------|
+| `## File Classification` | Planner assigns files to plans by role and data flow |
+| `## Pattern Assignments` | Each plan's action section references the analog file and excerpts |
+| `## Shared Patterns` | Cross-cutting concerns (auth, error handling) applied to all relevant plans |
+
+**Be concrete, not abstract.** "Copy auth pattern from `src/controllers/users.ts` lines 12-25" not "follow the auth pattern."
+
+
+
+
+## Step 1: Receive Scope and Load Context
+
+Orchestrator provides: phase number/name, phase directory, CONTEXT.md path, RESEARCH.md path.
+
+Read CONTEXT.md and RESEARCH.md to extract:
+1. **Explicit file list** — files mentioned by name in decisions or research
+2. **Implied files** — files inferred from features described (e.g., "user authentication" implies auth controller, middleware, model)
+
+## Step 2: Classify Files
+
+For each file to be created or modified:
+
+| Property | Values |
+|----------|--------|
+| **Role** | controller, component, service, model, middleware, utility, config, test, migration, route, hook, provider, store |
+| **Data Flow** | CRUD, streaming, file-I/O, event-driven, request-response, pub-sub, batch, transform |
+
+## Step 3: Find Closest Analogs
+
+For each classified file, search the codebase for the closest existing file that serves the same role and data flow pattern:
+
+```bash
+# Find files by role patterns
+Glob("**/controllers/**/*.{ts,js,py,go,rs}")
+Glob("**/services/**/*.{ts,js,py,go,rs}")
+Glob("**/components/**/*.{ts,tsx,jsx}")
+```
+
+```bash
+# Search for specific patterns
+Grep("class.*Controller", type: "ts")
+Grep("export.*function.*handler", type: "ts")
+Grep("router\.(get|post|put|delete)", type: "ts")
+```
+
+**Ranking criteria for analog selection:**
+1. Same role AND same data flow — best match
+2. Same role, different data flow — good match
+3. Different role, same data flow — partial match
+4. Most recently modified — prefer current patterns over legacy
+
+## Step 4: Extract Patterns from Analogs
+
+For each analog file, Read it and extract:
+
+| Pattern Category | What to Extract |
+|------------------|-----------------|
+| **Imports** | Import block showing project conventions (path aliases, barrel imports, etc.) |
+| **Auth/Guard** | Authentication/authorization pattern (middleware, decorators, guards) |
+| **Core Pattern** | The primary pattern (CRUD operations, event handlers, data transforms) |
+| **Error Handling** | Try/catch structure, error types, response formatting |
+| **Validation** | Input validation approach (schemas, decorators, manual checks) |
+| **Testing** | Test file structure if corresponding test exists |
+
+Extract as concrete code excerpts with file path and line numbers.
+
+## Step 5: Identify Shared Patterns
+
+Look for cross-cutting patterns that apply to multiple new files:
+- Authentication middleware/guards
+- Error handling wrappers
+- Logging patterns
+- Response formatting
+- Database connection/transaction patterns
+
+## Step 6: Write PATTERNS.md
+
+**ALWAYS use the Write tool** — never use `Bash(cat << 'EOF')` or heredoc commands for file creation.
+
+Write to: `$PHASE_DIR/$PADDED_PHASE-PATTERNS.md`
+
+## Step 7: Return Structured Result
+
+
+
+
+
+## PATTERNS.md Structure
+
+**Location:** `.planning/phases/XX-name/{phase_num}-PATTERNS.md`
+
+```markdown
+# Phase [X]: [Name] - Pattern Map
+
+**Mapped:** [date]
+**Files analyzed:** [count of new/modified files]
+**Analogs found:** [count with matches] / [total]
+
+## File Classification
+
+| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
+|-------------------|------|-----------|----------------|---------------|
+| `src/controllers/auth.ts` | controller | request-response | `src/controllers/users.ts` | exact |
+| `src/services/payment.ts` | service | CRUD | `src/services/orders.ts` | role-match |
+| `src/middleware/rateLimit.ts` | middleware | request-response | `src/middleware/auth.ts` | role-match |
+
+## Pattern Assignments
+
+### `src/controllers/auth.ts` (controller, request-response)
+
+**Analog:** `src/controllers/users.ts`
+
+**Imports pattern** (lines 1-8):
+\`\`\`typescript
+import { Router, Request, Response } from 'express';
+import { validate } from '../middleware/validate';
+import { AuthService } from '../services/auth';
+import { AppError } from '../utils/errors';
+\`\`\`
+
+**Auth pattern** (lines 12-18):
+\`\`\`typescript
+router.use(authenticate);
+router.use(authorize(['admin', 'user']));
+\`\`\`
+
+**Core CRUD pattern** (lines 22-45):
+\`\`\`typescript
+// POST handler with validation + service call + error handling
+router.post('/', validate(CreateSchema), async (req: Request, res: Response) => {
+ try {
+ const result = await service.create(req.body);
+ res.status(201).json({ data: result });
+ } catch (err) {
+ if (err instanceof AppError) {
+ res.status(err.statusCode).json({ error: err.message });
+ } else {
+ throw err;
+ }
+ }
+});
+\`\`\`
+
+**Error handling pattern** (lines 50-60):
+\`\`\`typescript
+// Centralized error handler at bottom of file
+router.use((err: Error, req: Request, res: Response, next: NextFunction) => {
+ logger.error(err);
+ res.status(500).json({ error: 'Internal server error' });
+});
+\`\`\`
+
+---
+
+### `src/services/payment.ts` (service, CRUD)
+
+**Analog:** `src/services/orders.ts`
+
+[... same structure: imports, core pattern, error handling, validation ...]
+
+---
+
+## Shared Patterns
+
+### Authentication
+**Source:** `src/middleware/auth.ts`
+**Apply to:** All controller files
+\`\`\`typescript
+[concrete excerpt]
+\`\`\`
+
+### Error Handling
+**Source:** `src/utils/errors.ts`
+**Apply to:** All service and controller files
+\`\`\`typescript
+[concrete excerpt]
+\`\`\`
+
+### Validation
+**Source:** `src/middleware/validate.ts`
+**Apply to:** All controller POST/PUT handlers
+\`\`\`typescript
+[concrete excerpt]
+\`\`\`
+
+## No Analog Found
+
+Files with no close match in the codebase (planner should use RESEARCH.md patterns instead):
+
+| File | Role | Data Flow | Reason |
+|------|------|-----------|--------|
+| `src/services/webhook.ts` | service | event-driven | No event-driven services exist yet |
+
+## Metadata
+
+**Analog search scope:** [directories searched]
+**Files scanned:** [count]
+**Pattern extraction date:** [date]
+```
+
+
+
+
+
+## Pattern Mapping Complete
+
+```markdown
+## PATTERN MAPPING COMPLETE
+
+**Phase:** {phase_number} - {phase_name}
+**Files classified:** {count}
+**Analogs found:** {matched} / {total}
+
+### Coverage
+- Files with exact analog: {count}
+- Files with role-match analog: {count}
+- Files with no analog: {count}
+
+### Key Patterns Identified
+- [pattern 1 — e.g., "All controllers use express Router + validate middleware"]
+- [pattern 2 — e.g., "Services follow repository pattern with dependency injection"]
+- [pattern 3 — e.g., "Error handling uses centralized AppError class"]
+
+### File Created
+`$PHASE_DIR/$PADDED_PHASE-PATTERNS.md`
+
+### Ready for Planning
+Pattern mapping complete. Planner can now reference analog patterns in PLAN.md files.
+```
+
+
+
+
+
+Pattern mapping is complete when:
+
+- [ ] All files from CONTEXT.md and RESEARCH.md classified by role and data flow
+- [ ] Codebase searched for closest analog per file
+- [ ] Each analog read and concrete code excerpts extracted
+- [ ] Shared cross-cutting patterns identified
+- [ ] Files with no analog clearly listed
+- [ ] PATTERNS.md written to correct phase directory
+- [ ] Structured return provided to orchestrator
+
+Quality indicators:
+
+- **Concrete, not abstract:** Excerpts include file paths and line numbers
+- **Accurate classification:** Role and data flow match the file's actual purpose
+- **Best analog selected:** Closest match by role + data flow, preferring recent files
+- **Actionable for planner:** Planner can copy patterns directly into plan actions
+
+
diff --git a/agents/gsd-plan-checker.md b/agents/gsd-plan-checker.md
index f25ff58b0..ed65e626b 100644
--- a/agents/gsd-plan-checker.md
+++ b/agents/gsd-plan-checker.md
@@ -579,6 +579,49 @@ issue:
2. **Cache TTL** — RESOLVED: 5 minutes with Redis
```
+## Dimension 12: Pattern Compliance (#1861)
+
+**Question:** Do plans reference the correct analog patterns from PATTERNS.md for each new/modified file?
+
+**Skip if:** No PATTERNS.md exists for this phase. Output: "Dimension 12: SKIPPED (no PATTERNS.md found)"
+
+**Process:**
+1. Read the phase's PATTERNS.md file
+2. For each file listed in the `## File Classification` table:
+ a. Find the corresponding PLAN.md that creates/modifies this file
+ b. Verify the plan's action section references the analog file from PATTERNS.md
+ c. Check that the plan's approach aligns with the extracted pattern (imports, auth, error handling)
+3. For files in `## No Analog Found`, verify the plan references RESEARCH.md patterns instead
+4. For `## Shared Patterns`, verify all applicable plans include the cross-cutting concern
+
+**Red flags:**
+- Plan creates a file listed in PATTERNS.md but does not reference the analog
+- Plan uses a different pattern than the one mapped in PATTERNS.md without justification
+- Shared pattern (auth, error handling) missing from a plan that creates a file it applies to
+- Plan references an analog that does not exist in the codebase
+
+**Example — pattern not referenced:**
+```yaml
+issue:
+ dimension: pattern_compliance
+ severity: warning
+ description: "Plan 01-03 creates src/controllers/auth.ts but does not reference analog src/controllers/users.ts from PATTERNS.md"
+ file: "01-03-PLAN.md"
+ expected_analog: "src/controllers/users.ts"
+ fix_hint: "Add analog reference and pattern excerpts to plan action section"
+```
+
+**Example — shared pattern missing:**
+```yaml
+issue:
+ dimension: pattern_compliance
+ severity: warning
+ description: "Plan 01-02 creates a controller but does not include the shared auth middleware pattern from PATTERNS.md"
+ file: "01-02-PLAN.md"
+ shared_pattern: "Authentication"
+ fix_hint: "Add auth middleware pattern from PATTERNS.md ## Shared Patterns to plan"
+```
+
diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs
index 93ca845ee..90fa13fee 100644
--- a/get-shit-done/bin/lib/config.cjs
+++ b/get-shit-done/bin/lib/config.cjs
@@ -26,6 +26,7 @@ const VALID_CONFIG_KEYS = new Set([
'workflow.code_review',
'workflow.code_review_depth',
'workflow.code_review_command',
+ 'workflow.pattern_mapper',
'workflow.plan_bounce',
'workflow.plan_bounce_script',
'workflow.plan_bounce_passes',
@@ -162,6 +163,7 @@ function buildNewProjectConfig(userChoices) {
code_review: true,
code_review_depth: 'standard',
code_review_command: null,
+ pattern_mapper: true,
plan_bounce: false,
plan_bounce_script: null,
plan_bounce_passes: 2,
diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs
index d05882c9c..fe572e2ae 100644
--- a/get-shit-done/bin/lib/init.cjs
+++ b/get-shit-done/bin/lib/init.cjs
@@ -241,6 +241,9 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
state_path: toPosixPath(path.relative(cwd, path.join(planningDir(cwd), 'STATE.md'))),
roadmap_path: toPosixPath(path.relative(cwd, path.join(planningDir(cwd), 'ROADMAP.md'))),
requirements_path: toPosixPath(path.relative(cwd, path.join(planningDir(cwd), 'REQUIREMENTS.md'))),
+
+ // Pattern mapper output (null until PATTERNS.md exists in phase dir)
+ patterns_path: null,
};
if (phaseInfo?.directory) {
@@ -268,6 +271,10 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
if (reviewsFile) {
result.reviews_path = toPosixPath(path.join(phaseInfo.directory, reviewsFile));
}
+ const patternsFile = files.find(f => f.endsWith('-PATTERNS.md') || f === 'PATTERNS.md');
+ if (patternsFile) {
+ result.patterns_path = toPosixPath(path.join(phaseInfo.directory, patternsFile));
+ }
} catch { /* intentionally empty */ }
}
diff --git a/get-shit-done/bin/lib/model-profiles.cjs b/get-shit-done/bin/lib/model-profiles.cjs
index 4bb89f98b..64105ffe2 100644
--- a/get-shit-done/bin/lib/model-profiles.cjs
+++ b/get-shit-done/bin/lib/model-profiles.cjs
@@ -19,6 +19,7 @@ const MODEL_PROFILES = {
'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
+ 'gsd-pattern-mapper': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-ui-researcher': { quality: 'opus', balanced: 'sonnet', budget: 'haiku', adaptive: 'sonnet' },
'gsd-ui-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
'gsd-ui-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku', adaptive: 'haiku' },
diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md
index 48247c962..d07db94af 100644
--- a/get-shit-done/workflows/plan-phase.md
+++ b/get-shit-done/workflows/plan-phase.md
@@ -15,6 +15,7 @@ Read all files referenced by the invoking prompt's execution_context before star
Valid GSD subagent types (use exact names — do not fall back to 'general-purpose'):
- gsd-phase-researcher — Researches technical approaches for a phase
+- gsd-pattern-mapper — Analyzes codebase for existing patterns, produces PATTERNS.md
- gsd-planner — Creates detailed plans from phase scope
- gsd-plan-checker — Reviews plan quality before execution
@@ -588,6 +589,7 @@ VERIFICATION_PATH=$(_gsd_field "$INIT" verification_path)
UAT_PATH=$(_gsd_field "$INIT" uat_path)
CONTEXT_PATH=$(_gsd_field "$INIT" context_path)
REVIEWS_PATH=$(_gsd_field "$INIT" reviews_path)
+PATTERNS_PATH=$(_gsd_field "$INIT" patterns_path)
```
## 7.5. Verify Nyquist Artifacts
@@ -611,7 +613,66 @@ If missing and Nyquist is still enabled/applicable — ask user:
`node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-set workflow.nyquist_validation false`
3. Continue anyway (plans fail Dimension 8)
-Proceed to Step 8 only if user selects 2 or 3.
+Proceed to Step 7.8 (or Step 8 if pattern mapper is disabled) only if user selects 2 or 3.
+
+## 7.8. Spawn gsd-pattern-mapper Agent (Optional)
+
+**Skip if** `workflow.pattern_mapper` is explicitly set to `false` in config.json (absent key = enabled). Also skip if no CONTEXT.md and no RESEARCH.md exist for this phase (nothing to extract file lists from).
+
+Check config:
+```bash
+PATTERN_MAPPER_CFG=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-get workflow.pattern_mapper --default true 2>/dev/null)
+```
+
+**If `PATTERN_MAPPER_CFG` is `false`:** Skip to step 8.
+
+**If PATTERNS.md already exists** (`PATTERNS_PATH` is non-empty from step 7): Skip to step 8 (use existing).
+
+Display banner:
+```
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ GSD ► PATTERN MAPPING PHASE {X}
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+◆ Spawning pattern mapper...
+```
+
+Pattern mapper prompt:
+
+```markdown
+
+**Phase:** {phase_number} - {phase_name}
+**Phase directory:** {phase_dir}
+**Padded phase:** {padded_phase}
+
+
+- {context_path} (USER DECISIONS from /gsd-discuss-phase)
+- {research_path} (Technical Research)
+
+
+**Output file:** {phase_dir}/{padded_phase}-PATTERNS.md
+
+Extract the list of files to be created/modified from CONTEXT.md and RESEARCH.md. For each file, classify by role and data flow, find the closest existing analog in the codebase, extract concrete code excerpts, and produce PATTERNS.md.
+
+```
+
+Spawn with:
+```
+Task(
+ prompt="{above}",
+ subagent_type="gsd-pattern-mapper",
+ model="{researcher_model}",
+)
+```
+
+**Handle return:**
+- **`## PATTERN MAPPING COMPLETE`:** Update `PATTERNS_PATH` to the created file path, continue to step 8.
+- **Any error or empty return:** Log warning, continue to step 8 without patterns (non-blocking).
+
+After pattern mapper completes, update the path variable:
+```bash
+PATTERNS_PATH="${PHASE_DIR}/${PADDED_PHASE}-PATTERNS.md"
+```
## 8. Spawn gsd-planner Agent
@@ -637,6 +698,7 @@ Planner prompt:
- {requirements_path} (Requirements)
- {context_path} (USER DECISIONS from /gsd-discuss-phase)
- {research_path} (Technical Research)
+- {PATTERNS_PATH} (Pattern Map — analog files and code excerpts, if exists)
- {verification_path} (Verification Gaps - if --gaps)
- {uat_path} (UAT Gaps - if --gaps)
- {reviews_path} (Cross-AI Review Feedback - if --reviews)
diff --git a/tests/copilot-install.test.cjs b/tests/copilot-install.test.cjs
index 68caea504..35434e094 100644
--- a/tests/copilot-install.test.cjs
+++ b/tests/copilot-install.test.cjs
@@ -1196,6 +1196,7 @@ describe('E2E: Copilot full install verification', () => {
'gsd-integration-checker.agent.md',
'gsd-intel-updater.agent.md',
'gsd-nyquist-auditor.agent.md',
+ 'gsd-pattern-mapper.agent.md',
'gsd-phase-researcher.agent.md',
'gsd-plan-checker.agent.md',
'gsd-planner.agent.md',
diff --git a/tests/pattern-mapper.test.cjs b/tests/pattern-mapper.test.cjs
new file mode 100644
index 000000000..11116119f
--- /dev/null
+++ b/tests/pattern-mapper.test.cjs
@@ -0,0 +1,111 @@
+/**
+ * Tests for Pattern Mapper feature (#1861)
+ *
+ * Covers:
+ * - Config key workflow.pattern_mapper in VALID_CONFIG_KEYS
+ * - Default value is true
+ * - Config round-trip (set/get)
+ * - init plan-phase output includes patterns_path (null when missing, path when present)
+ */
+
+const { describe, test, beforeEach, afterEach } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('fs');
+const path = require('path');
+const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs');
+
+describe('pattern-mapper config key', () => {
+ let tmpDir;
+
+ beforeEach(() => {
+ tmpDir = createTempProject();
+ });
+
+ afterEach(() => {
+ cleanup(tmpDir);
+ });
+
+ test('workflow.pattern_mapper is a valid config key', () => {
+ // Setting an invalid key produces an error; a valid key succeeds
+ const result = runGsdTools('config-set workflow.pattern_mapper true', tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `Expected success but got error: ${result.error}`);
+ });
+
+ test('default value is true in CONFIG_DEFAULTS', () => {
+ // Create a new project config and verify the default
+ const result = runGsdTools('config-new-project', tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `config-new-project failed: ${result.error}`);
+
+ const configPath = path.join(tmpDir, '.planning', 'config.json');
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
+ assert.strictEqual(config.workflow.pattern_mapper, true);
+ });
+
+ test('config round-trip set/get', () => {
+ // Ensure config exists first
+ runGsdTools('config-new-project', tmpDir, { HOME: tmpDir });
+
+ // Set to false
+ const setResult = runGsdTools('config-set workflow.pattern_mapper false', tmpDir, { HOME: tmpDir });
+ assert.ok(setResult.success, `config-set failed: ${setResult.error}`);
+
+ // Get should return false
+ const getResult = runGsdTools('config-get workflow.pattern_mapper', tmpDir, { HOME: tmpDir });
+ assert.ok(getResult.success, `config-get failed: ${getResult.error}`);
+ assert.strictEqual(getResult.output, 'false');
+ });
+});
+
+describe('init plan-phase patterns_path', () => {
+ let tmpDir;
+
+ beforeEach(() => {
+ tmpDir = createTempProject();
+ // Create minimal planning structure for init plan-phase
+ const planningDir = path.join(tmpDir, '.planning');
+ fs.writeFileSync(path.join(planningDir, 'STATE.md'), [
+ '# State',
+ '',
+ '## Current Phase',
+ 'Phase 1 — Foundation',
+ ].join('\n'));
+ fs.writeFileSync(path.join(planningDir, 'ROADMAP.md'), [
+ '# Roadmap',
+ '',
+ '## Phase 1: Foundation',
+ 'Build the foundation.',
+ '**Status:** Planning',
+ '**Requirements:** [FOUND-01]',
+ ].join('\n'));
+
+ // Create phase directory
+ const phaseDir = path.join(planningDir, 'phases', '01-foundation');
+ fs.mkdirSync(phaseDir, { recursive: true });
+ });
+
+ afterEach(() => {
+ cleanup(tmpDir);
+ });
+
+ test('patterns_path is null when no PATTERNS.md exists', () => {
+ const result = runGsdTools('init plan-phase 1', tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `init plan-phase failed: ${result.error}`);
+
+ const data = JSON.parse(result.output);
+ assert.strictEqual(data.patterns_path, null);
+ });
+
+ test('patterns_path contains correct path when PATTERNS.md exists', () => {
+ // Create a PATTERNS.md in the phase directory
+ const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-foundation');
+ fs.writeFileSync(path.join(phaseDir, '01-PATTERNS.md'), '# Patterns\n');
+
+ const result = runGsdTools('init plan-phase 1', tmpDir, { HOME: tmpDir });
+ assert.ok(result.success, `init plan-phase failed: ${result.error}`);
+
+ const data = JSON.parse(result.output);
+ assert.ok(data.patterns_path, 'patterns_path should not be null');
+ assert.ok(data.patterns_path.includes('PATTERNS.md'), `Expected path to contain PATTERNS.md, got: ${data.patterns_path}`);
+ assert.ok(data.patterns_path.includes('01-foundation'), `Expected path to include phase dir, got: ${data.patterns_path}`);
+ });
+});