diff --git a/agents/gsd-nyquist-auditor.md b/agents/gsd-nyquist-auditor.md new file mode 100644 index 00000000..31d1b68e --- /dev/null +++ b/agents/gsd-nyquist-auditor.md @@ -0,0 +1,176 @@ +--- +name: gsd-nyquist-auditor +description: Fills Nyquist validation gaps by generating tests and verifying coverage for phase requirements +tools: + - Read + - Write + - Edit + - Bash + - Glob + - Grep +color: "#8B5CF6" +--- + + +GSD Nyquist auditor. Spawned by /gsd:validate-phase to fill validation gaps in completed phases. + +For each gap in ``: generate minimal behavioral test, run it, debug if failing (max 3 iterations), report results. + +**Mandatory Initial Read:** If prompt contains ``, load ALL listed files before any action. + +**Implementation files are READ-ONLY.** Only create/modify: test files, fixtures, VALIDATION.md. Implementation bugs → ESCALATE. Never fix implementation. + + + + + +Read ALL files from ``. Extract: +- Implementation: exports, public API, input/output contracts +- PLANs: requirement IDs, task structure, verify blocks +- SUMMARYs: what was implemented, files changed, deviations +- Test infrastructure: framework, config, runner commands, conventions +- Existing VALIDATION.md: current map, compliance status + + + +For each gap in ``: + +1. Read related implementation files +2. Identify observable behavior the requirement demands +3. Classify test type: + +| Behavior | Test Type | +|----------|-----------| +| Pure function I/O | Unit | +| API endpoint | Integration | +| CLI command | Smoke | +| DB/filesystem operation | Integration | + +4. Map to test file path per project conventions + +Action by gap type: +- `no_test_file` → Create test file +- `test_fails` → Diagnose and fix the test (not impl) +- `no_automated_command` → Determine command, update map + + + +Convention discovery: existing tests → framework defaults → fallback. + +| Framework | File Pattern | Runner | Assert Style | +|-----------|-------------|--------|--------------| +| pytest | `test_{name}.py` | `pytest {file} -v` | `assert result == expected` | +| jest | `{name}.test.ts` | `npx jest {file}` | `expect(result).toBe(expected)` | +| vitest | `{name}.test.ts` | `npx vitest run {file}` | `expect(result).toBe(expected)` | +| go test | `{name}_test.go` | `go test -v -run {Name}` | `if got != want { t.Errorf(...) }` | + +Per gap: Write test file. One focused test per requirement behavior. Arrange/Act/Assert. Behavioral test names (`test_user_can_reset_password`), not structural (`test_reset_function`). + + + +Execute each test. If passes: record success, next gap. If fails: enter debug loop. + +Run every test. Never mark untested tests as passing. + + + +Max 3 iterations per failing test. + +| Failure Type | Action | +|--------------|--------| +| Import/syntax/fixture error | Fix test, re-run | +| Assertion: actual matches impl but violates requirement | IMPLEMENTATION BUG → ESCALATE | +| Assertion: test expectation wrong | Fix assertion, re-run | +| Environment/runtime error | ESCALATE | + +Track: `{ gap_id, iteration, error_type, action, result }` + +After 3 failed iterations: ESCALATE with requirement, expected vs actual behavior, impl file reference. + + + +Resolved gaps: `{ task_id, requirement, test_type, automated_command, file_path, status: "green" }` +Escalated gaps: `{ task_id, requirement, reason, debug_iterations, last_error }` + +Return one of three formats below. + + + + + + +## GAPS FILLED + +```markdown +## GAPS FILLED + +**Phase:** {N} — {name} +**Resolved:** {count}/{count} + +### Tests Created +| # | File | Type | Command | +|---|------|------|---------| +| 1 | {path} | {unit/integration/smoke} | `{cmd}` | + +### Verification Map Updates +| Task ID | Requirement | Command | Status | +|---------|-------------|---------|--------| +| {id} | {req} | `{cmd}` | green | + +### Files for Commit +{test file paths} +``` + +## PARTIAL + +```markdown +## PARTIAL + +**Phase:** {N} — {name} +**Resolved:** {M}/{total} | **Escalated:** {K}/{total} + +### Resolved +| Task ID | Requirement | File | Command | Status | +|---------|-------------|------|---------|--------| +| {id} | {req} | {file} | `{cmd}` | green | + +### Escalated +| Task ID | Requirement | Reason | Iterations | +|---------|-------------|--------|------------| +| {id} | {req} | {reason} | {N}/3 | + +### Files for Commit +{test file paths for resolved gaps} +``` + +## ESCALATE + +```markdown +## ESCALATE + +**Phase:** {N} — {name} +**Resolved:** 0/{total} + +### Details +| Task ID | Requirement | Reason | Iterations | +|---------|-------------|--------|------------| +| {id} | {req} | {reason} | {N}/3 | + +### Recommendations +- **{req}:** {manual test instructions or implementation fix needed} +``` + + + + +- [ ] All `` loaded before any action +- [ ] Each gap analyzed with correct test type +- [ ] Tests follow project conventions +- [ ] Tests verify behavior, not structure +- [ ] Every test executed — none marked passing without running +- [ ] Implementation files never modified +- [ ] Max 3 debug iterations per gap +- [ ] Implementation bugs escalated, not fixed +- [ ] Structured return provided (GAPS FILLED / PARTIAL / ESCALATE) +- [ ] Test files listed for commit + diff --git a/agents/gsd-phase-researcher.md b/agents/gsd-phase-researcher.md index ab93e540..dc722a39 100644 --- a/agents/gsd-phase-researcher.md +++ b/agents/gsd-phase-researcher.md @@ -306,7 +306,7 @@ Verified patterns from official sources: ## Validation Architecture -> Skip this section entirely if workflow.nyquist_validation is false in .planning/config.json +> Skip this section entirely if workflow.nyquist_validation is explicitly set to false in .planning/config.json. If the key is absent, treat as enabled. ### Test Framework | Property | Value | @@ -372,7 +372,7 @@ INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHA Extract from init JSON: `phase_dir`, `padded_phase`, `phase_number`, `commit_docs`. -Also read `.planning/config.json` — if `workflow.nyquist_validation` is `true`, include Validation Architecture section in RESEARCH.md. If `false`, skip it. +Also read `.planning/config.json` — include Validation Architecture section in RESEARCH.md unless `workflow.nyquist_validation` is explicitly `false`. If the key is absent or `true`, include the section. Then read CONTEXT.md if exists: ```bash @@ -408,7 +408,7 @@ For each domain: Context7 first → Official docs → WebSearch → Cross-verify ## Step 4: Validation Architecture Research (if nyquist_validation enabled) -**Skip if** workflow.nyquist_validation is false. +**Skip if** workflow.nyquist_validation is explicitly set to false. If absent, treat as enabled. ### Detect Test Infrastructure Scan for: test config files (pytest.ini, jest.config.*, vitest.config.*), test directories (test/, tests/, __tests__/), test files (*.test.*, *.spec.*), package.json test scripts. diff --git a/agents/gsd-plan-checker.md b/agents/gsd-plan-checker.md index 7aa32150..392927e3 100644 --- a/agents/gsd-plan-checker.md +++ b/agents/gsd-plan-checker.md @@ -316,7 +316,20 @@ issue: ## Dimension 8: Nyquist Compliance -Skip if: `workflow.nyquist_validation` is false, phase has no RESEARCH.md, or RESEARCH.md has no "Validation Architecture" section. Output: "Dimension 8: SKIPPED (nyquist_validation disabled or not applicable)" +Skip if: `workflow.nyquist_validation` is explicitly set to `false` in config.json (absent key = enabled), phase has no RESEARCH.md, or RESEARCH.md has no "Validation Architecture" section. Output: "Dimension 8: SKIPPED (nyquist_validation disabled or not applicable)" + +### Check 8e — VALIDATION.md Existence (Gate) + +Before running checks 8a-8d, verify VALIDATION.md exists: + +```bash +ls "${PHASE_DIR}"/*-VALIDATION.md 2>/dev/null +``` + +**If missing:** **BLOCKING FAIL** — "VALIDATION.md not found for phase {N}. Re-run `/gsd:plan-phase {N} --research` to regenerate." +Skip checks 8a-8d entirely. Report Dimension 8 as FAIL with this single issue. + +**If exists:** Proceed to checks 8a-8d. ### Check 8a — Automated Verify Presence diff --git a/commands/gsd/validate-phase.md b/commands/gsd/validate-phase.md new file mode 100644 index 00000000..bdc1ad77 --- /dev/null +++ b/commands/gsd/validate-phase.md @@ -0,0 +1,35 @@ +--- +name: gsd:validate-phase +description: Retroactively audit and fill Nyquist validation gaps for a completed phase +argument-hint: "[phase number]" +allowed-tools: + - Read + - Write + - Edit + - Bash + - Glob + - Grep + - Task + - AskUserQuestion +--- + +Audit Nyquist validation coverage for a completed phase. Three states: +- (A) VALIDATION.md exists — audit and fill gaps +- (B) No VALIDATION.md, SUMMARY.md exists — reconstruct from artifacts +- (C) Phase not executed — exit with guidance + +Output: updated VALIDATION.md + generated test files. + + + +@~/.claude/get-shit-done/workflows/validate-phase.md + + + +Phase: $ARGUMENTS — optional, defaults to last completed phase. + + + +Execute @~/.claude/get-shit-done/workflows/validate-phase.md. +Preserve all workflow gates. + diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index 7b279083..3ea248f8 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -115,6 +115,37 @@ lack automated verify commands will not be approved. **Disable:** Set `workflow.nyquist_validation: false` in `/gsd:settings` for rapid prototyping phases where test infrastructure isn't the focus. +### Retroactive Validation (`/gsd:validate-phase`) + +For phases executed before Nyquist validation existed, or for existing codebases +with only traditional test suites, retroactively audit and fill coverage gaps: + +``` + /gsd:validate-phase N + | + +-- Detect state (VALIDATION.md exists? SUMMARY.md exists?) + | + +-- Discover: scan implementation, map requirements to tests + | + +-- Analyze gaps: which requirements lack automated verification? + | + +-- Present gap plan for approval + | + +-- Spawn auditor: generate tests, run, debug (max 3 attempts) + | + +-- Update VALIDATION.md + | + +-- COMPLIANT -> all requirements have automated checks + +-- PARTIAL -> some gaps escalated to manual-only +``` + +The auditor never modifies implementation code — only test files and +VALIDATION.md. If a test reveals an implementation bug, it's flagged as an +escalation for you to address. + +**When to use:** After executing phases that were planned before Nyquist was +enabled, or after `/gsd:audit-milestone` surfaces Nyquist compliance gaps. + ### Execution Wave Coordination ``` diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs index 5c5717ed..61d8798b 100644 --- a/get-shit-done/bin/lib/config.cjs +++ b/get-shit-done/bin/lib/config.cjs @@ -61,7 +61,7 @@ function cmdConfigEnsureSection(cwd, raw) { research: true, plan_check: true, verifier: true, - nyquist_validation: false, + nyquist_validation: true, }, parallelization: true, brave_search: hasBraveSearch, diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 420317e2..6db9f954 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -27,6 +27,7 @@ const MODEL_PROFILES = { 'gsd-verifier': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' }, 'gsd-plan-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' }, 'gsd-integration-checker': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' }, + 'gsd-nyquist-auditor': { quality: 'sonnet', balanced: 'sonnet', budget: 'haiku' }, }; // ─── Output helpers ─────────────────────────────────────────────────────────── @@ -76,7 +77,7 @@ function loadConfig(cwd) { research: true, plan_checker: true, verifier: true, - nyquist_validation: false, + nyquist_validation: true, parallelization: true, brave_search: false, }; diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 2e0d5db6..7eba9ece 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -617,6 +617,18 @@ function cmdValidateHealth(cwd, options, raw) { } } + // ─── Check 5b: Nyquist validation key presence ────────────────────────── + if (fs.existsSync(configPath)) { + try { + const configRaw = fs.readFileSync(configPath, 'utf-8'); + const configParsed = JSON.parse(configRaw); + if (configParsed.workflow && configParsed.workflow.nyquist_validation === undefined) { + addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /gsd:health --repair to add key', true); + if (!repairs.includes('addNyquistKey')) repairs.push('addNyquistKey'); + } + } catch {} + } + // ─── Check 6: Phase directory naming (NN-name format) ───────────────────── try { const entries = fs.readdirSync(phasesDir, { withFileTypes: true }); @@ -646,6 +658,24 @@ function cmdValidateHealth(cwd, options, raw) { } } catch {} + // ─── Check 7b: Nyquist VALIDATION.md consistency ──────────────────────── + try { + const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true }); + for (const e of phaseEntries) { + if (!e.isDirectory()) continue; + const phaseFiles = fs.readdirSync(path.join(phasesDir, e.name)); + const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md')); + const hasValidation = phaseFiles.some(f => f.endsWith('-VALIDATION.md')); + if (hasResearch && !hasValidation) { + const researchFile = phaseFiles.find(f => f.endsWith('-RESEARCH.md')); + const researchContent = fs.readFileSync(path.join(phasesDir, e.name, researchFile), 'utf-8'); + if (researchContent.includes('## Validation Architecture')) { + addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, 'Re-run /gsd:plan-phase with --research to regenerate'); + } + } + } + } catch {} + // ─── Check 8: Run existing consistency checks ───────────────────────────── // Inline subset of cmdValidateConsistency if (fs.existsSync(roadmapPath)) { @@ -730,6 +760,23 @@ function cmdValidateHealth(cwd, options, raw) { repairActions.push({ action: repair, success: true, path: 'STATE.md' }); break; } + case 'addNyquistKey': { + if (fs.existsSync(configPath)) { + try { + const configRaw = fs.readFileSync(configPath, 'utf-8'); + const configParsed = JSON.parse(configRaw); + if (!configParsed.workflow) configParsed.workflow = {}; + if (configParsed.workflow.nyquist_validation === undefined) { + configParsed.workflow.nyquist_validation = true; + fs.writeFileSync(configPath, JSON.stringify(configParsed, null, 2), 'utf-8'); + } + repairActions.push({ action: repair, success: true, path: 'config.json' }); + } catch (err) { + repairActions.push({ action: repair, success: false, error: err.message }); + } + } + break; + } } } catch (err) { repairActions.push({ action: repair, success: false, error: err.message }); diff --git a/get-shit-done/references/model-profiles.md b/get-shit-done/references/model-profiles.md index ad401e27..3e9b326c 100644 --- a/get-shit-done/references/model-profiles.md +++ b/get-shit-done/references/model-profiles.md @@ -17,6 +17,7 @@ Model profiles control which Claude model each GSD agent uses. This allows balan | gsd-verifier | sonnet | sonnet | haiku | | gsd-plan-checker | sonnet | sonnet | haiku | | gsd-integration-checker | sonnet | sonnet | haiku | +| gsd-nyquist-auditor | sonnet | sonnet | haiku | ## Profile Philosophy diff --git a/get-shit-done/templates/config.json b/get-shit-done/templates/config.json index 79af2a09..dde3cf78 100644 --- a/get-shit-done/templates/config.json +++ b/get-shit-done/templates/config.json @@ -6,7 +6,7 @@ "plan_check": true, "verifier": true, "auto_advance": false, - "nyquist_validation": false + "nyquist_validation": true }, "planning": { "commit_docs": true, diff --git a/get-shit-done/workflows/audit-milestone.md b/get-shit-done/workflows/audit-milestone.md index 7eee9397..65086799 100644 --- a/get-shit-done/workflows/audit-milestone.md +++ b/get-shit-done/workflows/audit-milestone.md @@ -127,6 +127,30 @@ For each REQ-ID, determine status using all three sources: **Orphan detection:** Requirements present in REQUIREMENTS.md traceability table but absent from ALL phase VERIFICATION.md files MUST be flagged as orphaned. Orphaned requirements are treated as `unsatisfied` — they were assigned but never verified by any phase. +## 5.5. Nyquist Compliance Discovery + +Skip if `workflow.nyquist_validation` is explicitly `false` (absent = enabled). + +```bash +NYQUIST_CONFIG=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config get workflow.nyquist_validation --raw 2>/dev/null) +``` + +If `false`: skip entirely. + +For each phase directory, check `*-VALIDATION.md`. If exists, parse frontmatter (`nyquist_compliant`, `wave_0_complete`). + +Classify per phase: + +| Status | Condition | +|--------|-----------| +| COMPLIANT | `nyquist_compliant: true` and all tasks green | +| PARTIAL | VALIDATION.md exists, `nyquist_compliant: false` or red/pending | +| MISSING | No VALIDATION.md | + +Add to audit YAML: `nyquist: { compliant_phases, partial_phases, missing_phases, overall }` + +Discovery only — never auto-calls `/gsd:validate-phase`. + ## 6. Aggregate into v{version}-MILESTONE-AUDIT.md Create `.planning/v{version}-v{version}-MILESTONE-AUDIT.md` with: @@ -227,6 +251,14 @@ All requirements covered. Cross-phase integration verified. E2E flows complete. {For each flow gap:} - **{flow name}:** breaks at {step} +### Nyquist Coverage + +| Phase | VALIDATION.md | Compliant | Action | +|-------|---------------|-----------|--------| +| {phase} | exists/missing | true/false/partial | `/gsd:validate-phase {N}` | + +Phases needing validation: run `/gsd:validate-phase {N}` for each flagged phase. + ─────────────────────────────────────────────────────────────── ## ▶ Next Up @@ -293,5 +325,7 @@ All requirements met. No critical blockers. Accumulated tech debt needs review. - [ ] Integration checker spawned with milestone requirement IDs - [ ] v{version}-MILESTONE-AUDIT.md created with structured requirement gap objects - [ ] FAIL gate enforced — any unsatisfied requirement forces gaps_found status +- [ ] Nyquist compliance scanned for all milestone phases (if enabled) +- [ ] Missing VALIDATION.md phases flagged with validate-phase suggestion - [ ] Results presented with actionable next steps diff --git a/get-shit-done/workflows/health.md b/get-shit-done/workflows/health.md index 3dec273b..54b7a142 100644 --- a/get-shit-done/workflows/health.md +++ b/get-shit-done/workflows/health.md @@ -136,6 +136,8 @@ Report final status. | W005 | warning | Phase directory naming mismatch | No | | W006 | warning | Phase in ROADMAP but no directory | No | | W007 | warning | Phase on disk but not in ROADMAP | No | +| W008 | warning | config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip) | Yes | +| W009 | warning | Phase has Validation Architecture in RESEARCH.md but no VALIDATION.md | No | | I001 | info | Plan without SUMMARY (may be in progress) | No | @@ -147,6 +149,7 @@ Report final status. | createConfig | Create config.json with defaults | None | | resetConfig | Delete + recreate config.json | Loses custom settings | | regenerateState | Create STATE.md from ROADMAP structure | Loses session history | +| addNyquistKey | Add workflow.nyquist_validation: true to config.json | None — matches existing default | **Not repairable (too risky):** - PROJECT.md, ROADMAP.md content diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md index 19360251..82e03ee8 100644 --- a/get-shit-done/workflows/new-project.md +++ b/get-shit-done/workflows/new-project.md @@ -177,6 +177,7 @@ Create `.planning/config.json` with mode set to "yolo": "research": true|false, "plan_check": true|false, "verifier": true|false, + "nyquist_validation": depth !== "quick", "auto_advance": true } } @@ -475,7 +476,8 @@ Create `.planning/config.json` with all settings: "workflow": { "research": true|false, "plan_check": true|false, - "verifier": true|false + "verifier": true|false, + "nyquist_validation": depth !== "quick" } } ``` diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index bfa8d136..4d3a3c51 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -219,30 +219,26 @@ Task( - **`## RESEARCH COMPLETE`:** Display confirmation, continue to step 6 - **`## RESEARCH BLOCKED`:** Display blocker, offer: 1) Provide context, 2) Skip research, 3) Abort -## 5.5. Create Validation Strategy (if Nyquist enabled) +## 5.5. Create Validation Strategy -**Skip if:** `nyquist_validation_enabled` is false from INIT JSON. - -After researcher completes, check if RESEARCH.md contains a Validation Architecture section: +MANDATORY unless `nyquist_validation_enabled` is false. ```bash grep -l "## Validation Architecture" "${PHASE_DIR}"/*-RESEARCH.md 2>/dev/null ``` **If found:** -1. Read validation template from `~/.claude/get-shit-done/templates/VALIDATION.md` -2. Write to `${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md` -3. Fill frontmatter: replace `{N}` with phase number, `{phase-slug}` with phase slug, `{date}` with current date -4. If `commit_docs` is true: +1. Read template: `~/.claude/get-shit-done/templates/VALIDATION.md` +2. Write to `${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md` (use Write tool) +3. Fill frontmatter: `{N}` → phase number, `{phase-slug}` → slug, `{date}` → current date +4. Verify: ```bash -node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit-docs "docs(phase-${PHASE}): add validation strategy" +test -f "${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md" && echo "VALIDATION_CREATED=true" || echo "VALIDATION_CREATED=false" ``` +5. If `VALIDATION_CREATED=false`: STOP — do not proceed to Step 6 +6. If `commit_docs`: `commit-docs "docs(phase-${PHASE}): add validation strategy"` -**If not found (and nyquist enabled):** Display warning: -``` -⚠ Nyquist validation enabled but researcher did not produce a Validation Architecture section. - Continuing without validation strategy. Plans may fail Dimension 8 check. -``` +**If not found:** Warn and continue — plans may fail Dimension 8. ## 6. Check Existing Plans @@ -266,6 +262,21 @@ UAT_PATH=$(printf '%s\n' "$INIT" | jq -r '.uat_path // empty') CONTEXT_PATH=$(printf '%s\n' "$INIT" | jq -r '.context_path // empty') ``` +## 7.5. Verify Nyquist Artifacts + +Skip if `nyquist_validation_enabled` is false. + +```bash +VALIDATION_EXISTS=$(ls "${PHASE_DIR}"/*-VALIDATION.md 2>/dev/null | head -1) +``` + +If missing and Nyquist enabled — ask user: +1. Re-run: `/gsd:plan-phase {PHASE} --research` +2. Disable Nyquist in config +3. Continue anyway (plans fail Dimension 8) + +Proceed to Step 8 only if user selects 2 or 3. + ## 8. Spawn gsd-planner Agent Display banner: diff --git a/get-shit-done/workflows/settings.md b/get-shit-done/workflows/settings.md index 1c78b03c..262eef49 100644 --- a/get-shit-done/workflows/settings.md +++ b/get-shit-done/workflows/settings.md @@ -28,7 +28,7 @@ Parse current values (default to `true` if not present): - `workflow.research` — spawn researcher during plan-phase - `workflow.plan_check` — spawn plan checker during plan-phase - `workflow.verifier` — spawn verifier during execute-phase -- `workflow.nyquist_validation` — validation architecture research during plan-phase +- `workflow.nyquist_validation` — validation architecture research during plan-phase (default: true if absent) - `model_profile` — which model each agent uses (default: `balanced`) - `git.branching_strategy` — branching approach (default: `"none"`) diff --git a/get-shit-done/workflows/validate-phase.md b/get-shit-done/workflows/validate-phase.md new file mode 100644 index 00000000..bc013d48 --- /dev/null +++ b/get-shit-done/workflows/validate-phase.md @@ -0,0 +1,166 @@ + +Audit Nyquist validation gaps for a completed phase. Generate missing tests. Update VALIDATION.md. + + + +@~/.claude/get-shit-done/references/ui-brand.md + + + + +## 0. Initialize + +```bash +INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init phase-op "${PHASE_ARG}") +``` + +Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`. + +```bash +AUDITOR_MODEL=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" resolve-model gsd-nyquist-auditor --raw) +NYQUIST_CFG=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config get workflow.nyquist_validation --raw) +``` + +If `NYQUIST_CFG` is `false`: exit with "Nyquist validation is disabled. Enable via /gsd:settings." + +Display banner: `GSD > VALIDATE PHASE {N}: {name}` + +## 1. Detect Input State + +```bash +VALIDATION_FILE=$(ls "${PHASE_DIR}"/*-VALIDATION.md 2>/dev/null | head -1) +SUMMARY_FILES=$(ls "${PHASE_DIR}"/*-SUMMARY.md 2>/dev/null) +``` + +- **State A** (`VALIDATION_FILE` non-empty): Audit existing +- **State B** (`VALIDATION_FILE` empty, `SUMMARY_FILES` non-empty): Reconstruct from artifacts +- **State C** (`SUMMARY_FILES` empty): Exit — "Phase {N} not executed. Run /gsd:execute-phase {N} first." + +## 2. Discovery + +### 2a. Read Phase Artifacts + +Read all PLAN and SUMMARY files. Extract: task lists, requirement IDs, key-files changed, verify blocks. + +### 2b. Build Requirement-to-Task Map + +Per task: `{ task_id, plan_id, wave, requirement_ids, has_automated_command }` + +### 2c. Detect Test Infrastructure + +State A: Parse from existing VALIDATION.md Test Infrastructure table. +State B: Filesystem scan: + +```bash +find . -name "pytest.ini" -o -name "jest.config.*" -o -name "vitest.config.*" -o -name "pyproject.toml" 2>/dev/null | head -10 +find . \( -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" \) -not -path "*/node_modules/*" 2>/dev/null | head -40 +``` + +### 2d. Cross-Reference + +Match each requirement to existing tests by filename, imports, test descriptions. Record: requirement → test_file → status. + +## 3. Gap Analysis + +Classify each requirement: + +| Status | Criteria | +|--------|----------| +| COVERED | Test exists, targets behavior, runs green | +| PARTIAL | Test exists, failing or incomplete | +| MISSING | No test found | + +Build: `{ task_id, requirement, gap_type, suggested_test_path, suggested_command }` + +No gaps → skip to Step 6, set `nyquist_compliant: true`. + +## 4. Present Gap Plan + +Call AskUserQuestion with gap table and options: +1. "Fix all gaps" → Step 5 +2. "Skip — mark manual-only" → add to Manual-Only, Step 6 +3. "Cancel" → exit + +## 5. Spawn gsd-nyquist-auditor + +``` +Task( + prompt="Read ~/.claude/agents/gsd-nyquist-auditor.md for instructions.\n\n" + + "{PLAN, SUMMARY, impl files, VALIDATION.md}" + + "{gap list}" + + "{framework, config, commands}" + + "Never modify impl files. Max 3 debug iterations. Escalate impl bugs.", + subagent_type="gsd-nyquist-auditor", + model="{AUDITOR_MODEL}", + description="Fill validation gaps for Phase {N}" +) +``` + +Handle return: +- `## GAPS FILLED` → record tests + map updates, Step 6 +- `## PARTIAL` → record resolved, move escalated to manual-only, Step 6 +- `## ESCALATE` → move all to manual-only, Step 6 + +## 6. Generate/Update VALIDATION.md + +**State B (create):** +1. Read template from `~/.claude/get-shit-done/templates/VALIDATION.md` +2. Fill: frontmatter, Test Infrastructure, Per-Task Map, Manual-Only, Sign-Off +3. Write to `${PHASE_DIR}/${PADDED_PHASE}-VALIDATION.md` + +**State A (update):** +1. Update Per-Task Map statuses, add escalated to Manual-Only, update frontmatter +2. Append audit trail: + +```markdown +## Validation Audit {date} +| Metric | Count | +|--------|-------| +| Gaps found | {N} | +| Resolved | {M} | +| Escalated | {K} | +``` + +## 7. Commit + +```bash +git add {test_files} +git commit -m "test(phase-${PHASE}): add Nyquist validation tests" + +node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit-docs "docs(phase-${PHASE}): add/update validation strategy" +``` + +## 8. Results + Routing + +**Compliant:** +``` +GSD > PHASE {N} IS NYQUIST-COMPLIANT +All requirements have automated verification. +▶ Next: /gsd:audit-milestone +``` + +**Partial:** +``` +GSD > PHASE {N} VALIDATED (PARTIAL) +{M} automated, {K} manual-only. +▶ Retry: /gsd:validate-phase {N} +``` + +Display `/clear` reminder. + + + + +- [ ] Nyquist config checked (exit if disabled) +- [ ] Input state detected (A/B/C) +- [ ] State C exits cleanly +- [ ] PLAN/SUMMARY files read, requirement map built +- [ ] Test infrastructure detected +- [ ] Gaps classified (COVERED/PARTIAL/MISSING) +- [ ] User gate with gap table +- [ ] Auditor spawned with complete context +- [ ] All three return formats handled +- [ ] VALIDATION.md created or updated +- [ ] Test files committed separately +- [ ] Results with routing presented + diff --git a/tests/core.test.cjs b/tests/core.test.cjs index 9f51d7d8..f5a5ff61 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -60,6 +60,7 @@ describe('loadConfig', () => { assert.strictEqual(config.plan_checker, true); assert.strictEqual(config.brave_search, false); assert.strictEqual(config.parallelization, true); + assert.strictEqual(config.nyquist_validation, true); }); test('reads model_profile from config.json', () => { diff --git a/tests/verify-health.test.cjs b/tests/verify-health.test.cjs index 3bf48b68..59309447 100644 --- a/tests/verify-health.test.cjs +++ b/tests/verify-health.test.cjs @@ -347,6 +347,103 @@ describe('validate health command', () => { ); }); + // ─── Check 5b: Nyquist validation key presence (W008) ───────────────────── + + test('detects W008 when workflow.nyquist_validation absent from config', () => { + writeMinimalProjectMd(tmpDir); + writeMinimalRoadmap(tmpDir, ['1']); + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 in progress.\n'); + // Config with workflow section but WITHOUT nyquist_validation key + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ model_profile: 'balanced', workflow: { research: true } }, null, 2) + ); + fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true }); + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + output.warnings.some(w => w.code === 'W008'), + `Expected W008 in warnings: ${JSON.stringify(output.warnings)}` + ); + }); + + test('does not emit W008 when nyquist_validation is explicitly set', () => { + writeMinimalProjectMd(tmpDir); + writeMinimalRoadmap(tmpDir, ['1']); + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 in progress.\n'); + // Config with workflow.nyquist_validation explicitly set + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ model_profile: 'balanced', workflow: { research: true, nyquist_validation: true } }, null, 2) + ); + fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true }); + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + !output.warnings.some(w => w.code === 'W008'), + `Should not have W008: ${JSON.stringify(output.warnings)}` + ); + }); + + // ─── Check 7b: Nyquist VALIDATION.md consistency (W009) ────────────────── + + test('detects W009 when RESEARCH.md has Validation Architecture but no VALIDATION.md', () => { + writeMinimalProjectMd(tmpDir); + writeMinimalRoadmap(tmpDir, ['1']); + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 in progress.\n'); + writeValidConfigJson(tmpDir); + // Create phase dir with RESEARCH.md containing Validation Architecture + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-setup'); + fs.mkdirSync(phaseDir, { recursive: true }); + fs.writeFileSync( + path.join(phaseDir, '01-RESEARCH.md'), + '# Research\n\n## Validation Architecture\n\nSome validation content.\n' + ); + // No VALIDATION.md + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + output.warnings.some(w => w.code === 'W009'), + `Expected W009 in warnings: ${JSON.stringify(output.warnings)}` + ); + }); + + test('does not emit W009 when VALIDATION.md exists alongside RESEARCH.md', () => { + writeMinimalProjectMd(tmpDir); + writeMinimalRoadmap(tmpDir, ['1']); + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 in progress.\n'); + writeValidConfigJson(tmpDir); + // Create phase dir with both RESEARCH.md and VALIDATION.md + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-setup'); + fs.mkdirSync(phaseDir, { recursive: true }); + fs.writeFileSync( + path.join(phaseDir, '01-RESEARCH.md'), + '# Research\n\n## Validation Architecture\n\nSome validation content.\n' + ); + fs.writeFileSync( + path.join(phaseDir, '01-VALIDATION.md'), + '# Validation\n\nValidation content.\n' + ); + + const result = runGsdTools('validate health', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + !output.warnings.some(w => w.code === 'W009'), + `Should not have W009: ${JSON.stringify(output.warnings)}` + ); + }); + // ─── Overall status ──────────────────────────────────────────────────────── test("returns 'healthy' when all checks pass", () => { @@ -509,6 +606,32 @@ describe('validate health --repair command', () => { assert.ok(backupContent.includes('Phase 99'), 'backup should contain the original STATE.md content'); }); + test('adds nyquist_validation key to config.json via addNyquistKey repair', () => { + writeMinimalStateMd(tmpDir, '# Session State\n\nPhase 1 in progress.\n'); + // Config with workflow section but missing nyquist_validation + const configPath = path.join(tmpDir, '.planning', 'config.json'); + fs.writeFileSync( + configPath, + JSON.stringify({ model_profile: 'balanced', workflow: { research: true } }, null, 2) + ); + + const result = runGsdTools('validate health --repair', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.ok( + Array.isArray(output.repairs_performed), + `Expected repairs_performed array: ${JSON.stringify(output)}` + ); + const addKeyAction = output.repairs_performed.find(r => r.action === 'addNyquistKey'); + assert.ok(addKeyAction, `Expected addNyquistKey action: ${JSON.stringify(output.repairs_performed)}`); + assert.strictEqual(addKeyAction.success, true, 'addNyquistKey should succeed'); + + // Read config.json and verify workflow.nyquist_validation is true + const diskConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + assert.strictEqual(diskConfig.workflow.nyquist_validation, true, 'nyquist_validation should be true'); + }); + test('reports repairable_count correctly', () => { // No config.json (W003, repairable=true) and no STATE.md (E004, repairable=true) const configPath = path.join(tmpDir, '.planning', 'config.json');