@open-agent-toolkit/cli 0.1.63 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/assets/agents/oat-phase-implementer.md +9 -1
  2. package/assets/agents/oat-reviewer.md +8 -1
  3. package/assets/docs/cli-utilities/backlog-lifecycle.md +2 -2
  4. package/assets/docs/cli-utilities/config-and-local-state.md +20 -6
  5. package/assets/docs/cli-utilities/tool-packs.md +1 -0
  6. package/assets/docs/cli-utilities/workflow-gates.md +3 -1
  7. package/assets/docs/contributing/code.md +18 -0
  8. package/assets/docs/contributing/skills.md +22 -0
  9. package/assets/docs/workflows/projects/artifacts.md +30 -0
  10. package/assets/docs/workflows/projects/dispatch-ceiling.md +13 -0
  11. package/assets/public-package-versions.json +4 -4
  12. package/assets/skills/oat-pjm-add-backlog-item/SKILL.md +17 -72
  13. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +165 -165
  14. package/assets/skills/oat-project-document/SKILL.md +8 -1
  15. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +165 -165
  16. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +165 -165
  17. package/assets/skills/oat-project-plan-writing/SKILL.md +48 -12
  18. package/assets/skills/oat-project-pr-final/SKILL.md +9 -1
  19. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +165 -165
  20. package/assets/skills/oat-project-quick-start/SKILL.md +8 -1
  21. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +165 -165
  22. package/assets/skills/oat-project-review-provide/SKILL.md +9 -1
  23. package/assets/skills/oat-project-review-receive/SKILL.md +9 -1
  24. package/assets/skills/oat-project-summary/SKILL.md +16 -4
  25. package/assets/templates/plan.md +2 -0
  26. package/dist/commands/backlog/archive.d.ts.map +1 -1
  27. package/dist/commands/backlog/archive.js +5 -5
  28. package/dist/commands/backlog/index.d.ts +4 -0
  29. package/dist/commands/backlog/index.d.ts.map +1 -1
  30. package/dist/commands/backlog/index.js +55 -0
  31. package/dist/commands/backlog/new.d.ts +27 -0
  32. package/dist/commands/backlog/new.d.ts.map +1 -0
  33. package/dist/commands/backlog/new.js +158 -0
  34. package/dist/commands/backlog/regenerate-index.d.ts.map +1 -1
  35. package/dist/commands/backlog/regenerate-index.js +42 -3
  36. package/dist/commands/decision/index.d.ts.map +1 -1
  37. package/dist/commands/decision/index.js +4 -0
  38. package/dist/commands/decision/new.d.ts +2 -0
  39. package/dist/commands/decision/new.d.ts.map +1 -1
  40. package/dist/commands/decision/new.js +4 -2
  41. package/dist/commands/doctor/index.d.ts +1 -0
  42. package/dist/commands/doctor/index.d.ts.map +1 -1
  43. package/dist/commands/doctor/index.js +3 -0
  44. package/dist/commands/doctor/stale-invocations.d.ts +3 -0
  45. package/dist/commands/doctor/stale-invocations.d.ts.map +1 -0
  46. package/dist/commands/doctor/stale-invocations.js +173 -0
  47. package/dist/commands/gate/index.d.ts +1 -0
  48. package/dist/commands/gate/index.d.ts.map +1 -1
  49. package/dist/commands/gate/index.js +9 -2
  50. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  51. package/dist/commands/project/new/scaffold.js +24 -9
  52. package/dist/commands/tools/update/index.d.ts.map +1 -1
  53. package/dist/commands/tools/update/index.js +22 -12
  54. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-provide
3
- version: 1.3.15
3
+ version: 1.3.16
4
4
  description: Use when the user explicitly asks to review an OAT project — e.g. "review project", "review the project", "run project review", or confirms a previously offered review. Do NOT auto-invoke on completed work alone. Resolves a project review scope and offers before running.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -92,6 +92,14 @@ Run the `oat-project-review-provide` skill and it will:
92
92
  2. Ask scope (task/phase/final/range)
93
93
  3. Confirm before running
94
94
 
95
+ ## Artifact Hygiene
96
+
97
+ Artifact hygiene contract: Before finishing or committing, format every file you created or edited. Use the concrete write/fix formatting command supplied by the governing plan, task, or brief. If none is usable, discover the repository's documented write/fix command from applicable `AGENTS.md`/`CLAUDE.md` instructions and relevant package manifests; do not infer or hardcode a formatter. Prefer a file-scoped invocation when supported, and avoid rewriting unrelated files. If no command is discoverable, warn once with `no format command discovered in repo instructions; skipping`, then continue.
98
+
99
+ After formatting, run only repository checks relevant to the files changed;
100
+ writing prose artifacts or review bookkeeping does not imply unrelated full
101
+ test suites.
102
+
95
103
  ## Process
96
104
 
97
105
  ### Step 0: Resolve Project or Explicit Review Target
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-review-receive
3
- version: 1.5.6
3
+ version: 1.5.7
4
4
  description: Use when the user explicitly asks to receive review findings for an OAT project — e.g. "receive review", "process review", "process the project review", or confirms a previously offered review-receive step. Do NOT auto-invoke merely because a review file exists. Resolves the latest review and offers before acting.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -70,6 +70,14 @@ When executing this skill, provide lightweight progress feedback so the user can
70
70
  - Routing to oat-project-implement
71
71
  - For `artifact` reviews: updating reviewed artifact files directly after user confirmation
72
72
 
73
+ ## Artifact Hygiene
74
+
75
+ Artifact hygiene contract: Before finishing or committing, format every file you created or edited. Use the concrete write/fix formatting command supplied by the governing plan, task, or brief. If none is usable, discover the repository's documented write/fix command from applicable `AGENTS.md`/`CLAUDE.md` instructions and relevant package manifests; do not infer or hardcode a formatter. Prefer a file-scoped invocation when supported, and avoid rewriting unrelated files. If no command is discoverable, warn once with `no format command discovered in repo instructions; skipping`, then continue.
76
+
77
+ After formatting, run only repository checks relevant to the files changed;
78
+ writing prose artifacts or review bookkeeping does not imply unrelated full
79
+ test suites.
80
+
73
81
  ## Process
74
82
 
75
83
  ### Step 0: Resolve Active Project
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-summary
3
- version: 1.3.0
3
+ version: 1.3.2
4
4
  description: Use when the user requests or confirms summarizing an active OAT project — e.g. "summarize the project", "generate the summary", "run oat-project-summary", or confirms a previously offered summary run. Do NOT auto-invoke when implementation completes. Generates summary.md from project artifacts as institutional memory.
5
5
  disable-model-invocation: false
6
6
  user-invocable: true
@@ -69,6 +69,13 @@ If you catch yourself:
69
69
  2. Return to summary generation
70
70
  3. Keep content grounded in artifacts
71
71
 
72
+ ## Artifact Hygiene
73
+
74
+ Artifact hygiene contract: Before finishing or committing, format every file you created or edited. Use the concrete write/fix formatting command supplied by the governing plan, task, or brief. If none is usable, discover the repository's documented write/fix command from applicable `AGENTS.md`/`CLAUDE.md` instructions and relevant package manifests; do not infer or hardcode a formatter. Prefer a file-scoped invocation when supported, and avoid rewriting unrelated files. If no command is discoverable, warn once with `no format command discovered in repo instructions; skipping`, then continue.
75
+
76
+ After formatting, run only repository checks relevant to the files changed;
77
+ writing prose artifacts does not imply unrelated full test suites.
78
+
72
79
  ## Process
73
80
 
74
81
  ### Step 0: Resolve Active Project
@@ -271,7 +278,12 @@ test -f .oat/repo/reference/decisions/index.md || oat decision init
271
278
 
272
279
  **6.4 — Idempotent, date-independent promotion (critical).** For each decision in `## Key Decisions`:
273
280
 
274
- 1. **Derive title + rationale.** The decision's bold lead-in / first clause becomes the **title** (a short noun phrase). The remaining explanatory text becomes the **rationale**, passed verbatim as `--context`.
281
+ 1. **Derive title + complete sections.** Ground every value in the Key Decision and its project artifacts:
282
+ - The bold lead-in / first clause becomes the **title** (a short noun phrase).
283
+ - The problem, constraint, or motivating rationale becomes **context**.
284
+ - The choice that was made becomes the **decision**.
285
+ - The resulting tradeoffs, follow-on effects, or operational implications become **consequences**.
286
+ Each section must contain concrete grounded prose; do not pass placeholder content.
275
287
  2. **Compute the slug the CLI would use.** The CLI generates the record ID as `DR-<YYMMDD>-<slug>`, where `<slug>` is the lowercased, ASCII-folded, hyphen-collapsed form of the title, capped at 30 characters at the last whole-word boundary with trailing stop-words (`a, an, the, of, for, and, to, in, on, as, with`) trimmed (the same slug rule the CLI applies). Compute that `<slug>` for the title.
276
288
  3. **Dedup on the exact slug, ignoring only the date prefix.** A record ID is `DR-<YYMMDD>-<slug>`, where the date is exactly six digits. Check whether a record for this slug already exists by stripping that fixed `DR-<6 digits>-` prefix from existing record IDs and comparing the remaining slug for **exact equality**. Anchor the date to exactly six characters so the slug must match in full:
277
289
 
@@ -287,10 +299,10 @@ test -f .oat/repo/reference/decisions/index.md || oat decision init
287
299
  - Otherwise → create it:
288
300
 
289
301
  ```bash
290
- oat decision new "<title>" --status accepted --context "<rationale>"
302
+ oat decision new "<title>" --status accepted --context "<context>" --decision "<decision>" --consequences "<consequences>"
291
303
  ```
292
304
 
293
- The command generates the deterministic `DR-YYMMDD-slug` ID, seeds the body from `.oat/templates/decision.md`, and regenerates the managed index automatically — do not hand-edit `index.md`. Optionally pass `--created-at "<project completion date>"` when a project completion date is available, so the record's date reflects when the decision was made.
305
+ The command generates the deterministic `DR-YYMMDD-slug` ID, fills every decision body section, and regenerates the managed index automatically — do not hand-edit `index.md`. Optionally pass `--created-at "<project completion date>"` when a project completion date is available, so the record's date reflects when the decision was made.
294
306
 
295
307
  Because of the date-independent slug dedup, this step is **safe to run every time `summary.md` is (re)generated** — including the pr-final refresh and revision re-runs — without ever creating duplicate decision records. Already-promoted decisions are skipped; only genuinely new Key Decisions become new records.
296
308
 
@@ -65,6 +65,8 @@ Codex effort values are preferred controls. `oat-project-implement` caps them wh
65
65
 
66
66
  ---
67
67
 
68
+ RED/GREEN/Refactor is the recommended default where work is testable, not a validator requirement. Other task-body shapes, including non-TDD shapes, are allowed when appropriate, provided the plan preserves stable `pNN-tNN` IDs, per-task verification, and atomic commits.
69
+
68
70
  ## Phase 1: {Phase Name}
69
71
 
70
72
  ### Task p01-t01: {Task Name}
@@ -1 +1 @@
1
- {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/archive.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,iBAAiB,EAGvB,MAAM,sBAAsB,CAAC;AAoB9B,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,CAAC,GAAG,CAAK;CAKjD;AAkID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAkHnC"}
1
+ {"version":3,"file":"archive.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/archive.ts"],"names":[],"mappings":"AASA,OAAO,EAEL,KAAK,iBAAiB,EAGvB,MAAM,sBAAsB,CAAC;AAmB9B,mDAAmD;AACnD,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;IAC5B,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,CAAC,GAAG,CAAK;CAKjD;AAkID;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAqHnC"}
@@ -8,7 +8,6 @@ import { regenerateBacklogIndex } from './regenerate-index.js';
8
8
  import { BACKLOG_ITEM_STATUSES, extractBacklogStatus, isValidBacklogStatus, } from './shared/item-status.js';
9
9
  const execFileAsync = promisify(execFile);
10
10
  const COMPLETED_HEADING = '## Completed Items';
11
- const TODO_SUMMARY = 'TODO: summarize outcome';
12
11
  const STARTER_COMPLETED = [
13
12
  '# OAT Backlog Completed',
14
13
  '',
@@ -178,6 +177,10 @@ export async function archiveBacklogItem(backlogRoot, id, options = {}) {
178
177
  throw new BacklogArchiveError(`Backlog item ${itemsPath} has invalid status "${currentStatus ?? ''}". Valid statuses: ${BACKLOG_ITEM_STATUSES.join(', ')}. Fix: correct the \`status\` field manually, then re-run \`oat backlog archive ${id}\`.`);
179
178
  }
180
179
  const targetStatus = options.wontDo ? 'wont_do' : 'closed';
180
+ const summary = options.summary?.trim() ?? '';
181
+ if (targetStatus === 'closed' && summary.length === 0) {
182
+ throw new BacklogArchiveError(`Closing backlog item ${id} requires a non-empty outcome summary. Fix: rerun with \`--summary "<outcome>"\`.`);
183
+ }
181
184
  const now = options.now ?? new Date();
182
185
  const updatedIso = now.toISOString().replace(/\.\d{3}Z$/, 'Z');
183
186
  const entryDate = updatedIso.slice(0, 10);
@@ -188,11 +191,8 @@ export async function archiveBacklogItem(backlogRoot, id, options = {}) {
188
191
  // 5. completed.md entry — always for `closed`; only with a summary for
189
192
  // `wont_do`.
190
193
  let completedEntry = 'skipped';
191
- const shouldWriteEntry = targetStatus === 'closed' || Boolean(options.summary);
194
+ const shouldWriteEntry = targetStatus === 'closed' || summary.length > 0;
192
195
  if (shouldWriteEntry) {
193
- const summary = options.summary?.trim()
194
- ? options.summary.trim()
195
- : TODO_SUMMARY;
196
196
  const entryLine = `- ${entryDate} — ${id} — ${title} — ${summary}`;
197
197
  const completedPath = join(backlogRoot, 'completed.md');
198
198
  let scaffoldedFile = false;
@@ -1,14 +1,18 @@
1
1
  import { buildCommandContext } from '../../app/command-context.js';
2
+ import { resolveAssetsRoot } from '../../fs/assets.js';
2
3
  import { resolveProjectRoot } from '../../fs/paths.js';
3
4
  import { Command } from 'commander';
4
5
  import { archiveBacklogItem } from './archive.js';
5
6
  import { initializeBacklog } from './init.js';
7
+ import { createBacklogItem } from './new.js';
6
8
  import { regenerateBacklogIndex } from './regenerate-index.js';
7
9
  interface BacklogCommandDependencies {
8
10
  buildCommandContext: typeof buildCommandContext;
9
11
  resolveProjectRoot: typeof resolveProjectRoot;
12
+ resolveAssetsRoot: typeof resolveAssetsRoot;
10
13
  initializeBacklog: typeof initializeBacklog;
11
14
  regenerateBacklogIndex: typeof regenerateBacklogIndex;
15
+ createBacklogItem: typeof createBacklogItem;
12
16
  archiveBacklogItem: typeof archiveBacklogItem;
13
17
  pathExists: (path: string) => Promise<boolean>;
14
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,kBAAkB,EAAuB,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAqB5D,UAAU,0BAA0B;IAClC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChD;AA0CD,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,OAAO,CAAC,0BAA0B,CAAM,GAClD,OAAO,CAiMT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,kBAAkB,EAAuB,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AA8B5D,UAAU,0BAA0B;IAClC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChD;AA4CD,wBAAgB,oBAAoB,CAClC,SAAS,GAAE,OAAO,CAAC,0BAA0B,CAAM,GAClD,OAAO,CAqQT"}
@@ -2,10 +2,12 @@ import { access } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { buildCommandContext } from '../../app/command-context.js';
4
4
  import { readGlobalOptions } from '../shared/shared.utils.js';
5
+ import { resolveAssetsRoot } from '../../fs/assets.js';
5
6
  import { resolveProjectRoot } from '../../fs/paths.js';
6
7
  import { Command } from 'commander';
7
8
  import { archiveBacklogItem, BacklogArchiveError } from './archive.js';
8
9
  import { initializeBacklog } from './init.js';
10
+ import { createBacklogItem } from './new.js';
9
11
  import { regenerateBacklogIndex } from './regenerate-index.js';
10
12
  import { generateBacklogId } from './shared/generate-id.js';
11
13
  async function pathExistsDefault(path) {
@@ -26,8 +28,10 @@ async function pathExistsDefault(path) {
26
28
  const DEFAULT_DEPENDENCIES = {
27
29
  buildCommandContext,
28
30
  resolveProjectRoot,
31
+ resolveAssetsRoot,
29
32
  initializeBacklog,
30
33
  regenerateBacklogIndex,
34
+ createBacklogItem,
31
35
  archiveBacklogItem,
32
36
  pathExists: pathExistsDefault,
33
37
  };
@@ -60,6 +64,57 @@ export function createBacklogCommand(overrides = {}) {
60
64
  }
61
65
  process.exitCode = 0;
62
66
  });
67
+ cmd
68
+ .command('new')
69
+ .description('Create a new file-backed backlog item')
70
+ .argument('<title>', 'Backlog item title')
71
+ .option('--priority <priority>', 'Item priority', 'medium')
72
+ .option('--scope <scope>', 'Item scope', 'task')
73
+ .option('--scope-estimate <size>', 'Optional scope estimate (XS|S|M|L|XL|XXL)')
74
+ .option('--labels <labels>', 'Comma-delimited labels', (value) => value.split(','))
75
+ .option('--description <text>', 'Initial description body text')
76
+ .option('--backlog-root <path>', 'Backlog root directory (defaults to .oat/repo/pjm/backlog)')
77
+ .action(async (title, options, command) => {
78
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
79
+ try {
80
+ const projectRoot = await dependencies.resolveProjectRoot(context.cwd);
81
+ const backlogRoot = await resolveBacklogRoot(context, options.backlogRoot, dependencies);
82
+ const assetsRoot = await dependencies.resolveAssetsRoot();
83
+ const result = await dependencies.createBacklogItem({
84
+ backlogRoot,
85
+ assetsRoot,
86
+ templatesRoot: resolve(projectRoot, '.oat', 'templates'),
87
+ title,
88
+ priority: options.priority,
89
+ scope: options.scope,
90
+ scopeEstimate: options.scopeEstimate,
91
+ labels: options.labels,
92
+ description: options.description,
93
+ });
94
+ if (context.json) {
95
+ context.logger.json({ status: 'ok', ...result });
96
+ }
97
+ else {
98
+ for (const warning of result.index.warnings) {
99
+ context.logger.warn(warning);
100
+ }
101
+ context.logger.info(`Created backlog item ${result.id}`);
102
+ context.logger.info(`Wrote ${result.filePath}`);
103
+ context.logger.info(`Regenerated backlog index with ${result.index.itemCount} ${result.index.itemCount === 1 ? 'item' : 'items'}.`);
104
+ }
105
+ process.exitCode = 0;
106
+ }
107
+ catch (error) {
108
+ const message = error instanceof Error ? error.message : String(error);
109
+ if (context.json) {
110
+ context.logger.json({ status: 'error', message });
111
+ }
112
+ else {
113
+ context.logger.error(message);
114
+ }
115
+ process.exitCode = 1;
116
+ }
117
+ });
63
118
  cmd
64
119
  .command('regenerate-index')
65
120
  .description('Regenerate the managed backlog index table')
@@ -0,0 +1,27 @@
1
+ import { initializeBacklog } from './init.js';
2
+ import { regenerateBacklogIndex, type RegenerateBacklogIndexResult } from './regenerate-index.js';
3
+ export interface CreateBacklogItemOptions {
4
+ backlogRoot: string;
5
+ assetsRoot: string;
6
+ templatesRoot?: string;
7
+ title: string;
8
+ priority?: string;
9
+ scope?: string;
10
+ scopeEstimate?: string;
11
+ labels?: string[];
12
+ description?: string;
13
+ createdAt?: string;
14
+ }
15
+ export interface CreateBacklogItemResult {
16
+ id: string;
17
+ backlogRoot: string;
18
+ filePath: string;
19
+ templatePath: string;
20
+ index: RegenerateBacklogIndexResult;
21
+ }
22
+ export interface CreateBacklogItemDependencies {
23
+ initializeBacklog: typeof initializeBacklog;
24
+ regenerateBacklogIndex: typeof regenerateBacklogIndex;
25
+ }
26
+ export declare function createBacklogItem(options: CreateBacklogItemOptions, overrides?: Partial<CreateBacklogItemDependencies>): Promise<CreateBacklogItemResult>;
27
+ //# sourceMappingURL=new.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/new.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EACL,sBAAsB,EACtB,KAAK,4BAA4B,EAClC,MAAM,oBAAoB,CAAC;AAS5B,MAAM,WAAW,wBAAwB;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,4BAA4B,CAAC;CACrC;AAED,MAAM,WAAW,6BAA6B;IAC5C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;CACvD;AAuLD,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,wBAAwB,EACjC,SAAS,GAAE,OAAO,CAAC,6BAA6B,CAAM,GACrD,OAAO,CAAC,uBAAuB,CAAC,CAqDlC"}
@@ -0,0 +1,158 @@
1
+ import { access, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { stripTemplateFrontmatter } from '../shared/strip-template-frontmatter.js';
4
+ import YAML from 'yaml';
5
+ import { initializeBacklog } from './init.js';
6
+ import { regenerateBacklogIndex, } from './regenerate-index.js';
7
+ import { generateBacklogId } from './shared/generate-id.js';
8
+ const PRIORITIES = ['urgent', 'high', 'medium', 'low', 'none'];
9
+ const SCOPES = ['idea', 'task', 'feature', 'initiative'];
10
+ const SCOPE_ESTIMATES = ['XS', 'S', 'M', 'L', 'XL', 'XXL'];
11
+ const DESCRIPTION_PLACEHOLDER = '{Describe the problem, request, or capability tracked by this backlog item.}';
12
+ const DEFAULT_DEPENDENCIES = {
13
+ initializeBacklog,
14
+ regenerateBacklogIndex,
15
+ };
16
+ async function pathExists(path) {
17
+ try {
18
+ await access(path);
19
+ return true;
20
+ }
21
+ catch (error) {
22
+ const code = error && typeof error === 'object' && 'code' in error
23
+ ? String(error.code)
24
+ : null;
25
+ if (code !== 'ENOENT') {
26
+ throw error;
27
+ }
28
+ return false;
29
+ }
30
+ }
31
+ async function resolveBacklogTemplate(assetsRoot, templatesRoot) {
32
+ const candidates = [
33
+ ...(templatesRoot ? [join(templatesRoot, 'backlog-item.md')] : []),
34
+ join(assetsRoot, 'templates', 'backlog-item.md'),
35
+ ];
36
+ for (const path of candidates) {
37
+ try {
38
+ return { content: await readFile(path, 'utf8'), path };
39
+ }
40
+ catch (error) {
41
+ const code = error && typeof error === 'object' && 'code' in error
42
+ ? String(error.code)
43
+ : null;
44
+ if (code !== 'ENOENT') {
45
+ throw error;
46
+ }
47
+ }
48
+ }
49
+ throw new Error('Template backlog-item.md was not found in repo-local templates or bundled assets.');
50
+ }
51
+ function normalizeChoice(field, value, fallback, allowed, transform) {
52
+ const normalized = transform(value ?? fallback);
53
+ if (!allowed.includes(normalized)) {
54
+ throw new Error(`Invalid backlog ${field} "${value}". Valid values: ${allowed.join(', ')}.`);
55
+ }
56
+ return normalized;
57
+ }
58
+ function normalizeInputs(options) {
59
+ const title = options.title.trim();
60
+ if (!title ||
61
+ title.includes('\r') ||
62
+ title.includes('\n') ||
63
+ title.includes('\0')) {
64
+ throw new Error('Backlog title must be a non-empty single line.');
65
+ }
66
+ const date = new Date(options.createdAt ?? new Date().toISOString());
67
+ if (Number.isNaN(date.getTime())) {
68
+ throw new Error(`Invalid backlog creation timestamp: ${options.createdAt}`);
69
+ }
70
+ const priority = normalizeChoice('priority', options.priority, 'medium', PRIORITIES, (value) => value.trim().toLowerCase());
71
+ const scope = normalizeChoice('scope', options.scope, 'task', SCOPES, (value) => value.trim().toLowerCase());
72
+ const scopeEstimate = options.scopeEstimate === undefined
73
+ ? null
74
+ : normalizeChoice('scope estimate', options.scopeEstimate, '', SCOPE_ESTIMATES, (value) => value.trim().toUpperCase());
75
+ const labels = (options.labels ?? []).map((label) => label.trim());
76
+ if (labels.some((label) => !label ||
77
+ label.includes('\r') ||
78
+ label.includes('\n') ||
79
+ label.includes('\0'))) {
80
+ throw new Error('Backlog labels must be non-empty single-line values.');
81
+ }
82
+ if (options.description !== undefined &&
83
+ (!options.description.trim() || options.description.includes('\0'))) {
84
+ throw new Error('Backlog description must contain non-empty text.');
85
+ }
86
+ return {
87
+ title,
88
+ timestamp: date.toISOString(),
89
+ priority,
90
+ scope,
91
+ scopeEstimate,
92
+ labels,
93
+ description: options.description,
94
+ };
95
+ }
96
+ function renderBacklogItem(template, values) {
97
+ let body = stripTemplateFrontmatter(template).replace(/^\r?\n+/, '');
98
+ if (values.description !== undefined) {
99
+ body = body.replace(DESCRIPTION_PLACEHOLDER, () => values.description);
100
+ }
101
+ const frontmatter = YAML.stringify({
102
+ id: values.id,
103
+ title: values.title,
104
+ status: 'open',
105
+ priority: values.priority,
106
+ scope: values.scope,
107
+ scope_estimate: values.scopeEstimate,
108
+ labels: values.labels,
109
+ assignee: null,
110
+ created: values.timestamp,
111
+ updated: values.timestamp,
112
+ associated_issues: [],
113
+ external_plans: [],
114
+ }).trimEnd();
115
+ return `---\n${frontmatter}\n---\n\n${body.trimEnd()}\n`;
116
+ }
117
+ export async function createBacklogItem(options, overrides = {}) {
118
+ const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
119
+ const values = normalizeInputs(options);
120
+ const id = generateBacklogId(values.title, values.timestamp);
121
+ const filePath = join(options.backlogRoot, 'items', `${id}.md`);
122
+ const archivedPath = join(options.backlogRoot, 'archived', `${id}.md`);
123
+ if ((await pathExists(filePath)) || (await pathExists(archivedPath))) {
124
+ throw new Error(`Backlog item ${id} already exists. Use a more specific title to disambiguate.`);
125
+ }
126
+ const template = await resolveBacklogTemplate(options.assetsRoot, options.templatesRoot);
127
+ const content = renderBacklogItem(template.content, { id, ...values });
128
+ await dependencies.initializeBacklog(options.backlogRoot);
129
+ const indexPath = join(options.backlogRoot, 'index.md');
130
+ const originalIndex = await readFile(indexPath, 'utf8');
131
+ await writeFile(filePath, content, { encoding: 'utf8', flag: 'wx' });
132
+ let index;
133
+ try {
134
+ index = await dependencies.regenerateBacklogIndex(options.backlogRoot);
135
+ }
136
+ catch (error) {
137
+ const rollback = await Promise.allSettled([
138
+ rm(filePath),
139
+ writeFile(indexPath, originalIndex, 'utf8'),
140
+ ]);
141
+ const rollbackErrors = rollback
142
+ .filter((result) => result.status === 'rejected')
143
+ .map((result) => result.reason);
144
+ if (rollbackErrors.length > 0) {
145
+ const rollbackError = new AggregateError([error, ...rollbackErrors], `Backlog item ${id} creation failed and rollback was incomplete.`);
146
+ rollbackError.cause = error;
147
+ throw rollbackError;
148
+ }
149
+ throw error;
150
+ }
151
+ return {
152
+ id,
153
+ backlogRoot: options.backlogRoot,
154
+ filePath,
155
+ templatePath: template.path,
156
+ index,
157
+ };
158
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"regenerate-index.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/regenerate-index.ts"],"names":[],"mappings":"AAwGA,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,CAAC,CAsDvC"}
1
+ {"version":3,"file":"regenerate-index.d.ts","sourceRoot":"","sources":["../../../src/commands/backlog/regenerate-index.ts"],"names":[],"mappings":"AAgJA,MAAM,WAAW,4BAA4B;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,CAAC,CA6DvC"}
@@ -52,9 +52,29 @@ function compareItems(a, b) {
52
52
  }
53
53
  return 0;
54
54
  }
55
+ function encodeMarkdownTableCell(value) {
56
+ return value
57
+ .replaceAll('&', '&amp;')
58
+ .replaceAll('\\', '\\\\')
59
+ .replaceAll('|', '\\|')
60
+ .replaceAll('<', '&lt;')
61
+ .replaceAll('>', '&gt;')
62
+ .replaceAll('\r', ' ')
63
+ .replaceAll('\n', ' ');
64
+ }
55
65
  function renderManagedSection(items) {
56
66
  const rows = items.length > 0
57
- ? items.map((item) => `| ${item.id} | ${item.title} | ${item.status} | ${item.priority} | ${item.scope} | ${item.scopeEstimate} |`)
67
+ ? items.map((item) => {
68
+ const cells = [
69
+ item.id,
70
+ item.title,
71
+ item.status,
72
+ item.priority,
73
+ item.scope,
74
+ item.scopeEstimate,
75
+ ].map(encodeMarkdownTableCell);
76
+ return `| ${cells.join(' | ')} |`;
77
+ })
58
78
  : ['| _No backlog items yet_ | - | - | - | - | - |'];
59
79
  return [
60
80
  INDEX_START,
@@ -64,6 +84,23 @@ function renderManagedSection(items) {
64
84
  INDEX_END,
65
85
  ].join('\n');
66
86
  }
87
+ function findStandaloneMarker(content, marker, fromIndex = 0) {
88
+ let lineStart = 0;
89
+ while (lineStart <= content.length) {
90
+ const lineEnd = content.indexOf('\n', lineStart);
91
+ const contentEnd = lineEnd === -1 ? content.length : lineEnd;
92
+ const rawLine = content.slice(lineStart, contentEnd);
93
+ const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
94
+ if (lineStart >= fromIndex && line === marker) {
95
+ return lineStart;
96
+ }
97
+ if (lineEnd === -1) {
98
+ break;
99
+ }
100
+ lineStart = lineEnd + 1;
101
+ }
102
+ return -1;
103
+ }
67
104
  export async function regenerateBacklogIndex(backlogRoot) {
68
105
  const itemsDir = join(backlogRoot, 'items');
69
106
  const indexPath = join(backlogRoot, 'index.md');
@@ -85,8 +122,10 @@ export async function regenerateBacklogIndex(backlogRoot) {
85
122
  }
86
123
  items.sort(compareItems);
87
124
  const content = await readFile(indexPath, 'utf8');
88
- const startIndex = content.indexOf(INDEX_START);
89
- const endIndex = content.indexOf(INDEX_END);
125
+ const startIndex = findStandaloneMarker(content, INDEX_START);
126
+ const endIndex = startIndex === -1
127
+ ? -1
128
+ : findStandaloneMarker(content, INDEX_END, startIndex + INDEX_START.length);
90
129
  if (startIndex === -1 || endIndex === -1) {
91
130
  throw new Error(`Managed backlog index markers missing in ${indexPath}. Expected the exact marker pair:\n${INDEX_START}\n${INDEX_END}\nRun \`oat backlog init\` if the backlog scaffold is missing, or restore those exact markers in \`backlog/index.md\` before rerunning \`oat backlog regenerate-index\`.`);
92
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/decision/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAuB7D,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,yBAAyB,EAAE,OAAO,yBAAyB,CAAC;IAC5D,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAClD,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;CACvD;AA8CD,wBAAgB,qBAAqB,CACnC,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CA+LT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/decision/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAuB,MAAM,sBAAsB,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAyB7D,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,kBAAkB,EAAE,OAAO,kBAAkB,CAAC;IAC9C,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,yBAAyB,EAAE,OAAO,yBAAyB,CAAC;IAC5D,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,oBAAoB,EAAE,OAAO,oBAAoB,CAAC;IAClD,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;CACvD;AA8CD,wBAAgB,qBAAqB,CACnC,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAmMT"}
@@ -102,6 +102,8 @@ export function createDecisionCommand(overrides = {}) {
102
102
  .option('--decisions-root <path>', 'Decisions root directory (defaults to .oat/repo/reference/decisions)')
103
103
  .option('--status <status>', 'Decision status', 'proposed')
104
104
  .option('--context <text>', 'Initial context body text')
105
+ .option('--decision <text>', 'Initial decision body text')
106
+ .option('--consequences <text>', 'Initial consequences body text')
105
107
  .option('--created-at <timestamp>', 'Creation timestamp seed for reproducible ID generation')
106
108
  .action(async (title, options, command) => {
107
109
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
@@ -116,6 +118,8 @@ export function createDecisionCommand(overrides = {}) {
116
118
  title,
117
119
  status: options.status,
118
120
  context: options.context,
121
+ decision: options.decision,
122
+ consequences: options.consequences,
119
123
  createdAt: options.createdAt,
120
124
  });
121
125
  if (context.json) {
@@ -5,6 +5,8 @@ export interface CreateDecisionRecordOptions {
5
5
  title: string;
6
6
  status?: string;
7
7
  context?: string;
8
+ decision?: string;
9
+ consequences?: string;
8
10
  createdAt?: string;
9
11
  }
10
12
  export interface CreateDecisionRecordResult {
@@ -1 +1 @@
1
- {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/decision/new.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0HD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CA8CrC"}
1
+ {"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../../src/commands/decision/new.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,2BAA2B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA4HD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,0BAA0B,CAAC,CAgDrC"}
@@ -68,8 +68,8 @@ function renderDecisionRecord(template, options) {
68
68
  date: options.date,
69
69
  status: options.status,
70
70
  context: options.context,
71
- decision: 'TODO',
72
- consequences: 'TODO',
71
+ decision: options.decision,
72
+ consequences: options.consequences,
73
73
  });
74
74
  const frontmatter = YAML.stringify({
75
75
  id: options.id,
@@ -95,6 +95,8 @@ export async function createDecisionRecord(options) {
95
95
  date: createdAt.date,
96
96
  status,
97
97
  context: options.context ?? 'TODO',
98
+ decision: options.decision ?? 'TODO',
99
+ consequences: options.consequences ?? 'TODO',
98
100
  });
99
101
  await assertDecisionIndexScaffold(options.decisionsRoot);
100
102
  await mkdir(dirname(filePath), { recursive: true });
@@ -29,6 +29,7 @@ interface DoctorDependencies {
29
29
  processEnv: NodeJS.ProcessEnv;
30
30
  runPjmDoctorChecks: (repoRoot: string, options?: PjmDoctorOptions) => Promise<DoctorCheck[]>;
31
31
  checkSkillVersions: (scopeRoot: string, assetsRoot: string, pathExists: (path: string) => Promise<boolean>) => Promise<SkillVersionReport>;
32
+ checkStaleInvocations: (repoRoot: string) => Promise<DoctorCheck>;
32
33
  }
33
34
  interface OutdatedSkillVersion {
34
35
  skill: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAY9B,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAO9D,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,UAAU,kBAAkB;IAC1B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,gBAAgB,EAAE,CAChB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,cAAc,KACpB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1D,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,cAAc,EAAE,CACd,SAAS,EAAE,MAAM,KACd,OAAO,CACV,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CACnE,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5B,kBAAkB,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,KAC3C,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAClC;AAED,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,UAAU,kBAAkB;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0BAA0B,EAAE,MAAM,CAAC;IACnC,cAAc,EAAE,oBAAoB,EAAE,CAAC;CACxC;AAkxBD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,kBAAkB,CAAM,GAC1C,OAAO,CAcT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAY9B,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,cAAc,EAGnB,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAO9D,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,UAAU,kBAAkB;IAC1B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,gBAAgB,EAAE,CAChB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,cAAc,KACpB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1D,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,cAAc,EAAE,CACd,SAAS,EAAE,MAAM,KACd,OAAO,CACV,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CACnE,CAAC;IACF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7C,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,0BAA0B,EAAE,OAAO,0BAA0B,CAAC;IAC9D,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5B,kBAAkB,EAAE,CAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,KAC3C,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,qBAAqB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;CACnE;AAED,UAAU,oBAAoB;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,UAAU,kBAAkB;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0BAA0B,EAAE,MAAM,CAAC;IACnC,cAAc,EAAE,oBAAoB,EAAE,CAAC;CACxC;AAqxBD,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,kBAAkB,CAAM,GAC1C,OAAO,CAcT"}
@@ -23,6 +23,7 @@ import { validateMatrixCell, } from '../../providers/identity/availability.js';
23
23
  import { createDispatchValidationPassContext, validateDispatchMatrixRefs, } from '../../providers/identity/dispatch-validation.js';
24
24
  import { formatDoctorResults } from '../../ui/output.js';
25
25
  import { Command } from 'commander';
26
+ import { checkStaleInvocations } from './stale-invocations.js';
26
27
  async function pathExistsDefault(path) {
27
28
  try {
28
29
  await access(path);
@@ -143,6 +144,7 @@ function createDependencies() {
143
144
  // Default binding remains self-contained, but still honors the caller-
144
145
  // provided pathExists dependency from runChecksForScope when available.
145
146
  checkSkillVersions: (scopeRoot, assetsRoot, pathExists = pathExistsDefault) => checkSkillVersionsDefault(scopeRoot, assetsRoot, pathExists),
147
+ checkStaleInvocations,
146
148
  };
147
149
  }
148
150
  function collectDispatchMatrixCellRefs(layers) {
@@ -505,6 +507,7 @@ async function runChecksForScope(scope, scopeRoot, userConfigDir, dependencies)
505
507
  });
506
508
  checks.push(...(await runCodexChecksForScope(scope, scopeRoot, dirname(userConfigDir), dependencies)));
507
509
  if (scope === 'project') {
510
+ checks.push(await dependencies.checkStaleInvocations(scopeRoot));
508
511
  try {
509
512
  const assetsRoot = await dependencies.resolveAssetsRoot();
510
513
  const skillVersions = await dependencies.checkSkillVersions(scopeRoot, assetsRoot, dependencies.pathExists);
@@ -0,0 +1,3 @@
1
+ import type { DoctorCheck } from '../../ui/output.js';
2
+ export declare function checkStaleInvocations(repoRoot: string): Promise<DoctorCheck>;
3
+ //# sourceMappingURL=stale-invocations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stale-invocations.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/stale-invocations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoK9C,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CA6CtB"}