@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 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"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"}
@@ -15,7 +15,7 @@ import { resolveEffectiveConfig } from '../../../config/resolve.js';
15
15
  import { CliError } from '../../../errors/cli-error.js';
16
16
  import { resolveAssetsRoot } from '../../../fs/assets.js';
17
17
  import { dirExists, fileExists } from '../../../fs/io.js';
18
- import { isBriefMode, } from '@open-agent-toolkit/control-plane';
18
+ import { isBriefMode, MILESTONES_TABLE_HEADER, } from '@open-agent-toolkit/control-plane';
19
19
  import { assertValidProjectStateContent } from '../../../validation/project-state.js';
20
20
  const DEFAULT_DEPENDENCIES = {
21
21
  resolveDefaultScope,
@@ -49,6 +49,13 @@ const TEMPLATES_BY_MODE = {
49
49
  { source: 'plan-brief.md', target: 'plan.md' },
50
50
  { source: 'implementation-brief.md', target: 'implementation.md' },
51
51
  ],
52
+ // Goal shares the brief templates; scaffold renders its plan source and the
53
+ // canonical Milestones table header (see renderGoalBrief).
54
+ goal: [
55
+ 'state.md',
56
+ { source: 'plan-brief.md', target: 'plan.md' },
57
+ { source: 'implementation-brief.md', target: 'implementation.md' },
58
+ ],
52
59
  };
53
60
  function normalizeTemplateEntry(entry) {
54
61
  return typeof entry === 'string' ? { source: entry, target: entry } : entry;
@@ -145,6 +152,23 @@ const STATE_TEMPLATE_BY_MODE = {
145
152
  ],
146
153
  nextMilestone: 'Run `oat-project-brief` to author and approve the brief',
147
154
  },
155
+ goal: {
156
+ hillCheckpoints: '[]',
157
+ phase: 'plan',
158
+ status: 'Plan',
159
+ currentPhase: 'Plan - Authoring the goal Project Brief and its milestones',
160
+ artifacts: [
161
+ '- **Plan:** `plan.md` (scaffolded goal Project Brief template — not started)',
162
+ '- **Implementation:** `implementation.md` (scaffolded brief journal template — not started)',
163
+ '- **Research:** `references/research/` (empty)',
164
+ ],
165
+ progress: [
166
+ '- ✓ Goal project scaffolded',
167
+ '- ✓ Brief and implementation artifacts scaffolded',
168
+ '- ⧗ Awaiting goal interview, milestones and brief authoring',
169
+ ],
170
+ nextMilestone: 'Run `oat-project-goal` to author the brief and its milestones and approve the first milestone',
171
+ },
148
172
  };
149
173
  function replaceOatPlaceholders(template, replacements) {
150
174
  return template.replace(OAT_PLACEHOLDER_PATTERN, (placeholder, token) => replacements[token] ?? placeholder);
@@ -245,6 +269,9 @@ async function scaffoldModeTemplates(home, repoRoot, absoluteProjectPath, projec
245
269
  if ((mode === 'lite' || isBriefMode(mode)) && target === 'plan.md') {
246
270
  rendered = rendered.replace(/^---\n/, '---\noat_template: true\n');
247
271
  }
272
+ if (mode === 'goal' && target === 'plan.md') {
273
+ rendered = renderGoalBrief(rendered);
274
+ }
248
275
  assertNoUnresolvedOatPlaceholders(rendered, source);
249
276
  if (target === 'state.md') {
250
277
  assertValidProjectStateContent(rendered, { filePath: dest });
@@ -254,6 +281,25 @@ async function scaffoldModeTemplates(home, repoRoot, absoluteProjectPath, projec
254
281
  }
255
282
  return { createdFiles, skippedFiles };
256
283
  }
284
+ const BRIEF_PLAN_SOURCE_PATTERN = /^oat_plan_source:[ \t]*brief\b[^\n]*$/m;
285
+ const BRIEF_MILESTONES_PLACEHOLDER_PATTERN = /^<!-- Reserved for goal mode\.[^\n]*-->[ \t]*$/m;
286
+ const MILESTONES_HEADING_LINE_PATTERN = /^## Milestones[ \t]*$/m;
287
+ const GOAL_MILESTONES_GUIDANCE = '<!-- One row per milestone: its id (m01, m02, …) and its ordered, comma-separated phase ids. Every phase, including revision phases, belongs to exactly one milestone. -->';
288
+ /**
289
+ * Goal shares the brief template: render `oat_plan_source: goal` (the
290
+ * template hardcodes `brief`) and replace the brief-only Milestones
291
+ * placeholder with the canonical table header the shared Milestones reader
292
+ * parses. A template without the placeholder still gets the header under its
293
+ * `## Milestones` heading.
294
+ */
295
+ function renderGoalBrief(rendered) {
296
+ const withSource = rendered.replace(BRIEF_PLAN_SOURCE_PATTERN, 'oat_plan_source: goal');
297
+ const milestonesBlock = `${GOAL_MILESTONES_GUIDANCE}\n\n${MILESTONES_TABLE_HEADER}`;
298
+ if (BRIEF_MILESTONES_PLACEHOLDER_PATTERN.test(withSource)) {
299
+ return withSource.replace(BRIEF_MILESTONES_PLACEHOLDER_PATTERN, milestonesBlock);
300
+ }
301
+ return withSource.replace(MILESTONES_HEADING_LINE_PATTERN, `## Milestones\n\n${milestonesBlock}`);
302
+ }
257
303
  async function scaffoldProjectLog(home, repoRoot, absoluteProjectPath, projectName, today) {
258
304
  const templateFile = 'project-log.md';
259
305
  const dest = join(absoluteProjectPath, templateFile);
@@ -15,7 +15,7 @@ export interface LitePlanSections {
15
15
  productBehavior?: string;
16
16
  technicalDesign?: string;
17
17
  }
18
- type PromotionReason = 'promoted' | 'unsupported-target' | 'not-lite' | 'lite-plan-reference-exists' | 'invalid-lite-plan' | 'brief-archive-exists' | 'extracted-phases' | 'invalid-brief-plan' | 'missing-phase-head' | 'scope-unresolved' | 'project-unreadable' | 'template-unreadable' | 'write-failed' | 'persistence-failed';
18
+ type PromotionReason = 'promoted' | 'unsupported-target' | 'not-lite' | 'lite-plan-reference-exists' | 'invalid-lite-plan' | 'brief-archive-exists' | 'goal-milestone-active' | 'goal-delivery-unsettled' | 'goal-recovery-pending' | 'extracted-phases' | 'invalid-brief-plan' | 'missing-phase-head' | 'scope-unresolved' | 'project-unreadable' | 'template-unreadable' | 'write-failed' | 'persistence-failed';
19
19
  interface PromotionRecovery extends ExactPathCommitResult {
20
20
  identity: string;
21
21
  message: string;
@@ -31,6 +31,8 @@ export type PromotionResult = {
31
31
  reason: Exclude<PromotionReason, 'promoted'>;
32
32
  files: string[];
33
33
  phases?: string[];
34
+ /** Goal: the registry entries that are not yet settled. */
35
+ deliveries?: string[];
34
36
  recovery?: PromotionRecovery;
35
37
  };
36
38
  export interface ProjectPromoteDependencies {
@@ -51,7 +53,17 @@ export interface ProjectPromoteDependencies {
51
53
  now: () => Date;
52
54
  }
53
55
  export declare function parseLitePlanSections(planContent: string, liteTemplateContent?: string): LitePlanSections | null;
56
+ /**
57
+ * A goal promotes only at a settled delivery boundary: the active milestone
58
+ * is `delivered`, every registry entry is `at_endpoint` or `superseded` (none
59
+ * `registering` or `open`; an empty registry is not settled), and no recovery
60
+ * is pending. Extracted phases are
61
+ * refused by the shared migration. Returns the refusal, or null when settled.
62
+ */
63
+ export declare function goalPromotionRefusal(stateFrontmatter: Record<string, unknown>): PromotionResult | null;
54
64
  export type BriefPhaseState = 'accepted' | 'active' | 'pending' | 'provisional';
65
+ /** The brief-family mode a promotion starts from. */
66
+ export type BriefSourceMode = 'brief' | 'goal';
55
67
  export interface BriefMigration {
56
68
  plan: string;
57
69
  implementation: string;
@@ -80,6 +92,8 @@ export declare function migrateBriefToQuick(input: {
80
92
  implementationContent: string;
81
93
  projectName: string;
82
94
  today: string;
95
+ /** The actual source mode, named in provenance; defaults to `brief`. */
96
+ sourceMode?: BriefSourceMode;
83
97
  }): BriefMigrationResult;
84
98
  export declare function createProjectPromoteCommand(overrides?: Partial<ProjectPromoteDependencies>): Command;
85
99
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"promote.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/promote/promote.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"promote.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/promote/promote.ts"],"names":[],"mappings":"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"}
@@ -245,7 +245,13 @@ async function promoteProject(projectPath, options, context, dependencies) {
245
245
  catch {
246
246
  return { status: 'refused', reason: 'project-unreadable', files: [] };
247
247
  }
248
- if (stateFrontmatter['oat_workflow_mode'] === 'brief') {
248
+ const mode = stateFrontmatter['oat_workflow_mode'];
249
+ if (mode === 'brief' || mode === 'goal') {
250
+ if (mode === 'goal') {
251
+ const unsettled = goalPromotionRefusal(stateFrontmatter);
252
+ if (unsettled !== null)
253
+ return unsettled;
254
+ }
249
255
  return promoteBriefProject({
250
256
  repoRoot,
251
257
  projectRoot,
@@ -254,6 +260,8 @@ async function promoteProject(projectPath, options, context, dependencies) {
254
260
  planPath,
255
261
  stateContent,
256
262
  planContent,
263
+ sourceMode: mode,
264
+ stateFrontmatter,
257
265
  }, context, dependencies);
258
266
  }
259
267
  if (stateFrontmatter['oat_workflow_mode'] !== 'lite') {
@@ -346,6 +354,77 @@ async function promoteProject(projectPath, options, context, dependencies) {
346
354
  };
347
355
  }
348
356
  // ---------------------------------------------------------------------------
357
+ // goal → quick: the settled delivery boundary (B3)
358
+ // ---------------------------------------------------------------------------
359
+ /** Registry statuses at which a delivery no longer needs goal machinery. */
360
+ const SETTLED_DELIVERY_STATUSES = new Set(['at_endpoint', 'superseded']);
361
+ /** The goal state carried verbatim into the promoted state and the archive. */
362
+ const GOAL_HISTORY_KEYS = [
363
+ 'oat_deliveries',
364
+ 'oat_milestone_gates',
365
+ 'oat_goal_outcome_audit',
366
+ ];
367
+ /**
368
+ * A goal promotes only at a settled delivery boundary: the active milestone
369
+ * is `delivered`, every registry entry is `at_endpoint` or `superseded` (none
370
+ * `registering` or `open`; an empty registry is not settled), and no recovery
371
+ * is pending. Extracted phases are
372
+ * refused by the shared migration. Returns the refusal, or null when settled.
373
+ */
374
+ export function goalPromotionRefusal(stateFrontmatter) {
375
+ if (stateFrontmatter['oat_milestone_status'] !== 'delivered') {
376
+ return { status: 'refused', reason: 'goal-milestone-active', files: [] };
377
+ }
378
+ const recovery = stateFrontmatter['oat_goal_recovery'];
379
+ if (recovery !== null && recovery !== undefined) {
380
+ return { status: 'refused', reason: 'goal-recovery-pending', files: [] };
381
+ }
382
+ const registry = stateFrontmatter['oat_deliveries'];
383
+ const entries = Array.isArray(registry) ? registry : [];
384
+ const unsettled = entries
385
+ .filter((entry) => typeof entry !== 'object' ||
386
+ entry === null ||
387
+ !SETTLED_DELIVERY_STATUSES.has(String(entry['status'])))
388
+ .map((entry) => typeof entry === 'object' && entry !== null
389
+ ? String(entry['delivery_id'] ?? '?')
390
+ : '?');
391
+ // `delivered` means registered, open and review-ready (B4): a delivered
392
+ // milestone with no registry entry is contradictory, never vacuously settled.
393
+ if (!Array.isArray(registry) ||
394
+ entries.length === 0 ||
395
+ unsettled.length > 0) {
396
+ return {
397
+ status: 'refused',
398
+ reason: 'goal-delivery-unsettled',
399
+ files: [],
400
+ deliveries: unsettled,
401
+ };
402
+ }
403
+ return null;
404
+ }
405
+ /**
406
+ * The goal archive: the original brief followed by its delivery history, so
407
+ * the registry, milestone gates and outcome audit survive under the archive
408
+ * as well as in the promoted `state.md`.
409
+ */
410
+ function renderGoalArchive(planContent, stateFrontmatter) {
411
+ const history = {};
412
+ for (const key of GOAL_HISTORY_KEYS) {
413
+ history[key] = stateFrontmatter[key] ?? null;
414
+ }
415
+ return `${planContent.trimEnd()}
416
+
417
+ ## Delivery History (archived at promotion)
418
+
419
+ The goal's delivery registry, milestone gate carriers and outcome audit as
420
+ recorded in \`state.md\` when the project was promoted to quick.
421
+
422
+ \`\`\`yaml
423
+ ${YAML.stringify(history).trimEnd()}
424
+ \`\`\`
425
+ `;
426
+ }
427
+ // ---------------------------------------------------------------------------
349
428
  // brief → quick
350
429
  // ---------------------------------------------------------------------------
351
430
  const BRIEF_ARCHIVE_FILE = 'references/brief-archive.md';
@@ -509,21 +588,21 @@ function trimBlank(lines) {
509
588
  * Type and Artifact are review-event identity) and one provenance line under
510
589
  * the heading marking the rows recorded before promotion.
511
590
  */
512
- function reviewsWithProvenance(section, today) {
591
+ function reviewsWithProvenance(section, today, sourceMode) {
513
592
  return [
514
593
  section.lines[0],
515
594
  '',
516
- `Rows dated on or before ${today} were recorded pre-promotion under workflow mode brief.`,
595
+ `Rows dated on or before ${today} were recorded pre-promotion under workflow mode ${sourceMode}.`,
517
596
  '',
518
597
  ...trimBlank(section.lines.slice(1)),
519
598
  ];
520
599
  }
521
- function renderPlanTask(record) {
600
+ function renderPlanTask(record, sourceMode) {
522
601
  const commit = record.commit && record.commit !== '-' ? record.commit : null;
523
602
  return [
524
603
  `### Task ${record.taskId}: ${record.title}`,
525
604
  '',
526
- `> Accepted before promotion (brief). Execution record: \`implementation.md\`${commit ? ` (commit \`${commit}\`)` : ''}.`,
605
+ `> Accepted before promotion (${sourceMode}). Execution record: \`implementation.md\`${commit ? ` (commit \`${commit}\`)` : ''}.`,
527
606
  ...(trimBlank(record.body).length > 0
528
607
  ? ['', ...trimBlank(record.body)]
529
608
  : []),
@@ -537,6 +616,7 @@ function renderPlanTask(record) {
537
616
  */
538
617
  export function migrateBriefToQuick(input) {
539
618
  const { planContent, implementationContent, projectName, today } = input;
619
+ const sourceMode = input.sourceMode ?? 'brief';
540
620
  const markers = parsePhaseMarkers(implementationContent);
541
621
  if ([...markers.values()].some((marker) => marker.status === 'extracted')) {
542
622
  return { ok: false, reason: 'extracted-phases' };
@@ -584,7 +664,7 @@ export function migrateBriefToQuick(input) {
584
664
  const output = [
585
665
  `# Implementation Plan: ${projectName}`,
586
666
  '',
587
- `> Promoted from a brief on ${today}. The original brief is archived at \`${BRIEF_ARCHIVE_FILE}\`; its outcomes, decisions, known unknowns and delivery authority are carried into \`discovery.md\`.`,
667
+ `> Promoted from a ${sourceMode === 'goal' ? 'goal brief' : 'brief'} on ${today}. The original brief is archived at \`${BRIEF_ARCHIVE_FILE}\`; its outcomes, decisions, known unknowns and delivery authority are carried into \`discovery.md\`.`,
588
668
  '',
589
669
  `**Goal:** ${goal ? goal.split('\n\n')[0].replace(/\n/g, ' ') : '{Author from discovery}'}`,
590
670
  '',
@@ -625,8 +705,9 @@ export function migrateBriefToQuick(input) {
625
705
  .map((r) => `${r.taskId} (${r.status})`)
626
706
  .join(', ')}`, '');
627
707
  }
628
- for (const record of completed)
629
- lines.push(...renderPlanTask(record));
708
+ for (const record of completed) {
709
+ lines.push(...renderPlanTask(record, sourceMode));
710
+ }
630
711
  if (state === 'accepted' && completed.length === 0) {
631
712
  const next = Math.max(0, ...phaseRecords.map((r) => r.ordinal)) + 1;
632
713
  const taskId = `${taskPrefixForPhase(phaseId)}-t${String(next).padStart(2, '0')}`;
@@ -656,7 +737,7 @@ export function migrateBriefToQuick(input) {
656
737
  if (/^delivery\b/i.test(title))
657
738
  continue;
658
739
  if (title.toLowerCase() === 'reviews') {
659
- output.push(...reviewsWithProvenance(section, today), '');
740
+ output.push(...reviewsWithProvenance(section, today, sourceMode), '');
660
741
  continue;
661
742
  }
662
743
  if (title.toLowerCase() === 'references') {
@@ -675,7 +756,7 @@ export function migrateBriefToQuick(input) {
675
756
  frontmatter['oat_template'] = true;
676
757
  frontmatter['oat_template_name'] = 'plan';
677
758
  frontmatter['oat_plan_source'] = 'quick';
678
- frontmatter['oat_promoted_from'] = 'brief';
759
+ frontmatter['oat_promoted_from'] = sourceMode;
679
760
  frontmatter['oat_promotion_pending_phases'] = pendingPhases;
680
761
  const plan = `---\n${YAML.stringify(frontmatter)}---\n\n${output
681
762
  .join('\n')
@@ -809,6 +890,7 @@ async function promoteBriefProject(input, context, dependencies) {
809
890
  implementationContent,
810
891
  projectName,
811
892
  today,
893
+ sourceMode: input.sourceMode,
812
894
  });
813
895
  }
814
896
  catch {
@@ -852,7 +934,7 @@ async function promoteBriefProject(input, context, dependencies) {
852
934
  'plan.md',
853
935
  'state.md',
854
936
  ];
855
- const identity = `promote:${projectRoot}:brief-to-quick:${randomUUID()}`;
937
+ const identity = `promote:${projectRoot}:${input.sourceMode}-to-quick:${randomUUID()}`;
856
938
  const writtenFiles = [];
857
939
  try {
858
940
  await dependencies.mkdir(join(projectRoot, 'references'), {
@@ -860,7 +942,9 @@ async function promoteBriefProject(input, context, dependencies) {
860
942
  });
861
943
  await dependencies.writeFile(join(projectRoot, 'discovery.md'), discoveryContent, 'utf8');
862
944
  writtenFiles.push('discovery.md');
863
- await dependencies.writeFile(archivePath, input.planContent, 'utf8');
945
+ await dependencies.writeFile(archivePath, input.sourceMode === 'goal'
946
+ ? renderGoalArchive(input.planContent, input.stateFrontmatter)
947
+ : input.planContent, 'utf8');
864
948
  writtenFiles.push(BRIEF_ARCHIVE_FILE);
865
949
  if (migration.implementationChanged) {
866
950
  await dependencies.writeFile(implementationPath, migration.implementation, 'utf8');
@@ -892,8 +976,8 @@ async function promoteBriefProject(input, context, dependencies) {
892
976
  export function createProjectPromoteCommand(overrides = {}) {
893
977
  const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
894
978
  return new Command('promote')
895
- .description('Promote an authored lite or brief project to the quick workflow')
896
- .argument('<project-path>', 'Lite or brief project path to promote')
979
+ .description('Promote an authored lite, brief or settled goal project to the quick workflow')
980
+ .argument('<project-path>', 'Lite, brief or goal project path to promote')
897
981
  .requiredOption('--to <mode>', 'Target workflow mode')
898
982
  .action(async (projectPath, options, command) => {
899
983
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
@@ -905,7 +989,7 @@ export function createProjectPromoteCommand(overrides = {}) {
905
989
  context.logger.info(`Promoted ${projectPath} to quick.`);
906
990
  }
907
991
  else {
908
- context.logger.error(`Promotion refused: ${result.reason}${result.phases ? ` (${result.phases.join(', ')})` : ''}.${result.recovery ? ` Exact-path persistence ${result.recovery.outcome}; attempts ${result.recovery.attempts}; lock ${result.recovery.lockClass ?? 'none'}; identity ${result.recovery.identity}; receipt ${result.recovery.receipt ?? 'unavailable'}. ${result.recovery.error ?? ''} Recover existing Quick artifacts with: ${result.recovery.command}` : ''}`);
992
+ context.logger.error(`Promotion refused: ${result.reason}${result.phases ? ` (${result.phases.join(', ')})` : ''}${result.deliveries ? ` (${result.deliveries.join(', ')})` : ''}.${result.recovery ? ` Exact-path persistence ${result.recovery.outcome}; attempts ${result.recovery.attempts}; lock ${result.recovery.lockClass ?? 'none'}; identity ${result.recovery.identity}; receipt ${result.recovery.receipt ?? 'unavailable'}. ${result.recovery.error ?? ''} Recover existing Quick artifacts with: ${result.recovery.command}` : ''}`);
909
993
  }
910
994
  process.exitCode = result.status === 'promoted' ? 0 : 1;
911
995
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/validate-plan/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,UAAU,+BAA+B;IACvC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;CACjE;AAMD,wBAAgB,gCAAgC,CAC9C,SAAS,GAAE,OAAO,CAAC,+BAA+B,CAAM,GACvD,OAAO,CAmIT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/validate-plan/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,UAAU,+BAA+B;IACvC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;CACjE;AAMD,wBAAgB,gCAAgC,CAC9C,SAAS,GAAE,OAAO,CAAC,+BAA+B,CAAM,GACvD,OAAO,CAgJT"}
@@ -67,10 +67,20 @@ export function createProjectValidatePlanCommand(overrides = {}) {
67
67
  const workflowMode = toWorkflowMode(stateFrontmatter?.['oat_workflow_mode']);
68
68
  const profile = resolvePlanProfile(workflowMode);
69
69
  const phaseIds = extractPhaseIds(content, profile);
70
+ let implementationContent = null;
71
+ if (profile === 'goal') {
72
+ try {
73
+ implementationContent = readFileSync(join(projectPath, 'implementation.md'), 'utf-8');
74
+ }
75
+ catch {
76
+ implementationContent = null;
77
+ }
78
+ }
70
79
  const issues = validatePlanForProfile(content, {
71
80
  profile,
72
81
  workflowMode,
73
82
  stateFrontmatter,
83
+ implementationContent,
74
84
  });
75
85
  const profileErrors = issues
76
86
  .filter((issue) => issue.severity === 'error')
@@ -1,4 +1,4 @@
1
- import { type WorkflowMode } from '@open-agent-toolkit/control-plane';
1
+ import { type MilestoneMembershipErrorCode, type WorkflowMode } from '@open-agent-toolkit/control-plane';
2
2
  /**
3
3
  * Plan validation profiles. Each workflow mode validates `plan.md` against
4
4
  * one profile:
@@ -8,21 +8,30 @@ import { type WorkflowMode } from '@open-agent-toolkit/control-plane';
8
8
  * - `lite`: the lite single-phase rules.
9
9
  * - `brief`: taskless phase blocks (`**Intent:**`, `**Exit evidence:**`,
10
10
  * optional `**Provisional:**`). Phase ids come from `## Phase` headings.
11
- * - `goal`: reserved for the goal mode; until its milestone rules land it
12
- * validates exactly like `brief`.
11
+ * - `goal`: every `brief` rule plus the goal milestone rules: membership
12
+ * read through the control-plane Milestones reader (its error codes map
13
+ * onto `goal-*` codes), frozen delivery scopes from `oat_deliveries`,
14
+ * parallel groups inside one milestone, `oat_plan_source: goal`, and
15
+ * canonical phase ids in persisted registry lists.
13
16
  */
14
17
  export type PlanProfile = 'task' | 'lite' | 'brief' | 'goal';
15
18
  export interface ValidationContext {
16
19
  profile: PlanProfile;
17
20
  workflowMode: WorkflowMode | null;
18
21
  stateFrontmatter: Record<string, unknown> | null;
22
+ /**
23
+ * The journal (`implementation.md`), when readable. Goal membership reads it
24
+ * so a phase the journal marks extracted need not belong to a milestone.
25
+ */
26
+ implementationContent?: string | null;
19
27
  }
20
28
  export type BriefValidationErrorCode = 'brief-no-phases' | 'brief-duplicate-phase' | 'brief-phase-missing-intent' | 'brief-phase-missing-exit-evidence' | 'brief-provisional-invalid' | 'brief-first-phase-provisional' | 'brief-malformed-phase-heading';
21
29
  export type BriefValidationWarningCode = 'brief-task-headings-ignored';
30
+ export type GoalValidationErrorCode = 'goal-milestones-missing' | 'goal-milestones-malformed' | 'goal-duplicate-milestone' | 'goal-milestone-empty' | 'goal-milestone-unknown-phase' | 'goal-phase-in-multiple-milestones' | 'goal-phase-without-milestone' | 'goal-shipped-milestone-changed' | 'goal-delivered-scope-changed' | 'goal-group-crosses-milestone' | 'goal-plan-source' | 'goal-noncanonical-phase-id';
22
31
  export type LiteValidationErrorCode = 'lite-multi-phase' | 'lite-parallel-groups' | 'lite-criterion-without-command';
23
32
  export type ValidationIssue = {
24
33
  severity: 'error';
25
- code: BriefValidationErrorCode | LiteValidationErrorCode;
34
+ code: BriefValidationErrorCode | GoalValidationErrorCode | LiteValidationErrorCode;
26
35
  message: string;
27
36
  } | {
28
37
  severity: 'warning';
@@ -51,4 +60,10 @@ export declare function parsePhaseHeadingLine(line: string): {
51
60
  } | null;
52
61
  export declare function extractPhaseIds(content: string, profile: PlanProfile): string[];
53
62
  export declare function validatePlanForProfile(content: string, ctx: ValidationContext): ValidationIssue[];
63
+ /**
64
+ * Each Milestones reader error code maps onto exactly one goal rule code, so
65
+ * the reader stays the one membership predicate and validation never
66
+ * re-parses the table.
67
+ */
68
+ export declare const GOAL_MEMBERSHIP_RULE_CODES: Readonly<Record<MilestoneMembershipErrorCode, GoalValidationErrorCode>>;
54
69
  //# sourceMappingURL=profiles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/validate-plan/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,mCAAmC,CAAC;AAI3C;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,MAAM,wBAAwB,GAChC,iBAAiB,GACjB,uBAAuB,GACvB,4BAA4B,GAC5B,mCAAmC,GACnC,2BAA2B,GAC3B,+BAA+B,GAC/B,+BAA+B,CAAC;AAEpC,MAAM,MAAM,0BAA0B,GAAG,6BAA6B,CAAC;AAEvE,MAAM,MAAM,uBAAuB,GAC/B,kBAAkB,GAClB,sBAAsB,GACtB,gCAAgC,CAAC;AAErC,MAAM,MAAM,eAAe,GACvB;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,wBAAwB,GAAG,uBAAuB,CAAC;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,EAAE,0BAA0B,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,WAAW,CAIzE;AAMD,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EACA,kBAAkB,GAClB,sBAAsB,GACtB,gCAAgC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAeN,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACtC,wBAAwB,CA8C1B;AAeD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,GACX;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG1C;AAiBD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,WAAW,GACnB,MAAM,EAAE,CAIV;AA0LD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,iBAAiB,GACrB,eAAe,EAAE,CAcnB"}
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/validate-plan/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,4BAA4B,EACjC,KAAK,YAAY,EAClB,MAAM,mCAAmC,CAAC;AAI3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,WAAW,CAAC;IACrB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACjD;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,MAAM,wBAAwB,GAChC,iBAAiB,GACjB,uBAAuB,GACvB,4BAA4B,GAC5B,mCAAmC,GACnC,2BAA2B,GAC3B,+BAA+B,GAC/B,+BAA+B,CAAC;AAEpC,MAAM,MAAM,0BAA0B,GAAG,6BAA6B,CAAC;AAEvE,MAAM,MAAM,uBAAuB,GAC/B,yBAAyB,GACzB,2BAA2B,GAC3B,0BAA0B,GAC1B,sBAAsB,GACtB,8BAA8B,GAC9B,mCAAmC,GACnC,8BAA8B,GAC9B,gCAAgC,GAChC,8BAA8B,GAC9B,8BAA8B,GAC9B,kBAAkB,GAClB,4BAA4B,CAAC;AAEjC,MAAM,MAAM,uBAAuB,GAC/B,kBAAkB,GAClB,sBAAsB,GACtB,gCAAgC,CAAC;AAErC,MAAM,MAAM,eAAe,GACvB;IACE,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EACA,wBAAwB,GACxB,uBAAuB,GACvB,uBAAuB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,EAAE,0BAA0B,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,WAAW,CAIzE;AAMD,MAAM,MAAM,wBAAwB,GAChC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EACA,kBAAkB,GAClB,sBAAsB,GACtB,gCAAgC,CAAC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAeN,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACtC,wBAAwB,CA8C1B;AAeD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,GACX;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG1C;AAiBD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,WAAW,GACnB,MAAM,EAAE,CAIV;AA0LD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,iBAAiB,GACrB,eAAe,EAAE,CAenB;AAMD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAC/C,MAAM,CAAC,4BAA4B,EAAE,uBAAuB,CAAC,CAS9D,CAAC"}
@@ -1,4 +1,4 @@
1
- import { isBriefMode, parseProvisionalPhaseIds, parseTaskProgress, recordLineMask, } from '@open-agent-toolkit/control-plane';
1
+ import { isBriefMode, normalizeMilestoneId, normalizePhaseId, parseProvisionalPhaseIds, parseTaskProgress, readMilestoneMembership, recordLineMask, } from '@open-agent-toolkit/control-plane';
2
2
  import { parseFrontmatterFromContent } from './frontmatter.js';
3
3
  export function resolvePlanProfile(mode) {
4
4
  if (mode === 'lite')
@@ -261,9 +261,160 @@ export function validatePlanForProfile(content, ctx) {
261
261
  : [{ severity: 'error', code: result.code, message: result.message }];
262
262
  }
263
263
  case 'brief':
264
- case 'goal':
265
264
  return validateBriefPlan(content);
265
+ case 'goal':
266
+ return [...validateBriefPlan(content), ...validateGoalPlan(content, ctx)];
266
267
  case 'task':
267
268
  return [];
268
269
  }
269
270
  }
271
+ // ---------------------------------------------------------------------------
272
+ // goal profile
273
+ // ---------------------------------------------------------------------------
274
+ /**
275
+ * Each Milestones reader error code maps onto exactly one goal rule code, so
276
+ * the reader stays the one membership predicate and validation never
277
+ * re-parses the table.
278
+ */
279
+ export const GOAL_MEMBERSHIP_RULE_CODES = {
280
+ 'milestones-missing': 'goal-milestones-missing',
281
+ 'milestones-malformed': 'goal-milestones-malformed',
282
+ 'milestone-duplicate': 'goal-duplicate-milestone',
283
+ 'milestone-empty': 'goal-milestone-empty',
284
+ 'milestone-unknown-phase': 'goal-milestone-unknown-phase',
285
+ 'phase-in-multiple-milestones': 'goal-phase-in-multiple-milestones',
286
+ 'phase-without-milestone': 'goal-phase-without-milestone',
287
+ };
288
+ function goalError(code, message) {
289
+ return { severity: 'error', code, message: `${code}: ${message}` };
290
+ }
291
+ function isRecord(value) {
292
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
293
+ }
294
+ function rawList(value) {
295
+ return Array.isArray(value) ? value : [];
296
+ }
297
+ function readRawDeliveries(state) {
298
+ return rawList(state?.['oat_deliveries'])
299
+ .filter(isRecord)
300
+ .map((entry, index) => ({
301
+ label: typeof entry['delivery_id'] === 'string'
302
+ ? entry['delivery_id']
303
+ : `oat_deliveries[${index}]`,
304
+ milestone: normalizeMilestoneId(entry['milestone']),
305
+ scope: rawList(entry['scope'])
306
+ .map((token) => normalizePhaseId(String(token)))
307
+ .filter((token) => token !== null),
308
+ }));
309
+ }
310
+ /**
311
+ * Persisted phase-id lists in the goal registry. The state reader normalizes
312
+ * these and drops tokens that are not phase ids; validation reports every
313
+ * token that is not already canonical so the drift is repaired at its source.
314
+ */
315
+ function persistedPhaseIdLists(state) {
316
+ if (state === null)
317
+ return [];
318
+ const lists = [];
319
+ const carrierLists = (prefix, carrier) => {
320
+ if (isRecord(carrier) && isRecord(carrier['subject'])) {
321
+ lists.push({
322
+ field: `${prefix}.subject.scope`,
323
+ values: rawList(carrier['subject']['scope']),
324
+ });
325
+ }
326
+ };
327
+ const recovery = state['oat_goal_recovery'];
328
+ if (isRecord(recovery)) {
329
+ lists.push({
330
+ field: 'oat_goal_recovery.phase_ids',
331
+ values: rawList(recovery['phase_ids']),
332
+ });
333
+ }
334
+ for (const [index, entry] of rawList(state['oat_deliveries']).entries()) {
335
+ if (!isRecord(entry))
336
+ continue;
337
+ const prefix = `oat_deliveries[${index}]`;
338
+ lists.push({ field: `${prefix}.scope`, values: rawList(entry['scope']) });
339
+ if (isRecord(entry['integrated_review'])) {
340
+ lists.push({
341
+ field: `${prefix}.integrated_review.scope`,
342
+ values: rawList(entry['integrated_review']['scope']),
343
+ });
344
+ }
345
+ carrierLists(`${prefix}.exit_gate`, entry['exit_gate']);
346
+ }
347
+ const gates = state['oat_milestone_gates'];
348
+ if (isRecord(gates)) {
349
+ for (const [milestone, carriers] of Object.entries(gates)) {
350
+ if (!isRecord(carriers))
351
+ continue;
352
+ carrierLists(`oat_milestone_gates.${milestone}.plan`, carriers['plan']);
353
+ carrierLists(`oat_milestone_gates.${milestone}.exit`, carriers['exit']);
354
+ }
355
+ }
356
+ return lists;
357
+ }
358
+ function validateGoalPlan(content, ctx) {
359
+ const issues = [];
360
+ const frontmatter = parseFrontmatterFromContent(content);
361
+ const planData = frontmatter.kind === 'invalid' ? {} : frontmatter.data;
362
+ if (planData['oat_plan_source'] !== 'goal') {
363
+ issues.push(goalError('goal-plan-source', `Goal plans must declare "oat_plan_source: goal"; found ${JSON.stringify(planData['oat_plan_source'] ?? null)}.`));
364
+ }
365
+ const membership = readMilestoneMembership(content, ctx.implementationContent ?? '');
366
+ for (const error of membership.errors) {
367
+ issues.push(goalError(GOAL_MEMBERSHIP_RULE_CODES[error.code], error.message));
368
+ }
369
+ const owners = new Map();
370
+ for (const row of membership.milestones) {
371
+ for (const phaseId of row.phaseIds) {
372
+ if (!owners.has(phaseId))
373
+ owners.set(phaseId, row.milestoneId);
374
+ }
375
+ }
376
+ const declaredMilestones = new Set(membership.milestones.map((row) => row.milestoneId));
377
+ const declaredPhases = new Set(extractBriefPhaseIds(content));
378
+ for (const delivery of readRawDeliveries(ctx.stateFrontmatter)) {
379
+ if (!membership.present || delivery.milestone === null)
380
+ continue;
381
+ if (!declaredMilestones.has(delivery.milestone)) {
382
+ issues.push(goalError('goal-shipped-milestone-changed', `Delivery ${delivery.label} shipped milestone ${delivery.milestone}, which the Milestones table no longer declares.`));
383
+ continue;
384
+ }
385
+ for (const phaseId of delivery.scope) {
386
+ if (!declaredPhases.has(phaseId)) {
387
+ issues.push(goalError('goal-delivered-scope-changed', `Delivery ${delivery.label} froze ${phaseId} in its scope, but the brief no longer declares it.`));
388
+ continue;
389
+ }
390
+ const owner = owners.get(phaseId);
391
+ if (owner !== undefined && owner !== delivery.milestone) {
392
+ issues.push(goalError('goal-delivered-scope-changed', `Delivery ${delivery.label} froze ${phaseId} under ${delivery.milestone}, but the Milestones table now assigns it to ${owner}.`));
393
+ }
394
+ else if (owner === undefined) {
395
+ issues.push(goalError('goal-delivered-scope-changed', `Delivery ${delivery.label} froze ${phaseId} under ${delivery.milestone}, but the Milestones table no longer lists it there.`));
396
+ }
397
+ }
398
+ }
399
+ for (const [index, group] of rawList(planData['oat_plan_parallel_groups']).entries()) {
400
+ const groupOwners = new Set();
401
+ for (const member of rawList(group)) {
402
+ const phaseId = normalizePhaseId(String(member));
403
+ const owner = phaseId === null ? undefined : owners.get(phaseId);
404
+ if (owner !== undefined)
405
+ groupOwners.add(owner);
406
+ }
407
+ if (groupOwners.size > 1) {
408
+ issues.push(goalError('goal-group-crosses-milestone', `Parallel group ${index} spans milestones ${[...groupOwners].join(', ')}; a group must sit inside one milestone.`));
409
+ }
410
+ }
411
+ for (const { field, values } of persistedPhaseIdLists(ctx.stateFrontmatter)) {
412
+ for (const value of values) {
413
+ const token = typeof value === 'string' ? value : String(value);
414
+ if (normalizePhaseId(token) !== token) {
415
+ issues.push(goalError('goal-noncanonical-phase-id', `${field} holds ${JSON.stringify(value)}, which is not a canonical phase id like "p01" or "p-rev1".`));
416
+ }
417
+ }
418
+ }
419
+ return issues;
420
+ }
@@ -24,7 +24,7 @@ export declare const SKILL_GATE_OVERRIDE_SOURCE = "state.md:oat_skill_gate_overr
24
24
  * `skills.test.ts` pins this constant against the live `oat_gateable: true`
25
25
  * declarations so the two cannot drift apart.
26
26
  */
27
- export declare const GATE_AWARE_SKILLS: readonly ['oat-project-brief', 'oat-project-implement', 'oat-project-import-plan', 'oat-project-lite', 'oat-project-plan', 'oat-project-quick-start'];
27
+ export declare const GATE_AWARE_SKILLS: readonly ['oat-project-brief', 'oat-project-goal', 'oat-project-implement', 'oat-project-import-plan', 'oat-project-lite', 'oat-project-plan', 'oat-project-quick-start'];
28
28
  export type SkillGateOverrideValue = typeof SKILL_GATE_OVERRIDE_DISABLED;
29
29
  export interface ProjectSkillGateOverrides {
30
30
  /** False when the key is absent entirely: absence means follow configuration. */