* feat: add unified post-planning gap checker (closes #2493) Adds a unified post-planning gap checker as Step 13e of plan-phase.md. After all plans are generated and committed, scans REQUIREMENTS.md and CONTEXT.md <decisions> against every PLAN.md in the phase directory and emits a single Source | Item | Status table. Why - The existing Requirements Coverage Gate (§13) blocks/re-plans on REQ gaps but emits two separate per-source signals. Issue #2493 asks for one unified report after planning so that requirements AND discuss-phase decisions slipping through are surfaced in one place before execution starts. What - New workflow.post_planning_gaps boolean config key, default true, added to VALID_CONFIG_KEYS, CONFIG_DEFAULTS, hardcoded.workflow, and cmdConfigSet (boolean validation). - New get-shit-done/bin/lib/decisions.cjs — shared parser for CONTEXT.md <decisions> blocks (D-NN entries). Designed for reuse by the related #2492 plan/verify decision gates. - New get-shit-done/bin/lib/gap-checker.cjs — parses REQUIREMENTS.md (checkbox + traceability table forms), reads CONTEXT.md decisions, walks PHASE_DIR/*-PLAN.md, runs word-boundary coverage detection (REQ-1 must not match REQ-10), formats a sorted report. - New gsd-tools gap-analysis CLI command wired through gsd-tools.cjs. - workflows/plan-phase.md gains §13e between §13d (commit plans) and §14 (Present Final Status). Existing §13 gate preserved — §13e is additive and non-blocking. - sdk/prompts/workflows/plan-phase.md gets an equivalent post_planning_gaps step for headless mode. - Docs: CONFIGURATION.md, references/planning-config.md, INVENTORY.md, INVENTORY-MANIFEST.json all updated. Tests - tests/post-planning-gaps-2493.test.cjs: 30 test cases covering step insertion position, decisions parser, gap detector behavior (covered/not-covered, false-positive guard, missing-file resilience, malformed-input resilience, gate on/off, deterministic natural sort), and full config integration. - Full suite: 5234 / 5234 pass. Design decisions - Numbered §13e (sub-step), not §14 — §14 already exists (Present Final Status); inserting before it preserves downstream auto-advance step numbers. - Existing §13 gate kept, not replaced — §13 blocks/re-plans on REQ gaps; §13e is the unified post-hoc report. Per spec: "default behavior MUST be backward compatible." - Word-boundary ID matching avoids REQ-1 matching REQ-10 and avoids brittle semantic/substring matching. - Shared decisions.cjs parser so #2492 can reuse the same regex. - Natural-sort keys (REQ-02 before REQ-10) for deterministic output. - Boolean validation in cmdConfigSet rejects non-boolean values matches the precedent set by drift_threshold/drift_action. Closes #2493 * fix(#2493): expose post_planning_gaps in loadConfig() + sync schema example Address CodeRabbit review on PR #2610: - core.cjs loadConfig(): return post_planning_gaps from both the config.json branch and the global ~/.gsd/defaults.json fallback so callers can rely on config.post_planning_gaps regardless of whether the key is present (comment 3127977404, Major). - docs/CONFIGURATION.md: add workflow.post_planning_gaps to the Full Schema JSON example so copy/paste users see the new toggle alongside security_block_on (comment 3127977392, Minor). - tests/post-planning-gaps-2493.test.cjs: regression coverage for loadConfig() — default true when key absent, honors explicit true/false from workflow.post_planning_gaps.
49 lines
1.3 KiB
JavaScript
49 lines
1.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Shared parser for CONTEXT.md `<decisions>` blocks.
|
|
*
|
|
* Used by:
|
|
* - gap-checker.cjs (#2493 post-planning gap analysis)
|
|
* - intended for #2492 (plan-phase decision gate, verify-phase decision validator)
|
|
*
|
|
* Format produced by discuss-phase.md:
|
|
*
|
|
* <decisions>
|
|
* ## Implementation Decisions
|
|
*
|
|
* ### Category
|
|
* - **D-01:** Decision text
|
|
* - **D-02:** Another decision
|
|
* </decisions>
|
|
*
|
|
* D-IDs outside the <decisions> block are ignored. Missing block returns [].
|
|
*/
|
|
|
|
/**
|
|
* Parse the <decisions> section of a CONTEXT.md string.
|
|
*
|
|
* @param {string|null|undefined} contextMd - File contents, may be empty/missing.
|
|
* @returns {Array<{id: string, text: string}>}
|
|
*/
|
|
function parseDecisions(contextMd) {
|
|
if (!contextMd || typeof contextMd !== 'string') return [];
|
|
const blockMatch = contextMd.match(/<decisions>([\s\S]*?)<\/decisions>/);
|
|
if (!blockMatch) return [];
|
|
const block = blockMatch[1];
|
|
|
|
const decisionRe = /^\s*-\s*\*\*(D-[A-Za-z0-9_-]+):\*\*\s*(.+?)\s*$/gm;
|
|
const out = [];
|
|
const seen = new Set();
|
|
let m;
|
|
while ((m = decisionRe.exec(block)) !== null) {
|
|
const id = m[1];
|
|
if (seen.has(id)) continue;
|
|
seen.add(id);
|
|
out.push({ id, text: m[2] });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
module.exports = { parseDecisions };
|