From b0f672f88c18ed3f5ae95cd9076f4b73241da1fe Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 17 Jul 2026 14:01:38 -0400 Subject: [PATCH] fix(#2337): capture and surface todo severity (#2381) add-todo.md gains a confirm-based infer_severity step (infer from the blocker/major/minor/cosmetic taxonomy, confirm via AskUserQuestion with TEXT_MODE fallback, before writing) and a severity frontmatter field. cmdListTodos and cmdInitTodos now surface severity, backward-compatible (key omitted when absent), in parity. Closes #2337. Admin-merged (self-review bypass) with full green CI. --- .changeset/silly-voles-snooze.md | 5 ++ gsd-core/workflows/add-todo.md | 29 +++++++++ src/commands.cts | 6 +- src/init.cts | 4 ++ tests/commands.test.cjs | 37 +++++++++++ tests/fix-2337-add-todo-severity.test.cjs | 64 +++++++++++++++++++ .../golden-install-parity/antigravity.json | 2 +- .../golden-install-parity/augment.json | 2 +- .../golden-install-parity/claude-local.json | 2 +- .../golden-install-parity/claude.json | 2 +- .../fixtures/golden-install-parity/cline.json | 2 +- .../golden-install-parity/codebuddy.json | 2 +- .../fixtures/golden-install-parity/codex.json | 2 +- .../golden-install-parity/copilot.json | 2 +- .../golden-install-parity/cursor.json | 2 +- .../golden-install-parity/hermes.json | 2 +- .../fixtures/golden-install-parity/kilo.json | 2 +- .../fixtures/golden-install-parity/kimi.json | 2 +- .../golden-install-parity/opencode.json | 2 +- tests/fixtures/golden-install-parity/pi.json | 2 +- .../fixtures/golden-install-parity/qwen.json | 2 +- .../fixtures/golden-install-parity/trae.json | 2 +- .../golden-install-parity/windsurf.json | 2 +- .../fixtures/golden-install-parity/zcode.json | 2 +- tests/init.test.cjs | 22 +++++++ tests/workflow-size-baseline.json | 2 +- 26 files changed, 185 insertions(+), 20 deletions(-) create mode 100644 .changeset/silly-voles-snooze.md create mode 100644 tests/fix-2337-add-todo-severity.test.cjs diff --git a/.changeset/silly-voles-snooze.md b/.changeset/silly-voles-snooze.md new file mode 100644 index 000000000..579a4a7c4 --- /dev/null +++ b/.changeset/silly-voles-snooze.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2381 +--- +**Todo severity is now captured and surfaced end-to-end** — `/gsd-capture` (add-todo) now confirms a severity (blocker/major/minor/cosmetic) before writing a todo instead of silently omitting it, and `gsd-tools list-todos` / `init todos` now include the `severity` field in their JSON output (omitted for older todos that have none), so a backlog can be triaged by severity instead of by re-reading every file. (#2337) diff --git a/gsd-core/workflows/add-todo.md b/gsd-core/workflows/add-todo.md index 53499058d..7f1cfbfce 100644 --- a/gsd-core/workflows/add-todo.md +++ b/gsd-core/workflows/add-todo.md @@ -61,6 +61,34 @@ Infer area from file paths: Use existing area from step 2 if similar match exists. + +Infer a **suggested** severity from the same blocker/major/minor/cosmetic taxonomy `verify-work.md`'s `severity_inference` uses — then CONFIRM it with the user before writing. Never silently auto-assign: a mis-tagged severity silently corrupts backlog triage, which is exactly the signal this field exists to provide. + +Suggest from the user's natural-language description: + +| User says | Suggest | +|-----------|---------| +| "crashes", "error", "exception", "fails completely", "data loss" | blocker | +| "doesn't work", "nothing happens", "wrong behavior" | major | +| "works but...", "slow", "weird", "minor issue" | minor | +| "color", "spacing", "alignment", "looks off" | cosmetic | + +Default the suggestion to **major** if unclear. + +**Text mode (`workflow.text_mode: true` in config or `--text` flag):** Set `TEXT_MODE=true` if `--text` is present in `$ARGUMENTS` OR `text_mode` from init JSON is `true`. When TEXT_MODE is active, replace the `AskUserQuestion` below with a plain-text numbered list of the four options and ask the user to type their choice number. Required for non-Claude runtimes (OpenAI Codex, Gemini CLI, etc.) where `AskUserQuestion` is unavailable. + +Confirm with AskUserQuestion (present the suggested value first): +- header: "Severity?" +- question: "Suggested severity: [suggested]. Confirm or change:" +- options: + - "blocker" — breaks a workflow or loses data; fix first + - "major" — wrong behavior with no workaround + - "minor" — works, but with a workaround or annoyance + - "cosmetic" — visual/polish only + +Carry the confirmed value into `severity` in the create_file frontmatter. + + ```bash # Search for key words from title in existing todos @@ -97,6 +125,7 @@ Write to `.planning/todos/pending/${date}-${slug}.md`: created: [timestamp] title: [title] area: [area] +severity: [blocker|major|minor|cosmetic — confirmed in infer_severity step] files: - [file:lines] --- diff --git a/src/commands.cts b/src/commands.cts index 0d8c60e81..006d405f0 100644 --- a/src/commands.cts +++ b/src/commands.cts @@ -165,7 +165,7 @@ function cmdListTodos(cwd: string, area: string | undefined, raw: boolean): void const pendingDir = path.join(planningDir(cwd), 'todos', 'pending'); let count = 0; - const todos: Array<{ file: string; created: string; title: string; area: string; path: string }> = []; + const todos: Array<{ file: string; created: string; title: string; area: string; path: string; severity?: string }> = []; try { const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md')); @@ -176,6 +176,9 @@ function cmdListTodos(cwd: string, area: string | undefined, raw: boolean): void const createdMatch = content.match(/^created:\s*(.+)$/m); const titleMatch = content.match(/^title:\s*(.+)$/m); const areaMatch = content.match(/^area:\s*(.+)$/m); + // #2337: surface severity when present. Omit the key entirely for todos + // with no severity line so existing consumers of this JSON are unaffected. + const severityMatch = content.match(/^severity:\s*(.+)$/m); const todoArea = areaMatch ? areaMatch[1].trim() : 'general'; @@ -189,6 +192,7 @@ function cmdListTodos(cwd: string, area: string | undefined, raw: boolean): void title: titleMatch ? titleMatch[1].trim() : 'Untitled', area: todoArea, path: toPosixPath(path.relative(cwd, path.join(pendingDir, file))), + ...(severityMatch ? { severity: severityMatch[1].trim() } : {}), }); } } catch { /* intentionally empty */ } diff --git a/src/init.cts b/src/init.cts index a4548a8a1..c61e35854 100644 --- a/src/init.cts +++ b/src/init.cts @@ -1164,6 +1164,9 @@ function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void const createdMatch = content.match(/^created:\s*(.+)$/m); const titleMatch = content.match(/^title:\s*(.+)$/m); const areaMatch = content.match(/^area:\s*(.+)$/m); + // #2337: kept in parity with cmdListTodos — surface severity when + // present, omit the key entirely for todos with no severity line. + const severityMatch = content.match(/^severity:\s*(.+)$/m); const todoArea = areaMatch ? areaMatch[1].trim() : 'general'; if (area && todoArea !== area) continue; @@ -1180,6 +1183,7 @@ function cmdInitTodos(cwd: string, area: string | undefined, raw: boolean): void path.join(planningDir(cwd), 'todos', 'pending', file), ), ), + ...(severityMatch ? { severity: severityMatch[1].trim() } : {}), }); } catch { /* intentionally empty */ diff --git a/tests/commands.test.cjs b/tests/commands.test.cjs index 60541bfd6..0e5c42fef 100644 --- a/tests/commands.test.cjs +++ b/tests/commands.test.cjs @@ -1053,6 +1053,43 @@ describe('list-todos command', () => { assert.strictEqual(output.todos[0].area, 'general', 'missing area defaults to general'); assert.strictEqual(output.todos[0].created, 'unknown', 'missing created defaults to unknown'); }); + + // ── #2337: severity must be surfaced when present, omitted when absent ────── + // cmdListTodos parsed created/title/area but silently dropped severity, so + // audit-open and status summaries could not triage by blocker/major/minor/ + // cosmetic even for todos an agent had correctly hand-tagged. + test('surfaces severity when the frontmatter carries it (#2337)', () => { + const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending'); + fs.mkdirSync(pendingDir, { recursive: true }); + + fs.writeFileSync(path.join(pendingDir, 'crash.md'), + 'title: Fix data-loss crash\narea: core\ncreated: 2026-02-01\nseverity: blocker\n'); + + const result = runGsdTools('list-todos', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + const todo = output.todos.find(t => t.file === 'crash.md'); + assert.ok(todo, 'crash.md should be in results'); + assert.strictEqual(todo.severity, 'blocker', 'severity must be surfaced verbatim'); + }); + + test('omits the severity key for todos with no severity line — backward compatible (#2337)', () => { + const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending'); + fs.mkdirSync(pendingDir, { recursive: true }); + + fs.writeFileSync(path.join(pendingDir, 'legacy.md'), + 'title: Legacy todo\narea: docs\ncreated: 2026-02-02\n'); + + const result = runGsdTools('list-todos', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + const todo = output.todos.find(t => t.file === 'legacy.md'); + assert.ok(todo, 'legacy.md should be in results'); + assert.ok(!('severity' in todo), + 'severity key must be ABSENT (not null/empty) for a todo with no severity line'); + }); }); // ───────────────────────────────────────────────────────────────────────────── diff --git a/tests/fix-2337-add-todo-severity.test.cjs b/tests/fix-2337-add-todo-severity.test.cjs new file mode 100644 index 000000000..6d597edd1 --- /dev/null +++ b/tests/fix-2337-add-todo-severity.test.cjs @@ -0,0 +1,64 @@ +// allow-test-rule: source-text-is-the-product #2337 +// +// The add-todo.md workflow IS the runtime contract an agent follows when +// capturing a todo. #2337: it had no severity step and no severity frontmatter +// field, so severity discipline depended on an out-of-band convention the agent +// had to remember — and production todos landed with no `severity:` at all. +// +// These assertions guard the specific must-haves (issue #2337 acceptance +// criteria 1): a confirm-based `infer_severity` step positioned BEFORE the todo +// is written, and a `severity` field in the create_file frontmatter template. +// The golden-install-parity hash catches ANY change to this file, but not +// WHICH change — this test pins the two structural guarantees so a future edit +// can't silently drop the step while still updating the golden. + +'use strict'; + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const ADD_TODO = path.join(__dirname, '..', 'gsd-core', 'workflows', 'add-todo.md'); + +describe('add-todo.md severity capture (#2337)', () => { + const md = fs.readFileSync(ADD_TODO, 'utf8'); + + test('defines an infer_severity step', () => { + assert.match(md, //, + 'add-todo.md must define an infer_severity step'); + }); + + test('infer_severity runs before the file is written (create_file)', () => { + const sev = md.indexOf(''); + const create = md.indexOf(''); + assert.ok(sev !== -1 && create !== -1, 'both steps must exist'); + assert.ok(sev < create, + 'infer_severity must precede create_file so severity is confirmed before write'); + }); + + test('infer_severity confirms with the user — never a silent auto-assignment', () => { + const stepStart = md.indexOf(''); + const stepEnd = md.indexOf('', stepStart); + const step = md.slice(stepStart, stepEnd); + assert.match(step, /AskUserQuestion/, + 'severity must be confirmed via AskUserQuestion (with the TEXT_MODE fallback), not auto-assigned'); + assert.match(step, /TEXT_MODE/, + 'must provide the non-Claude-runtime numbered-list fallback'); + }); + + test('the create_file frontmatter template carries a severity field', () => { + assert.match(md, /^severity:\s*\[blocker\|major\|minor\|cosmetic/m, + 'create_file frontmatter template must include a severity field'); + }); + + test('severity taxonomy matches verify-work.md (blocker/major/minor/cosmetic)', () => { + const stepStart = md.indexOf(''); + const stepEnd = md.indexOf('', stepStart); + const step = md.slice(stepStart, stepEnd); + for (const level of ['blocker', 'major', 'minor', 'cosmetic']) { + assert.match(step, new RegExp(level), + `severity taxonomy must include "${level}" to stay aligned with verify-work.md's severity_inference`); + } + }); +}); diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json index 726d6185b..bddb14e05 100644 --- a/tests/fixtures/golden-install-parity/antigravity.json +++ b/tests/fixtures/golden-install-parity/antigravity.json @@ -200,7 +200,7 @@ "gsd-core/workflows/add-backlog.md": "8cc2e884a021b79b", "gsd-core/workflows/add-phase.md": "f82afa2f93be19fa", "gsd-core/workflows/add-tests.md": "ad6d49634e04ca7c", - "gsd-core/workflows/add-todo.md": "de1ac76acfcc0133", + "gsd-core/workflows/add-todo.md": "e410a72c168e43c0", "gsd-core/workflows/ai-integration-phase.md": "22f5466085c47c00", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "3ecffffe78021e0a", diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json index 0f8d8ba50..5a27a26e0 100644 --- a/tests/fixtures/golden-install-parity/augment.json +++ b/tests/fixtures/golden-install-parity/augment.json @@ -271,7 +271,7 @@ "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", - "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", + "gsd-core/workflows/add-todo.md": "953314cdfef56bc4", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json index 3ab9268b9..928b2ea72 100644 --- a/tests/fixtures/golden-install-parity/claude-local.json +++ b/tests/fixtures/golden-install-parity/claude-local.json @@ -270,7 +270,7 @@ "gsd-core/workflows/add-backlog.md": "8d05775f367d1e48", "gsd-core/workflows/add-phase.md": "7285e6e8894a41c5", "gsd-core/workflows/add-tests.md": "ffbaf55a25f455a2", - "gsd-core/workflows/add-todo.md": "1fc850476cd8340d", + "gsd-core/workflows/add-todo.md": "0a0f4d718ea50bea", "gsd-core/workflows/ai-integration-phase.md": "3503f52a7356caf0", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "816c71b0ef2d8c1d", diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json index 35b1bbd21..6f91d499d 100644 --- a/tests/fixtures/golden-install-parity/claude.json +++ b/tests/fixtures/golden-install-parity/claude.json @@ -199,7 +199,7 @@ "gsd-core/workflows/add-backlog.md": "485c4b6673ddf832", "gsd-core/workflows/add-phase.md": "0d90083e9c17bec1", "gsd-core/workflows/add-tests.md": "c4b133a102d36a27", - "gsd-core/workflows/add-todo.md": "8488f10f56ac24ca", + "gsd-core/workflows/add-todo.md": "2c5176263ad4ae8f", "gsd-core/workflows/ai-integration-phase.md": "a898d99b8d844215", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json index 85a84bb71..c9bf87995 100644 --- a/tests/fixtures/golden-install-parity/cline.json +++ b/tests/fixtures/golden-install-parity/cline.json @@ -203,7 +203,7 @@ "gsd-core/workflows/add-backlog.md": "6c4393113fa9d209", "gsd-core/workflows/add-phase.md": "cac9b86f66ab26f5", "gsd-core/workflows/add-tests.md": "0354376bffdc0080", - "gsd-core/workflows/add-todo.md": "b71e80304fe484eb", + "gsd-core/workflows/add-todo.md": "aa89018230c87f13", "gsd-core/workflows/ai-integration-phase.md": "5159c6bdf102f74b", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", "gsd-core/workflows/audit-fix.md": "9bb427cd3b4075d5", diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json index 495457431..998851fd9 100644 --- a/tests/fixtures/golden-install-parity/codebuddy.json +++ b/tests/fixtures/golden-install-parity/codebuddy.json @@ -271,7 +271,7 @@ "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", - "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", + "gsd-core/workflows/add-todo.md": "953314cdfef56bc4", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json index 6403b27dc..9b55870c7 100644 --- a/tests/fixtures/golden-install-parity/codex.json +++ b/tests/fixtures/golden-install-parity/codex.json @@ -306,7 +306,7 @@ "gsd-core/workflows/add-backlog.md": "62431b27424ff1ff", "gsd-core/workflows/add-phase.md": "27f282bf5b8af59d", "gsd-core/workflows/add-tests.md": "5cdb15508c4b6075", - "gsd-core/workflows/add-todo.md": "097e171f797737df", + "gsd-core/workflows/add-todo.md": "776e554602e3d1b7", "gsd-core/workflows/ai-integration-phase.md": "1dfa15d8f28c022d", "gsd-core/workflows/analyze-dependencies.md": "f799abc00907377f", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json index 0921d6cb4..39abc4365 100644 --- a/tests/fixtures/golden-install-parity/copilot.json +++ b/tests/fixtures/golden-install-parity/copilot.json @@ -201,7 +201,7 @@ "gsd-core/workflows/add-backlog.md": "98f2dbb7c7cc93c2", "gsd-core/workflows/add-phase.md": "34e6dd38c25b5b61", "gsd-core/workflows/add-tests.md": "7f5da4d2a9fa8a1a", - "gsd-core/workflows/add-todo.md": "10253591112b7d06", + "gsd-core/workflows/add-todo.md": "f86f4efa25a5d0d6", "gsd-core/workflows/ai-integration-phase.md": "ef0c2474cee76b00", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "9fdfe8a7fbcd1417", diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json index d2b540eb0..3f1f6e7a5 100644 --- a/tests/fixtures/golden-install-parity/cursor.json +++ b/tests/fixtures/golden-install-parity/cursor.json @@ -271,7 +271,7 @@ "gsd-core/workflows/add-backlog.md": "2926457ed07b4500", "gsd-core/workflows/add-phase.md": "0d90083e9c17bec1", "gsd-core/workflows/add-tests.md": "37f031c4b3236a32", - "gsd-core/workflows/add-todo.md": "d483ebe8edcf193a", + "gsd-core/workflows/add-todo.md": "1ee0525664fb062f", "gsd-core/workflows/ai-integration-phase.md": "b79f320d59a68773", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json index 8955f3477..584d48859 100644 --- a/tests/fixtures/golden-install-parity/hermes.json +++ b/tests/fixtures/golden-install-parity/hermes.json @@ -200,7 +200,7 @@ "gsd-core/workflows/add-backlog.md": "79a453a2270483fb", "gsd-core/workflows/add-phase.md": "b4c4b32c111a116a", "gsd-core/workflows/add-tests.md": "02f9e11ad2371c00", - "gsd-core/workflows/add-todo.md": "5fe3ddc3227e0e22", + "gsd-core/workflows/add-todo.md": "8ba173153be63a40", "gsd-core/workflows/ai-integration-phase.md": "ddab2912d025db65", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "2f21bd575bcedaf1", diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json index 5690e9054..2574ab408 100644 --- a/tests/fixtures/golden-install-parity/kilo.json +++ b/tests/fixtures/golden-install-parity/kilo.json @@ -271,7 +271,7 @@ "gsd-core/workflows/add-backlog.md": "485c4b6673ddf832", "gsd-core/workflows/add-phase.md": "0d90083e9c17bec1", "gsd-core/workflows/add-tests.md": "6e54f3d71a202671", - "gsd-core/workflows/add-todo.md": "5082510bf0449204", + "gsd-core/workflows/add-todo.md": "31bc520d77b14139", "gsd-core/workflows/ai-integration-phase.md": "c85c6afdc64f0da6", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json index 887ae42b6..583838a3b 100644 --- a/tests/fixtures/golden-install-parity/kimi.json +++ b/tests/fixtures/golden-install-parity/kimi.json @@ -264,7 +264,7 @@ "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", - "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", + "gsd-core/workflows/add-todo.md": "953314cdfef56bc4", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json index 369592b74..47cb7a0e3 100644 --- a/tests/fixtures/golden-install-parity/opencode.json +++ b/tests/fixtures/golden-install-parity/opencode.json @@ -271,7 +271,7 @@ "gsd-core/workflows/add-backlog.md": "08289088a88d410f", "gsd-core/workflows/add-phase.md": "5154a5fb3e0d4370", "gsd-core/workflows/add-tests.md": "b4deb88c74d491c7", - "gsd-core/workflows/add-todo.md": "72fcef9fd0cafda5", + "gsd-core/workflows/add-todo.md": "36b6d7fec9631ec7", "gsd-core/workflows/ai-integration-phase.md": "90e5f97018715b18", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "2871c5d0a4b671fa", diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json index cd7fd0e54..6987ec514 100644 --- a/tests/fixtures/golden-install-parity/pi.json +++ b/tests/fixtures/golden-install-parity/pi.json @@ -167,7 +167,7 @@ "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", - "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", + "gsd-core/workflows/add-todo.md": "953314cdfef56bc4", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json index acd3549a2..9e4c29dd0 100644 --- a/tests/fixtures/golden-install-parity/qwen.json +++ b/tests/fixtures/golden-install-parity/qwen.json @@ -200,7 +200,7 @@ "gsd-core/workflows/add-backlog.md": "cee41d5fc52d4e37", "gsd-core/workflows/add-phase.md": "cc63a3e108c9a065", "gsd-core/workflows/add-tests.md": "7cd945118e83246e", - "gsd-core/workflows/add-todo.md": "5e037b444e657557", + "gsd-core/workflows/add-todo.md": "83426131a56b8e77", "gsd-core/workflows/ai-integration-phase.md": "afdc7c15f03a95fc", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "aaa1b74e001322b6", diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json index 81c8d0b25..195556b23 100644 --- a/tests/fixtures/golden-install-parity/trae.json +++ b/tests/fixtures/golden-install-parity/trae.json @@ -200,7 +200,7 @@ "gsd-core/workflows/add-backlog.md": "1851e97996d445d5", "gsd-core/workflows/add-phase.md": "09bdebbc62f3310e", "gsd-core/workflows/add-tests.md": "8ad08d29415f8f43", - "gsd-core/workflows/add-todo.md": "0fe07cbd29ec252b", + "gsd-core/workflows/add-todo.md": "fefa923ed596225f", "gsd-core/workflows/ai-integration-phase.md": "d469eb120e52de0f", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "76607add3257cb37", diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json index 993ac9591..6b339eb8b 100644 --- a/tests/fixtures/golden-install-parity/windsurf.json +++ b/tests/fixtures/golden-install-parity/windsurf.json @@ -200,7 +200,7 @@ "gsd-core/workflows/add-backlog.md": "61169973a10b1321", "gsd-core/workflows/add-phase.md": "f9b1f7cc229b57f8", "gsd-core/workflows/add-tests.md": "d51e69bb433535a4", - "gsd-core/workflows/add-todo.md": "cab0d8215579fbdd", + "gsd-core/workflows/add-todo.md": "50d17053c6cfbc31", "gsd-core/workflows/ai-integration-phase.md": "8948988717506320", "gsd-core/workflows/analyze-dependencies.md": "77aff48f97fa6f1c", "gsd-core/workflows/audit-fix.md": "aa052e15623d759d", diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json index 739e11b42..4c920fa53 100644 --- a/tests/fixtures/golden-install-parity/zcode.json +++ b/tests/fixtures/golden-install-parity/zcode.json @@ -271,7 +271,7 @@ "gsd-core/workflows/add-backlog.md": "1bc7377b105194fc", "gsd-core/workflows/add-phase.md": "46e0551ffdd8ce1a", "gsd-core/workflows/add-tests.md": "24b0d8157a9ccb8b", - "gsd-core/workflows/add-todo.md": "cc0efe270004c8fb", + "gsd-core/workflows/add-todo.md": "953314cdfef56bc4", "gsd-core/workflows/ai-integration-phase.md": "40c217869a06981f", "gsd-core/workflows/analyze-dependencies.md": "52942af10f140717", "gsd-core/workflows/audit-fix.md": "eedb2da4bffb7575", diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 635050878..abba1e92b 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -1013,6 +1013,28 @@ describe('cmdInitTodos', () => { assert.strictEqual(task1.path, '.planning/todos/pending/task-1.md'); }); + // ── #2337: init todos must surface severity too, in parity with list-todos ── + test('surfaces severity when present, omits the key when absent (#2337)', () => { + const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending'); + fs.mkdirSync(pendingDir, { recursive: true }); + + fs.writeFileSync(path.join(pendingDir, 'tagged.md'), + 'title: Crash on save\narea: core\ncreated: 2026-02-25\nseverity: blocker'); + fs.writeFileSync(path.join(pendingDir, 'untagged.md'), + 'title: Old todo\narea: docs\ncreated: 2026-02-24'); + + const result = runGsdTools('init todos', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + const tagged = output.todos.find(t => t.file === 'tagged.md'); + const untagged = output.todos.find(t => t.file === 'untagged.md'); + assert.ok(tagged && untagged, 'both todos should be present'); + assert.strictEqual(tagged.severity, 'blocker', 'severity surfaced verbatim when present'); + assert.ok(!('severity' in untagged), + 'severity key ABSENT (backward compatible) for a todo with no severity line'); + }); + test('area filter returns only matching todos', () => { const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending'); fs.mkdirSync(pendingDir, { recursive: true }); diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 059b7f92f..e9c0a3940 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -2,7 +2,7 @@ "add-backlog.md": 7176, "add-phase.md": 7247, "add-tests.md": 16961, - "add-todo.md": 8996, + "add-todo.md": 10734, "ai-integration-phase.md": 14805, "analyze-dependencies.md": 3887, "audit-fix.md": 11717,