diff --git a/agents/gsd-plan-checker.md b/agents/gsd-plan-checker.md index 3d406a320..f25ff58b0 100644 --- a/agents/gsd-plan-checker.md +++ b/agents/gsd-plan-checker.md @@ -338,6 +338,8 @@ issue: - `"future enhancement"`, `"placeholder"`, `"basic version"`, `"minimal"` - `"will be wired later"`, `"dynamic in future"`, `"skip for now"` - `"not wired to"`, `"not connected to"`, `"stub"` + - `"too complex"`, `"too difficult"`, `"challenging"`, `"non-trivial"` (when used to justify omission) + - Time estimates used as scope justification: `"would take"`, `"hours"`, `"days"`, `"minutes"` (in sizing context) 2. For each match, cross-reference with the CONTEXT.md decision it claims to implement 3. Compare: does the task deliver what D-XX actually says, or a reduced version? 4. If reduced: BLOCKER — the planner must either deliver fully or propose phase split diff --git a/agents/gsd-planner.md b/agents/gsd-planner.md index c33eee0ab..0c2063993 100644 --- a/agents/gsd-planner.md +++ b/agents/gsd-planner.md @@ -98,38 +98,47 @@ The orchestrator provides user decisions in `` tags from `/gsd-d - "v1", "v2", "simplified version", "static for now", "hardcoded for now" - "future enhancement", "placeholder", "basic version", "minimal implementation" - "will be wired later", "dynamic in future phase", "skip for now" -- Any language that reduces a CONTEXT.md decision to less than what the user decided +- Any language that reduces a source artifact decision to less than what was specified **The rule:** If D-XX says "display cost calculated from billing table in impulses", the plan MUST deliver cost calculated from billing table in impulses. NOT "static label /min" as a "v1". -**When the phase is too complex to implement ALL decisions:** +**When the plan set cannot cover all source items within context budget:** -Do NOT silently simplify decisions. Instead: +Do NOT silently omit features. Instead: -1. **Create a decision coverage matrix** mapping every D-XX to a plan/task -2. **If any D-XX cannot fit** within the plan budget (too many tasks, too complex): +1. **Create a multi-source coverage audit** (see below) covering ALL four artifact types +2. **If any item cannot fit** within the plan budget (context cost exceeds capacity): - Return `## PHASE SPLIT RECOMMENDED` to the orchestrator - - Propose how to split: which D-XX groups form natural sub-phases - - Example: "D-01 to D-19 = Phase 17a (processing core), D-20 to D-27 = Phase 17b (billing + config UX)" -3. The orchestrator will present the split to the user for approval + - Propose how to split: which item groups form natural sub-phases +3. The orchestrator presents the split to the user for approval 4. After approval, plan each sub-phase within budget -**Why this matters:** The user spent time making decisions. Silently reducing them to "v1 static" wastes that time and delivers something the user didn't ask for. Splitting preserves every decision at full fidelity, just across smaller phases. +## Multi-Source Coverage Audit (MANDATORY in every plan set) -**Decision coverage matrix (MANDATORY in every plan set):** +@planner-source-audit.md for full format, examples, and gap-handling rules. -Before finalizing plans, produce internally: +Audit ALL four source types before finalizing: **GOAL** (ROADMAP phase goal), **REQ** (phase_req_ids from REQUIREMENTS.md), **RESEARCH** (RESEARCH.md features/constraints), **CONTEXT** (D-XX decisions from CONTEXT.md). -``` -D-XX | Plan | Task | Full/Partial | Notes -D-01 | 01 | 1 | Full | -D-02 | 01 | 2 | Full | -D-23 | 03 | 1 | PARTIAL | ← BLOCKER: must be Full or split phase -``` +Every item must be COVERED by a plan. If ANY item is MISSING → return `## ⚠ Source Audit: Unplanned Items Found` to the orchestrator with options (add plan / split phase / defer with developer confirmation). Never finalize silently with gaps. -If ANY decision is "Partial" → either fix the task to deliver fully, or return PHASE SPLIT RECOMMENDED. +Exclusions (not gaps): Deferred Ideas in CONTEXT.md, items scoped to other phases, RESEARCH.md "out of scope" items. + +## The Planner Does Not Decide What Is Too Hard + +@planner-source-audit.md for constraint examples. + +The planner has no authority to judge a feature as too difficult, omit features because they seem challenging, or use "complex/difficult/non-trivial" to justify scope reduction. + +**Only three legitimate reasons to split or flag:** +1. **Context cost:** implementation would consume >50% of a single agent's context window +2. **Missing information:** required data not present in any source artifact +3. **Dependency conflict:** feature cannot be built until another phase ships + +If a feature has none of these three constraints, it gets planned. Period. + + ## Solo Developer + Claude Workflow @@ -137,7 +146,7 @@ If ANY decision is "Partial" → either fix the task to deliver fully, or return Planning for ONE person (the user) and ONE implementer (Claude). - No teams, stakeholders, ceremonies, coordination overhead - User = visionary/product owner, Claude = builder -- Estimate effort in Claude execution time, not human dev time +- Estimate effort in context window cost, not time ## Plans Are Prompts @@ -165,7 +174,8 @@ Plan -> Execute -> Ship -> Learn -> Repeat **Anti-enterprise patterns (delete if seen):** - Team structures, RACI matrices, stakeholder management - Sprint ceremonies, change management processes -- Human dev time estimates (hours, days, weeks) +- Time estimates in human units (see ``) +- Complexity/difficulty as scope justification (see ``) - Documentation for documentation's sake @@ -246,13 +256,19 @@ Every task has four required fields: ## Task Sizing -Each task: **15-60 minutes** Claude execution time. +Each task targets **10–30% context consumption**. -| Duration | Action | -|----------|--------| -| < 15 min | Too small — combine with related task | -| 15-60 min | Right size | -| > 60 min | Too large — split | +| Context Cost | Action | +|--------------|--------| +| < 10% context | Too small — combine with a related task | +| 10-30% context | Right size — proceed | +| > 30% context | Too large — split into two tasks | + +**Context cost signals (use these, not time estimates):** +- Files modified: 0-3 = ~10-15%, 4-6 = ~20-30%, 7+ = ~40%+ (split) +- New subsystem: ~25-35% +- Migration + data transform: ~30-40% +- Pure config/wiring: ~5-10% **Too large signals:** Touches >3-5 files, multiple distinct chunks, action section >1 paragraph. @@ -336,49 +352,9 @@ Record in `user_setup` frontmatter. Only include what Claude literally cannot do - `creates`: What this produces - `has_checkpoint`: Requires user interaction? -**Example with 6 tasks:** +**Example:** A→C, B→D, C+D→E, E→F(checkpoint). Waves: {A,B} → {C,D} → {E} → {F}. -``` -Task A (User model): needs nothing, creates src/models/user.ts -Task B (Product model): needs nothing, creates src/models/product.ts -Task C (User API): needs Task A, creates src/api/users.ts -Task D (Product API): needs Task B, creates src/api/products.ts -Task E (Dashboard): needs Task C + D, creates src/components/Dashboard.tsx -Task F (Verify UI): checkpoint:human-verify, needs Task E - -Graph: - A --> C --\ - --> E --> F - B --> D --/ - -Wave analysis: - Wave 1: A, B (independent roots) - Wave 2: C, D (depend only on Wave 1) - Wave 3: E (depends on Wave 2) - Wave 4: F (checkpoint, depends on Wave 3) -``` - -## Vertical Slices vs Horizontal Layers - -**Vertical slices (PREFER):** -``` -Plan 01: User feature (model + API + UI) -Plan 02: Product feature (model + API + UI) -Plan 03: Order feature (model + API + UI) -``` -Result: All three run parallel (Wave 1) - -**Horizontal layers (AVOID):** -``` -Plan 01: Create User model, Product model, Order model -Plan 02: Create User API, Product API, Order API -Plan 03: Create User UI, Product UI, Order UI -``` -Result: Fully sequential (02 needs 01, 03 needs 02) - -**When vertical slices work:** Features are independent, self-contained, no cross-feature dependencies. - -**When horizontal layers necessary:** Shared foundation required (auth before protected features), genuine type dependencies, infrastructure setup. +**Prefer vertical slices** (User feature: model+API+UI) over horizontal layers (all models → all APIs → all UIs). Vertical = parallel. Horizontal = sequential. Use horizontal only when shared foundation is required. ## File Ownership for Parallel Execution @@ -404,11 +380,11 @@ Plans should complete within ~50% context (not 80%). No context anxiety, quality **Each plan: 2-3 tasks maximum.** -| Task Complexity | Tasks/Plan | Context/Task | Total | -|-----------------|------------|--------------|-------| -| Simple (CRUD, config) | 3 | ~10-15% | ~30-45% | -| Complex (auth, payments) | 2 | ~20-30% | ~40-50% | -| Very complex (migrations) | 1-2 | ~30-40% | ~30-50% | +| Context Weight | Tasks/Plan | Context/Task | Total | +|----------------|------------|--------------|-------| +| Light (CRUD, config) | 3 | ~10-15% | ~30-45% | +| Medium (auth, payments) | 2 | ~20-30% | ~40-50% | +| Heavy (migrations, multi-subsystem) | 1-2 | ~30-40% | ~30-50% | ## Split Signals @@ -419,7 +395,7 @@ Plans should complete within ~50% context (not 80%). No context anxiety, quality - Checkpoint + implementation in same plan - Discovery + implementation in same plan -**CONSIDER splitting:** >5 files total, complex domains, uncertainty about approach, natural semantic boundaries. +**CONSIDER splitting:** >5 files total, natural semantic boundaries, context cost estimate exceeds 40% for a single plan. See `` for prohibited split reasons. ## Granularity Calibration @@ -429,22 +405,7 @@ Plans should complete within ~50% context (not 80%). No context anxiety, quality | Standard | 3-5 | 2-3 | | Fine | 5-10 | 2-3 | -Derive plans from actual work. Granularity determines compression tolerance, not a target. Don't pad small work to hit a number. Don't compress complex work to look efficient. - -## Context Per Task Estimates - -| Files Modified | Context Impact | -|----------------|----------------| -| 0-3 files | ~10-15% (small) | -| 4-6 files | ~20-30% (medium) | -| 7+ files | ~40%+ (split) | - -| Complexity | Context/Task | -|------------|--------------| -| Simple CRUD | ~15% | -| Business logic | ~25% | -| Complex algorithms | ~40% | -| Domain modeling | ~35% | +Derive plans from actual work. Granularity determines compression tolerance, not a target. diff --git a/get-shit-done/references/planner-source-audit.md b/get-shit-done/references/planner-source-audit.md new file mode 100644 index 000000000..11d59acc7 --- /dev/null +++ b/get-shit-done/references/planner-source-audit.md @@ -0,0 +1,73 @@ +# Planner Source Audit & Authority Limits + +Reference for `agents/gsd-planner.md` — extended rules for multi-source coverage audits and planner authority constraints. + +## Multi-Source Coverage Audit Format + +Before finalizing plans, produce a **source audit** covering ALL four artifact types: + +``` +SOURCE | ID | Feature/Requirement | Plan | Status | Notes +--------- | ------- | ---------------------------- | ----- | --------- | ------ +GOAL | — | {phase goal from ROADMAP.md} | 01-03 | COVERED | +REQ | REQ-14 | OAuth login with Google + GH | 02 | COVERED | +REQ | REQ-22 | Email verification flow | 03 | COVERED | +RESEARCH | — | Rate limiting on auth routes | 01 | COVERED | +RESEARCH | — | Refresh token rotation | NONE | ⚠ MISSING | No plan covers this +CONTEXT | D-01 | Use jose library for JWT | 02 | COVERED | +CONTEXT | D-04 | 15min access / 7day refresh | 02 | COVERED | +``` + +### Four Source Types + +1. **GOAL** — The `goal:` field from ROADMAP.md for this phase. The primary success condition. +2. **REQ** — Every REQ-ID in `phase_req_ids`. Cross-reference REQUIREMENTS.md for descriptions. +3. **RESEARCH** — Technical approaches, discovered constraints, and features identified in RESEARCH.md. Exclude items explicitly marked "out of scope" or "future work" by the researcher. +4. **CONTEXT** — Every D-XX decision from CONTEXT.md `` section. + +### What is NOT a Gap + +Do not flag these as MISSING: +- Items in `## Deferred Ideas` in CONTEXT.md — developer chose to defer these +- Items scoped to a different phase via `phase_req_ids` — not assigned to this phase +- Items in RESEARCH.md explicitly marked "out of scope" or "future work" by the researcher + +### Handling MISSING Items + +If ANY row is `⚠ MISSING`, do NOT finalize the plan set silently. Return to the orchestrator: + +``` +## ⚠ Source Audit: Unplanned Items Found + +The following items from source artifacts have no corresponding plan: + +1. **{SOURCE}: {item description}** (from {artifact file}, section "{section}") + - {why this was identified as required} + + Options: + A) Add a plan to cover this item + B) Split phase: move to a sub-phase + C) Defer explicitly: add to backlog with developer confirmation + + → Awaiting developer decision before finalizing plan set. +``` + +If ALL rows are COVERED → return `## PLANNING COMPLETE` as normal. + +--- + +## Authority Limits — Constraint Examples + +The planner's only legitimate reasons to split or flag a feature are **constraints**, not judgments about difficulty: + +**Valid (constraints):** +- ✓ "This task touches 9 files and would consume ~45% context — split into two tasks" +- ✓ "No API key or endpoint is defined in any source artifact — need developer input" +- ✓ "This feature depends on the auth system built in Phase 03, which is not yet complete" + +**Invalid (difficulty judgments):** +- ✗ "This is complex and would be difficult to implement correctly" +- ✗ "Integrating with an external service could take a long time" +- ✗ "This is a challenging feature that might be better left to a future phase" + +If a feature has none of the three legitimate constraints (context cost, missing information, dependency conflict), it gets planned. Period. diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index 6903e3529..84718b2c2 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -719,41 +719,70 @@ Task( ## 9. Handle Planner Return - **`## PLANNING COMPLETE`:** Display plan count. If `--skip-verify` or `plan_checker_enabled` is false (from init): skip to step 13. Otherwise: step 10. -- **`## PHASE SPLIT RECOMMENDED`:** The planner determined the phase is too complex to implement all user decisions without simplifying them. Handle in step 9b. +- **`## PHASE SPLIT RECOMMENDED`:** The planner determined the phase exceeds the context budget for full-fidelity implementation of all source items. Handle in step 9b. +- **`## ⚠ Source Audit: Unplanned Items Found`:** The planner's multi-source coverage audit found items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions that are not covered by any plan. Handle in step 9c. - **`## CHECKPOINT REACHED`:** Present to user, get response, spawn continuation (step 12) - **`## PLANNING INCONCLUSIVE`:** Show attempts, offer: Add context / Retry / Manual ## 9b. Handle Phase Split Recommendation -When the planner returns `## PHASE SPLIT RECOMMENDED`, it means the phase has too many decisions to implement at full fidelity within the plan budget. The planner proposes groupings. +When the planner returns `## PHASE SPLIT RECOMMENDED`, it means the phase's source items exceed the context budget for full-fidelity implementation. The planner proposes groupings. **Extract from planner return:** - Proposed sub-phases (e.g., "17a: processing core (D-01 to D-19)", "17b: billing + config UX (D-20 to D-27)") -- Which D-XX decisions go in each sub-phase -- Why the split is necessary (decision count, complexity estimate) +- Which source items (REQ-IDs, D-XX decisions, RESEARCH items) go in each sub-phase +- Why the split is necessary (context cost estimate, file count) **Present to user:** ``` -## Phase {X} is too complex for full-fidelity implementation +## Phase {X} exceeds context budget for full-fidelity implementation -The planner found {N} decisions that cannot all be implemented without -simplifying some. Instead of reducing your decisions, we recommend splitting: +The planner found {N} source items that exceed the context budget when +planned at full fidelity. Instead of reducing scope, we recommend splitting: **Option 1: Split into sub-phases** -- Phase {X}a: {name} — {D-XX to D-YY} ({N} decisions) -- Phase {X}b: {name} — {D-XX to D-YY} ({M} decisions) +- Phase {X}a: {name} — {items} ({N} source items, ~{P}% context) +- Phase {X}b: {name} — {items} ({M} source items, ~{Q}% context) -**Option 2: Proceed anyway** (planner will attempt all, quality may degrade) +**Option 2: Proceed anyway** (planner will attempt all, quality may degrade past 50% context) -**Option 3: Prioritize** — you choose which decisions to implement now, +**Option 3: Prioritize** — you choose which items to implement now, rest become a follow-up phase ``` Use AskUserQuestion with these 3 options. **If "Split":** Use `/gsd-insert-phase` to create the sub-phases, then replan each. -**If "Proceed":** Return to planner with instruction to attempt all decisions at full fidelity, accepting more plans/tasks. -**If "Prioritize":** Use AskUserQuestion (multiSelect) to let user pick which D-XX are "now" vs "later". Create CONTEXT.md for each sub-phase with the selected decisions. +**If "Proceed":** Return to planner with instruction to attempt all items at full fidelity, accepting more plans/tasks. +**If "Prioritize":** Use AskUserQuestion (multiSelect) to let user pick which items are "now" vs "later". Create CONTEXT.md for each sub-phase with the selected items. + +## 9c. Handle Source Audit Gaps + +When the planner returns `## ⚠ Source Audit: Unplanned Items Found`, it means items from REQUIREMENTS.md, RESEARCH.md, ROADMAP goal, or CONTEXT.md decisions have no corresponding plan. + +**Extract from planner return:** +- Each unplanned item with its source artifact and section +- The planner's suggested options (A: add plan, B: split phase, C: defer with confirmation) + +**Present each gap to user.** For each unplanned item: + +``` +## ⚠ Unplanned: {item description} + +Source: {RESEARCH.md / REQUIREMENTS.md / ROADMAP goal / CONTEXT.md} +Details: {why the planner flagged this} + +Options: +1. Add a plan to cover this item (recommended) +2. Split phase — move to a sub-phase with related items +3. Defer — add to backlog (developer confirms this is intentional) +``` + +Use AskUserQuestion for each gap (or batch if multiple gaps). + +**If "Add plan":** Return to planner (step 8) with instruction to add plans covering the missing items, preserving existing plans. +**If "Split":** Use `/gsd-insert-phase` for overflow items, then replan. +**If "Defer":** Record in CONTEXT.md `## Deferred Ideas` with developer's confirmation. Proceed to step 10. ## 10. Spawn gsd-plan-checker Agent diff --git a/tests/planner-language-regression.test.cjs b/tests/planner-language-regression.test.cjs new file mode 100644 index 000000000..b43d96da2 --- /dev/null +++ b/tests/planner-language-regression.test.cjs @@ -0,0 +1,333 @@ +'use strict'; + +/** + * Planner Language Regression Tests (#2091, #2092) + * + * Prevents time-based reasoning and complexity-as-scope-justification + * from leaking back into planning artifacts via future PRs. + * + * These tests scan agent definitions, workflow files, and references + * for prohibited patterns that import human-world constraints into + * an AI execution context where those constraints do not exist. + */ + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.join(__dirname, '..'); +const AGENTS_DIR = path.join(ROOT, 'agents'); +const WORKFLOWS_DIR = path.join(ROOT, 'get-shit-done', 'workflows'); +const REFERENCES_DIR = path.join(ROOT, 'get-shit-done', 'references'); +const TEMPLATES_DIR = path.join(ROOT, 'get-shit-done', 'templates'); + +/** + * Collect all .md files from a directory (non-recursive). + */ +function mdFiles(dir) { + if (!fs.existsSync(dir)) return []; + return fs.readdirSync(dir) + .filter(f => f.endsWith('.md')) + .map(f => ({ name: f, path: path.join(dir, f) })); +} + +/** + * Collect all .md files recursively. + */ +function mdFilesRecursive(dir) { + if (!fs.existsSync(dir)) return []; + const results = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...mdFilesRecursive(full)); + } else if (entry.name.endsWith('.md')) { + results.push({ name: entry.name, path: full }); + } + } + return results; +} + +/** + * Files that define planning behavior — agents, workflows, references. + * These are the files where time-based and complexity-based scope + * reasoning must never appear. + */ +const PLANNING_FILES = [ + ...mdFiles(AGENTS_DIR), + ...mdFiles(WORKFLOWS_DIR), + ...mdFiles(REFERENCES_DIR), + ...mdFilesRecursive(TEMPLATES_DIR), +]; + +// -- Prohibited patterns -- + +/** + * Time-based task sizing patterns. + * Matches "15-60 minutes", "X minutes Claude execution time", etc. + * Does NOT match operational timeouts ("timeout: 5 minutes"), + * API docs examples ("100 requests per 15 minutes"), + * or human-readable timeout descriptions in workflow execution steps. + */ +const TIME_SIZING_PATTERNS = [ + // "N-M minutes" in task sizing context (not timeout context) + /each task[:\s]*\*?\*?\d+[-–]\d+\s*min/i, + // "minutes Claude execution time" or "minutes execution time" + /minutes?\s+(claude\s+)?execution\s+time/i, + // Duration-based sizing table rows: "< 15 min", "15-60 min", "> 60 min" + /[<>]\s*\d+\s*min\s*\|/i, +]; + +/** + * Complexity-as-scope-justification patterns. + * Matches "too complex to implement", "challenging feature", etc. + * Does NOT match legitimate uses like: + * - "complex domains" in research/discovery context (describing what to research) + * - "non-trivial" in verification context (confirming substantive code exists) + * - "challenging" in user-profiling context (quoting user reactions) + */ +const COMPLEXITY_SCOPE_PATTERNS = [ + // "too complex to" — always a scope-reduction justification + /too\s+complex\s+to/i, + // "too difficult" — always a scope-reduction justification + /too\s+difficult/i, + // "is too complex for" — scope justification (e.g. "Phase X is too complex for") + /is\s+too\s+complex\s+for/i, +]; + +/** + * Files allowed to contain certain patterns because they document + * the prohibition itself, or use the terms in non-scope-reduction context. + */ +const ALLOWLIST = { + // Plan-checker scans FOR these patterns — it's a detection list, not usage + 'gsd-plan-checker.md': ['complexity_scope', 'time_sizing'], + // Planner defines the prohibition and the authority limits — uses terms to explain what NOT to do + 'gsd-planner.md': ['complexity_scope'], + // Debugger uses "30+ minutes" as anti-pattern detection, not task sizing + 'gsd-debugger.md': ['time_sizing'], + // Doc-writer uses "15 minutes" in API rate limit example, "2 minutes" for doc quality + 'gsd-doc-writer.md': ['time_sizing'], + // Discovery-phase uses time for level descriptions (operational, not scope) + 'discovery-phase.md': ['time_sizing'], + // Explore uses "~30 seconds" as operational estimate + 'explore.md': ['time_sizing'], + // Review uses "up to 5 minutes" for CodeRabbit timeout + 'review.md': ['time_sizing'], + // Fast uses "under 2 minutes wall time" as operational constraint + 'fast.md': ['time_sizing'], + // Execute-phase uses "timeout: 5 minutes" for test runner + 'execute-phase.md': ['time_sizing'], + // Verify-phase uses "timeout: 5 minutes" for test runner + 'verify-phase.md': ['time_sizing'], + // Map-codebase documents subagent_timeout + 'map-codebase.md': ['time_sizing'], + // Help documents CodeRabbit timing + 'help.md': ['time_sizing'], +}; + +function isAllowlisted(fileName, category) { + const entry = ALLOWLIST[fileName]; + return entry && entry.includes(category); +} + +// -- Tests -- + +describe('Planner language regression — time-based task sizing (#2092)', () => { + for (const file of PLANNING_FILES) { + test(`${file.name} must not use time-based task sizing`, () => { + if (isAllowlisted(file.name, 'time_sizing')) return; + + const content = fs.readFileSync(file.path, 'utf-8'); + for (const pattern of TIME_SIZING_PATTERNS) { + const match = content.match(pattern); + assert.ok( + !match, + [ + `${file.name} contains time-based task sizing: "${match?.[0]}"`, + 'Task sizing must use context-window percentage, not time units.', + 'See issue #2092 for rationale.', + ].join('\n') + ); + } + }); + } +}); + +describe('Planner language regression — complexity-as-scope-justification (#2092)', () => { + for (const file of PLANNING_FILES) { + test(`${file.name} must not use complexity to justify scope reduction`, () => { + if (isAllowlisted(file.name, 'complexity_scope')) return; + + const content = fs.readFileSync(file.path, 'utf-8'); + for (const pattern of COMPLEXITY_SCOPE_PATTERNS) { + const match = content.match(pattern); + assert.ok( + !match, + [ + `${file.name} contains complexity-as-scope-justification: "${match?.[0]}"`, + 'Scope decisions must be based on context cost, missing information,', + 'or dependency conflicts — not perceived difficulty.', + 'See issue #2092 for rationale.', + ].join('\n') + ); + } + }); + } +}); + +describe('gsd-planner.md — required structural sections (#2091, #2092)', () => { + let plannerContent; + + test('planner file exists and is readable', () => { + const plannerPath = path.join(AGENTS_DIR, 'gsd-planner.md'); + assert.ok(fs.existsSync(plannerPath), 'agents/gsd-planner.md must exist'); + plannerContent = fs.readFileSync(plannerPath, 'utf-8'); + }); + + test('contains section', () => { + assert.ok( + plannerContent.includes(''), + 'gsd-planner.md must contain a section defining what the planner cannot decide' + ); + }); + + test('authority limits prohibit difficulty-based scope decisions', () => { + assert.ok( + plannerContent.includes('The planner has no authority to'), + 'planner_authority_limits must explicitly state what the planner cannot decide' + ); + }); + + test('authority limits list three legitimate split reasons: context cost, missing info, dependency', () => { + assert.ok( + plannerContent.includes('Context cost') || plannerContent.includes('context cost'), + 'authority limits must list context cost as a legitimate split reason' + ); + assert.ok( + plannerContent.includes('Missing information') || plannerContent.includes('missing information'), + 'authority limits must list missing information as a legitimate split reason' + ); + assert.ok( + plannerContent.includes('Dependency conflict') || plannerContent.includes('dependency conflict'), + 'authority limits must list dependency conflict as a legitimate split reason' + ); + }); + + test('task sizing uses context percentage, not time units', () => { + assert.ok( + plannerContent.includes('context consumption') || plannerContent.includes('context cost'), + 'task sizing must reference context consumption, not time' + ); + assert.ok( + !(/each task[:\s]*\*?\*?\d+[-–]\d+\s*min/i.test(plannerContent)), + 'task sizing must not use minutes as sizing unit' + ); + }); + + test('contains multi-source coverage audit (not just D-XX decisions)', () => { + assert.ok( + plannerContent.includes('Multi-Source Coverage Audit') || + plannerContent.includes('multi-source coverage audit'), + 'gsd-planner.md must contain a multi-source coverage audit, not just D-XX decision matrix' + ); + }); + + test('coverage audit includes all four source types: GOAL, REQ, RESEARCH, CONTEXT', () => { + // The planner file or its referenced planner-source-audit.md must define all four types. + // The inline compact version uses **GOAL**, **REQ**, **RESEARCH**, **CONTEXT**. + const refPath = path.join(ROOT, 'get-shit-done', 'references', 'planner-source-audit.md'); + const combined = plannerContent + (fs.existsSync(refPath) ? fs.readFileSync(refPath, 'utf-8') : ''); + + const hasGoal = combined.includes('**GOAL**'); + const hasReq = combined.includes('**REQ**'); + const hasResearch = combined.includes('**RESEARCH**'); + const hasContext = combined.includes('**CONTEXT**'); + + assert.ok(hasGoal, 'coverage audit must include GOAL source type (ROADMAP.md phase goal)'); + assert.ok(hasReq, 'coverage audit must include REQ source type (REQUIREMENTS.md)'); + assert.ok(hasResearch, 'coverage audit must include RESEARCH source type (RESEARCH.md)'); + assert.ok(hasContext, 'coverage audit must include CONTEXT source type (CONTEXT.md decisions)'); + }); + + test('coverage audit defines MISSING item handling with developer escalation', () => { + assert.ok( + plannerContent.includes('Source Audit: Unplanned Items Found') || + plannerContent.includes('MISSING'), + 'coverage audit must define handling for MISSING items' + ); + assert.ok( + plannerContent.includes('Awaiting developer decision') || + plannerContent.includes('developer confirmation'), + 'MISSING items must escalate to developer, not be silently dropped' + ); + }); +}); + +describe('plan-phase.md — source audit orchestration (#2091)', () => { + let workflowContent; + + test('plan-phase workflow exists and is readable', () => { + const workflowPath = path.join(WORKFLOWS_DIR, 'plan-phase.md'); + assert.ok(fs.existsSync(workflowPath), 'workflows/plan-phase.md must exist'); + workflowContent = fs.readFileSync(workflowPath, 'utf-8'); + }); + + test('step 9 handles Source Audit return from planner', () => { + assert.ok( + workflowContent.includes('Source Audit: Unplanned Items Found'), + 'plan-phase.md step 9 must handle the Source Audit return from the planner' + ); + }); + + test('step 9c exists for source audit gap handling', () => { + assert.ok( + workflowContent.includes('9c') && workflowContent.includes('Source Audit'), + 'plan-phase.md must have a step 9c for handling source audit gaps' + ); + }); + + test('step 9b does not use "too complex" language', () => { + // Extract just step 9b content (between "## 9b" and "## 9c" or "## 10") + const step9bMatch = workflowContent.match(/## 9b\.([\s\S]*?)(?=## 9c|## 10)/); + if (step9bMatch) { + const step9b = step9bMatch[1]; + assert.ok( + !step9b.includes('too complex'), + 'step 9b must not use "too complex" — use context budget language instead' + ); + } + }); + + test('phase split recommendation uses context budget framing', () => { + assert.ok( + workflowContent.includes('context budget') || workflowContent.includes('context cost'), + 'phase split recommendation must be framed in terms of context budget, not complexity' + ); + }); +}); + +describe('gsd-plan-checker.md — scope reduction detection includes time/complexity (#2092)', () => { + let checkerContent; + + test('plan-checker exists and is readable', () => { + const checkerPath = path.join(AGENTS_DIR, 'gsd-plan-checker.md'); + assert.ok(fs.existsSync(checkerPath), 'agents/gsd-plan-checker.md must exist'); + checkerContent = fs.readFileSync(checkerPath, 'utf-8'); + }); + + test('scope reduction scan includes complexity-based justification patterns', () => { + assert.ok( + checkerContent.includes('too complex') || checkerContent.includes('too difficult'), + 'plan-checker scope reduction scan must detect complexity-based justification language' + ); + }); + + test('scope reduction scan includes time-based justification patterns', () => { + assert.ok( + checkerContent.includes('would take') || checkerContent.includes('hours') || checkerContent.includes('minutes'), + 'plan-checker scope reduction scan must detect time-based justification language' + ); + }); +});