diff --git a/.changeset/fix-3072-findings-probe-assertions.md b/.changeset/fix-3072-findings-probe-assertions.md new file mode 100644 index 000000000..a959c2a59 --- /dev/null +++ b/.changeset/fix-3072-findings-probe-assertions.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3119 +--- +**Optional findings probe guard checks now use structured parsing** — regression tests now parse fenced bash blocks and validate sketch/spike findings probes as structured command records, ensuring non-fatal `|| true` guards are enforced without raw source grep assertions. \ No newline at end of file diff --git a/get-shit-done/workflows/discuss-phase.md b/get-shit-done/workflows/discuss-phase.md index 1028d3ec1..d1bf050b2 100644 --- a/get-shit-done/workflows/discuss-phase.md +++ b/get-shit-done/workflows/discuss-phase.md @@ -244,8 +244,8 @@ For each CONTEXT.md read: extract `` (locked preferences), `/dev/null | head -1) -SKETCH_FINDINGS=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1) +SPIKE_FINDINGS=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true) +SKETCH_FINDINGS=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true) RAW_SPIKES=$(ls .planning/spikes/MANIFEST.md 2>/dev/null) RAW_SKETCHES=$(ls .planning/sketches/MANIFEST.md 2>/dev/null) ``` diff --git a/get-shit-done/workflows/new-project.md b/get-shit-done/workflows/new-project.md index f00d44efd..069041f74 100644 --- a/get-shit-done/workflows/new-project.md +++ b/get-shit-done/workflows/new-project.md @@ -253,10 +253,10 @@ Check for existing spike and sketch work that should inform project setup: ```bash # Check for spike findings skill (project-local) -SPIKE_SKILL=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1) +SPIKE_SKILL=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true) # Check for sketch findings skill (project-local) -SKETCH_SKILL=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1) +SKETCH_SKILL=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true) # Check for raw spikes/sketches in .planning/ HAS_SPIKES=$(ls .planning/spikes/MANIFEST.md 2>/dev/null) diff --git a/get-shit-done/workflows/plan-phase.md b/get-shit-done/workflows/plan-phase.md index 74a79af68..21a7ffa8d 100644 --- a/get-shit-done/workflows/plan-phase.md +++ b/get-shit-done/workflows/plan-phase.md @@ -668,8 +668,8 @@ REVIEWS_PATH=$(_gsd_field "$INIT" reviews_path) PATTERNS_PATH=$(_gsd_field "$INIT" patterns_path) # Detect spike/sketch findings skills (project-local) -SPIKE_FINDINGS_PATH=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1) -SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1) +SPIKE_FINDINGS_PATH=$(ls ./.claude/skills/spike-findings-*/SKILL.md 2>/dev/null | head -1 || true) +SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true) ``` ## 7.5. Verify Nyquist Artifacts diff --git a/get-shit-done/workflows/ui-phase.md b/get-shit-done/workflows/ui-phase.md index c404e1aa7..affa168e3 100644 --- a/get-shit-done/workflows/ui-phase.md +++ b/get-shit-done/workflows/ui-phase.md @@ -31,7 +31,7 @@ Parse JSON for: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded Detect sketch findings: ```bash -SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1) +SKETCH_FINDINGS_PATH=$(ls ./.claude/skills/sketch-findings-*/SKILL.md 2>/dev/null | head -1 || true) ``` Resolve UI agent models: diff --git a/tests/bug-3072-optional-sketch-findings-guard.test.cjs b/tests/bug-3072-optional-sketch-findings-guard.test.cjs new file mode 100644 index 000000000..c22e603d6 --- /dev/null +++ b/tests/bug-3072-optional-sketch-findings-guard.test.cjs @@ -0,0 +1,62 @@ +'use strict'; + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const ROOT = path.join(__dirname, '..', 'get-shit-done', 'workflows'); + +function read(rel) { + return fs.readFileSync(path.join(ROOT, rel), 'utf8'); +} + +function extractFindingsProbesFromBashBlocks(markdown) { + const probes = []; + const fenceRe = /```bash\n([\s\S]*?)```/g; + let fenceMatch; + + while ((fenceMatch = fenceRe.exec(markdown)) !== null) { + const block = fenceMatch[1]; + const baseLine = markdown.slice(0, fenceMatch.index).split('\n').length; + const lines = block.split('\n'); + + lines.forEach((line, idx) => { + if (!line.includes('.claude/skills/')) return; + const kind = line.includes('sketch-findings-*/SKILL.md') + ? 'sketch' + : line.includes('spike-findings-*/SKILL.md') + ? 'spike' + : null; + if (!kind) return; + + probes.push({ + lineNumber: baseLine + idx, + commandText: line.trim(), + kind, + hasNonFatalGuard: /\|\|\s*true/.test(line), + }); + }); + } + + return probes; +} + +describe('bug #3072: optional sketch/spike findings probes are non-fatal', () => { + test('all sketch/spike findings SKILL.md ls probes include || true', () => { + const files = ['ui-phase.md', 'plan-phase.md', 'discuss-phase.md', 'new-project.md']; + const offenders = []; + + for (const file of files) { + const content = read(file); + const probes = extractFindingsProbesFromBashBlocks(content); + for (const probe of probes) { + if (!probe.hasNonFatalGuard) { + offenders.push(`${file}:${probe.lineNumber} ${probe.commandText}`); + } + } + } + + assert.deepStrictEqual(offenders, [], `missing non-fatal guard on optional findings probe:\n${offenders.join('\n')}`); + }); +});