From e30f9370d38573dc8031bd1d4224fdeec84518ff Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 10:27:55 -0400 Subject: [PATCH 1/8] test(#2133): execute fast.md log_to_state guard against both schemas Replaces the removed prose-regex test (bug-3805-*) that let the off-by-one ship green. Extracts the actual bash block deployed in fast.md's log_to_state step and EXECUTES it against real STATE.md fixtures, asserting on the filesystem result: a row is appended with a cell count matching the header for both the 5-column (non-validate) and 6-column (validate) schemas, and an unrecognized schema still skips with a warning. --- ...-2133-fast-md-log-to-state-schema.test.cjs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 tests/fix-2133-fast-md-log-to-state-schema.test.cjs diff --git a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs new file mode 100644 index 000000000..92452e841 --- /dev/null +++ b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs @@ -0,0 +1,177 @@ +/** + * #2133 — fast.md log_to_state schema gate is unreachable. + * + * PR #85 added a column-count guard to fast.md's log_to_state step that used + * `awk -F'|' '{print NF-1}'`. A markdown table header has both a leading and a + * trailing pipe, so NF counts (real columns + 2) and NF-1 is always one too + * high. The `-eq 5` test could therefore never hold for the 5-column header + * quick.md writes, so /gsd-fast has never appended a Quick Task row since #85. + * + * This test does what the removed prose-regex test (bug-3805-*) did not: it + * EXTRACTS the actual bash block deployed in fast.md and EXECUTES it against + * real STATE.md fixtures, asserting on the filesystem result (row appended, + * cell count aligned with header). It fails on the NF-1 bug and passes once + * the count uses NF-2 and both 5/6-column schemas are accepted. + */ + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const os = require('node:os'); +const { execFileSync } = require('node:child_process'); +const { createTempProject, cleanup } = require('./helpers.cjs'); + +const FAST_MD = path.join(__dirname, '..', 'gsd-core', 'workflows', 'fast.md'); + +const HEADER_5COL = '| # | Description | Date | Commit | Directory |'; +const SEP_5COL = '|---|-------------|------|--------|-----------|'; +const HEADER_6COL = '| # | Description | Date | Commit | Status | Directory |'; +const SEP_6COL = '|---|-------------|------|--------|--------|-----------|'; + +/** + * Extract the ```bash block embedded in fast.md's . + * This is the exact program the workflow runs — executing it is a behavioral + * test of the deployed product, not a source-grep over its prose. + */ +function extractLogToStateBash() { + const content = fs.readFileSync(FAST_MD, 'utf8'); + const stepTag = ''; + const stepStart = content.indexOf(stepTag); + assert.notEqual(stepStart, -1, 'fast.md must contain a log_to_state step'); + const stepEnd = content.indexOf('', stepStart); + assert.notEqual(stepEnd, -1, 'log_to_state step must close'); + const step = content.slice(stepStart, stepEnd); + const fenceStart = step.indexOf('```bash'); + assert.notEqual(fenceStart, -1, 'log_to_state step must contain a bash block'); + const codeStart = step.indexOf('\n', fenceStart) + 1; + const fenceEnd = step.indexOf('\n```', codeStart); + assert.notEqual(fenceEnd, -1, 'log_to_state bash block must close'); + return step.slice(codeStart, fenceEnd); +} + +function makeStateMd(headerLine, separatorLine, existingRows) { + return [ + '# Project State', + '', + '### Blockers/Concerns', + '', + 'None.', + '', + '### Quick Tasks Completed', + '', + headerLine, + separatorLine, + ...existingRows, + '', + ].join('\n'); +} + +/** Count `|` chars on a line — the invariant cell-count signal. */ +function pipeCount(line) { + return (line.match(/\|/g) || []).length; +} + +/** Data rows = lines starting with `|` that are not the separator or header. */ +function dataRows(content) { + return content.split(/\r?\n/).filter((l) => { + if (!l.startsWith('|')) return false; + if (/^[|][-: |]*[|]$/.test(l)) return false; // separator + if (/Description/.test(l)) return false; // header + return true; + }); +} + +describe('#2133 fast.md log_to_state schema gate', () => { + const bashBlock = extractLogToStateBash(); + const TASK_DESC = 'sample inline fix'; + + /** + * Run the extracted log_to_state bash against a temp project's STATE.md and + * return the post-run file content + captured stdout. + */ + function runAgainst(headerLine, separatorLine, existingRows) { + const tmpDir = createTempProject('fix-2133-'); + const statePath = path.join(tmpDir, '.planning', 'STATE.md'); + fs.writeFileSync(statePath, makeStateMd(headerLine, separatorLine, existingRows)); + let stdout = ''; + try { + stdout = execFileSync('bash', ['-c', bashBlock], { + cwd: tmpDir, + env: { ...process.env, TASK: TASK_DESC }, + encoding: 'utf8', + }); + } finally { + // cleanup deferred to caller via t.after where bound + } + const after = fs.readFileSync(statePath, 'utf8'); + return { tmpDir, stdout, after }; + } + + test('appends a 5-cell row to the 5-column (non-validate) schema (#27 stays fixed)', (t) => { + const before = makeStateMd(HEADER_5COL, SEP_5COL, ['| 1 | earlier task | 2026-07-01 | deadbee | — |']); + const { tmpDir, after } = runAgainst(HEADER_5COL, SEP_5COL, ['| 1 | earlier task | 2026-07-01 | deadbee | — |']); + t.after(() => cleanup(tmpDir)); + + const rowsBefore = dataRows(before).length; + const rowsAfter = dataRows(after).length; + assert.equal(rowsAfter - rowsBefore, 1, 'exactly one row must be appended'); + + const appended = dataRows(after).slice(-1)[0]; + assert.equal(pipeCount(appended), pipeCount(HEADER_5COL), + 'appended row pipe-count must match the 5-column header (no malformed row)'); + assert.ok(appended.includes(TASK_DESC), 'appended row must carry the task description'); + }); + + test('appends a 6-cell row to the 6-column (validate, with Status) schema', (t) => { + const before = makeStateMd(HEADER_6COL, SEP_6COL, []); + const { tmpDir, after } = runAgainst(HEADER_6COL, SEP_6COL, []); + t.after(() => cleanup(tmpDir)); + + const rowsBefore = dataRows(before).length; + const rowsAfter = dataRows(after).length; + assert.equal(rowsAfter - rowsBefore, 1, 'exactly one row must be appended to the 6-column table'); + + const appended = dataRows(after).slice(-1)[0]; + assert.equal(pipeCount(appended), pipeCount(HEADER_6COL), + 'appended row pipe-count must match the 6-column header (cell count aligned with header)'); + assert.ok(appended.includes(TASK_DESC), 'appended row must carry the task description'); + }); + + test('column count is derived with NF-2, not NF-1 (the off-by-one root cause)', () => { + // The deployed bash must compute the real column count. A 5-column header + // split on '|' yields NF=7; the correct real-column formula is NF-2=5. + assert.match(bashBlock, /NF-2/, 'column count must use NF-2 (real columns), not NF-1'); + assert.doesNotMatch(bashBlock, /NF-1/, 'the off-by-one NF-1 formula must be gone'); + }); + + test('unrecognized schema still skips with a warning (safety guard intact)', (t) => { + // A 3-column table quick.md never writes must NOT receive a row. + const weirdHeader = '| Alpha | Beta | Gamma |'; + const weirdSep = '|-------|------|-------|'; + const before = makeStateMd(weirdHeader, weirdSep, []); + const { tmpDir, stdout, after } = runAgainst(weirdHeader, weirdSep, []); + t.after(() => cleanup(tmpDir)); + + assert.equal(dataRows(after).length, dataRows(before).length, + 'no row may be appended for an unrecognized schema'); + assert.ok(/unrecognized schema/i.test(stdout), + 'the unrecognized-schema warning must be emitted'); + }); + + test('no Quick Tasks table → silent no-op', (t) => { + const tmpDir = createTempProject('fix-2133-noop-'); + t.after(() => cleanup(tmpDir)); + const statePath = path.join(tmpDir, '.planning', 'STATE.md'); + const before = '# Project State\n\n### Blockers/Concerns\n\nNone.\n'; + fs.writeFileSync(statePath, before); + const stdout = execFileSync('bash', ['-c', bashBlock], { + cwd: tmpDir, + env: { ...process.env, TASK: TASK_DESC }, + encoding: 'utf8', + }); + const after = fs.readFileSync(statePath, 'utf8'); + assert.equal(after, before, 'STATE.md must be untouched when no Quick Tasks table exists'); + assert.equal(stdout, '', 'no output when there is no table to update'); + }); +}); From 78b9b04f1d274050c4f87efa6f7d00072c50ebde Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 10:27:55 -0400 Subject: [PATCH 2/8] fix(#2133): correct fast.md log_to_state column-count gate (NF-2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The guard used `awk -F'|' '{print NF-1}'` but a markdown header has a leading and trailing pipe, so NF counts (real columns + 2); NF-1 is always one too high. The `-eq 5` test was therefore unsatisfiable for the very 5-column header quick.md writes, so /gsd-fast has never appended a Quick Task row since PR #85 (regression closing #27). - Count real columns with NF-2 (5 for the 5-col header, 6 for the 6-col). - Accept 5 OR 6 columns (quick.md Step 7b writes both shapes). - Select the appended row template by the detected count so its cell count always matches the header — keeps #27 fixed for the validate-mode table. Closes #2133 --- gsd-core/workflows/fast.md | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/gsd-core/workflows/fast.md b/gsd-core/workflows/fast.md index 2ebc9d9fa..6232580ef 100644 --- a/gsd-core/workflows/fast.md +++ b/gsd-core/workflows/fast.md @@ -72,22 +72,30 @@ malformed row. # Detect whether STATE.md has a Quick Tasks Completed table if grep -q "Quick Tasks Completed" .planning/STATE.md 2>/dev/null; then # Read the table header line to determine the column schema. - # quick.md Step 7 creates a 5-column table: - # | # | Description | Date | Commit | Directory | - # Count pipe characters in the header to determine column count. + # quick.md Step 7b writes two shapes: + # 5-column (non-validate): | # | Description | Date | Commit | Directory | + # 6-column (validate): | # | Description | Date | Commit | Status | Directory | HEADER_LINE=$(grep -A2 "Quick Tasks Completed" .planning/STATE.md 2>/dev/null | grep "^|" | head -1) - # Count columns: number of | separators minus 1 gives column count - COL_COUNT=$(echo "$HEADER_LINE" | awk -F'|' '{print NF-1}') + # Count REAL columns: a markdown header has a leading and a trailing pipe, so + # awk's NF counts (real columns + 2). NF-2 yields the real column count. + # (NF-1 was the off-by-one root cause of #2133: it returned the pipe count, + # making the `-eq 5` test unsatisfiable for the very header quick.md writes.) + COL_COUNT=$(echo "$HEADER_LINE" | awk -F'|' '{print NF-2}') + # Next row number + latest commit hash are schema-independent. + NEXT_NUM=$(awk '/Quick Tasks Completed/{found=1} found && /^\|/ && !/^[|][-: |]*[|]$/ && !/Description/{count++} END{print count+1}' .planning/STATE.md 2>/dev/null || echo "1") + COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "—") + + # Select the appended row's template by the detected column count so its cell + # count always matches the header (prevents the malformed-row symptom of #27). if [ "$COL_COUNT" -eq 5 ] && echo "$HEADER_LINE" | grep -qi "Description" && echo "$HEADER_LINE" | grep -qi "Commit" && echo "$HEADER_LINE" | grep -qi "Directory"; then - # 5-column schema from quick.md Step 7: | # | Description | Date | Commit | Directory | - # Determine the next row number by counting existing data rows (non-separator, non-header). - NEXT_NUM=$(awk '/Quick Tasks Completed/{found=1} found && /^\|/ && !/^[|][-: |]*[|]$/ && !/Description/{count++} END{print count+1}' .planning/STATE.md 2>/dev/null || echo "1") - # Get the latest commit hash (short) - COMMIT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "—") + # 5-column schema from quick.md Step 7b (non-validate). echo "| $NEXT_NUM | $TASK | $(date +%Y-%m-%d) | $COMMIT_HASH | — |" >> .planning/STATE.md + elif [ "$COL_COUNT" -eq 6 ] && echo "$HEADER_LINE" | grep -qi "Status" && echo "$HEADER_LINE" | grep -qi "Directory"; then + # 6-column schema from quick.md Step 7b (validate, with Status). + echo "| $NEXT_NUM | $TASK | $(date +%Y-%m-%d) | $COMMIT_HASH | — | — |" >> .planning/STATE.md else - # Unrecognized table schema — skip to avoid appending a malformed row. + # Unrecognized table schema — skip to avoid appending a malformed row (#27). echo "⚠ fast.md log_to_state: Quick Tasks Completed table has unrecognized schema (${COL_COUNT} columns); skipping STATE.md update." fi fi From c59a4a7abbc92dd8170a03c183f3eff29d981c0c Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 10:28:40 -0400 Subject: [PATCH 3/8] docs(#2133): add changeset fragment for fast.md log_to_state fix --- .changeset/happy-seals-roam.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/happy-seals-roam.md diff --git a/.changeset/happy-seals-roam.md b/.changeset/happy-seals-roam.md new file mode 100644 index 000000000..38b75389e --- /dev/null +++ b/.changeset/happy-seals-roam.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 0 +--- +**`/gsd-fast` now appends Quick Task rows to STATE.md again** — the log_to_state column-count guard used an off-by-one awk formula (`NF-1`) that was always one too high, so the schema gate rejected the very table quick.md creates and silently skipped the STATE.md update. Also now supports the 6-column validate-mode table. (#2133) From dca8bd9daacc8a2bf190df88191525135cb424ca Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 10:48:43 -0400 Subject: [PATCH 4/8] test(#2133): scope NF-2 assertion to the executable awk formula The first draft banned the literal /NF-1/ anywhere in the block, but the explanatory comment legitimately references 'NF-1' to document the off-by-one root cause. Match the COL_COUNT awk assignment specifically so the structural guard targets the executable formula, not the prose. --- tests/fix-2133-fast-md-log-to-state-schema.test.cjs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs index 92452e841..c2433b855 100644 --- a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs +++ b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs @@ -138,11 +138,17 @@ describe('#2133 fast.md log_to_state schema gate', () => { assert.ok(appended.includes(TASK_DESC), 'appended row must carry the task description'); }); - test('column count is derived with NF-2, not NF-1 (the off-by-one root cause)', () => { + test('column count awk uses NF-2 (real columns — the off-by-one root cause)', () => { // The deployed bash must compute the real column count. A 5-column header // split on '|' yields NF=7; the correct real-column formula is NF-2=5. - assert.match(bashBlock, /NF-2/, 'column count must use NF-2 (real columns), not NF-1'); - assert.doesNotMatch(bashBlock, /NF-1/, 'the off-by-one NF-1 formula must be gone'); + // (NF-1 was the off-by-one bug: it returned 6, making `-eq 5` unsatisfiable.) + // Match the executable COL_COUNT assignment specifically — the explanatory + // comment may still reference "NF-1" to document the history. + assert.match( + bashBlock, + /COL_COUNT=\$\(.+awk -F'\|' '\{print NF-2\}'\)/, + 'COL_COUNT must be derived via awk NF-2 (NF-1 was the off-by-one bug)' + ); }); test('unrecognized schema still skips with a warning (safety guard intact)', (t) => { From c6b4304bab8ae7db6bb76a8fd08a225535f2e265 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 10:48:43 -0400 Subject: [PATCH 5/8] test(#2133): regenerate golden + workflow-size baselines for fast.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fast.md log_to_state fix changes an installed workflow file, so the per- runtime golden-install-parity fixtures (18 runtimes) and the workflow-size baseline are recaptured. Diff is exactly one entry per fixture (the fast.md hash) and one baseline byte count — no spurious drift. --- tests/fixtures/golden-install-parity/antigravity.json | 2 +- tests/fixtures/golden-install-parity/augment.json | 2 +- tests/fixtures/golden-install-parity/claude-local.json | 2 +- tests/fixtures/golden-install-parity/claude.json | 2 +- tests/fixtures/golden-install-parity/cline.json | 2 +- tests/fixtures/golden-install-parity/codebuddy.json | 2 +- tests/fixtures/golden-install-parity/codex.json | 2 +- tests/fixtures/golden-install-parity/copilot.json | 2 +- tests/fixtures/golden-install-parity/cursor.json | 2 +- tests/fixtures/golden-install-parity/hermes.json | 2 +- tests/fixtures/golden-install-parity/kilo.json | 2 +- tests/fixtures/golden-install-parity/kimi.json | 2 +- tests/fixtures/golden-install-parity/opencode.json | 2 +- tests/fixtures/golden-install-parity/pi.json | 2 +- tests/fixtures/golden-install-parity/qwen.json | 2 +- tests/fixtures/golden-install-parity/trae.json | 2 +- tests/fixtures/golden-install-parity/windsurf.json | 2 +- tests/fixtures/golden-install-parity/zcode.json | 2 +- tests/workflow-size-baseline.json | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index d8ae76854..f8434131d 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -242,7 +242,7 @@ "gsd-core/workflows/execute-plan.md": "907af77eafc3d97b", "gsd-core/workflows/explore.md": "934c00f9f216dbdb", "gsd-core/workflows/extract-learnings.md": "167ea7f0e23bf496", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "b64f0309b8c3fde1", "gsd-core/workflows/graduation.md": "a9d8f15ba81a993f", "gsd-core/workflows/health.md": "1ac4d567ee95acfb", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 7c3f5e54d..0f84c588e 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -313,7 +313,7 @@ "gsd-core/workflows/execute-plan.md": "2c412310dce31a0b", "gsd-core/workflows/explore.md": "6c04f2e658d93261", "gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4", - "gsd-core/workflows/fast.md": "94136fb570d20a9d", + "gsd-core/workflows/fast.md": "8878ec034b401f36", "gsd-core/workflows/forensics.md": "0d500a3f5ab26913", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "107e3c72e76d9535", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index 7a1ffb44a..04a413952 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -312,7 +312,7 @@ "gsd-core/workflows/execute-plan.md": "f17623fd47e795dd", "gsd-core/workflows/explore.md": "95e463d4bdd6dadd", "gsd-core/workflows/extract-learnings.md": "fd75072c339b58bd", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "857d7b064f4cca21", "gsd-core/workflows/graduation.md": "ecf8da93e094fd2e", "gsd-core/workflows/health.md": "551e63aa6f3df711", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 1806cb45a..08fe97374 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -241,7 +241,7 @@ "gsd-core/workflows/execute-plan.md": "cce1a33fe9a0a32d", "gsd-core/workflows/explore.md": "b9eea1bac358c9ce", "gsd-core/workflows/extract-learnings.md": "d8177b0c13b7e5ee", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "c01da0178fb97b21", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "f934b1f1e9f3ae72", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 88c80049f..8350d5208 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -245,7 +245,7 @@ "gsd-core/workflows/execute-plan.md": "0c5551f99ee0a017", "gsd-core/workflows/explore.md": "e83af8ceae314cf9", "gsd-core/workflows/extract-learnings.md": "6f39375b7dc775f9", - "gsd-core/workflows/fast.md": "94136fb570d20a9d", + "gsd-core/workflows/fast.md": "8878ec034b401f36", "gsd-core/workflows/forensics.md": "9fc65a8eed5d8bfc", "gsd-core/workflows/graduation.md": "16fedecda36769eb", "gsd-core/workflows/health.md": "788fad84fa42eb9f", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index d76decfea..579fa9863 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -313,7 +313,7 @@ "gsd-core/workflows/execute-plan.md": "933d10547116794a", "gsd-core/workflows/explore.md": "6c04f2e658d93261", "gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4", - "gsd-core/workflows/fast.md": "94136fb570d20a9d", + "gsd-core/workflows/fast.md": "8878ec034b401f36", "gsd-core/workflows/forensics.md": "0d500a3f5ab26913", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "107e3c72e76d9535", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index de5c1cfb8..cc083c491 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -348,7 +348,7 @@ "gsd-core/workflows/execute-plan.md": "ac1f1d9ada00a91e", "gsd-core/workflows/explore.md": "2ef10d17c8864a04", "gsd-core/workflows/extract-learnings.md": "f716aa03fcb5f8da", - "gsd-core/workflows/fast.md": "13252d545947354d", + "gsd-core/workflows/fast.md": "bbf1f8e219ec9031", "gsd-core/workflows/forensics.md": "2e8a01b5b44e65f3", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "5b835fc606fd3e9b", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 4bc90d322..a8439206f 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -243,7 +243,7 @@ "gsd-core/workflows/execute-plan.md": "c5e9dae726db15cc", "gsd-core/workflows/explore.md": "5fd91a8510e1114b", "gsd-core/workflows/extract-learnings.md": "f34d0b1927545b18", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "459644dce26ee2ef", "gsd-core/workflows/graduation.md": "f013efc29096faf0", "gsd-core/workflows/health.md": "f343fd32a0e398e4", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 8baba5047..74482692a 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -313,7 +313,7 @@ "gsd-core/workflows/execute-plan.md": "83dc1bf7f73735c0", "gsd-core/workflows/explore.md": "b9eea1bac358c9ce", "gsd-core/workflows/extract-learnings.md": "d8177b0c13b7e5ee", - "gsd-core/workflows/fast.md": "0162075e44072447", + "gsd-core/workflows/fast.md": "91f126c4ef24217e", "gsd-core/workflows/forensics.md": "a65f817d4a515291", "gsd-core/workflows/graduation.md": "53a4a6fa3b4e6613", "gsd-core/workflows/health.md": "e4d770484b5e7496", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 24922ae2e..85456faf3 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -242,7 +242,7 @@ "gsd-core/workflows/execute-plan.md": "4dbe9b6f0c976245", "gsd-core/workflows/explore.md": "48770d68e8b9c132", "gsd-core/workflows/extract-learnings.md": "e9e167c718949c0b", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "91961b811917c5c4", "gsd-core/workflows/graduation.md": "d1fd52bbe41dcf34", "gsd-core/workflows/health.md": "c622a5ad0d347d30", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index fea494096..f5415312f 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -313,7 +313,7 @@ "gsd-core/workflows/execute-plan.md": "8dc89b35582407f7", "gsd-core/workflows/explore.md": "14242d36d4822df6", "gsd-core/workflows/extract-learnings.md": "d8177b0c13b7e5ee", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "c01da0178fb97b21", "gsd-core/workflows/graduation.md": "5cc8638dabcbfd40", "gsd-core/workflows/health.md": "064b3668d5d9569a", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index b0eeb106a..588971b2a 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -306,7 +306,7 @@ "gsd-core/workflows/execute-plan.md": "c8502b7475d797a7", "gsd-core/workflows/explore.md": "6c04f2e658d93261", "gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4", - "gsd-core/workflows/fast.md": "94136fb570d20a9d", + "gsd-core/workflows/fast.md": "8878ec034b401f36", "gsd-core/workflows/forensics.md": "0d500a3f5ab26913", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "107e3c72e76d9535", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index ead5eb11b..14fdb2be1 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -313,7 +313,7 @@ "gsd-core/workflows/execute-plan.md": "e8de8ea661c1fe81", "gsd-core/workflows/explore.md": "7f5f9231cfd3089b", "gsd-core/workflows/extract-learnings.md": "92b3c0979604b7d0", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "9354cb830152fd28", "gsd-core/workflows/graduation.md": "13080b5c24eec27d", "gsd-core/workflows/health.md": "a91e0a8e5d20b2e9", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index 9eddb6574..0343e82e8 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -209,7 +209,7 @@ "gsd-core/workflows/execute-plan.md": "ff172c3540b52e9d", "gsd-core/workflows/explore.md": "6c04f2e658d93261", "gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4", - "gsd-core/workflows/fast.md": "94136fb570d20a9d", + "gsd-core/workflows/fast.md": "8878ec034b401f36", "gsd-core/workflows/forensics.md": "0d500a3f5ab26913", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "107e3c72e76d9535", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 33f2ca449..9e4b5b154 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -242,7 +242,7 @@ "gsd-core/workflows/execute-plan.md": "503b0ced0731dc38", "gsd-core/workflows/explore.md": "e1a83a8982532e5b", "gsd-core/workflows/extract-learnings.md": "dd4fdb88605de49a", - "gsd-core/workflows/fast.md": "54fe93778b45a7eb", + "gsd-core/workflows/fast.md": "0242082ca646819a", "gsd-core/workflows/forensics.md": "82800a3138ac1da9", "gsd-core/workflows/graduation.md": "40401655435beee3", "gsd-core/workflows/health.md": "043de14edb6a9723", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 8e1d0f720..c3f50fa61 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -242,7 +242,7 @@ "gsd-core/workflows/execute-plan.md": "4910f75bab2040ab", "gsd-core/workflows/explore.md": "8a5437aa0c239c38", "gsd-core/workflows/extract-learnings.md": "3fcc858b20d0d0e6", - "gsd-core/workflows/fast.md": "0162075e44072447", + "gsd-core/workflows/fast.md": "91f126c4ef24217e", "gsd-core/workflows/forensics.md": "665546666547875d", "gsd-core/workflows/graduation.md": "1ca877cda258a5de", "gsd-core/workflows/health.md": "1f9fd2deea45896f", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index ec771fa9d..af8f5a005 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -242,7 +242,7 @@ "gsd-core/workflows/execute-plan.md": "c8567fb4438b4404", "gsd-core/workflows/explore.md": "04e461ff8159a24e", "gsd-core/workflows/extract-learnings.md": "af793bdf4ffd1c8a", - "gsd-core/workflows/fast.md": "0162075e44072447", + "gsd-core/workflows/fast.md": "91f126c4ef24217e", "gsd-core/workflows/forensics.md": "3d1ce16b5f605592", "gsd-core/workflows/graduation.md": "a766039ff6ca653f", "gsd-core/workflows/health.md": "7b19d6e2c0357c3e", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 5cf749414..dc29fc5e4 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -313,7 +313,7 @@ "gsd-core/workflows/execute-plan.md": "ed874410972d32b7", "gsd-core/workflows/explore.md": "6c04f2e658d93261", "gsd-core/workflows/extract-learnings.md": "b6f01ca3d8f58de4", - "gsd-core/workflows/fast.md": "94136fb570d20a9d", + "gsd-core/workflows/fast.md": "8878ec034b401f36", "gsd-core/workflows/forensics.md": "0d500a3f5ab26913", "gsd-core/workflows/graduation.md": "47f1594c88c08501", "gsd-core/workflows/health.md": "107e3c72e76d9535", diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index b478680b8..42a15a636 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -28,7 +28,7 @@ "execute-plan.md": 32655, "explore.md": 10541, "extract-learnings.md": 12893, - "fast.md": 4149, + "fast.md": 4790, "forensics.md": 12531, "graduation.md": 11622, "health.md": 11868, From 30469ba75196f45ed6dc358f47e554693bb67077 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 11:08:20 -0400 Subject: [PATCH 6/8] test(#2133): drop unused os import (lint clean) --- tests/fix-2133-fast-md-log-to-state-schema.test.cjs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs index c2433b855..6a9fea60f 100644 --- a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs +++ b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs @@ -18,7 +18,6 @@ const { test, describe } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); -const os = require('node:os'); const { execFileSync } = require('node:child_process'); const { createTempProject, cleanup } = require('./helpers.cjs'); From cb48b2b48f945ab22b8e26fc0d43238b49acd8cf Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 11:13:48 -0400 Subject: [PATCH 7/8] test(#2133): drop no-op try/finally in runAgainst helper (review) --- ...fix-2133-fast-md-log-to-state-schema.test.cjs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs index 6a9fea60f..8445bd42b 100644 --- a/tests/fix-2133-fast-md-log-to-state-schema.test.cjs +++ b/tests/fix-2133-fast-md-log-to-state-schema.test.cjs @@ -93,16 +93,12 @@ describe('#2133 fast.md log_to_state schema gate', () => { const tmpDir = createTempProject('fix-2133-'); const statePath = path.join(tmpDir, '.planning', 'STATE.md'); fs.writeFileSync(statePath, makeStateMd(headerLine, separatorLine, existingRows)); - let stdout = ''; - try { - stdout = execFileSync('bash', ['-c', bashBlock], { - cwd: tmpDir, - env: { ...process.env, TASK: TASK_DESC }, - encoding: 'utf8', - }); - } finally { - // cleanup deferred to caller via t.after where bound - } + // Cleanup is bound by each caller via t.after(tmpDir). + const stdout = execFileSync('bash', ['-c', bashBlock], { + cwd: tmpDir, + env: { ...process.env, TASK: TASK_DESC }, + encoding: 'utf8', + }); const after = fs.readFileSync(statePath, 'utf8'); return { tmpDir, stdout, after }; } From f5370ef9c0342341a181c9564102eb6bde6c7d23 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 11:26:24 -0400 Subject: [PATCH 8/8] docs(#2133): backfill PR number in changeset fragment --- .changeset/happy-seals-roam.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/happy-seals-roam.md b/.changeset/happy-seals-roam.md index 38b75389e..10d8f7d65 100644 --- a/.changeset/happy-seals-roam.md +++ b/.changeset/happy-seals-roam.md @@ -1,5 +1,5 @@ --- type: Fixed -pr: 0 +pr: 2214 --- **`/gsd-fast` now appends Quick Task rows to STATE.md again** — the log_to_state column-count guard used an off-by-one awk formula (`NF-1`) that was always one too high, so the schema gate rejected the very table quick.md creates and silently skipped the STATE.md update. Also now supports the 6-column validate-mode table. (#2133)