@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.
- package/README.md +2 -1
- package/assets/agents/oat-phase-implementer.md +26 -3
- package/assets/agents/oat-reviewer.md +8 -3
- package/assets/bundle-metadata.json +1 -1
- package/assets/docs/reference/cli-reference.md +12 -7
- package/assets/docs/reference/oat-directory-structure.md +1 -0
- package/assets/docs/reference/project-artifacts.md +56 -1
- package/assets/docs/reference/project-state-machine.md +41 -7
- package/assets/docs/skills/brainstorm.md +3 -2
- package/assets/docs/skills/index.md +13 -10
- package/assets/docs/workflows/advanced/autonomy.md +3 -1
- package/assets/docs/workflows/advanced/dispatch-ceiling.md +2 -2
- package/assets/docs/workflows/advanced/workflow-gates.md +17 -4
- package/assets/docs/workflows/approvals-and-automation.md +21 -10
- package/assets/docs/workflows/choose-workflow.md +17 -9
- package/assets/docs/workflows/ideas/index.md +1 -1
- package/assets/docs/workflows/ideas/lifecycle.md +3 -2
- package/assets/docs/workflows/projects/closeout/closeout-skills.md +80 -7
- package/assets/docs/workflows/projects/closeout/pr-flow.md +7 -1
- package/assets/docs/workflows/projects/execution/execution-skills.md +16 -3
- package/assets/docs/workflows/projects/execution/implementation-execution.md +1 -1
- package/assets/docs/workflows/projects/execution/picking-up-projects.md +11 -1
- package/assets/docs/workflows/projects/lifecycle.md +108 -9
- package/assets/docs/workflows/projects/planning/hill-checkpoints.md +6 -6
- package/assets/docs/workflows/projects/planning/planning-skills.md +9 -7
- package/assets/docs/workflows/projects/planning/starting-projects.md +57 -11
- package/assets/docs/workflows/projects/reviews/index.md +22 -1
- package/assets/docs/workflows/projects/reviews/review-flavors.md +5 -3
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-brainstorm/SKILL.md +34 -9
- package/assets/skills/oat-brainstorm/references/destinations.md +6 -3
- package/assets/skills/oat-dispatch-subagents/SKILL.md +1 -1
- package/assets/skills/oat-dispatch-subagents/references/record-schema.md +6 -0
- package/assets/skills/oat-docs/SKILL.md +7 -3
- package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
- package/assets/skills/oat-project-autonomous/SKILL.md +9 -3
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +151 -132
- package/assets/skills/oat-project-brief/SKILL.md +1 -1
- package/assets/skills/oat-project-brief/references/brief-contract.md +465 -9
- package/assets/skills/oat-project-brief/references/docs/autonomy-contract.md +151 -132
- package/assets/skills/oat-project-brief/references/docs/gate-approval-record.md +9 -0
- package/assets/skills/oat-project-capture/SKILL.md +2 -2
- package/assets/skills/oat-project-complete/SKILL.md +14 -5
- package/assets/skills/oat-project-discover/SKILL.md +3 -2
- package/assets/skills/oat-project-document/SKILL.md +12 -6
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +151 -132
- package/assets/skills/oat-project-goal/SKILL.md +697 -0
- package/assets/skills/oat-project-goal/references/docs/autonomy-contract.md +412 -0
- package/assets/skills/oat-project-goal/references/docs/gate-approval-record.md +93 -0
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +67 -0
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +151 -132
- package/assets/skills/oat-project-implement/references/docs/gate-approval-record.md +9 -0
- package/assets/skills/oat-project-implement/references/phase-execution.md +10 -3
- package/assets/skills/oat-project-implement/references/plan-and-resume.md +28 -1
- package/assets/skills/oat-project-lite/SKILL.md +1 -1
- package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +151 -132
- package/assets/skills/oat-project-new/SKILL.md +3 -3
- package/assets/skills/oat-project-next/SKILL.md +45 -15
- package/assets/skills/oat-project-next/references/docs/gate-approval-record.md +9 -0
- package/assets/skills/oat-project-plan/SKILL.md +14 -3
- package/assets/skills/oat-project-plan-writing/SKILL.md +6 -4
- package/assets/skills/oat-project-pr-final/SKILL.md +51 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +151 -132
- package/assets/skills/oat-project-pr-progress/SKILL.md +97 -3
- package/assets/skills/oat-project-progress/SKILL.md +38 -5
- package/assets/skills/oat-project-progress/references/docs/gate-approval-record.md +9 -0
- package/assets/skills/oat-project-quick-start/SKILL.md +14 -9
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +151 -132
- package/assets/skills/oat-project-quick-start/references/docs/gate-approval-record.md +9 -0
- package/assets/skills/oat-project-reconcile/SKILL.md +31 -2
- package/assets/skills/oat-project-review-provide/SKILL.md +79 -13
- package/assets/skills/oat-project-review-receive/SKILL.md +70 -1
- package/assets/skills/oat-project-revise/SKILL.md +25 -1
- package/assets/skills/oat-project-summary/SKILL.md +60 -5
- package/assets/skills/oat-wave-execute/SKILL.md +2 -2
- package/assets/templates/state.md +1 -1
- package/dist/commands/project/closeout-check/closeout-invariant.d.ts +119 -3
- package/dist/commands/project/closeout-check/closeout-invariant.d.ts.map +1 -1
- package/dist/commands/project/closeout-check/closeout-invariant.js +454 -2
- package/dist/commands/project/closeout-check/index.d.ts +21 -3
- package/dist/commands/project/closeout-check/index.d.ts.map +1 -1
- package/dist/commands/project/closeout-check/index.js +190 -4
- package/dist/commands/project/complete-state/index.d.ts +2 -1
- package/dist/commands/project/complete-state/index.d.ts.map +1 -1
- package/dist/commands/project/complete-state/index.js +7 -1
- package/dist/commands/project/delivery/check.d.ts +66 -0
- package/dist/commands/project/delivery/check.d.ts.map +1 -0
- package/dist/commands/project/delivery/check.js +232 -0
- package/dist/commands/project/delivery/evidence.d.ts +171 -0
- package/dist/commands/project/delivery/evidence.d.ts.map +1 -0
- package/dist/commands/project/delivery/evidence.js +902 -0
- package/dist/commands/project/delivery/index.d.ts +7 -0
- package/dist/commands/project/delivery/index.d.ts.map +1 -0
- package/dist/commands/project/delivery/index.js +13 -0
- package/dist/commands/project/delivery/refresh.d.ts +110 -0
- package/dist/commands/project/delivery/refresh.d.ts.map +1 -0
- package/dist/commands/project/delivery/refresh.js +441 -0
- package/dist/commands/project/index.d.ts.map +1 -1
- package/dist/commands/project/index.js +2 -0
- package/dist/commands/project/log/append.d.ts +6 -0
- package/dist/commands/project/log/append.d.ts.map +1 -1
- package/dist/commands/project/log/append.js +6 -1
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +47 -1
- package/dist/commands/project/promote/promote.d.ts +15 -1
- package/dist/commands/project/promote/promote.d.ts.map +1 -1
- package/dist/commands/project/promote/promote.js +99 -15
- package/dist/commands/project/validate-plan/index.d.ts.map +1 -1
- package/dist/commands/project/validate-plan/index.js +10 -0
- package/dist/commands/project/validate-plan/profiles.d.ts +19 -4
- package/dist/commands/project/validate-plan/profiles.d.ts.map +1 -1
- package/dist/commands/project/validate-plan/profiles.js +153 -2
- package/dist/commands/shared/frontmatter.d.ts +1 -1
- package/dist/commands/shared/frontmatter.d.ts.map +1 -1
- package/dist/commands/shared/frontmatter.js +1 -0
- package/dist/commands/state/generate.d.ts.map +1 -1
- package/dist/commands/state/generate.js +35 -4
- package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-manifest.js +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAErE,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAEL,iBAAiB,EAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,4BAA4B,EAC7B,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAGL,mBAAmB,EAGnB,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAKtD,OAAO,
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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'] =
|
|
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}
|
|
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.
|
|
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
|
|
896
|
-
.argument('<project-path>', 'Lite or
|
|
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,
|
|
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`:
|
|
12
|
-
*
|
|
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,
|
|
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. */
|