feat: add gsd-pattern-mapper agent for codebase pattern analysis (#1861)
Add a new pattern mapper agent that analyzes the codebase for existing patterns before planning, producing PATTERNS.md with per-file analog assignments and code excerpts. Integrated into plan-phase workflow as Step 7.8 (between research and planning), controlled by the workflow.pattern_mapper config key (default: true). Changes: - New agent: agents/gsd-pattern-mapper.md - New config key: workflow.pattern_mapper in VALID_CONFIG_KEYS and CONFIG_DEFAULTS - init plan-phase: patterns_path field in JSON output - plan-phase.md: Step 7.8 spawns pattern mapper, PATTERNS_PATH in planner files_to_read - gsd-plan-checker.md: Dimension 12 (Pattern Compliance) - model-profiles.cjs: gsd-pattern-mapper profile entry - Tests: tests/pattern-mapper.test.cjs (5 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
319
agents/gsd-pattern-mapper.md
Normal file
319
agents/gsd-pattern-mapper.md
Normal file
@@ -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"
|
||||
---
|
||||
|
||||
<role>
|
||||
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 `<files_to_read>` 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.
|
||||
</role>
|
||||
|
||||
<project_context>
|
||||
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.
|
||||
</project_context>
|
||||
|
||||
<upstream_input>
|
||||
**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) |
|
||||
</upstream_input>
|
||||
|
||||
<downstream_consumer>
|
||||
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."
|
||||
</downstream_consumer>
|
||||
|
||||
<execution_flow>
|
||||
|
||||
## 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
|
||||
|
||||
</execution_flow>
|
||||
|
||||
<output_format>
|
||||
|
||||
## 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]
|
||||
```
|
||||
|
||||
</output_format>
|
||||
|
||||
<structured_returns>
|
||||
|
||||
## 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.
|
||||
```
|
||||
|
||||
</structured_returns>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
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
|
||||
|
||||
</success_criteria>
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
</verification_dimensions>
|
||||
|
||||
<verification_process>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */ }
|
||||
}
|
||||
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -15,6 +15,7 @@ Read all files referenced by the invoking prompt's execution_context before star
|
||||
<available_agent_types>
|
||||
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
|
||||
</available_agent_types>
|
||||
@@ -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
|
||||
<pattern_mapping_context>
|
||||
**Phase:** {phase_number} - {phase_name}
|
||||
**Phase directory:** {phase_dir}
|
||||
**Padded phase:** {padded_phase}
|
||||
|
||||
<files_to_read>
|
||||
- {context_path} (USER DECISIONS from /gsd-discuss-phase)
|
||||
- {research_path} (Technical Research)
|
||||
</files_to_read>
|
||||
|
||||
**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.
|
||||
</pattern_mapping_context>
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
111
tests/pattern-mapper.test.cjs
Normal file
111
tests/pattern-mapper.test.cjs
Normal file
@@ -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}`);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user