@open-agent-toolkit/cli 0.1.64 → 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 (38) hide show
  1. package/assets/docs/cli-utilities/backlog-lifecycle.md +2 -2
  2. package/assets/docs/cli-utilities/config-and-local-state.md +20 -6
  3. package/assets/docs/cli-utilities/tool-packs.md +1 -0
  4. package/assets/docs/cli-utilities/workflow-gates.md +3 -1
  5. package/assets/docs/contributing/code.md +18 -0
  6. package/assets/public-package-versions.json +4 -4
  7. package/assets/skills/oat-pjm-add-backlog-item/SKILL.md +17 -72
  8. package/assets/skills/oat-project-summary/SKILL.md +9 -4
  9. package/assets/templates/plan.md +2 -0
  10. package/dist/commands/backlog/archive.d.ts.map +1 -1
  11. package/dist/commands/backlog/archive.js +5 -5
  12. package/dist/commands/backlog/index.d.ts +4 -0
  13. package/dist/commands/backlog/index.d.ts.map +1 -1
  14. package/dist/commands/backlog/index.js +55 -0
  15. package/dist/commands/backlog/new.d.ts +27 -0
  16. package/dist/commands/backlog/new.d.ts.map +1 -0
  17. package/dist/commands/backlog/new.js +158 -0
  18. package/dist/commands/backlog/regenerate-index.d.ts.map +1 -1
  19. package/dist/commands/backlog/regenerate-index.js +42 -3
  20. package/dist/commands/decision/index.d.ts.map +1 -1
  21. package/dist/commands/decision/index.js +4 -0
  22. package/dist/commands/decision/new.d.ts +2 -0
  23. package/dist/commands/decision/new.d.ts.map +1 -1
  24. package/dist/commands/decision/new.js +4 -2
  25. package/dist/commands/doctor/index.d.ts +1 -0
  26. package/dist/commands/doctor/index.d.ts.map +1 -1
  27. package/dist/commands/doctor/index.js +3 -0
  28. package/dist/commands/doctor/stale-invocations.d.ts +3 -0
  29. package/dist/commands/doctor/stale-invocations.d.ts.map +1 -0
  30. package/dist/commands/doctor/stale-invocations.js +173 -0
  31. package/dist/commands/gate/index.d.ts +1 -0
  32. package/dist/commands/gate/index.d.ts.map +1 -1
  33. package/dist/commands/gate/index.js +5 -1
  34. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  35. package/dist/commands/project/new/scaffold.js +24 -9
  36. package/dist/commands/tools/update/index.d.ts.map +1 -1
  37. package/dist/commands/tools/update/index.js +22 -12
  38. package/package.json +2 -2
@@ -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('&', '&')
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":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"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"}
@@ -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"}
@@ -0,0 +1,173 @@
1
+ import { readdir, readFile, stat } from 'node:fs/promises';
2
+ import { extname, join } from 'node:path';
3
+ const KNOWN_STALE_INVOCATIONS = [
4
+ {
5
+ id: 'global-scope-sync',
6
+ pattern: /\boat\s+--scope(?:\s+|=)all\s+sync\b/g,
7
+ replacement: 'oat sync --scope all',
8
+ },
9
+ ];
10
+ const SCANNED_EXTENSIONS = new Set([
11
+ '.bash',
12
+ '.cjs',
13
+ '.fish',
14
+ '.js',
15
+ '.json',
16
+ '.jsonc',
17
+ '.md',
18
+ '.mdc',
19
+ '.mdx',
20
+ '.mjs',
21
+ '.mts',
22
+ '.sh',
23
+ '.toml',
24
+ '.ts',
25
+ '.yaml',
26
+ '.yml',
27
+ '.zsh',
28
+ ]);
29
+ const EXCLUDED_DIRECTORY_NAMES = new Set([
30
+ '.git',
31
+ '.next',
32
+ '.turbo',
33
+ 'archived',
34
+ 'build',
35
+ 'coverage',
36
+ 'dist',
37
+ 'node_modules',
38
+ 'out',
39
+ ]);
40
+ const GENERATED_PROVIDER_ROOTS = new Set([
41
+ '.claude',
42
+ '.codex',
43
+ '.cursor',
44
+ '.gemini',
45
+ ]);
46
+ const GENERATED_GITHUB_DIRECTORIES = new Set([
47
+ 'agents',
48
+ 'instructions',
49
+ 'prompts',
50
+ 'skills',
51
+ ]);
52
+ const ALLOW_MARKER = 'oat-doctor: allow-stale-invocation';
53
+ const MAX_FILE_BYTES = 1024 * 1024;
54
+ const MAX_SCANNED_FILES = 10_000;
55
+ const MAX_REPORTED_MATCHES = 50;
56
+ function isExcludedDirectory(parts) {
57
+ const name = parts.at(-1);
58
+ if (!name || EXCLUDED_DIRECTORY_NAMES.has(name)) {
59
+ return true;
60
+ }
61
+ if (parts.length === 1 && name === '.worktrees') {
62
+ return true;
63
+ }
64
+ if (GENERATED_PROVIDER_ROOTS.has(name)) {
65
+ return true;
66
+ }
67
+ const parent = parts.at(-2);
68
+ if (parent === '.oat' && (name === 'projects' || name === 'sync')) {
69
+ return true;
70
+ }
71
+ return parent === '.github' && GENERATED_GITHUB_DIRECTORIES.has(name);
72
+ }
73
+ function isScannableFile(relativePath) {
74
+ const parts = relativePath.split('/');
75
+ const fileName = parts.at(-1) ?? '';
76
+ if (/\.(?:test|spec)\.[^.]+$/.test(fileName)) {
77
+ return false;
78
+ }
79
+ if (fileName === 'copilot-instructions.md' && parts.at(-2) === '.github') {
80
+ return false;
81
+ }
82
+ return SCANNED_EXTENSIONS.has(extname(fileName).toLowerCase());
83
+ }
84
+ async function listScannableFiles(repoRoot) {
85
+ const files = [];
86
+ async function walk(parts) {
87
+ if (files.length >= MAX_SCANNED_FILES) {
88
+ return;
89
+ }
90
+ const entries = await readdir(join(repoRoot, ...parts), {
91
+ withFileTypes: true,
92
+ });
93
+ entries.sort((left, right) => left.name.localeCompare(right.name));
94
+ for (const entry of entries) {
95
+ if (files.length >= MAX_SCANNED_FILES) {
96
+ break;
97
+ }
98
+ if (entry.isSymbolicLink()) {
99
+ continue;
100
+ }
101
+ const childParts = [...parts, entry.name];
102
+ if (entry.isDirectory()) {
103
+ if (!isExcludedDirectory(childParts)) {
104
+ await walk(childParts);
105
+ }
106
+ continue;
107
+ }
108
+ const relativePath = childParts.join('/');
109
+ if (entry.isFile() && isScannableFile(relativePath)) {
110
+ files.push(relativePath);
111
+ }
112
+ }
113
+ }
114
+ await walk([]);
115
+ return files;
116
+ }
117
+ function findMatches(relativePath, content) {
118
+ const matches = [];
119
+ const lines = content.split(/\r?\n/);
120
+ for (const [lineIndex, line] of lines.entries()) {
121
+ if (line.includes(ALLOW_MARKER)) {
122
+ continue;
123
+ }
124
+ for (const stale of KNOWN_STALE_INVOCATIONS) {
125
+ stale.pattern.lastIndex = 0;
126
+ for (const match of line.matchAll(stale.pattern)) {
127
+ matches.push({
128
+ relativePath,
129
+ line: lineIndex + 1,
130
+ invocation: match[0],
131
+ replacement: stale.replacement,
132
+ });
133
+ }
134
+ }
135
+ }
136
+ return matches;
137
+ }
138
+ export async function checkStaleInvocations(repoRoot) {
139
+ const matches = [];
140
+ for (const relativePath of await listScannableFiles(repoRoot)) {
141
+ const absolutePath = join(repoRoot, relativePath);
142
+ const metadata = await stat(absolutePath);
143
+ if (metadata.size > MAX_FILE_BYTES) {
144
+ continue;
145
+ }
146
+ matches.push(...findMatches(relativePath, await readFile(absolutePath, 'utf8')));
147
+ if (matches.length >= MAX_REPORTED_MATCHES) {
148
+ break;
149
+ }
150
+ }
151
+ if (matches.length === 0) {
152
+ return {
153
+ name: 'project:stale_invocations',
154
+ description: 'Known-stale CLI invocation grammar',
155
+ status: 'pass',
156
+ message: 'No known-stale CLI invocations found in repository scripts or documentation.',
157
+ };
158
+ }
159
+ const evidence = matches
160
+ .slice(0, MAX_REPORTED_MATCHES)
161
+ .map((match) => `${match.relativePath}:${match.line} (\`${match.invocation}\` → \`${match.replacement}\`)`)
162
+ .join(', ');
163
+ const replacements = [...new Set(matches.map((match) => match.replacement))];
164
+ return {
165
+ name: 'project:stale_invocations',
166
+ description: 'Known-stale CLI invocation grammar',
167
+ status: 'warn',
168
+ message: `Known-stale CLI invocations found: ${evidence}.`,
169
+ fix: `Update each invocation to the current command grammar: ${replacements
170
+ .map((replacement) => `\`${replacement}\``)
171
+ .join(', ')}.`,
172
+ };
173
+ }
@@ -26,6 +26,7 @@ interface ProcessRunOptions {
26
26
  livenessIntervalMs?: number;
27
27
  onLiveness?: (snapshot: GateLivenessSnapshot) => void;
28
28
  purpose: 'host-detection' | 'availability' | 'execute';
29
+ stdin: 'ignore' | 'inherit';
29
30
  stdio: 'ignore' | 'inherit' | 'pipe';
30
31
  timeoutMs: number;
31
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/gate/index.ts"],"names":[],"mappings":"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"}
@@ -206,7 +206,9 @@ async function runChildProcess(command, args, options) {
206
206
  const child = spawn(command, args, {
207
207
  cwd: options.cwd,
208
208
  env: options.env,
209
- stdio: options.stdio === 'pipe' ? ['inherit', 'pipe', 'pipe'] : options.stdio,
209
+ stdio: options.stdio === 'pipe'
210
+ ? [options.stdin, 'pipe', 'pipe']
211
+ : options.stdio,
210
212
  });
211
213
  const recordActivity = () => {
212
214
  lastActivityAt = Date.now();
@@ -814,6 +816,7 @@ async function checkArgv(argv, purpose, context, dependencies) {
814
816
  cwd: context.cwd,
815
817
  env: dependencies.processEnv,
816
818
  purpose,
819
+ stdin: 'ignore',
817
820
  stdio: 'ignore',
818
821
  timeoutMs: GATE_CHECK_TIMEOUT_MS,
819
822
  });
@@ -898,6 +901,7 @@ async function executeTarget(selected, prompt, context, dependencies) {
898
901
  }
899
902
  },
900
903
  purpose: 'execute',
904
+ stdin: 'ignore',
901
905
  stdio: 'pipe',
902
906
  timeoutMs,
903
907
  });
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA2SD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAoFhC"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,WAAW,GACX,kBAAkB,GAClB,qBAAqB,GACrB,iBAAiB,GACjB,QAAQ,CAAC;AAEb,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,oBAAoB,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA4UD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAoFhC"}
@@ -20,6 +20,7 @@ const TEMPLATES_BY_MODE = {
20
20
  quick: ['state.md', 'discovery.md', 'plan.md', 'implementation.md'],
21
21
  import: ['state.md', 'plan.md', 'implementation.md'],
22
22
  };
23
+ const OAT_PLACEHOLDER_PATTERN = /(?<!\{)\{\s*(OAT_[A-Z0-9_]+)\s*\}(?!\})/g;
23
24
  const STATE_TEMPLATE_BY_MODE = {
24
25
  'spec-driven': {
25
26
  hillCheckpoints: "['discovery', 'design']",
@@ -79,6 +80,17 @@ const STATE_TEMPLATE_BY_MODE = {
79
80
  nextMilestone: 'Run `oat-project-import-plan` to normalize the external plan',
80
81
  },
81
82
  };
83
+ function replaceOatPlaceholders(template, replacements) {
84
+ return template.replace(OAT_PLACEHOLDER_PATTERN, (placeholder, token) => replacements[token] ?? placeholder);
85
+ }
86
+ function assertNoUnresolvedOatPlaceholders(rendered, templateFile) {
87
+ const unresolved = [
88
+ ...new Set([...rendered.matchAll(OAT_PLACEHOLDER_PATTERN)].map(([placeholder]) => placeholder)),
89
+ ];
90
+ if (unresolved.length > 0) {
91
+ throw new Error(`Cannot scaffold ${templateFile}: unresolved OAT placeholder(s): ${unresolved.join(', ')}`);
92
+ }
93
+ }
82
94
  function validateProjectName(name) {
83
95
  if (name.startsWith('-')) {
84
96
  throw new Error(`Invalid project name "${name}". Project names must not start with a dash.`);
@@ -89,17 +101,19 @@ function validateProjectName(name) {
89
101
  }
90
102
  function applyTemplateReplacements(template, projectName, today, nowUtc, mode) {
91
103
  const stateContent = STATE_TEMPLATE_BY_MODE[mode];
92
- return template
104
+ const oatReplacements = {
105
+ OAT_HILL_CHECKPOINTS: stateContent.hillCheckpoints,
106
+ OAT_WORKFLOW_MODE: mode,
107
+ OAT_PHASE: stateContent.phase,
108
+ OAT_STATUS: stateContent.status,
109
+ OAT_CURRENT_PHASE: stateContent.currentPhase,
110
+ OAT_ARTIFACTS: stateContent.artifacts.join('\n'),
111
+ OAT_PROGRESS: stateContent.progress.join('\n'),
112
+ OAT_NEXT_MILESTONE: stateContent.nextMilestone,
113
+ };
114
+ return replaceOatPlaceholders(template, oatReplacements)
93
115
  .replaceAll('{Project Name}', projectName)
94
116
  .replaceAll('YYYY-MM-DD', today)
95
- .replaceAll('{OAT_HILL_CHECKPOINTS}', stateContent.hillCheckpoints)
96
- .replaceAll('{OAT_WORKFLOW_MODE}', mode)
97
- .replaceAll('{OAT_PHASE}', stateContent.phase)
98
- .replaceAll('{OAT_STATUS}', stateContent.status)
99
- .replaceAll('{OAT_CURRENT_PHASE}', stateContent.currentPhase)
100
- .replaceAll('{OAT_ARTIFACTS}', stateContent.artifacts.join('\n'))
101
- .replaceAll('{OAT_PROGRESS}', stateContent.progress.join('\n'))
102
- .replaceAll('{OAT_NEXT_MILESTONE}', stateContent.nextMilestone)
103
117
  .replaceAll(/oat_project_created:\s*null/gi, `oat_project_created: "${nowUtc}"`)
104
118
  .replaceAll(/oat_project_state_updated:\s*null/gi, `oat_project_state_updated: "${nowUtc}"`)
105
119
  .replaceAll(/\n?oat_template:\s*true\s*\n/gi, '\n')
@@ -176,6 +190,7 @@ async function scaffoldModeTemplates(userOatRoot, repoRoot, projectPath, project
176
190
  }
177
191
  const template = await readFile(src, 'utf8');
178
192
  const rendered = applyTemplateReplacements(template, projectName, today, nowUtc, mode);
193
+ assertNoUnresolvedOatPlaceholders(rendered, templateFile);
179
194
  if (templateFile === 'state.md') {
180
195
  assertValidProjectStateContent(rendered, { filePath: dest });
181
196
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/update/index.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAKvE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAE7B,MAAM,gBAAgB,CAAC;AAgBxB,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GAClD,MAAM,EAAE,CAaV;AAgCD,wBAAgB,wBAAwB,CACtC,YAAY,GAAE,uBAA6C,EAC3D,gBAAgB,GAAE,oBAA8C,GAC/D,OAAO,CAoKT;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAI7E;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,GACnB,OAAO,CAOT;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,OAAO,GACd,MAAM,CAMR"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/update/index.ts"],"names":[],"mappings":"AAeA,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAKvE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAE7B,MAAM,gBAAgB,CAAC;AAgBxB,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GAClD,MAAM,EAAE,CAaV;AAgCD,wBAAgB,wBAAwB,CACtC,YAAY,GAAE,uBAA6C,EAC3D,gBAAgB,GAAE,oBAA8C,GAC/D,OAAO,CAqKT;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAI7E;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,GACnB,OAAO,CAOT;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,OAAO,GACd,MAAM,CAMR"}