@open-agent-toolkit/cli 0.3.19 → 0.3.21

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 (121) hide show
  1. package/README.md +2 -1
  2. package/assets/agents/oat-phase-implementer.md +26 -3
  3. package/assets/agents/oat-reviewer.md +8 -3
  4. package/assets/bundle-metadata.json +1 -1
  5. package/assets/docs/reference/cli-reference.md +12 -7
  6. package/assets/docs/reference/oat-directory-structure.md +1 -0
  7. package/assets/docs/reference/project-artifacts.md +56 -1
  8. package/assets/docs/reference/project-state-machine.md +41 -7
  9. package/assets/docs/skills/brainstorm.md +3 -2
  10. package/assets/docs/skills/index.md +13 -10
  11. package/assets/docs/workflows/advanced/autonomy.md +3 -1
  12. package/assets/docs/workflows/advanced/dispatch-ceiling.md +2 -2
  13. package/assets/docs/workflows/advanced/workflow-gates.md +17 -4
  14. package/assets/docs/workflows/approvals-and-automation.md +21 -10
  15. package/assets/docs/workflows/choose-workflow.md +17 -9
  16. package/assets/docs/workflows/ideas/index.md +1 -1
  17. package/assets/docs/workflows/ideas/lifecycle.md +3 -2
  18. package/assets/docs/workflows/projects/closeout/closeout-skills.md +80 -7
  19. package/assets/docs/workflows/projects/closeout/pr-flow.md +7 -1
  20. package/assets/docs/workflows/projects/execution/execution-skills.md +16 -3
  21. package/assets/docs/workflows/projects/execution/implementation-execution.md +1 -1
  22. package/assets/docs/workflows/projects/execution/picking-up-projects.md +11 -1
  23. package/assets/docs/workflows/projects/lifecycle.md +108 -9
  24. package/assets/docs/workflows/projects/planning/hill-checkpoints.md +6 -6
  25. package/assets/docs/workflows/projects/planning/planning-skills.md +9 -7
  26. package/assets/docs/workflows/projects/planning/starting-projects.md +57 -11
  27. package/assets/docs/workflows/projects/reviews/index.md +22 -1
  28. package/assets/docs/workflows/projects/reviews/review-flavors.md +5 -3
  29. package/assets/public-package-versions.json +4 -4
  30. package/assets/skills/oat-brainstorm/SKILL.md +34 -9
  31. package/assets/skills/oat-brainstorm/references/destinations.md +6 -3
  32. package/assets/skills/oat-dispatch-subagents/SKILL.md +1 -1
  33. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +6 -0
  34. package/assets/skills/oat-docs/SKILL.md +7 -3
  35. package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
  36. package/assets/skills/oat-project-autonomous/SKILL.md +9 -3
  37. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +151 -132
  38. package/assets/skills/oat-project-brief/SKILL.md +1 -1
  39. package/assets/skills/oat-project-brief/references/brief-contract.md +465 -9
  40. package/assets/skills/oat-project-brief/references/docs/autonomy-contract.md +151 -132
  41. package/assets/skills/oat-project-brief/references/docs/gate-approval-record.md +9 -0
  42. package/assets/skills/oat-project-capture/SKILL.md +2 -2
  43. package/assets/skills/oat-project-complete/SKILL.md +14 -5
  44. package/assets/skills/oat-project-discover/SKILL.md +3 -2
  45. package/assets/skills/oat-project-document/SKILL.md +12 -6
  46. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +151 -132
  47. package/assets/skills/oat-project-goal/SKILL.md +697 -0
  48. package/assets/skills/oat-project-goal/references/docs/autonomy-contract.md +412 -0
  49. package/assets/skills/oat-project-goal/references/docs/gate-approval-record.md +93 -0
  50. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  51. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +67 -0
  52. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +151 -132
  53. package/assets/skills/oat-project-implement/references/docs/gate-approval-record.md +9 -0
  54. package/assets/skills/oat-project-implement/references/phase-execution.md +10 -3
  55. package/assets/skills/oat-project-implement/references/plan-and-resume.md +28 -1
  56. package/assets/skills/oat-project-lite/SKILL.md +1 -1
  57. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +151 -132
  58. package/assets/skills/oat-project-new/SKILL.md +3 -3
  59. package/assets/skills/oat-project-next/SKILL.md +45 -15
  60. package/assets/skills/oat-project-next/references/docs/gate-approval-record.md +9 -0
  61. package/assets/skills/oat-project-plan/SKILL.md +14 -3
  62. package/assets/skills/oat-project-plan-writing/SKILL.md +6 -4
  63. package/assets/skills/oat-project-pr-final/SKILL.md +51 -1
  64. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +151 -132
  65. package/assets/skills/oat-project-pr-progress/SKILL.md +97 -3
  66. package/assets/skills/oat-project-progress/SKILL.md +38 -5
  67. package/assets/skills/oat-project-progress/references/docs/gate-approval-record.md +9 -0
  68. package/assets/skills/oat-project-quick-start/SKILL.md +14 -9
  69. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +151 -132
  70. package/assets/skills/oat-project-quick-start/references/docs/gate-approval-record.md +9 -0
  71. package/assets/skills/oat-project-reconcile/SKILL.md +31 -2
  72. package/assets/skills/oat-project-review-provide/SKILL.md +79 -13
  73. package/assets/skills/oat-project-review-receive/SKILL.md +70 -1
  74. package/assets/skills/oat-project-revise/SKILL.md +25 -1
  75. package/assets/skills/oat-project-summary/SKILL.md +60 -5
  76. package/assets/skills/oat-wave-execute/SKILL.md +2 -2
  77. package/assets/templates/state.md +1 -1
  78. package/dist/commands/project/closeout-check/closeout-invariant.d.ts +119 -3
  79. package/dist/commands/project/closeout-check/closeout-invariant.d.ts.map +1 -1
  80. package/dist/commands/project/closeout-check/closeout-invariant.js +454 -2
  81. package/dist/commands/project/closeout-check/index.d.ts +21 -3
  82. package/dist/commands/project/closeout-check/index.d.ts.map +1 -1
  83. package/dist/commands/project/closeout-check/index.js +190 -4
  84. package/dist/commands/project/complete-state/index.d.ts +2 -1
  85. package/dist/commands/project/complete-state/index.d.ts.map +1 -1
  86. package/dist/commands/project/complete-state/index.js +7 -1
  87. package/dist/commands/project/delivery/check.d.ts +66 -0
  88. package/dist/commands/project/delivery/check.d.ts.map +1 -0
  89. package/dist/commands/project/delivery/check.js +232 -0
  90. package/dist/commands/project/delivery/evidence.d.ts +171 -0
  91. package/dist/commands/project/delivery/evidence.d.ts.map +1 -0
  92. package/dist/commands/project/delivery/evidence.js +902 -0
  93. package/dist/commands/project/delivery/index.d.ts +7 -0
  94. package/dist/commands/project/delivery/index.d.ts.map +1 -0
  95. package/dist/commands/project/delivery/index.js +13 -0
  96. package/dist/commands/project/delivery/refresh.d.ts +110 -0
  97. package/dist/commands/project/delivery/refresh.d.ts.map +1 -0
  98. package/dist/commands/project/delivery/refresh.js +441 -0
  99. package/dist/commands/project/index.d.ts.map +1 -1
  100. package/dist/commands/project/index.js +2 -0
  101. package/dist/commands/project/log/append.d.ts +6 -0
  102. package/dist/commands/project/log/append.d.ts.map +1 -1
  103. package/dist/commands/project/log/append.js +6 -1
  104. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  105. package/dist/commands/project/new/scaffold.js +47 -1
  106. package/dist/commands/project/promote/promote.d.ts +15 -1
  107. package/dist/commands/project/promote/promote.d.ts.map +1 -1
  108. package/dist/commands/project/promote/promote.js +99 -15
  109. package/dist/commands/project/validate-plan/index.d.ts.map +1 -1
  110. package/dist/commands/project/validate-plan/index.js +10 -0
  111. package/dist/commands/project/validate-plan/profiles.d.ts +19 -4
  112. package/dist/commands/project/validate-plan/profiles.d.ts.map +1 -1
  113. package/dist/commands/project/validate-plan/profiles.js +153 -2
  114. package/dist/commands/shared/frontmatter.d.ts +1 -1
  115. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  116. package/dist/commands/shared/frontmatter.js +1 -0
  117. package/dist/commands/state/generate.d.ts.map +1 -1
  118. package/dist/commands/state/generate.js +35 -4
  119. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  120. package/dist/commands/tools/shared/pack-manifest.js +1 -0
  121. package/package.json +2 -2
@@ -1,14 +1,27 @@
1
- import { readFile as defaultReadFile } from 'node:fs/promises';
2
- import { isAbsolute, join } from 'node:path';
1
+ import { readFile as defaultReadFile, readdir } from 'node:fs/promises';
2
+ import { basename, dirname, isAbsolute, join } from 'node:path';
3
3
  import { buildCommandContext } from '../../../app/command-context.js';
4
+ import { loadGoalDeliveryProject } from '../../project/delivery/check.js';
5
+ import { deliveryEvidenceScope, evaluateDeliveryReview, evaluateGateCarrier, } from '../../project/delivery/evidence.js';
6
+ import { observeMember } from '../../project/delivery/refresh.js';
7
+ import { defaultGhRunner, } from '../../project/links/refresh.js';
8
+ import { defaultGitRunner } from '../../project/sync/git.js';
4
9
  import { readGlobalOptions } from '../../shared/shared.utils.js';
5
10
  import { resolveEffectiveConfig } from '../../../config/resolve.js';
6
11
  import { CliError } from '../../../errors/cli-error.js';
7
12
  import { dirExists, fileExists } from '../../../fs/io.js';
8
13
  import { resolveProjectRoot } from '../../../fs/paths.js';
14
+ import { getProjectState, parsePhaseMarkers, } from '@open-agent-toolkit/control-plane';
9
15
  import { Command } from 'commander';
10
- import { evaluateCloseout, formatCloseoutRefusal, } from './closeout-invariant.js';
11
- export { evaluateCloseout, formatCloseoutRefusal, } from './closeout-invariant.js';
16
+ import YAML from 'yaml';
17
+ import { evaluateCloseout, formatCloseoutRefusal, readStateWorkflowMode, resolveDeliveryGraph, } from './closeout-invariant.js';
18
+ export { evaluateCloseout, evaluateGoalBoundary, evaluateGoalCoverage, formatCloseoutRefusal, resolveDeliveryGraph, } from './closeout-invariant.js';
19
+ async function listDirectories(path) {
20
+ const entries = await readdir(path, { withFileTypes: true });
21
+ return entries
22
+ .filter((entry) => entry.isDirectory())
23
+ .map((entry) => entry.name);
24
+ }
12
25
  export const DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES = {
13
26
  buildCommandContext,
14
27
  resolveProjectRoot,
@@ -17,7 +30,176 @@ export const DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES = {
17
30
  dirExists,
18
31
  fileExists,
19
32
  env: process.env,
33
+ gh: defaultGhRunner,
34
+ git: defaultGitRunner,
35
+ getProjectState,
36
+ listDirectories,
20
37
  };
38
+ async function readOptional(dependencies, path) {
39
+ try {
40
+ return await dependencies.readFile(path, 'utf8');
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ function frontmatterRecord(content) {
47
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
48
+ if (!match)
49
+ return null;
50
+ try {
51
+ const parsed = YAML.parse(match[1] ?? '');
52
+ return parsed !== null &&
53
+ typeof parsed === 'object' &&
54
+ !Array.isArray(parsed)
55
+ ? parsed
56
+ : null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * Find an extracted phase's child project by slug under any scope of the
64
+ * projects root, and read its lifecycle and `oat_extracted_from` backlink.
65
+ */
66
+ async function observeChild(projectsRoot, slug, dependencies) {
67
+ const unresolved = (status) => ({
68
+ slug,
69
+ status,
70
+ lifecycle: null,
71
+ extractedFrom: null,
72
+ });
73
+ let scopes;
74
+ try {
75
+ scopes = await dependencies.listDirectories(projectsRoot);
76
+ }
77
+ catch {
78
+ return unresolved('unreadable');
79
+ }
80
+ const candidates = [];
81
+ for (const scope of scopes) {
82
+ const statePath = join(projectsRoot, scope, slug, 'state.md');
83
+ if (await dependencies.fileExists(statePath)) {
84
+ candidates.push(statePath);
85
+ }
86
+ }
87
+ if (candidates.length === 0)
88
+ return unresolved('missing');
89
+ if (candidates.length > 1)
90
+ return unresolved('ambiguous');
91
+ const content = await readOptional(dependencies, candidates[0]);
92
+ const frontmatter = content === null ? null : frontmatterRecord(content);
93
+ if (frontmatter === null)
94
+ return unresolved('unreadable');
95
+ const read = (key) => typeof frontmatter[key] === 'string' ? frontmatter[key] : null;
96
+ return {
97
+ slug,
98
+ status: 'found',
99
+ lifecycle: read('oat_lifecycle'),
100
+ extractedFrom: read('oat_extracted_from'),
101
+ };
102
+ }
103
+ /**
104
+ * Load the goal boundary inputs read-only (A3): the brief, the journal, the
105
+ * registry with fresh member observations merged in (never persisted; that is
106
+ * `delivery refresh`'s job), each extracted child's facts, and the shared
107
+ * evaluator's verdict for every terminal delivery's effective scope.
108
+ */
109
+ export async function loadGoalCloseoutInputs(repoRoot, targetProjectPath, dependencies) {
110
+ const planContent = await readOptional(dependencies, join(targetProjectPath, 'plan.md'));
111
+ const implementationContent = await readOptional(dependencies, join(targetProjectPath, 'implementation.md'));
112
+ const inputs = {
113
+ goalSlug: basename(targetProjectPath),
114
+ planContent,
115
+ implementationContent,
116
+ deliveries: null,
117
+ observationFailures: [],
118
+ children: [],
119
+ evidence: {},
120
+ };
121
+ if (implementationContent !== null) {
122
+ const projectsRoot = dirname(dirname(targetProjectPath));
123
+ const slugs = new Set();
124
+ for (const markers of parsePhaseMarkers(implementationContent).values()) {
125
+ if (markers.status === 'extracted' && markers.extractedTo !== null) {
126
+ slugs.add(markers.extractedTo);
127
+ }
128
+ }
129
+ for (const slug of slugs) {
130
+ inputs.children.push(await observeChild(projectsRoot, slug, dependencies));
131
+ }
132
+ }
133
+ let project;
134
+ try {
135
+ project = await loadGoalDeliveryProject(targetProjectPath, repoRoot, {
136
+ resolveProjectRoot: async () => repoRoot,
137
+ readFile: (path, encoding) => dependencies.readFile(path, encoding),
138
+ getProjectState: dependencies.getProjectState,
139
+ });
140
+ }
141
+ catch {
142
+ return inputs;
143
+ }
144
+ const registered = project.state.deliveries ?? [];
145
+ // Observe every member of every non-superseded delivery read-only.
146
+ const observed = [];
147
+ for (const delivery of registered) {
148
+ if (delivery.status === 'superseded' || delivery.status === 'registering') {
149
+ observed.push(delivery);
150
+ continue;
151
+ }
152
+ const members = [];
153
+ for (const member of delivery.members) {
154
+ const observation = member.url === null
155
+ ? 'a member has no URL to observe'
156
+ : await observeMember(dependencies.gh, member.url);
157
+ if (typeof observation === 'string') {
158
+ inputs.observationFailures.push(`${delivery.delivery_id} ${member.url ?? '(no url)'}: ${observation}`);
159
+ members.push(member);
160
+ }
161
+ else {
162
+ members.push({
163
+ ...member,
164
+ observed_head: observation.head,
165
+ observed_base: observation.base,
166
+ status: observation.status,
167
+ });
168
+ }
169
+ }
170
+ observed.push({ ...delivery, members });
171
+ }
172
+ inputs.deliveries = observed;
173
+ if (inputs.observationFailures.length > 0) {
174
+ return inputs;
175
+ }
176
+ // Evaluate evidence for every terminal delivery over its effective scope.
177
+ const graph = resolveDeliveryGraph(observed, project.state.progress.phases.map((phase) => phase.phaseId));
178
+ for (const terminal of graph.terminals) {
179
+ const { delivery, effectiveScope } = terminal;
180
+ if (delivery.status === 'registering')
181
+ continue;
182
+ const evidenceScope = deliveryEvidenceScope(project, observed, delivery);
183
+ const fact = {
184
+ scope: effectiveScope,
185
+ review: await evaluateDeliveryReview(dependencies.git, evidenceScope, delivery, effectiveScope),
186
+ // The accepted exit-gate binding persisted with the delivery's evidence,
187
+ // never the milestone's current working carrier.
188
+ exit: await evaluateGateCarrier(delivery.exit_gate, {
189
+ kind: 'code',
190
+ scope: effectiveScope,
191
+ match: 'covers',
192
+ overrideDisabled: project.exitGateDisabled,
193
+ }, {
194
+ git: dependencies.git,
195
+ scope: evidenceScope,
196
+ publishedHeads: delivery.members.map((member) => member.observed_head),
197
+ }),
198
+ };
199
+ inputs.evidence[delivery.delivery_id] = fact;
200
+ }
201
+ return inputs;
202
+ }
21
203
  /**
22
204
  * Resolve a project, read its `state.md` from disk, and evaluate the closeout
23
205
  * invariant. Shared by `closeout-check` and `complete-state` so both read the
@@ -36,8 +218,12 @@ export async function checkProjectCloseout(projectPath, options, context, depend
36
218
  throw new CliError(`Project state.md not found: ${statePath}`, 1);
37
219
  }
38
220
  const stateContent = await dependencies.readFile(statePath, 'utf8');
221
+ const goal = readStateWorkflowMode(stateContent) === 'goal'
222
+ ? await loadGoalCloseoutInputs(repoRoot, targetProjectPath, dependencies)
223
+ : undefined;
39
224
  const result = await evaluateCloseout({
40
225
  stateContent,
226
+ ...(goal === undefined ? {} : { goal }),
41
227
  autonomousFlag: options.autonomous,
42
228
  env: dependencies.env,
43
229
  resolveConfigured: async () => {
@@ -1,9 +1,10 @@
1
1
  import { readFile as defaultReadFile, writeFile as defaultWriteFile } from 'node:fs/promises';
2
2
  import { buildCommandContext, type CommandContext } from '../../../app/command-context.js';
3
+ import { type CloseoutInputDependencies } from '../../project/closeout-check/index.js';
3
4
  import { resolveEffectiveConfig } from '../../../config/resolve.js';
4
5
  import { dirExists, fileExists } from '../../../fs/io.js';
5
6
  import { Command } from 'commander';
6
- interface ProjectCompleteStateDependencies {
7
+ interface ProjectCompleteStateDependencies extends CloseoutInputDependencies {
7
8
  buildCommandContext: (options: Parameters<typeof buildCommandContext>[0]) => CommandContext;
8
9
  resolveProjectRoot: (cwd: string) => Promise<string>;
9
10
  readFile: typeof defaultReadFile;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/complete-state/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAMhF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,UAAU,gCAAgC;IACxC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AA4ED,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,gCAAgC,CAAM,GACxD,OAAO,CA+BT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/complete-state/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAChF,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,UAAU,gCAAiC,SAAQ,yBAAyB;IAC1E,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAkFD,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,gCAAgC,CAAM,GACxD,OAAO,CA+BT"}
@@ -1,6 +1,6 @@
1
1
  import { readFile as defaultReadFile, writeFile as defaultWriteFile, } from 'node:fs/promises';
2
2
  import { buildCommandContext } from '../../../app/command-context.js';
3
- import { checkProjectCloseout, formatCloseoutRefusal, } from '../../project/closeout-check/index.js';
3
+ import { checkProjectCloseout, DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES, formatCloseoutRefusal, } from '../../project/closeout-check/index.js';
4
4
  import { readGlobalOptions } from '../../shared/shared.utils.js';
5
5
  import { resolveEffectiveConfig } from '../../../config/resolve.js';
6
6
  import { CliError } from '../../../errors/cli-error.js';
@@ -10,6 +10,12 @@ import { assertValidProjectStateFilesystemContent } from '../../../validation/pr
10
10
  import { Command } from 'commander';
11
11
  import { renderCompletedProjectState } from './state-utils.js';
12
12
  const DEFAULT_DEPENDENCIES = {
13
+ // Goal boundary observation (gh, git, project reader, child lookup) is
14
+ // read-only and shared with closeout-check.
15
+ gh: DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES.gh,
16
+ git: DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES.git,
17
+ getProjectState: DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES.getProjectState,
18
+ listDirectories: DEFAULT_CLOSEOUT_CHECK_DEPENDENCIES.listDirectories,
13
19
  buildCommandContext,
14
20
  resolveProjectRoot,
15
21
  readFile: defaultReadFile,
@@ -0,0 +1,66 @@
1
+ import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
2
+ import { type GitRunner } from '../../project/sync/git.js';
3
+ import { type GoalDelivery, type ProjectState } from '@open-agent-toolkit/control-plane';
4
+ import { Command } from 'commander';
5
+ import { type CarrierEvaluation, type ReviewEvaluation } from './evidence.js';
6
+ export interface GoalDeliveryProject {
7
+ repoRoot: string;
8
+ projectPath: string;
9
+ projectDir: string;
10
+ statePath: string;
11
+ stateContent: string;
12
+ state: ProjectState;
13
+ /** Whether the project override currently disables the exit gate. */
14
+ exitGateDisabled: boolean;
15
+ }
16
+ export interface DeliveryProjectDependencies {
17
+ resolveProjectRoot: (cwd: string) => Promise<string>;
18
+ readFile: (path: string, encoding: 'utf8') => Promise<string>;
19
+ getProjectState: (projectPath: string) => Promise<ProjectState>;
20
+ }
21
+ export declare const DEFAULT_DELIVERY_PROJECT_DEPENDENCIES: DeliveryProjectDependencies;
22
+ /**
23
+ * Load a goal project for the delivery commands through the control-plane
24
+ * reader, so the registry, carriers, membership and Reviews rows are read
25
+ * exactly as the router reads them. Fails closed for any other mode.
26
+ */
27
+ export declare function loadGoalDeliveryProject(project: string, cwd: string, dependencies: DeliveryProjectDependencies): Promise<GoalDeliveryProject>;
28
+ /**
29
+ * A delivery's effective scope: its frozen scope plus every predecessor it
30
+ * replaces through `covers_scope_of`. A cycle or missing link stops the
31
+ * walk; the closeout coverage predicate reports those.
32
+ */
33
+ export declare function deliveryEffectiveScope(deliveries: readonly GoalDelivery[], delivery: GoalDelivery): string[];
34
+ export { reviewRowsFor } from './evidence.js';
35
+ export interface SubjectEvidenceReport {
36
+ subject: string;
37
+ kind: 'milestone' | 'delivery';
38
+ milestone: string | null;
39
+ scope: string[];
40
+ publishedHeads: (string | null)[];
41
+ review: ReviewEvaluation;
42
+ /**
43
+ * `source` names the carrier evaluated: `delivery check` judges the
44
+ * milestone's working carrier (`oat_milestone_gates[mNN].exit`), while the
45
+ * closeout boundary judges the delivery's persisted `exit_gate` binding.
46
+ */
47
+ exit: CarrierEvaluation & {
48
+ source: ExitCarrierSource;
49
+ };
50
+ /** Passed, fresh integrated review and an allowed, fresh exit carrier. */
51
+ deliverable: boolean;
52
+ }
53
+ /** Which exit carrier an evaluation judged. */
54
+ export type ExitCarrierSource = 'milestone-carrier' | 'delivery-binding';
55
+ /**
56
+ * Evaluate the delivery evidence for one subject. A registered delivery is
57
+ * judged against its persisted member observations; a milestone (before
58
+ * registration) against the project branch head.
59
+ */
60
+ export declare function evaluateSubjectEvidence(project: GoalDeliveryProject, subject: string, git: GitRunner): Promise<SubjectEvidenceReport>;
61
+ export interface DeliveryCheckDependencies extends DeliveryProjectDependencies {
62
+ buildCommandContext: (options: GlobalOptions) => CommandContext;
63
+ git: GitRunner;
64
+ }
65
+ export declare function createProjectDeliveryCheckCommand(overrides?: Partial<DeliveryCheckDependencies>): Command;
66
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/delivery/check.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAS9E,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAML,KAAK,iBAAiB,EAEtB,KAAK,gBAAgB,EACtB,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,qEAAqE;IACrE,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,2BAA2B;IAC1C,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CACjE;AAED,eAAO,MAAM,qCAAqC,EAAE,2BAKjD,CAAC;AAEJ;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,2BAA2B,GACxC,OAAO,CAAC,mBAAmB,CAAC,CA+B9B;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,YAAY,EAAE,EACnC,QAAQ,EAAE,YAAY,GACrB,MAAM,EAAE,CAgBV;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,cAAc,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAClC,MAAM,EAAE,gBAAgB,CAAC;IACzB;;;;OAIG;IACH,IAAI,EAAE,iBAAiB,GAAG;QAAE,MAAM,EAAE,iBAAiB,CAAA;KAAE,CAAC;IACxD,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,+CAA+C;AAC/C,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEzE;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,qBAAqB,CAAC,CA4HhC;AAED,MAAM,WAAW,yBAA0B,SAAQ,2BAA2B;IAC5E,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,GAAG,EAAE,SAAS,CAAC;CAChB;AAwBD,wBAAgB,iCAAiC,CAC/C,SAAS,GAAE,OAAO,CAAC,yBAAyB,CAAM,GACjD,OAAO,CAsDT"}
@@ -0,0 +1,232 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { isAbsolute, join, relative, sep } from 'node:path';
3
+ import { buildCommandContext, } from '../../../app/command-context.js';
4
+ import { defaultGitRunner } from '../../project/sync/git.js';
5
+ import { getFrontmatterBlock, parseSkillGateOverrides, SKILL_GATE_OVERRIDE_DISABLED, } from '../../shared/frontmatter.js';
6
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
7
+ import { CliError } from '../../../errors/cli-error.js';
8
+ import { resolveProjectRoot } from '../../../fs/paths.js';
9
+ import { getProjectState, milestonePhaseScope, normalizeMilestoneId, } from '@open-agent-toolkit/control-plane';
10
+ import { Command } from 'commander';
11
+ import { deliveryEvidenceScope, reviewRowsFor, evaluateDeliveryReview, evaluateGateCarrier, evaluateSuccessor, } from './evidence.js';
12
+ /** The skill whose gate the milestone exit carrier records. */
13
+ const EXIT_GATE_SKILL = 'oat-project-implement';
14
+ export const DEFAULT_DELIVERY_PROJECT_DEPENDENCIES = {
15
+ resolveProjectRoot,
16
+ readFile: (path, encoding) => readFile(path, encoding),
17
+ getProjectState,
18
+ };
19
+ /**
20
+ * Load a goal project for the delivery commands through the control-plane
21
+ * reader, so the registry, carriers, membership and Reviews rows are read
22
+ * exactly as the router reads them. Fails closed for any other mode.
23
+ */
24
+ export async function loadGoalDeliveryProject(project, cwd, dependencies) {
25
+ const repoRoot = await dependencies.resolveProjectRoot(cwd);
26
+ const projectPath = isAbsolute(project) ? project : join(repoRoot, project);
27
+ const statePath = join(projectPath, 'state.md');
28
+ let stateContent;
29
+ try {
30
+ stateContent = await dependencies.readFile(statePath, 'utf8');
31
+ }
32
+ catch {
33
+ throw new CliError(`Project state.md not found: ${statePath}`, 1);
34
+ }
35
+ const state = await dependencies.getProjectState(projectPath);
36
+ if (state.workflowMode !== 'goal') {
37
+ throw new CliError(`Delivery evidence applies to goal projects; ${project} is in ${state.workflowMode} mode.`, 1);
38
+ }
39
+ const overrides = parseSkillGateOverrides(getFrontmatterBlock(stateContent) ?? '', statePath);
40
+ return {
41
+ repoRoot,
42
+ projectPath,
43
+ projectDir: relative(repoRoot, projectPath).split(sep).join('/'),
44
+ statePath,
45
+ stateContent,
46
+ state,
47
+ exitGateDisabled: overrides.overrides[EXIT_GATE_SKILL] === SKILL_GATE_OVERRIDE_DISABLED,
48
+ };
49
+ }
50
+ /**
51
+ * A delivery's effective scope: its frozen scope plus every predecessor it
52
+ * replaces through `covers_scope_of`. A cycle or missing link stops the
53
+ * walk; the closeout coverage predicate reports those.
54
+ */
55
+ export function deliveryEffectiveScope(deliveries, delivery) {
56
+ const scope = [];
57
+ const seen = new Set();
58
+ let current = delivery;
59
+ while (current !== undefined && !seen.has(current.delivery_id)) {
60
+ seen.add(current.delivery_id);
61
+ for (const phaseId of current.scope) {
62
+ if (!scope.includes(phaseId))
63
+ scope.push(phaseId);
64
+ }
65
+ const predecessor = current.covers_scope_of;
66
+ current =
67
+ predecessor === null
68
+ ? undefined
69
+ : deliveries.find((entry) => entry.delivery_id === predecessor);
70
+ }
71
+ return scope;
72
+ }
73
+ export { reviewRowsFor } from './evidence.js';
74
+ /**
75
+ * Evaluate the delivery evidence for one subject. A registered delivery is
76
+ * judged against its persisted member observations; a milestone (before
77
+ * registration) against the project branch head.
78
+ */
79
+ export async function evaluateSubjectEvidence(project, subject, git) {
80
+ const deliveries = project.state.deliveries ?? [];
81
+ const delivery = deliveries.find((entry) => entry.delivery_id === subject);
82
+ if (delivery) {
83
+ const milestoneId = delivery.milestone;
84
+ const scope = deliveryEffectiveScope(deliveries, delivery);
85
+ const evidence = deliveryEvidenceScope(project, deliveries, delivery);
86
+ const publishedHeads = delivery.members.map((member) => member.observed_head);
87
+ const review = await evaluateDeliveryReview(git, evidence, delivery, scope);
88
+ const exitEvaluation = await evaluateGateCarrier(milestoneId === null
89
+ ? null
90
+ : (project.state.milestoneGates?.[milestoneId]?.exit ?? null), {
91
+ kind: 'code',
92
+ scope,
93
+ match: 'covers',
94
+ overrideDisabled: project.exitGateDisabled,
95
+ }, { git, scope: evidence, publishedHeads });
96
+ const exit = { ...exitEvaluation, source: 'milestone-carrier' };
97
+ return {
98
+ subject,
99
+ kind: 'delivery',
100
+ milestone: milestoneId,
101
+ scope,
102
+ publishedHeads,
103
+ review,
104
+ exit,
105
+ deliverable: review.fresh &&
106
+ review.disposition === 'passed' &&
107
+ exit.outcome === 'allowed' &&
108
+ exit.freshness === 'fresh',
109
+ };
110
+ }
111
+ const milestoneId = normalizeMilestoneId(subject);
112
+ if (milestoneId === null) {
113
+ throw new CliError(`Unknown delivery subject "${subject}": name a milestone (mNN) or a registered delivery (dNN).`, 1);
114
+ }
115
+ const membership = project.state.milestoneMembership;
116
+ const scope = membership
117
+ ? milestonePhaseScope(membership, milestoneId)
118
+ : null;
119
+ if (scope === null) {
120
+ const [firstError] = membership?.errors ?? [];
121
+ throw new CliError(firstError
122
+ ? `Milestone membership is invalid (${firstError.code}: ${firstError.message}); repair it with oat-project-goal.`
123
+ : `No ## Milestones row for ${milestoneId}.`, 1);
124
+ }
125
+ const head = await git.run(['rev-parse', '--verify', 'HEAD'], {
126
+ cwd: project.repoRoot,
127
+ allowFailure: true,
128
+ });
129
+ const publishedHead = head.code === 0 ? head.stdout.trim() : null;
130
+ const rows = reviewRowsFor(project.state.reviews, [milestoneId]);
131
+ const row = rows.at(-1) ?? null;
132
+ const evidence = {
133
+ repoRoot: project.repoRoot,
134
+ projectDir: project.projectDir,
135
+ evidenceArtifacts: rows
136
+ .map((entry) => entry.artifact)
137
+ .filter((artifact) => artifact !== '' && artifact !== '-'),
138
+ };
139
+ let review;
140
+ if (row === null || row.artifact === '' || row.artifact === '-') {
141
+ review = {
142
+ artifact: null,
143
+ disposition: row?.status ?? null,
144
+ fresh: false,
145
+ reason: `review-missing: no ${milestoneId} code review row names an artifact`,
146
+ members: [],
147
+ };
148
+ }
149
+ else {
150
+ const successor = await evaluateSuccessor(git, evidence, row.reviewedHead ?? null, publishedHead);
151
+ review = {
152
+ artifact: row.artifact,
153
+ disposition: row.status,
154
+ fresh: successor.fresh,
155
+ reason: successor.fresh ? null : `${successor.code}: ${successor.reason}`,
156
+ members: [],
157
+ };
158
+ }
159
+ const exit = {
160
+ ...(await evaluateGateCarrier(project.state.milestoneGates?.[milestoneId]?.exit ?? null, { kind: 'code', scope, overrideDisabled: project.exitGateDisabled }, { git, scope: evidence, publishedHeads: [publishedHead] })),
161
+ source: 'milestone-carrier',
162
+ };
163
+ return {
164
+ subject,
165
+ kind: 'milestone',
166
+ milestone: milestoneId,
167
+ scope,
168
+ publishedHeads: [publishedHead],
169
+ review,
170
+ exit,
171
+ deliverable: review.fresh &&
172
+ review.disposition?.toLowerCase() === 'passed' &&
173
+ exit.outcome === 'allowed' &&
174
+ exit.freshness === 'fresh',
175
+ };
176
+ }
177
+ const DEFAULT_CHECK_DEPENDENCIES = {
178
+ ...DEFAULT_DELIVERY_PROJECT_DEPENDENCIES,
179
+ buildCommandContext,
180
+ git: defaultGitRunner,
181
+ };
182
+ function formatEvidenceReport(report) {
183
+ const review = report.review.fresh
184
+ ? `fresh (${report.review.disposition ?? 'no disposition'})`
185
+ : `stale: ${report.review.reason ?? 'unknown'}`;
186
+ const exit = report.exit.freshness === 'fresh'
187
+ ? `${report.exit.outcome}, fresh`
188
+ : `${report.exit.outcome}, stale: ${report.exit.reason ?? 'unknown'}`;
189
+ return [
190
+ `Delivery evidence for ${report.subject} (${report.kind}; scope ${report.scope.join(', ')}):`,
191
+ ` integrated review: ${review}`,
192
+ ` exit carrier (${report.exit.source}): ${exit}`,
193
+ ` deliverable: ${report.deliverable ? 'yes' : 'no'}`,
194
+ ].join('\n');
195
+ }
196
+ export function createProjectDeliveryCheckCommand(overrides = {}) {
197
+ const dependencies = { ...DEFAULT_CHECK_DEPENDENCIES, ...overrides };
198
+ return new Command('check')
199
+ .description('Report integrated-review and milestone exit-carrier freshness for a goal milestone or delivery under the bookkeeping-successor contract (read-only; status undeliverable and exit 1 when not deliverable)')
200
+ .requiredOption('--project <path>', 'Goal project path')
201
+ .requiredOption('--subject <id>', 'Milestone (mNN) or registered delivery (dNN) to evaluate')
202
+ .action(async (options, command) => {
203
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
204
+ try {
205
+ const project = await loadGoalDeliveryProject(options.project, context.cwd, dependencies);
206
+ const report = await evaluateSubjectEvidence(project, options.subject, dependencies.git);
207
+ if (context.json) {
208
+ // `ok` means deliverable; an evaluated but refusing subject is
209
+ // `undeliverable`, with the full report and exit 1.
210
+ context.logger.json({
211
+ status: report.deliverable ? 'ok' : 'undeliverable',
212
+ ...report,
213
+ });
214
+ }
215
+ else {
216
+ context.logger.info(formatEvidenceReport(report));
217
+ }
218
+ // Fail closed: a caller that ignores the report still stops.
219
+ process.exitCode = report.deliverable ? 0 : 1;
220
+ }
221
+ catch (error) {
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ if (context.json) {
224
+ context.logger.json({ status: 'error', message });
225
+ }
226
+ else {
227
+ context.logger.error(message);
228
+ }
229
+ process.exitCode = error instanceof CliError ? error.exitCode : 1;
230
+ }
231
+ });
232
+ }