@open-agent-toolkit/cli 0.3.17 → 0.3.19

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 (109) hide show
  1. package/assets/agents/oat-phase-implementer.md +81 -3
  2. package/assets/agents/oat-reviewer.md +15 -5
  3. package/assets/bundle-metadata.json +1 -1
  4. package/assets/docs/reference/cli-reference.md +8 -5
  5. package/assets/docs/reference/oat-directory-structure.md +1 -0
  6. package/assets/docs/reference/project-artifacts.md +17 -1
  7. package/assets/docs/reference/project-state-machine.md +19 -4
  8. package/assets/docs/skills/brainstorm.md +2 -1
  9. package/assets/docs/skills/index.md +1 -0
  10. package/assets/docs/workflows/advanced/autonomy.md +9 -1
  11. package/assets/docs/workflows/advanced/workflow-gates.md +11 -6
  12. package/assets/docs/workflows/approvals-and-automation.md +13 -8
  13. package/assets/docs/workflows/choose-workflow.md +16 -8
  14. package/assets/docs/workflows/projects/closeout/closeout-skills.md +8 -3
  15. package/assets/docs/workflows/projects/closeout/pr-flow.md +2 -0
  16. package/assets/docs/workflows/projects/execution/execution-skills.md +14 -2
  17. package/assets/docs/workflows/projects/execution/implementation-execution.md +15 -0
  18. package/assets/docs/workflows/projects/execution/picking-up-projects.md +10 -1
  19. package/assets/docs/workflows/projects/execution/project-log.md +22 -0
  20. package/assets/docs/workflows/projects/lifecycle.md +73 -9
  21. package/assets/docs/workflows/projects/planning/hill-checkpoints.md +18 -1
  22. package/assets/docs/workflows/projects/planning/planning-skills.md +11 -10
  23. package/assets/docs/workflows/projects/planning/starting-projects.md +92 -20
  24. package/assets/docs/workflows/projects/reviews/index.md +15 -2
  25. package/assets/docs/workflows/projects/reviews/review-flavors.md +5 -3
  26. package/assets/public-package-versions.json +4 -4
  27. package/assets/skills/oat-brainstorm/SKILL.md +33 -9
  28. package/assets/skills/oat-brainstorm/references/destinations.md +9 -4
  29. package/assets/skills/oat-docs/SKILL.md +9 -4
  30. package/assets/skills/oat-pjm-review-backlog/SKILL.md +2 -2
  31. package/assets/skills/oat-project-autonomous/SKILL.md +14 -8
  32. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +17 -2
  33. package/assets/skills/oat-project-brief/SKILL.md +592 -0
  34. package/assets/skills/oat-project-brief/references/brief-contract.md +294 -0
  35. package/assets/skills/oat-project-brief/references/docs/autonomy-contract.md +393 -0
  36. package/assets/skills/oat-project-brief/references/docs/gate-approval-record.md +84 -0
  37. package/assets/skills/oat-project-capture/SKILL.md +2 -2
  38. package/assets/skills/oat-project-complete/SKILL.md +7 -2
  39. package/assets/skills/oat-project-discover/SKILL.md +3 -2
  40. package/assets/skills/oat-project-dispatch-subagents/SKILL.md +9 -1
  41. package/assets/skills/oat-project-document/SKILL.md +11 -1
  42. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +17 -2
  43. package/assets/skills/oat-project-implement/SKILL.md +1 -1
  44. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +40 -0
  45. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +17 -2
  46. package/assets/skills/oat-project-implement/references/docs/gate-approval-record.md +4 -0
  47. package/assets/skills/oat-project-implement/references/phase-execution.md +166 -1
  48. package/assets/skills/oat-project-implement/references/plan-and-resume.md +97 -3
  49. package/assets/skills/oat-project-import-plan/SKILL.md +5 -1
  50. package/assets/skills/oat-project-lite/SKILL.md +4 -1
  51. package/assets/skills/oat-project-lite/references/docs/autonomy-contract.md +17 -2
  52. package/assets/skills/oat-project-new/SKILL.md +5 -1
  53. package/assets/skills/oat-project-next/SKILL.md +31 -4
  54. package/assets/skills/oat-project-next/references/docs/gate-approval-record.md +4 -0
  55. package/assets/skills/oat-project-plan/SKILL.md +12 -2
  56. package/assets/skills/oat-project-plan-writing/SKILL.md +6 -4
  57. package/assets/skills/oat-project-pr-final/SKILL.md +15 -3
  58. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +17 -2
  59. package/assets/skills/oat-project-pr-progress/SKILL.md +31 -4
  60. package/assets/skills/oat-project-progress/SKILL.md +30 -8
  61. package/assets/skills/oat-project-progress/references/docs/gate-approval-record.md +4 -0
  62. package/assets/skills/oat-project-promote-spec-driven/SKILL.md +2 -2
  63. package/assets/skills/oat-project-quick-start/SKILL.md +60 -1
  64. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +17 -2
  65. package/assets/skills/oat-project-quick-start/references/docs/gate-approval-record.md +4 -0
  66. package/assets/skills/oat-project-reconcile/SKILL.md +65 -3
  67. package/assets/skills/oat-project-review-provide/SKILL.md +331 -12
  68. package/assets/skills/oat-project-review-receive/SKILL.md +86 -1
  69. package/assets/skills/oat-project-revise/SKILL.md +27 -4
  70. package/assets/skills/oat-project-summary/SKILL.md +95 -2
  71. package/assets/skills/oat-wave-execute/SKILL.md +3 -2
  72. package/assets/templates/implementation-brief.md +165 -0
  73. package/assets/templates/plan-brief.md +140 -0
  74. package/assets/templates/plan.md +1 -1
  75. package/assets/templates/state.md +8 -2
  76. package/dist/commands/init/tools/shared/skill-script-references.d.ts.map +1 -1
  77. package/dist/commands/init/tools/shared/skill-script-references.js +8 -1
  78. package/dist/commands/project/log/append.d.ts.map +1 -1
  79. package/dist/commands/project/log/append.js +70 -10
  80. package/dist/commands/project/new/scaffold.d.ts +1 -1
  81. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  82. package/dist/commands/project/new/scaffold.js +35 -7
  83. package/dist/commands/project/promote/promote.d.ts +32 -1
  84. package/dist/commands/project/promote/promote.d.ts.map +1 -1
  85. package/dist/commands/project/promote/promote.js +562 -5
  86. package/dist/commands/project/validate-plan/frontmatter.d.ts +12 -0
  87. package/dist/commands/project/validate-plan/frontmatter.d.ts.map +1 -0
  88. package/dist/commands/project/validate-plan/frontmatter.js +27 -0
  89. package/dist/commands/project/validate-plan/index.d.ts.map +1 -1
  90. package/dist/commands/project/validate-plan/index.js +52 -18
  91. package/dist/commands/project/validate-plan/profiles.d.ts +54 -0
  92. package/dist/commands/project/validate-plan/profiles.d.ts.map +1 -0
  93. package/dist/commands/project/validate-plan/profiles.js +269 -0
  94. package/dist/commands/project/validate-plan/validate-plan.d.ts +3 -19
  95. package/dist/commands/project/validate-plan/validate-plan.d.ts.map +1 -1
  96. package/dist/commands/project/validate-plan/validate-plan.js +5 -82
  97. package/dist/commands/shared/agents-md-lock.d.ts +3 -0
  98. package/dist/commands/shared/agents-md-lock.d.ts.map +1 -0
  99. package/dist/commands/shared/agents-md-lock.js +130 -0
  100. package/dist/commands/shared/agents-md.d.ts.map +1 -1
  101. package/dist/commands/shared/agents-md.js +26 -8
  102. package/dist/commands/shared/frontmatter.d.ts +1 -1
  103. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  104. package/dist/commands/shared/frontmatter.js +1 -0
  105. package/dist/commands/state/generate.d.ts.map +1 -1
  106. package/dist/commands/state/generate.js +51 -2
  107. package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
  108. package/dist/commands/tools/shared/pack-manifest.js +3 -0
  109. package/package.json +2 -2
@@ -1,9 +1,16 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
2
+ import { join, resolve } from 'node:path';
3
3
  import { buildCommandContext, } from '../../../app/command-context.js';
4
4
  import { readGlobalOptions } from '../../shared/shared.utils.js';
5
+ import { WORKFLOW_MODES, } from '@open-agent-toolkit/control-plane';
5
6
  import { Command } from 'commander';
6
- import { extractPhaseIdsFromPlan, parseFrontmatterFromContent, validateLitePlan, validateParallelGroups, } from './validate-plan.js';
7
+ import { extractPhaseIds, parseFrontmatterFromContent, resolvePlanProfile, validateParallelGroups, validatePlanForProfile, } from './validate-plan.js';
8
+ function toWorkflowMode(value) {
9
+ return typeof value === 'string' &&
10
+ WORKFLOW_MODES.includes(value)
11
+ ? value
12
+ : null;
13
+ }
7
14
  const DEFAULT_DEPENDENCIES = {
8
15
  buildCommandContext,
9
16
  };
@@ -15,7 +22,8 @@ export function createProjectValidatePlanCommand(overrides = {}) {
15
22
  .option('--project-path <path>', 'project directory containing plan.md', process.cwd())
16
23
  .action((options, command) => {
17
24
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
18
- const planPath = join(options.projectPath, 'plan.md');
25
+ const projectPath = resolve(context.cwd, options.projectPath);
26
+ const planPath = join(projectPath, 'plan.md');
19
27
  let content;
20
28
  try {
21
29
  content = readFileSync(planPath, 'utf-8');
@@ -46,27 +54,49 @@ export function createProjectValidatePlanCommand(overrides = {}) {
46
54
  return;
47
55
  }
48
56
  const groups = frontmatterResult.data['oat_plan_parallel_groups'];
49
- const phaseIds = extractPhaseIdsFromPlan(content);
50
- let workflowMode = null;
57
+ let stateFrontmatter = null;
51
58
  try {
52
- const stateContent = readFileSync(join(options.projectPath, 'state.md'), 'utf-8');
53
- const stateFrontmatter = parseFrontmatterFromContent(stateContent);
54
- const mode = stateFrontmatter.kind === 'invalid'
55
- ? null
56
- : stateFrontmatter.data['oat_workflow_mode'];
57
- workflowMode = typeof mode === 'string' ? mode : null;
59
+ const stateContent = readFileSync(join(projectPath, 'state.md'), 'utf-8');
60
+ const parsedState = parseFrontmatterFromContent(stateContent);
61
+ stateFrontmatter =
62
+ parsedState.kind === 'invalid' ? null : parsedState.data;
58
63
  }
59
64
  catch {
60
- workflowMode = null;
65
+ stateFrontmatter = null;
66
+ }
67
+ const workflowMode = toWorkflowMode(stateFrontmatter?.['oat_workflow_mode']);
68
+ const profile = resolvePlanProfile(workflowMode);
69
+ const phaseIds = extractPhaseIds(content, profile);
70
+ const issues = validatePlanForProfile(content, {
71
+ profile,
72
+ workflowMode,
73
+ stateFrontmatter,
74
+ });
75
+ const profileErrors = issues
76
+ .filter((issue) => issue.severity === 'error')
77
+ .map((issue) => issue.message);
78
+ const warnings = issues
79
+ .filter((issue) => issue.severity === 'warning')
80
+ .map((issue) => issue.message);
81
+ const warningPayload = warnings.length > 0 ? { warnings } : {};
82
+ if (!context.json) {
83
+ for (const warning of warnings) {
84
+ context.logger.warn(warning);
85
+ }
61
86
  }
62
- const liteResult = validateLitePlan(content, workflowMode);
63
- if (!liteResult.ok) {
87
+ if (profileErrors.length > 0) {
64
88
  if (context.json) {
65
- context.logger.json({ valid: false, errors: [liteResult.message] });
89
+ context.logger.json({
90
+ valid: false,
91
+ errors: profileErrors,
92
+ ...warningPayload,
93
+ });
66
94
  }
67
95
  else {
68
96
  context.logger.error('Plan validation failed:');
69
- context.logger.error(` - ${liteResult.message}`);
97
+ for (const err of profileErrors) {
98
+ context.logger.error(` - ${err}`);
99
+ }
70
100
  }
71
101
  process.exitCode = 1;
72
102
  return;
@@ -74,7 +104,7 @@ export function createProjectValidatePlanCommand(overrides = {}) {
74
104
  const result = validateParallelGroups(groups, phaseIds);
75
105
  if (result.valid) {
76
106
  if (context.json) {
77
- context.logger.json({ valid: true });
107
+ context.logger.json({ valid: true, ...warningPayload });
78
108
  }
79
109
  else {
80
110
  context.logger.success('Plan validation passed.');
@@ -83,7 +113,11 @@ export function createProjectValidatePlanCommand(overrides = {}) {
83
113
  return;
84
114
  }
85
115
  if (context.json) {
86
- context.logger.json({ valid: false, errors: result.errors });
116
+ context.logger.json({
117
+ valid: false,
118
+ errors: result.errors,
119
+ ...warningPayload,
120
+ });
87
121
  }
88
122
  else {
89
123
  context.logger.error('Plan validation failed:');
@@ -0,0 +1,54 @@
1
+ import { type WorkflowMode } from '@open-agent-toolkit/control-plane';
2
+ /**
3
+ * Plan validation profiles. Each workflow mode validates `plan.md` against
4
+ * one profile:
5
+ *
6
+ * - `task`: spec-driven, quick and import plans. Phase ids come from task
7
+ * headings; no profile-specific body rules.
8
+ * - `lite`: the lite single-phase rules.
9
+ * - `brief`: taskless phase blocks (`**Intent:**`, `**Exit evidence:**`,
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`.
13
+ */
14
+ export type PlanProfile = 'task' | 'lite' | 'brief' | 'goal';
15
+ export interface ValidationContext {
16
+ profile: PlanProfile;
17
+ workflowMode: WorkflowMode | null;
18
+ stateFrontmatter: Record<string, unknown> | null;
19
+ }
20
+ 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
+ export type BriefValidationWarningCode = 'brief-task-headings-ignored';
22
+ export type LiteValidationErrorCode = 'lite-multi-phase' | 'lite-parallel-groups' | 'lite-criterion-without-command';
23
+ export type ValidationIssue = {
24
+ severity: 'error';
25
+ code: BriefValidationErrorCode | LiteValidationErrorCode;
26
+ message: string;
27
+ } | {
28
+ severity: 'warning';
29
+ code: BriefValidationWarningCode;
30
+ message: string;
31
+ };
32
+ export declare function resolvePlanProfile(mode: WorkflowMode | null): PlanProfile;
33
+ export type LitePlanValidationResult = {
34
+ ok: true;
35
+ } | {
36
+ ok: false;
37
+ code: 'lite-multi-phase' | 'lite-parallel-groups' | 'lite-criterion-without-command';
38
+ message: string;
39
+ };
40
+ export declare function validateLitePlan(planContent: string, workflowMode: string | null | undefined): LitePlanValidationResult;
41
+ /**
42
+ * The normalized id and name of a `## Phase` heading line, or `null` when the
43
+ * line is not a well-formed phase heading. Delegates to the control-plane
44
+ * phase-unit parser so heading spellings (`## Phase 1:`, `## Phase p01:`,
45
+ * `## Phase p-rev1:`, `## Revision Phase 1:`) normalize exactly as progress
46
+ * reads them.
47
+ */
48
+ export declare function parsePhaseHeadingLine(line: string): {
49
+ phaseId: string;
50
+ name: string;
51
+ } | null;
52
+ export declare function extractPhaseIds(content: string, profile: PlanProfile): string[];
53
+ export declare function validatePlanForProfile(content: string, ctx: ValidationContext): ValidationIssue[];
54
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,269 @@
1
+ import { isBriefMode, parseProvisionalPhaseIds, parseTaskProgress, recordLineMask, } from '@open-agent-toolkit/control-plane';
2
+ import { parseFrontmatterFromContent } from './frontmatter.js';
3
+ export function resolvePlanProfile(mode) {
4
+ if (mode === 'lite')
5
+ return 'lite';
6
+ if (isBriefMode(mode))
7
+ return mode;
8
+ return 'task';
9
+ }
10
+ function validationCriteria(planContent) {
11
+ const heading = /^## Validation Criteria[ \t]*$/m.exec(planContent);
12
+ if (!heading)
13
+ return null;
14
+ const bodyStart = heading.index + heading[0].length;
15
+ const remaining = planContent.slice(bodyStart);
16
+ const nextHeading = /^##\s+/m.exec(remaining);
17
+ const body = remaining.slice(0, nextHeading?.index ?? remaining.length);
18
+ return body
19
+ .split('\n')
20
+ .filter((line) => /^\s*-\s+/.test(line))
21
+ .map((line) => line.replace(/^\s*-\s+(?:\[[ xX]\]\s*)?/, '').trim());
22
+ }
23
+ export function validateLitePlan(planContent, workflowMode) {
24
+ if (workflowMode !== 'lite') {
25
+ return { ok: true };
26
+ }
27
+ const phaseCount = planContent.match(/^## Phase\b.*$/gm)?.length ?? 0;
28
+ if (phaseCount !== 1) {
29
+ return {
30
+ ok: false,
31
+ code: 'lite-multi-phase',
32
+ message: `Lite plans must contain exactly one phase; found ${phaseCount}.`,
33
+ };
34
+ }
35
+ const frontmatter = parseFrontmatterFromContent(planContent);
36
+ const groups = frontmatter.kind === 'invalid'
37
+ ? undefined
38
+ : frontmatter.data['oat_plan_parallel_groups'];
39
+ if (groups !== undefined &&
40
+ groups !== null &&
41
+ !(Array.isArray(groups) && groups.length === 0)) {
42
+ return {
43
+ ok: false,
44
+ code: 'lite-parallel-groups',
45
+ message: 'Lite plans must declare no parallel groups.',
46
+ };
47
+ }
48
+ const criteria = validationCriteria(planContent);
49
+ const invalidCriterion = criteria?.find((criterion) => !criterion.startsWith('manual:') && !/`[^`\n]+`/.test(criterion));
50
+ if (!criteria || criteria.length === 0 || invalidCriterion !== undefined) {
51
+ return {
52
+ ok: false,
53
+ code: 'lite-criterion-without-command',
54
+ message: 'Every lite Validation Criteria bullet must name a command in backticks or start with manual: after the bullet marker.',
55
+ };
56
+ }
57
+ return { ok: true };
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // phase id extraction
61
+ // ---------------------------------------------------------------------------
62
+ function extractTaskHeadingPhaseIds(planContent) {
63
+ const matches = planContent.match(/### Task (p\d+)-t\d+/g) ?? [];
64
+ const ids = matches.map((m) => {
65
+ const parts = m.split(' ');
66
+ return parts[2].split('-')[0];
67
+ });
68
+ return Array.from(new Set(ids)).sort();
69
+ }
70
+ /**
71
+ * The normalized id and name of a `## Phase` heading line, or `null` when the
72
+ * line is not a well-formed phase heading. Delegates to the control-plane
73
+ * phase-unit parser so heading spellings (`## Phase 1:`, `## Phase p01:`,
74
+ * `## Phase p-rev1:`, `## Revision Phase 1:`) normalize exactly as progress
75
+ * reads them.
76
+ */
77
+ export function parsePhaseHeadingLine(line) {
78
+ const phase = parseTaskProgress(line, '', { phaseUnits: true }).phases[0];
79
+ return phase ? { phaseId: phase.phaseId, name: phase.name } : null;
80
+ }
81
+ function phaseIdOfHeading(line) {
82
+ return parsePhaseHeadingLine(line)?.phaseId ?? null;
83
+ }
84
+ /**
85
+ * Normalized `## Phase` heading ids in document order, outside fenced and
86
+ * indented code, each listed once.
87
+ */
88
+ function extractBriefPhaseIds(planContent) {
89
+ const ids = parseTaskProgress(planContent, '', {
90
+ phaseUnits: true,
91
+ }).phases.map((phase) => phase.phaseId);
92
+ return Array.from(new Set(ids));
93
+ }
94
+ export function extractPhaseIds(content, profile) {
95
+ return profile === 'brief' || profile === 'goal'
96
+ ? extractBriefPhaseIds(content)
97
+ : extractTaskHeadingPhaseIds(content);
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // brief profile
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Plan record lines, through the control-plane reader that phase-unit
104
+ * progress and quick plan readiness use, so a fenced example phase block
105
+ * never validates or fails a real plan.
106
+ */
107
+ function recordLines(content) {
108
+ const lines = content.split('\n');
109
+ const mask = recordLineMask(lines);
110
+ return lines.filter((_, index) => mask[index]);
111
+ }
112
+ const PHASE_HEADING_CANDIDATE = /^##[ \t]+(?:Revision[ \t]+)?Phase\b/;
113
+ const SECTION_HEADING = /^#{1,2}[ \t]/;
114
+ const ANY_HEADING = /^#/;
115
+ const TASK_HEADING = /^###[ \t]+Task\b/;
116
+ const FIELD_MARKER = /^\*\*[^*\n]+:\*\*/;
117
+ const INTENT_MARKER = /^\*\*Intent:\*\*(.*)$/;
118
+ const EXIT_EVIDENCE_MARKER = /^\*\*Exit evidence:\*\*(.*)$/;
119
+ const PROVISIONAL_LINE = /^\*\*Provisional:\*\*/;
120
+ // The control-plane parser's grammar: a space after the marker, then the value.
121
+ const PROVISIONAL_VALID = /^\*\*Provisional:\*\*\s+(?:yes|no)$/;
122
+ /**
123
+ * Whether a phase block carries `marker` with content: a non-empty value on
124
+ * the marker line, or at least one continuation line before the next blank
125
+ * line or field marker.
126
+ */
127
+ function hasFilledField(lines, marker) {
128
+ for (const [index, line] of lines.entries()) {
129
+ const match = line.trim().match(marker);
130
+ if (!match)
131
+ continue;
132
+ if ((match[1] ?? '').trim().length > 0)
133
+ return true;
134
+ const next = (lines[index + 1] ?? '').trim();
135
+ if (next.length > 0 && !FIELD_MARKER.test(next))
136
+ return true;
137
+ }
138
+ return false;
139
+ }
140
+ function readBriefPhaseBlocks(content) {
141
+ const blocks = [];
142
+ const malformed = [];
143
+ // Marker ownership ends at the next line starting with `#` (the phase block
144
+ // grammar); task-heading attribution lasts until the next `#`/`##` heading.
145
+ let owner = null;
146
+ let section = null;
147
+ for (const line of recordLines(content)) {
148
+ if (PHASE_HEADING_CANDIDATE.test(line)) {
149
+ const phaseId = phaseIdOfHeading(line);
150
+ if (phaseId == null) {
151
+ malformed.push(line.trim());
152
+ owner = null;
153
+ section = null;
154
+ continue;
155
+ }
156
+ const block = { phaseId, lines: [], taskHeadings: 0 };
157
+ blocks.push(block);
158
+ owner = block;
159
+ section = block;
160
+ continue;
161
+ }
162
+ if (SECTION_HEADING.test(line)) {
163
+ owner = null;
164
+ section = null;
165
+ continue;
166
+ }
167
+ if (TASK_HEADING.test(line) && section != null) {
168
+ section.taskHeadings += 1;
169
+ }
170
+ if (ANY_HEADING.test(line)) {
171
+ owner = null;
172
+ continue;
173
+ }
174
+ owner?.lines.push(line);
175
+ }
176
+ return { blocks, malformed };
177
+ }
178
+ function validateBriefPlan(content) {
179
+ const issues = [];
180
+ const { blocks, malformed } = readBriefPhaseBlocks(content);
181
+ for (const heading of malformed) {
182
+ issues.push({
183
+ severity: 'error',
184
+ code: 'brief-malformed-phase-heading',
185
+ message: `brief-malformed-phase-heading: "${heading}" is not a phase heading; use "## Phase pNN: <name>".`,
186
+ });
187
+ }
188
+ if (blocks.length === 0) {
189
+ issues.push({
190
+ severity: 'error',
191
+ code: 'brief-no-phases',
192
+ message: 'brief-no-phases: Brief plans must declare at least one "## Phase pNN: <name>" block.',
193
+ });
194
+ return issues;
195
+ }
196
+ const seen = new Set();
197
+ const reported = new Set();
198
+ for (const { phaseId } of blocks) {
199
+ if (seen.has(phaseId) && !reported.has(phaseId)) {
200
+ reported.add(phaseId);
201
+ issues.push({
202
+ severity: 'error',
203
+ code: 'brief-duplicate-phase',
204
+ message: `brief-duplicate-phase: Phase ${phaseId} is declared more than once.`,
205
+ });
206
+ }
207
+ seen.add(phaseId);
208
+ }
209
+ for (const block of blocks) {
210
+ if (!hasFilledField(block.lines, INTENT_MARKER)) {
211
+ issues.push({
212
+ severity: 'error',
213
+ code: 'brief-phase-missing-intent',
214
+ message: `brief-phase-missing-intent: Phase ${block.phaseId} has no non-empty **Intent:**.`,
215
+ });
216
+ }
217
+ if (!hasFilledField(block.lines, EXIT_EVIDENCE_MARKER)) {
218
+ issues.push({
219
+ severity: 'error',
220
+ code: 'brief-phase-missing-exit-evidence',
221
+ message: `brief-phase-missing-exit-evidence: Phase ${block.phaseId} has no non-empty **Exit evidence:**.`,
222
+ });
223
+ }
224
+ for (const line of block.lines) {
225
+ const trimmed = line.trim();
226
+ if (PROVISIONAL_LINE.test(trimmed) && !PROVISIONAL_VALID.test(trimmed)) {
227
+ issues.push({
228
+ severity: 'error',
229
+ code: 'brief-provisional-invalid',
230
+ message: `brief-provisional-invalid: Phase ${block.phaseId} has "${trimmed}"; use "**Provisional:** yes" or "**Provisional:** no".`,
231
+ });
232
+ }
233
+ }
234
+ }
235
+ const firstPhaseId = blocks[0].phaseId;
236
+ if (parseProvisionalPhaseIds(content).has(firstPhaseId)) {
237
+ issues.push({
238
+ severity: 'error',
239
+ code: 'brief-first-phase-provisional',
240
+ message: `brief-first-phase-provisional: The first phase (${firstPhaseId}) cannot be provisional.`,
241
+ });
242
+ }
243
+ const withTasks = blocks.filter((block) => block.taskHeadings > 0);
244
+ if (withTasks.length > 0) {
245
+ issues.push({
246
+ severity: 'warning',
247
+ code: 'brief-task-headings-ignored',
248
+ message: `brief-task-headings-ignored: Task headings in plan.md under ${withTasks
249
+ .map((block) => block.phaseId)
250
+ .join(', ')} do not count toward brief progress; record execution tasks in implementation.md.`,
251
+ });
252
+ }
253
+ return issues;
254
+ }
255
+ export function validatePlanForProfile(content, ctx) {
256
+ switch (ctx.profile) {
257
+ case 'lite': {
258
+ const result = validateLitePlan(content, 'lite');
259
+ return result.ok
260
+ ? []
261
+ : [{ severity: 'error', code: result.code, message: result.message }];
262
+ }
263
+ case 'brief':
264
+ case 'goal':
265
+ return validateBriefPlan(content);
266
+ case 'task':
267
+ return [];
268
+ }
269
+ }
@@ -1,28 +1,12 @@
1
- export type FrontmatterResult = {
2
- kind: 'ok';
3
- data: Record<string, unknown>;
4
- } | {
5
- kind: 'no-frontmatter';
6
- data: Record<string, unknown>;
7
- } | {
8
- kind: 'invalid';
9
- message: string;
10
- };
11
- export declare function parseFrontmatterFromContent(content: string): FrontmatterResult;
1
+ export { parseFrontmatterFromContent, type FrontmatterResult, } from './frontmatter.js';
2
+ export { extractPhaseIds, resolvePlanProfile, validateLitePlan, validatePlanForProfile, type LitePlanValidationResult, type PlanProfile, type ValidationContext, type ValidationIssue, } from './profiles.js';
12
3
  export type ValidationResult = {
13
4
  valid: true;
14
5
  } | {
15
6
  valid: false;
16
7
  errors: string[];
17
8
  };
18
- export type LitePlanValidationResult = {
19
- ok: true;
20
- } | {
21
- ok: false;
22
- code: 'lite-multi-phase' | 'lite-parallel-groups' | 'lite-criterion-without-command';
23
- message: string;
24
- };
25
- export declare function validateLitePlan(planContent: string, workflowMode: string | null | undefined): LitePlanValidationResult;
26
9
  export declare function validateParallelGroups(groups: unknown, phaseIds: readonly string[]): ValidationResult;
10
+ /** Task-profile phase ids: unique task-heading phase ids, sorted. */
27
11
  export declare function extractPhaseIdsFromPlan(planContent: string): string[];
28
12
  //# sourceMappingURL=validate-plan.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-plan.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/validate-plan/validate-plan.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,GACd,iBAAiB,CAyBnB;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEvC,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,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,gBAAgB,CAgDlB;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOrE"}
1
+ {"version":3,"file":"validate-plan.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/validate-plan/validate-plan.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,2BAA2B,EAC3B,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,gBAAgB,GACxB;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEvC,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,gBAAgB,CAgDlB;AAED,qEAAqE;AACrE,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAErE"}
@@ -1,79 +1,6 @@
1
- import YAML from 'yaml';
2
- const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/;
3
- export function parseFrontmatterFromContent(content) {
4
- const match = content.match(FRONTMATTER_PATTERN);
5
- if (!match?.[1]) {
6
- return { kind: 'no-frontmatter', data: {} };
7
- }
8
- try {
9
- const parsed = YAML.parse(match[1]);
10
- if (parsed !== null &&
11
- typeof parsed === 'object' &&
12
- !Array.isArray(parsed)) {
13
- return { kind: 'ok', data: parsed };
14
- }
15
- return {
16
- kind: 'invalid',
17
- message: 'Frontmatter did not parse as an object',
18
- };
19
- }
20
- catch (err) {
21
- const message = err instanceof Error ? err.message : String(err);
22
- return {
23
- kind: 'invalid',
24
- message: `Malformed YAML in frontmatter: ${message}`,
25
- };
26
- }
27
- }
28
- function validationCriteria(planContent) {
29
- const heading = /^## Validation Criteria[ \t]*$/m.exec(planContent);
30
- if (!heading)
31
- return null;
32
- const bodyStart = heading.index + heading[0].length;
33
- const remaining = planContent.slice(bodyStart);
34
- const nextHeading = /^##\s+/m.exec(remaining);
35
- const body = remaining.slice(0, nextHeading?.index ?? remaining.length);
36
- return body
37
- .split('\n')
38
- .filter((line) => /^\s*-\s+/.test(line))
39
- .map((line) => line.replace(/^\s*-\s+(?:\[[ xX]\]\s*)?/, '').trim());
40
- }
41
- export function validateLitePlan(planContent, workflowMode) {
42
- if (workflowMode !== 'lite') {
43
- return { ok: true };
44
- }
45
- const phaseCount = planContent.match(/^## Phase\b.*$/gm)?.length ?? 0;
46
- if (phaseCount !== 1) {
47
- return {
48
- ok: false,
49
- code: 'lite-multi-phase',
50
- message: `Lite plans must contain exactly one phase; found ${phaseCount}.`,
51
- };
52
- }
53
- const frontmatter = parseFrontmatterFromContent(planContent);
54
- const groups = frontmatter.kind === 'invalid'
55
- ? undefined
56
- : frontmatter.data['oat_plan_parallel_groups'];
57
- if (groups !== undefined &&
58
- groups !== null &&
59
- !(Array.isArray(groups) && groups.length === 0)) {
60
- return {
61
- ok: false,
62
- code: 'lite-parallel-groups',
63
- message: 'Lite plans must declare no parallel groups.',
64
- };
65
- }
66
- const criteria = validationCriteria(planContent);
67
- const invalidCriterion = criteria?.find((criterion) => !criterion.startsWith('manual:') && !/`[^`\n]+`/.test(criterion));
68
- if (!criteria || criteria.length === 0 || invalidCriterion !== undefined) {
69
- return {
70
- ok: false,
71
- code: 'lite-criterion-without-command',
72
- message: 'Every lite Validation Criteria bullet must name a command in backticks or start with manual: after the bullet marker.',
73
- };
74
- }
75
- return { ok: true };
76
- }
1
+ import { extractPhaseIds } from './profiles.js';
2
+ export { parseFrontmatterFromContent, } from './frontmatter.js';
3
+ export { extractPhaseIds, resolvePlanProfile, validateLitePlan, validatePlanForProfile, } from './profiles.js';
77
4
  export function validateParallelGroups(groups, phaseIds) {
78
5
  // Missing or empty → valid (sequential execution, no parallelism declared)
79
6
  if (groups === undefined || groups === null) {
@@ -116,11 +43,7 @@ export function validateParallelGroups(groups, phaseIds) {
116
43
  }
117
44
  return errors.length > 0 ? { valid: false, errors } : { valid: true };
118
45
  }
46
+ /** Task-profile phase ids: unique task-heading phase ids, sorted. */
119
47
  export function extractPhaseIdsFromPlan(planContent) {
120
- const matches = planContent.match(/### Task (p\d+)-t\d+/g) ?? [];
121
- const ids = matches.map((m) => {
122
- const parts = m.split(' ');
123
- return parts[2].split('-')[0];
124
- });
125
- return Array.from(new Set(ids)).sort();
48
+ return extractPhaseIds(planContent, 'task');
126
49
  }
@@ -0,0 +1,3 @@
1
+ /** Specific advisory serialization for cooperating AGENTS.md appenders. */
2
+ export declare function acquireAgentsMdAppendLock(repoRoot: string, targetPath: string): Promise<() => Promise<void>>;
3
+ //# sourceMappingURL=agents-md-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-md-lock.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/agents-md-lock.ts"],"names":[],"mappings":"AAOA,2EAA2E;AAC3E,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CA6I9B"}