diff --git a/aidd_docs/tasks/2026_07/2026_07_30_backlog-hardening/transaction-run.md b/aidd_docs/tasks/2026_07/2026_07_30_backlog-hardening/transaction-run.md index 705cddd46..3a04c6833 100644 --- a/aidd_docs/tasks/2026_07/2026_07_30_backlog-hardening/transaction-run.md +++ b/aidd_docs/tasks/2026_07/2026_07_30_backlog-hardening/transaction-run.md @@ -1,5 +1,5 @@ --- -status: todo +status: pending --- # Instruction: close what the live backlog runs left open diff --git a/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/plan.md b/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/plan.md index 2bde9bb9b..71a802da9 100644 --- a/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/plan.md +++ b/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/plan.md @@ -1,5 +1,5 @@ --- -status: planned +status: pending backlog: ai-driven-dev/framework#703 spec: ./spec.md --- diff --git a/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/spec.md b/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/spec.md index ab21e2f7c..4c471b47a 100644 --- a/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/spec.md +++ b/aidd_docs/tasks/2026_09/2026_09_02_install-surfaces-agree/spec.md @@ -1,5 +1,5 @@ --- -status: framed +status: proposed backlog: ai-driven-dev/framework#703 branch: fix/install-surfaces-agree base: next@627408fb diff --git a/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/plan.md b/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/plan.md index 8ad2216bc..cc1c93ddd 100644 --- a/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/plan.md +++ b/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/plan.md @@ -1,5 +1,5 @@ --- -status: delivered +status: done backlog: ai-driven-dev/framework#746 spec: ./spec.md --- diff --git a/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/spec.md b/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/spec.md index c4add3f34..7ed7aabb1 100644 --- a/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/spec.md +++ b/aidd_docs/tasks/2026_09/2026_09_03_trailer-survives-a-regenerated-hook/spec.md @@ -1,5 +1,5 @@ --- -status: delivered +status: done backlog: ai-driven-dev/framework#746 branch: fix/trailer-survives-a-regenerated-hook base: next@919aee30 diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-plan.md b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-plan.md index 30e618f38..c9bca9ffe 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-plan.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-plan.md @@ -1,7 +1,7 @@ --- date: 2026-05-25 scope: framework-build-copilot-flat MVP1 -status: ready +status: done references_spec: aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-spec.md branch: feat/framework-build-copilot-flat --- diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-spec.md b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-spec.md index 033ecf61c..f056c0ced 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-spec.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-flat-spec.md @@ -1,6 +1,6 @@ --- name: framework-build-copilot-flat -status: frozen +status: done date: 2026-05-25 target: copilot scope: MVP1-flat diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan-v2.md b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan-v2.md index 1c60316ea..94bbae0e3 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan-v2.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan-v2.md @@ -2,7 +2,7 @@ date: 2026-05-25 scope: MVP1 target_plugin: copilot -status: ready-for-implementation +status: done kind: delta spec: ./2026_05_25-framework-build-copilot-spec.md supersedes: ./2026_05_25-framework-build-copilot-plan.md diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan.md b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan.md index 3a7bb6156..ca0c0e821 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-plan.md @@ -3,7 +3,7 @@ date: 2026-05-25 scope: MVP1 target_plugin: copilot spec: ./2026_05_25-framework-build-copilot-spec.md -status: ready-for-implementation +status: done --- # Plan — `aidd framework build --target copilot` diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-spec.md b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-spec.md index c805a84fd..271e35161 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-spec.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_25-framework-build-copilot-spec.md @@ -1,6 +1,6 @@ --- name: framework-build-copilot -status: frozen-v2 +status: done date: 2026-05-25 target: copilot scope: MVP1 diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-plan.md b/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-plan.md index f5b45bf28..2fae3c94e 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-plan.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-plan.md @@ -1,6 +1,6 @@ --- name: 271-setup-cache-version-fix-plan -status: ready +status: done date: 2026-05-26 release: v4.5.1 (patch) issue: https://github.com/ai-driven-dev/aidd-cli/issues/271 diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-spec.md b/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-spec.md index 78a51cb5e..af69832d9 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-spec.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_26-271-setup-cache-version-fix-spec.md @@ -1,6 +1,6 @@ --- name: 271-setup-cache-version-fix -status: frozen +status: done date: 2026-05-26 release: v4.5.1 (patch) issue: https://github.com/ai-driven-dev/aidd-cli/issues/271 diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-plan.md b/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-plan.md index a972af730..605d406ee 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-plan.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-plan.md @@ -1,6 +1,6 @@ --- name: framework-build-codex -status: planned +status: done date: 2026-05-26 scope: MVP1 (Mode A + agents hybrid) target: codex diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-spec.md b/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-spec.md index 30ad26043..dd584c571 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-spec.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_26-framework-build-codex-spec.md @@ -1,6 +1,6 @@ --- name: framework-build-codex -status: frozen +status: done date: 2026-05-26 target: codex scope: MVP1 (Mode A + agents hybrid) diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-plan.md b/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-plan.md index 9feb05cca..cffd99e3b 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-plan.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-plan.md @@ -1,6 +1,6 @@ --- name: framework-build-multi-target-marketplace -status: planned +status: done objective: Extend `aidd framework build` marketplace mode to cover claude + cursor (copilot + codex already shipped; opencode deferred), and replace the framework.ts if/else strategy ladder with a (target,mode)→factory registry — copilot/codex output stays byte-identical. success_condition: "pnpm biome check && pnpm typecheck && pnpm vitest run all green, AND `node dist/cli.js framework build --source tests/fixtures/framework-real --target cursor --out /tmp/dist-cursor` exits 0 producing `.cursor-plugin/marketplace.json` + `plugins/

/.cursor-plugin/plugin.json` (no `tools`/`color` in agent .md), AND `--target claude` produces `.claude-plugin/marketplace.json` + `plugins/

/.claude-plugin/plugin.json`, AND copilot+codex golden snapshots are byte-identical to pre-refactor baseline." iteration: 0 diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-smoke.md b/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-smoke.md index 4491dfe09..aa439e385 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-smoke.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-smoke.md @@ -2,7 +2,7 @@ name: framework-build-multi-target-marketplace-smoke date: 2026-05-30 targets: [claude, cursor] -status: documented +status: done --- # Smoke test — `aidd framework build` claude + cursor targets diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-spec.md b/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-spec.md index 4192550c7..e5fd69db8 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-spec.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_30-framework-build-multi-target-marketplace-spec.md @@ -1,6 +1,6 @@ --- name: framework-build-multi-target-marketplace -status: draft +status: done date: 2026-05-30 targets: [claude, cursor] # codex + copilot already shipped; opencode deferred to flat phase mode: marketplace (Mode A) diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-conformance-matrix.md b/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-conformance-matrix.md index 0cdbf4b7e..174bf4dd3 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-conformance-matrix.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-conformance-matrix.md @@ -1,6 +1,6 @@ --- name: framework-build-conformance-matrix -status: verified +status: reviewed date: 2026-05-31 scope: aidd framework build — 5 tools × 2 modes (marketplace + flat) evidence: 34/34 live conformance checks pass (this doc), golden 9-cell matrix, integration + e2e suites, /tmp smoke diff --git a/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-unified-contract-flat-spec.md b/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-unified-contract-flat-spec.md index b01a34a63..f19423c58 100644 --- a/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-unified-contract-flat-spec.md +++ b/cli/aidd_docs/tasks/2026_05/2026_05_31-framework-build-unified-contract-flat-spec.md @@ -1,6 +1,6 @@ --- name: framework-build-unified-contract-flat -status: draft +status: done date: 2026-05-31 supersedes-architecture-of: 2026_05_30-framework-build-multi-target-marketplace (PR #279, marketplace claude+cursor) targets: [claude, cursor, copilot, codex, opencode] diff --git a/cli/aidd_docs/tasks/2026_06/2026_06_01-flat-hooks-per-tool-spec.md b/cli/aidd_docs/tasks/2026_06/2026_06_01-flat-hooks-per-tool-spec.md index 09bdb7bab..1925a19e9 100644 --- a/cli/aidd_docs/tasks/2026_06/2026_06_01-flat-hooks-per-tool-spec.md +++ b/cli/aidd_docs/tasks/2026_06/2026_06_01-flat-hooks-per-tool-spec.md @@ -1,6 +1,6 @@ --- name: flat-hooks-per-tool -status: draft +status: done date: 2026-06-01 scope: framework build --flat — correct hook config registration per tool follows: fix/flat-plugin-prefixed-names (#281) diff --git a/cli/aidd_docs/tasks/2026_07/2026_07_29_restore-reporting-findings/plan.md b/cli/aidd_docs/tasks/2026_07/2026_07_29_restore-reporting-findings/plan.md index 4701f1f85..00163106f 100644 --- a/cli/aidd_docs/tasks/2026_07/2026_07_29_restore-reporting-findings/plan.md +++ b/cli/aidd_docs/tasks/2026_07/2026_07_29_restore-reporting-findings/plan.md @@ -1,6 +1,6 @@ --- objective: "Restore tells the truth about what it did, and marketplace-mode plugins are handled the same way on every command." -status: findings-1-2-done +status: done --- # Plan: three findings surfaced during the cartography backlog diff --git a/lefthook.yml b/lefthook.yml index 00d136edb..f451a3b64 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -111,6 +111,15 @@ pre-commit: fi node scripts/check-doc-duplication.js + task-status: + glob: "{aidd_docs,cli/aidd_docs,kanban/aidd_docs}/**/*.md" + run: | + if ! command -v node >/dev/null 2>&1; then + echo "ℹ️ node not available; skipping task-status" + exit 0 + fi + node scripts/check-task-status.js + markdown-links: glob: "**/*.md" run: | diff --git a/scripts/__tests__/a-task-status-the-board-can-place.test.js b/scripts/__tests__/a-task-status-the-board-can-place.test.js new file mode 100644 index 000000000..732a530e8 --- /dev/null +++ b/scripts/__tests__/a-task-status-the-board-can-place.test.js @@ -0,0 +1,114 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const test = require("node:test"); + +const { + ALLOWED_STATUSES, + BANKS, + declaredStatus, + markdownFiles, + offVocabularyStatuses, +} = require("../check-task-status.js"); + +const root = path.resolve(__dirname, "../.."); + +test("declaredStatus reads the status from the frontmatter block only", () => { + const content = [ + "---", + "objective: ship it", + "status: pending", + "---", + "", + "# Plan", + "", + "status: garbage in the body is prose, not a declaration", + ].join("\n"); + + assert.deepEqual(declaredStatus(content), { value: "pending", line: 3 }); +}); + +test("declaredStatus reports nothing for a document without frontmatter or without status", () => { + assert.equal(declaredStatus("# Memory page\n\nNo frontmatter here."), undefined); + assert.equal(declaredStatus("---\nname: brief\n---\n\n# Brief"), undefined); +}); + +test("declaredStatus reads a CRLF document the way the board's parser does", () => { + // A Windows checkout's core.autocrlf writes CRLF (the CLI golden test normalizes it for + // the same reason); gray-matter parses it, so a CRLF status must not slip past the gate. + assert.deepEqual(declaredStatus("---\r\nstatus: draft\r\n---\r\n"), { + value: "draft", + line: 2, + }); +}); + +test("declaredStatus reads quoted values and inline comments the way YAML does", () => { + // gray-matter sees `pending` in both; flagging them would reject a document the board places. + assert.deepEqual(declaredStatus('---\nstatus: "pending"\n---\n'), { + value: "pending", + line: 2, + }); + assert.deepEqual(declaredStatus("---\nstatus: 'pending'\n---\n"), { + value: "pending", + line: 2, + }); + assert.deepEqual(declaredStatus("---\nstatus: pending # wip note\n---\n"), { + value: "pending", + line: 2, + }); +}); + +test("every lifecycle and creation status passes", () => { + for (const status of ALLOWED_STATUSES) { + const content = `---\nstatus: ${status}\n---\n`; + assert.deepEqual(offVocabularyStatuses(content), [], `"${status}" should be allowed`); + } +}); + +test("a status the board cannot place is flagged with its line", () => { + const content = "---\ntype: plan\nstatus: delivered\n---\n"; + + assert.deepEqual(offVocabularyStatuses(content), [{ value: "delivered", line: 3 }]); +}); + +test("every status the kanban maps onto a column is allowed here", () => { + // One-way: the board may map more than documents write, never less. A value the + // kanban places that this gate rejects would force authors off the board's own vocabulary. + const mappingSource = fs.readFileSync( + path.join(root, "kanban/src/domain/models/progress-status.ts"), + "utf-8" + ); + const mappingBlock = mappingSource.match( + /RAW_STATUS_TO_PROGRESS_STATUS[^=]*=\s*\{([^}]*)\}/u + ); + assert.ok(mappingBlock, "kanban raw-status mapping not found"); + + const mappedStatuses = [...mappingBlock[1].matchAll(/^\s*"?([\w-]+)"?:/gmu)] + .map((match) => match[1]) + .filter((status) => status !== "unknown"); + + assert.ok(mappedStatuses.length > 0, "no mapped statuses parsed"); + for (const status of mappedStatuses) { + assert.ok( + ALLOWED_STATUSES.includes(status), + `kanban maps "${status}" but the gate rejects it` + ); + } +}); + +test("the repository's own banks hold no off-vocabulary status", () => { + const violations = []; + + for (const bank of BANKS) { + const bankPath = path.join(root, bank); + if (!fs.existsSync(bankPath)) continue; + + for (const filePath of markdownFiles(bankPath)) { + for (const finding of offVocabularyStatuses(fs.readFileSync(filePath, "utf-8"))) { + violations.push(`${path.relative(root, filePath)}:${finding.line} "${finding.value}"`); + } + } + } + + assert.deepEqual(violations, []); +}); diff --git a/scripts/check-task-status.js b/scripts/check-task-status.js new file mode 100644 index 000000000..286ac8928 --- /dev/null +++ b/scripts/check-task-status.js @@ -0,0 +1,124 @@ +#!/usr/bin/env node +/** + * Fails when a task document declares a `status` the kanban board cannot place. + * + * The board (kanban/) renders the banks' documents as five columns and sends any + * unmapped status to `unknown` — a column the list and interactive views hide by + * default. An off-vocabulary value is therefore silent drift: the document looks + * tracked and no view shows it. This repository already paid for that with + * `delivered`, `planned` and `framed` living invisibly next to the real columns. + * + * The vocabulary is the plan lifecycle + * (plugins/aidd-dev/skills/01-plan/references/plan-status.md), the creation statuses + * the aidd-pm templates stamp, and `done` as phases reach it. The board's own + * mapping (kanban/src/domain/models/progress-status.ts) places every one of them; + * the test suite pins that alignment. + * + * Always whole-bank — the hook's glob decides only whether this runs, never what it + * reads — because a wrong status usually lands in a commit nothing else gates. + */ + +const fs = require("node:fs"); +const path = require("node:path"); + +const ROOT = path.resolve(__dirname, ".."); + +/** The task banks the board reads. Memory pages carry no status and pass untouched. */ +const BANKS = ["aidd_docs", "cli/aidd_docs", "kanban/aidd_docs"]; + +const ALLOWED_STATUSES = [ + // creation — plan creation and the aidd-pm templates + "pending", + "proposed", + "open", + "reported", + // lifecycle — written by the implement and review steps + "in-progress", + "done", + "implemented", + "reviewed", + "blocked", +]; + +/** + * The `status` declared in the leading frontmatter block, with its line; body prose never + * counts. Reads what the board's parser (gray-matter) would read: CRLF endings, quoted + * values and inline comments resolve to the same value, so the gate and the board never + * disagree about a document. A document declaring no status is out of scope on purpose: + * absence stays visible as the board's `unknown` fallback, and whether `tasks/` documents + * must declare one is a separate decision this gate does not preempt. + */ +function declaredStatus(content) { + const lines = content.split(/\r?\n/u); + if (lines[0] !== "---") return undefined; + + for (const [index, line] of lines.slice(1).entries()) { + if (line === "---") return undefined; + const match = line.match(/^status:\s*(.*?)\s*$/u); + if (match) return { value: yamlScalar(match[1]), line: index + 2 }; + } + + return undefined; +} + +/** The value YAML hands the board: inline comment dropped, surrounding quotes removed. */ +function yamlScalar(raw) { + let value = raw; + + const commentStart = value.search(/\s#/u); + if (commentStart !== -1) value = value.slice(0, commentStart).trimEnd(); + + const quoted = value.match(/^"(.*)"$/u) ?? value.match(/^'(.*)'$/u); + return quoted ? quoted[1] : value; +} + +function offVocabularyStatuses(content) { + const declared = declaredStatus(content); + if (declared === undefined || ALLOWED_STATUSES.includes(declared.value)) return []; + return [declared]; +} + +function markdownFiles(directory) { + const files = []; + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + const entryPath = path.join(directory, entry.name); + if (entry.isDirectory()) { + files.push(...markdownFiles(entryPath)); + } else if (entry.name.endsWith(".md")) { + files.push(entryPath); + } + } + return files; +} + +function main() { + const violations = []; + + for (const bank of BANKS) { + const bankPath = path.join(ROOT, bank); + if (!fs.existsSync(bankPath)) continue; + + for (const filePath of markdownFiles(bankPath)) { + for (const finding of offVocabularyStatuses(fs.readFileSync(filePath, "utf-8"))) { + violations.push( + `${path.relative(ROOT, filePath)}:${finding.line} status "${finding.value}"` + ); + } + } + } + + if (violations.length > 0) { + console.error("❌ status outside the board vocabulary (lands in the hidden `unknown` column):"); + for (const violation of violations) { + console.error(` ${violation}`); + } + console.error(`Allowed: ${ALLOWED_STATUSES.join(", ")}`); + process.exit(1); + } +} + +module.exports = { ALLOWED_STATUSES, BANKS, declaredStatus, markdownFiles, offVocabularyStatuses }; + +if (require.main === module) { + main(); +}