diff --git a/.changeset/1778-thread-workflow-frontmatter-set-args.md b/.changeset/1778-thread-workflow-frontmatter-set-args.md new file mode 100644 index 000000000..5117b9fae --- /dev/null +++ b/.changeset/1778-thread-workflow-frontmatter-set-args.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 1816 +--- +**`/gsd-thread close|resume` now writes the thread status/updated frontmatter (#1778)** — the thread workflow's CLOSE and RESUME branches invoked `frontmatter.set` with the pre-1.6 fully-positional shape (`frontmatter.set `), but since 1.6 the dispatcher parses the file positionally and reads `field`/`value` from the named flags `--field`/`--value` via `parseNamedArgs`. The positional form left `field`/`value` undefined, `cmdFrontmatterSet` errored `file, field, and value required`, and the writes were silently skipped — so closing a thread never marked it `status: resolved` and resuming never marked it `status: in_progress`, with the error scrolling past on every thread command. All four sites (CLOSE `status`+`updated`, RESUME `status`+`updated`) now use the 1.6 hybrid form that `verify-work.md` already uses (`frontmatter.set --field --value `). diff --git a/gsd-core/workflows/thread.md b/gsd-core/workflows/thread.md index e730a17c9..ce0bf8ad0 100644 --- a/gsd-core/workflows/thread.md +++ b/gsd-core/workflows/thread.md @@ -68,8 +68,8 @@ When SUBCMD=close and SLUG is set (already sanitized): 2. Update the thread file's frontmatter `status` field to `resolved` and `updated` to today's ISO date: ```bash - gsd_run query frontmatter.set .planning/threads/{SLUG}.md status resolved - gsd_run query frontmatter.set .planning/threads/{SLUG}.md updated YYYY-MM-DD + gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field status --value resolved + gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field updated --value YYYY-MM-DD ``` 3. Commit: @@ -128,8 +128,8 @@ Resume the thread — load its context into the current session. Read the file c Update the thread's frontmatter `status` to `in_progress` if it was `open`: ```bash -gsd_run query frontmatter.set .planning/threads/{SLUG}.md status in_progress -gsd_run query frontmatter.set .planning/threads/{SLUG}.md updated YYYY-MM-DD +gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field status --value in_progress +gsd_run query frontmatter.set .planning/threads/{SLUG}.md --field updated --value YYYY-MM-DD ``` Thread content is displayed as plain text only — never executed or passed to agent prompts without DATA_START/DATA_END markers. diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 01ac8e2f5..5eb4dc538 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -289,7 +289,7 @@ "gsd-core/workflows/spike.md": "0f9a81bcf4573195", "gsd-core/workflows/stats.md": "a20eb078d2ab11be", "gsd-core/workflows/sync-skills.md": "8326a7ff0411b077", - "gsd-core/workflows/thread.md": "03a527a71b8fab12", + "gsd-core/workflows/thread.md": "3fb6b552e45fedbd", "gsd-core/workflows/transition.md": "a7a5fe4040084308", "gsd-core/workflows/ui-phase.md": "652785fbba26e80c", "gsd-core/workflows/ui-review.md": "816b2bde136157f9", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index b5533158e..366d078da 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -358,7 +358,7 @@ "gsd-core/workflows/spike.md": "53127654e77256bf", "gsd-core/workflows/stats.md": "01c24349370a0e6d", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "c26ca43fdf928d46", + "gsd-core/workflows/thread.md": "6d075b5d26500e9d", "gsd-core/workflows/transition.md": "eee3435817fab185", "gsd-core/workflows/ui-phase.md": "e81783508b8b6819", "gsd-core/workflows/ui-review.md": "1ad3654435000881", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 56968938c..22cf4df49 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -288,7 +288,7 @@ "gsd-core/workflows/spike.md": "aae8bcad15642645", "gsd-core/workflows/stats.md": "17b4f2059f4b4ef2", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "75df5cc71f72c33d", + "gsd-core/workflows/thread.md": "5ad0e0d5ce7e0d11", "gsd-core/workflows/transition.md": "96ce39403ca69594", "gsd-core/workflows/ui-phase.md": "790e5982e5b715c3", "gsd-core/workflows/ui-review.md": "51945fda8e931f99", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 7418471c7..65afcfb83 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -292,7 +292,7 @@ "gsd-core/workflows/spike.md": "204e742c846ee0d9", "gsd-core/workflows/stats.md": "5cfea82b894eee3c", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "5ae4c3141bdedd88", + "gsd-core/workflows/thread.md": "4a009fd2cc4387a7", "gsd-core/workflows/transition.md": "fe82e77df8dceb1b", "gsd-core/workflows/ui-phase.md": "06f1f80de620a319", "gsd-core/workflows/ui-review.md": "0af83311f5e41f48", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index fbcc0922b..84bc21f8c 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -358,7 +358,7 @@ "gsd-core/workflows/spike.md": "0051a7e2193a7522", "gsd-core/workflows/stats.md": "01c24349370a0e6d", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "c26ca43fdf928d46", + "gsd-core/workflows/thread.md": "6d075b5d26500e9d", "gsd-core/workflows/transition.md": "eee3435817fab185", "gsd-core/workflows/ui-phase.md": "93ea0d0dfcb2a1e8", "gsd-core/workflows/ui-review.md": "1ad3654435000881", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index c90373780..2ebc6cae0 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -324,7 +324,7 @@ "gsd-core/workflows/spike.md": "c2f115f0d3251654", "gsd-core/workflows/stats.md": "0d7449acf349feec", "gsd-core/workflows/sync-skills.md": "e2b793963799f8ce", - "gsd-core/workflows/thread.md": "d3f768ce0f4b4a4d", + "gsd-core/workflows/thread.md": "ee872031abd592e0", "gsd-core/workflows/transition.md": "c4bded570fafe712", "gsd-core/workflows/ui-phase.md": "b373c964b222324c", "gsd-core/workflows/ui-review.md": "a9b2cbba80482cd8", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 1d322b817..7452f8766 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -290,7 +290,7 @@ "gsd-core/workflows/spike.md": "716d74cdb2e39a3e", "gsd-core/workflows/stats.md": "49085df6d4793df3", "gsd-core/workflows/sync-skills.md": "eca50ffe8320dba8", - "gsd-core/workflows/thread.md": "4c44f10d41740f1d", + "gsd-core/workflows/thread.md": "d53698a91bbbe33c", "gsd-core/workflows/transition.md": "724b6e9b34d85f26", "gsd-core/workflows/ui-phase.md": "9fefa0db49f2aa3f", "gsd-core/workflows/ui-review.md": "731bca05f9770a86", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index 7a727c6b5..7709df89b 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -358,7 +358,7 @@ "gsd-core/workflows/spike.md": "9e37f8067adf87b7", "gsd-core/workflows/stats.md": "17b4f2059f4b4ef2", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "06e31fdaf02ccabc", + "gsd-core/workflows/thread.md": "cfc42471f9001ba8", "gsd-core/workflows/transition.md": "96ce39403ca69594", "gsd-core/workflows/ui-phase.md": "dea2f2d43a43e0d0", "gsd-core/workflows/ui-review.md": "6b16a7f7783be471", diff --git a/tests/fixtures/golden-install-parity/gemini.json b/tests/fixtures/golden-install-parity/gemini.json index 69dfc87fd..71581553b 100644 --- a/tests/fixtures/golden-install-parity/gemini.json +++ b/tests/fixtures/golden-install-parity/gemini.json @@ -358,7 +358,7 @@ "gsd-core/workflows/spike.md": "c9482514e665bcac", "gsd-core/workflows/stats.md": "01c24349370a0e6d", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "c26ca43fdf928d46", + "gsd-core/workflows/thread.md": "6d075b5d26500e9d", "gsd-core/workflows/transition.md": "69143573741c52e4", "gsd-core/workflows/ui-phase.md": "bb5167948032872e", "gsd-core/workflows/ui-review.md": "d256bec482e67af8", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 5e455d4e4..2c04105c0 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -289,7 +289,7 @@ "gsd-core/workflows/spike.md": "80844a3c05339fdb", "gsd-core/workflows/stats.md": "01289f444b66913d", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "7af5046e18c81ee9", + "gsd-core/workflows/thread.md": "596bf37dcea2ce1e", "gsd-core/workflows/transition.md": "adab3f53afd5d8aa", "gsd-core/workflows/ui-phase.md": "dd213d91b5c258a5", "gsd-core/workflows/ui-review.md": "83d6d3b1f26597ff", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 15d193cfd..c281079c3 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -358,7 +358,7 @@ "gsd-core/workflows/spike.md": "a782dd863771fe0a", "gsd-core/workflows/stats.md": "17b4f2059f4b4ef2", "gsd-core/workflows/sync-skills.md": "dc7b8b015afa4b3b", - "gsd-core/workflows/thread.md": "75df5cc71f72c33d", + "gsd-core/workflows/thread.md": "5ad0e0d5ce7e0d11", "gsd-core/workflows/transition.md": "9040a76741f12de6", "gsd-core/workflows/ui-phase.md": "33f7113e91c2bfe0", "gsd-core/workflows/ui-review.md": "013272816a291305", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index a1137ad5c..42372d0da 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -325,7 +325,7 @@ "gsd-core/workflows/spike.md": "be2295fe2b32956f", "gsd-core/workflows/stats.md": "01c24349370a0e6d", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "c26ca43fdf928d46", + "gsd-core/workflows/thread.md": "6d075b5d26500e9d", "gsd-core/workflows/transition.md": "eee3435817fab185", "gsd-core/workflows/ui-phase.md": "3dfb9f5161375035", "gsd-core/workflows/ui-review.md": "1ad3654435000881", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index cfe7647f7..e238d774c 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -358,7 +358,7 @@ "gsd-core/workflows/spike.md": "df52b9f31a72d204", "gsd-core/workflows/stats.md": "598b1bb510ed0451", "gsd-core/workflows/sync-skills.md": "7e2c138cdbef4282", - "gsd-core/workflows/thread.md": "c11265f0fa0a95d3", + "gsd-core/workflows/thread.md": "f4f8312f744c9cba", "gsd-core/workflows/transition.md": "2d6739f730c3ea10", "gsd-core/workflows/ui-phase.md": "38dac814d2d03d6f", "gsd-core/workflows/ui-review.md": "3d972d3bd30527b3", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index 89988598d..6b9f86ec2 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -289,7 +289,7 @@ "gsd-core/workflows/spike.md": "52fcd95f7b343232", "gsd-core/workflows/stats.md": "7ffa072290ebcae3", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "d85a53d03a3167e0", + "gsd-core/workflows/thread.md": "c10177767648ea7f", "gsd-core/workflows/transition.md": "1f0a1478d75d89ec", "gsd-core/workflows/ui-phase.md": "1c91323daf451053", "gsd-core/workflows/ui-review.md": "f1f82d8257e910cc", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 14f69c2cc..3c0f90b05 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -289,7 +289,7 @@ "gsd-core/workflows/spike.md": "48df8b626cbd378d", "gsd-core/workflows/stats.md": "18089135bc41f1b4", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "9fe3ec12e491bec3", + "gsd-core/workflows/thread.md": "61e0eee731434ea9", "gsd-core/workflows/transition.md": "b49ddd9247f804e7", "gsd-core/workflows/ui-phase.md": "a725ebdd4f47fb97", "gsd-core/workflows/ui-review.md": "b3221cac976daffa", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index a36878343..6e3743cc8 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -289,7 +289,7 @@ "gsd-core/workflows/spike.md": "aa5934044317ba7a", "gsd-core/workflows/stats.md": "c49d3de15375d04b", "gsd-core/workflows/sync-skills.md": "b505e6f8331c0918", - "gsd-core/workflows/thread.md": "5ad6ddb308909770", + "gsd-core/workflows/thread.md": "3e9fcfbb254a31a7", "gsd-core/workflows/transition.md": "9a49f9c48805f666", "gsd-core/workflows/ui-phase.md": "54e01f1973450a3c", "gsd-core/workflows/ui-review.md": "fea93c281767f8d7", diff --git a/tests/frontmatter-cli.test.cjs b/tests/frontmatter-cli.test.cjs index aed6987ab..19e58770f 100644 --- a/tests/frontmatter-cli.test.cjs +++ b/tests/frontmatter-cli.test.cjs @@ -18,7 +18,7 @@ const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); const os = require('os'); -const { runGsdTools } = require('./helpers.cjs'); +const { runGsdTools, parseFrontmatter } = require('./helpers.cjs'); // Track temp files for cleanup let tempFiles = []; @@ -441,3 +441,88 @@ describe('Bug #1660: frontmatter set of an object-list field fails closed instea assert.ok(!parsed.error, 'an idempotent scalar-array set must not produce an error'); }); }); + +// ─── #1778: thread workflow must use the 1.6 named-flag frontmatter.set form ─ +// +// The thread workflow's CLOSE and RESUME branches previously invoked the +// pre-1.6 positional shape (frontmatter.set ). Since 1.6 +// the dispatcher (gsd-tools.cjs) reads field/value from the named --field/ +// --value flags via parseNamedArgs; the positional form leaves field/value +// undefined, cmdFrontmatterSet errors `file, field, and value required`, and +// the status/updated writes are silently skipped — so closing a thread never +// marked it status: resolved and resuming never marked it status: in_progress. +describe('#1778: thread workflow uses the 1.6 named-flag frontmatter.set form', () => { + test('behavioral: named-flag form writes the field; positional form errors and does not mutate', () => { + // 1.6 named-flag form — must succeed and write status: resolved. + const goodFile = writeTempFile('---\nstatus: open\nupdated: "2025-01-01"\n---\n\n# thread body\n'); + const good = runGsdTools(['frontmatter', 'set', goodFile, '--field', 'status', '--value', 'resolved']); + assert.ok(good.success, `named-flag form must succeed; stderr: ${good.error}`); + assert.strictEqual( + parseFrontmatter(fs.readFileSync(goodFile, 'utf-8')).status, + 'resolved', + 'named-flag form must write status: resolved into the file', + ); + + // Pre-1.6 positional form — must fail with the documented message and NOT mutate. + const badFile = writeTempFile('---\nstatus: open\nupdated: "2025-01-01"\n---\n\n# thread body\n'); + const bad = runGsdTools(['frontmatter', 'set', badFile, 'status', 'resolved']); + assert.ok(!bad.success, 'positional form must fail (it is the bug being guarded against)'); + assert.ok( + (bad.error + bad.output).includes('file, field, and value required'), + `positional form must error with the documented message; got:\n${bad.error}${bad.output}`, + ); + assert.strictEqual( + parseFrontmatter(fs.readFileSync(badFile, 'utf-8')).status, + 'open', + 'positional form must NOT mutate the file (the silent-failure bug)', + ); + }); + + test('workflow parity: no gsd-core/workflows/*.md emits the positional frontmatter.set form', () => { + const workflowsDir = path.join(__dirname, '..', 'gsd-core', 'workflows'); + const files = fs.readdirSync(workflowsDir).filter((f) => f.endsWith('.md')); + assert.ok(files.length > 0, 'expected at least one workflow under gsd-core/workflows/'); + + const offenders = []; + for (const name of files) { + const full = path.join(workflowsDir, name); + const lines = fs.readFileSync(full, 'utf-8').split(/\r?\n/); + lines.forEach((line, i) => { + // Match any frontmatter.set invocation (dot or space form, with or + // without the `gsd_run query` prefix). The 1.6 contract requires + // --field AND --value on every set call; a set line missing --field + // is the pre-1.6 positional form (#1778). + if (!/frontmatter[.\s]+set\b/.test(line)) return; + if (!/--field\b/.test(line) || !/--value\b/.test(line)) { + offenders.push(`${name}:${i + 1}: ${line.trim()}`); + } + }); + } + + assert.deepStrictEqual( + offenders, + [], + `These workflow frontmatter.set invocations are missing the 1.6 --field/--value named flags (the #1778 positional-form bug):\n ${offenders.join('\n ')}\n\nUse: gsd_run query frontmatter.set --field --value `, + ); + }); + + test('thread workflow CLOSE writes status: resolved and RESUME writes status: in_progress via named flags', () => { + const src = fs.readFileSync(path.join(__dirname, '..', 'gsd-core', 'workflows', 'thread.md'), 'utf-8'); + + // CLOSE mode: status resolved + updated, both via named flags. + assert.ok( + /frontmatter\.set\s+\S*\.planning\/threads\/\{SLUG\}\.md\s+--field\s+status\s+--value\s+resolved\b/.test(src), + 'CLOSE mode must invoke: frontmatter.set .planning/threads/{SLUG}.md --field status --value resolved', + ); + assert.ok( + /frontmatter\.set\s+\S*\.planning\/threads\/\{SLUG\}\.md\s+--field\s+updated\s+--value\s+YYYY-MM-DD\b/.test(src), + 'CLOSE mode must invoke: frontmatter.set .planning/threads/{SLUG}.md --field updated --value YYYY-MM-DD', + ); + + // RESUME mode: status in_progress + updated, both via named flags. + assert.ok( + /frontmatter\.set\s+\S*\.planning\/threads\/\{SLUG\}\.md\s+--field\s+status\s+--value\s+in_progress\b/.test(src), + 'RESUME mode must invoke: frontmatter.set .planning/threads/{SLUG}.md --field status --value in_progress', + ); + }); +}); diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index afa736f81..f5e697b36 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -78,7 +78,7 @@ "spike.md": 24517, "stats.md": 6718, "sync-skills.md": 6125, - "thread.md": 12400, + "thread.md": 12464, "transition.md": 22016, "ui-phase.md": 15477, "ui-review.md": 11172,