* fix(#1778): use 1.6 named-flag frontmatter.set form in thread workflow The thread workflow's CLOSE and RESUME branches called frontmatter.set with the pre-1.6 fully-positional shape (frontmatter.set <file> <field> <value>). Since 1.6 the dispatcher (gsd-tools.cjs) parses the file positionally and reads field/value from the named flags --field/--value via parseNamedArgs; the positional form leaves field/value undefined, cmdFrontmatterSet errors 'file, field, and value required', and the status/updated writes are silently skipped. Closing a thread never marked it status: resolved and resuming never marked it status: in_progress. Switch all four sites (CLOSE status+updated, RESUME status+updated) to the 1.6 hybrid form that verify-work.md already uses: frontmatter.set <file> --field <field> --value <value> Add a regression test with three guards: (1) behavioral — the named-flag form writes the field while the positional form errors with the documented message and does not mutate the file; (2) workflow parity — no workflow under gsd-core/workflows/ emits the positional form, so a future edit that reintroduces it anywhere fails CI; (3) thread-specific — CLOSE writes status: resolved and RESUME writes status: in_progress via the named flags. * docs(#1778): add changeset fragment for thread workflow frontmatter fix * docs(#1778): fix unclosed inline-code backtick in changeset fragment * fix(#1778): move regression into owning test + regen baselines lint-regression-test-names rejects new bug-NNNN-*.test.cjs files; move the #1778 regression (behavioral named-vs-positional + workflow-parity scan + thread CLOSE/RESUME assertions) into tests/frontmatter-cli.test.cjs, the canonical home for frontmatter CLI regressions, and delete the standalone file. frontmatter-cli.test.cjs already carries the allow-test-rule exemption for workflow .md content tests. gsd-core/workflows/thread.md ships to every runtime and is size-tracked, so recapture the 16 golden-install-parity fixtures (thread.md hash) and the per-file workflow size baseline (thread.md 12400 -> 12464) via UPDATE_GOLDEN=1 and npm run size:baseline.
This commit is contained in:
5
.changeset/1778-thread-workflow-frontmatter-set-args.md
Normal file
5
.changeset/1778-thread-workflow-frontmatter-set-args.md
Normal file
@@ -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 <file> <field> <value>`), 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 <file> --field <field> --value <value>`).
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 <file> <field> <value>). 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 <file> --field <field> --value <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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user