@open-agent-toolkit/cli 0.1.5 → 0.1.7

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 (84) hide show
  1. package/assets/agents/oat-reviewer.md +10 -1
  2. package/assets/docs/workflows/projects/artifacts.md +17 -0
  3. package/assets/docs/workflows/projects/index.md +3 -0
  4. package/assets/docs/workflows/projects/splitting.md +79 -0
  5. package/assets/docs/workflows/skills/index.md +2 -0
  6. package/assets/public-package-versions.json +4 -4
  7. package/assets/skills/oat-brainstorm/SKILL.md +43 -3
  8. package/assets/skills/oat-project-discover/SKILL.md +72 -8
  9. package/assets/skills/oat-project-implement/SKILL.md +21 -1
  10. package/assets/skills/oat-project-quick-start/SKILL.md +14 -5
  11. package/assets/skills/oat-project-review-provide/SKILL.md +9 -1
  12. package/assets/skills/oat-project-review-receive/SKILL.md +21 -1
  13. package/assets/skills/oat-project-split/SKILL.md +82 -0
  14. package/assets/skills/oat-project-summary/SKILL.md +15 -13
  15. package/assets/templates/implementation.md +5 -5
  16. package/assets/templates/state.md +6 -1
  17. package/assets/templates/summary.md +2 -1
  18. package/dist/__tests__/skills/split-flow-fixtures.d.ts +50 -0
  19. package/dist/__tests__/skills/split-flow-fixtures.d.ts.map +1 -0
  20. package/dist/__tests__/skills/split-flow-fixtures.js +161 -0
  21. package/dist/commands/init/tools/shared/skill-manifest.d.ts +1 -1
  22. package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
  23. package/dist/commands/init/tools/shared/skill-manifest.js +1 -0
  24. package/dist/commands/project/complete-discovery/index.d.ts +16 -0
  25. package/dist/commands/project/complete-discovery/index.d.ts.map +1 -0
  26. package/dist/commands/project/complete-discovery/index.js +123 -0
  27. package/dist/commands/project/complete-state/index.d.ts.map +1 -1
  28. package/dist/commands/project/complete-state/index.js +5 -0
  29. package/dist/commands/project/index.d.ts.map +1 -1
  30. package/dist/commands/project/index.js +4 -0
  31. package/dist/commands/project/list.d.ts +6 -0
  32. package/dist/commands/project/list.d.ts.map +1 -1
  33. package/dist/commands/project/list.js +37 -4
  34. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  35. package/dist/commands/project/new/scaffold.js +4 -0
  36. package/dist/commands/project/open/index.d.ts.map +1 -1
  37. package/dist/commands/project/open/index.js +9 -3
  38. package/dist/commands/project/pause/index.d.ts.map +1 -1
  39. package/dist/commands/project/pause/index.js +7 -1
  40. package/dist/commands/project/split/evaluate-signals.d.ts +8 -0
  41. package/dist/commands/project/split/evaluate-signals.d.ts.map +1 -0
  42. package/dist/commands/project/split/evaluate-signals.js +47 -0
  43. package/dist/commands/project/split/index.d.ts +3 -0
  44. package/dist/commands/project/split/index.d.ts.map +1 -0
  45. package/dist/commands/project/split/index.js +11 -0
  46. package/dist/commands/project/split/run.d.ts +21 -0
  47. package/dist/commands/project/split/run.d.ts.map +1 -0
  48. package/dist/commands/project/split/run.js +231 -0
  49. package/dist/commands/project/split/validate-plan.d.ts +14 -0
  50. package/dist/commands/project/split/validate-plan.d.ts.map +1 -0
  51. package/dist/commands/project/split/validate-plan.js +62 -0
  52. package/dist/commands/shared/frontmatter.d.ts +9 -0
  53. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  54. package/dist/commands/shared/frontmatter.js +46 -0
  55. package/dist/commands/state/generate.d.ts.map +1 -1
  56. package/dist/commands/state/generate.js +38 -6
  57. package/dist/projects/split/child-plan.d.ts +46 -0
  58. package/dist/projects/split/child-plan.d.ts.map +1 -0
  59. package/dist/projects/split/child-plan.js +107 -0
  60. package/dist/projects/split/document-validation.d.ts +14 -0
  61. package/dist/projects/split/document-validation.d.ts.map +1 -0
  62. package/dist/projects/split/document-validation.js +106 -0
  63. package/dist/projects/split/finalize.d.ts +7 -0
  64. package/dist/projects/split/finalize.d.ts.map +1 -0
  65. package/dist/projects/split/finalize.js +32 -0
  66. package/dist/projects/split/resume.d.ts +19 -0
  67. package/dist/projects/split/resume.d.ts.map +1 -0
  68. package/dist/projects/split/resume.js +107 -0
  69. package/dist/projects/split/seed-children.d.ts +9 -0
  70. package/dist/projects/split/seed-children.d.ts.map +1 -0
  71. package/dist/projects/split/seed-children.js +122 -0
  72. package/dist/projects/split/signals.d.ts +10 -0
  73. package/dist/projects/split/signals.d.ts.map +1 -0
  74. package/dist/projects/split/signals.js +18 -0
  75. package/dist/projects/split/validation.d.ts +14 -0
  76. package/dist/projects/split/validation.d.ts.map +1 -0
  77. package/dist/projects/split/validation.js +104 -0
  78. package/dist/projects/split/write-parent.d.ts +16 -0
  79. package/dist/projects/split/write-parent.d.ts.map +1 -0
  80. package/dist/projects/split/write-parent.js +176 -0
  81. package/dist/validation/project-state.d.ts +50 -0
  82. package/dist/validation/project-state.d.ts.map +1 -0
  83. package/dist/validation/project-state.js +279 -0
  84. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/project/list.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAClE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAgGD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAcT"}
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/project/list.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAClE,mBAAmB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC3E,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAwJD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAkBT"}
@@ -1,5 +1,6 @@
1
1
  import { isAbsolute, join } from 'node:path';
2
2
  import { buildCommandContext, } from '../../app/command-context.js';
3
+ import { parseFrontmatterField } from '../shared/frontmatter.js';
3
4
  import { resolveProjectsRoot } from '../shared/oat-paths.js';
4
5
  import { readGlobalOptions } from '../shared/shared.utils.js';
5
6
  import { resolveProjectRoot } from '../../fs/paths.js';
@@ -10,8 +11,39 @@ const DEFAULT_DEPENDENCIES = {
10
11
  resolveProjectRoot,
11
12
  resolveProjectsRoot,
12
13
  listProjects,
14
+ readProjectMetadata,
13
15
  processEnv: process.env,
14
16
  };
17
+ async function readProjectMetadata(projectPath) {
18
+ const stateFile = join(projectPath, 'state.md');
19
+ const [kind, phase, phaseStatus] = await Promise.all([
20
+ parseFrontmatterField(stateFile, 'oat_kind'),
21
+ parseFrontmatterField(stateFile, 'oat_phase'),
22
+ parseFrontmatterField(stateFile, 'oat_phase_status'),
23
+ ]);
24
+ return {
25
+ kind: kind || 'implementation',
26
+ phase: phase || 'discovery',
27
+ phaseStatus: phaseStatus || 'in_progress',
28
+ };
29
+ }
30
+ function isTerminalCoordinationProject(metadata) {
31
+ return (metadata.kind === 'coordination' &&
32
+ metadata.phase === 'decomposition' &&
33
+ metadata.phaseStatus === 'complete');
34
+ }
35
+ async function filterProjectsForList(projects, projectsRoot, includeCoordination, dependencies) {
36
+ if (includeCoordination)
37
+ return projects;
38
+ const filtered = [];
39
+ for (const project of projects) {
40
+ const metadata = await dependencies.readProjectMetadata(join(projectsRoot, project.name));
41
+ if (!isTerminalCoordinationProject(metadata)) {
42
+ filtered.push(project);
43
+ }
44
+ }
45
+ return filtered;
46
+ }
15
47
  function formatProjectTable(projects) {
16
48
  if (projects.length === 0) {
17
49
  return ['No tracked projects found.'];
@@ -48,14 +80,14 @@ function formatProjectTable(projects) {
48
80
  ].join(' '));
49
81
  return [header, divider, ...lines];
50
82
  }
51
- async function runProjectList(context, dependencies) {
83
+ async function runProjectList(context, dependencies, options) {
52
84
  try {
53
85
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
54
86
  const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
55
87
  const absoluteProjectsRoot = isAbsolute(projectsRoot)
56
88
  ? projectsRoot
57
89
  : join(repoRoot, projectsRoot);
58
- const projects = await dependencies.listProjects(absoluteProjectsRoot);
90
+ const projects = await filterProjectsForList(await dependencies.listProjects(absoluteProjectsRoot), absoluteProjectsRoot, options.includeCoordination ?? false, dependencies);
59
91
  if (context.json) {
60
92
  context.logger.json({ status: 'ok', projects });
61
93
  }
@@ -84,8 +116,9 @@ export function createProjectListCommand(overrides = {}) {
84
116
  };
85
117
  return new Command('list')
86
118
  .description('List tracked OAT projects')
87
- .action(async (_options, command) => {
119
+ .option('--include-coordination', 'Include completed coordination parent projects')
120
+ .action(async (options, command) => {
88
121
  const context = dependencies.buildCommandContext(readGlobalOptions(command));
89
- await runProjectList(context, dependencies);
122
+ await runProjectList(context, dependencies, options);
90
123
  });
91
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAqMD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAqDhC"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/new/scaffold.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAwMD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAqDhC"}
@@ -4,6 +4,7 @@ import { resolveProjectsRoot } from '../../shared/oat-paths.js';
4
4
  import { generateStateDashboard } from '../../state/generate.js';
5
5
  import { setActiveProject } from '../../../config/oat-config.js';
6
6
  import { fileExists } from '../../../fs/io.js';
7
+ import { assertValidProjectStateContent } from '../../../validation/project-state.js';
7
8
  const TEMPLATES_BY_MODE = {
8
9
  'spec-driven': [
9
10
  'state.md',
@@ -117,6 +118,9 @@ async function scaffoldModeTemplates(repoRoot, projectPath, projectName, mode, t
117
118
  }
118
119
  const template = await readFile(src, 'utf8');
119
120
  const rendered = applyTemplateReplacements(template, projectName, today, nowUtc, mode);
121
+ if (templateFile === 'state.md') {
122
+ assertValidProjectStateContent(rendered, { filePath: dest });
123
+ }
120
124
  await writeFile(dest, rendered, 'utf8');
121
125
  createdFiles.push(templateFile);
122
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/open/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAYhF,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AA+JD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAkBT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/open/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAYhF,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,cAAc,EAIpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,uBAAuB;IAC/B,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,KACnB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAkKD,wBAAgB,wBAAwB,CACtC,SAAS,GAAE,OAAO,CAAC,uBAAuB,CAAM,GAC/C,OAAO,CAkBT"}
@@ -9,6 +9,7 @@ import { generateStateDashboard as defaultGenerateStateDashboard, } from '../../
9
9
  import { readOatLocalConfig, setActiveProject, writeOatLocalConfig, } from '../../../config/oat-config.js';
10
10
  import { dirExists, fileExists } from '../../../fs/io.js';
11
11
  import { resolveProjectRoot } from '../../../fs/paths.js';
12
+ import { assertValidProjectStateFilesystemContent } from '../../../validation/project-state.js';
12
13
  import { Command } from 'commander';
13
14
  const DEFAULT_DEPENDENCIES = {
14
15
  buildCommandContext,
@@ -25,7 +26,7 @@ const DEFAULT_DEPENDENCIES = {
25
26
  processEnv: process.env,
26
27
  now: () => new Date(),
27
28
  };
28
- async function maybeResumePausedProject(statePath, dependencies) {
29
+ async function maybeResumePausedProject(statePath, projectPath, dependencies) {
29
30
  const stateContent = await dependencies.readFile(statePath, 'utf8');
30
31
  const frontmatter = getFrontmatterBlock(stateContent);
31
32
  if (!frontmatter) {
@@ -39,7 +40,12 @@ async function maybeResumePausedProject(statePath, dependencies) {
39
40
  nextBlock = removeFrontmatterField(nextBlock, 'oat_pause_reason');
40
41
  nextBlock = upsertFrontmatterField(nextBlock, 'oat_project_state_updated', dependencies.now().toISOString(), true).nextBlock;
41
42
  if (nextBlock !== frontmatter) {
42
- await dependencies.writeFile(statePath, replaceFrontmatter(stateContent, nextBlock), 'utf8');
43
+ const nextContent = replaceFrontmatter(stateContent, nextBlock);
44
+ await assertValidProjectStateFilesystemContent(nextContent, {
45
+ filePath: statePath,
46
+ projectPath,
47
+ });
48
+ await dependencies.writeFile(statePath, nextContent, 'utf8');
43
49
  }
44
50
  return true;
45
51
  }
@@ -76,7 +82,7 @@ async function runProjectOpen(projectName, options, context, dependencies) {
76
82
  process.exitCode = 0;
77
83
  return;
78
84
  }
79
- const resumedFromPaused = await maybeResumePausedProject(statePath, dependencies);
85
+ const resumedFromPaused = await maybeResumePausedProject(statePath, fullProjectPath, dependencies);
80
86
  await dependencies.setActiveProject(repoRoot, projectPath);
81
87
  if (localConfig.lastPausedProject === projectPath) {
82
88
  const updatedConfig = await dependencies.readOatLocalConfig(repoRoot);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pause/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAShF,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAyID,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAsBT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/pause/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,eAAe,EAC3B,SAAS,IAAI,gBAAgB,EAC9B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAShF,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAG/C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CACnB,OAAO,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAC/C,cAAc,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,kBAAkB,EAAE,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,KAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,sBAAsB,EAAE,CAAC,OAAO,EAAE;QAChC,QAAQ,EAAE,MAAM,CAAC;KAClB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACnC,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,SAAS,EAAE,OAAO,gBAAgB,CAAC;IACnC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;IAC9B,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AA0ID,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAsBT"}
@@ -9,6 +9,7 @@ import { generateStateDashboard as defaultGenerateStateDashboard, } from '../../
9
9
  import { clearActiveProject, readOatLocalConfig, } from '../../../config/oat-config.js';
10
10
  import { dirExists, fileExists } from '../../../fs/io.js';
11
11
  import { resolveProjectRoot } from '../../../fs/paths.js';
12
+ import { assertValidProjectStateFilesystemContent } from '../../../validation/project-state.js';
12
13
  import { Command } from 'commander';
13
14
  const DEFAULT_DEPENDENCIES = {
14
15
  buildCommandContext,
@@ -67,7 +68,12 @@ async function runProjectPause(projectName, options, context, dependencies) {
67
68
  nextBlock = removeFrontmatterField(nextBlock, 'oat_pause_reason');
68
69
  }
69
70
  if (nextBlock !== frontmatter) {
70
- await dependencies.writeFile(statePath, replaceFrontmatter(content, nextBlock), 'utf8');
71
+ const nextContent = replaceFrontmatter(content, nextBlock);
72
+ await assertValidProjectStateFilesystemContent(nextContent, {
73
+ filePath: statePath,
74
+ projectPath: fullProjectPath,
75
+ });
76
+ await dependencies.writeFile(statePath, nextContent, 'utf8');
71
77
  }
72
78
  const pointerCleared = activeProject === projectPath;
73
79
  if (pointerCleared) {
@@ -0,0 +1,8 @@
1
+ import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
2
+ import { Command } from 'commander';
3
+ interface EvaluateSignalsDependencies {
4
+ buildCommandContext: (options: GlobalOptions) => CommandContext;
5
+ }
6
+ export declare function createEvaluateSignalsCommand(overrides?: Partial<EvaluateSignalsDependencies>): Command;
7
+ export {};
8
+ //# sourceMappingURL=evaluate-signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate-signals.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/evaluate-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAepC,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;CACjE;AAqBD,wBAAgB,4BAA4B,CAC1C,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAyBT"}
@@ -0,0 +1,47 @@
1
+ import { buildCommandContext, } from '../../../app/command-context.js';
2
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
3
+ import { Command } from 'commander';
4
+ import { evaluateSignals } from '../../../projects/split/signals.js';
5
+ const SIGNALS = [
6
+ 'independently-shippable',
7
+ 'no-shared-design-surface',
8
+ 'expect-separate-prs',
9
+ 'distinct-subsystems',
10
+ ];
11
+ const DEFAULT_DEPENDENCIES = {
12
+ buildCommandContext,
13
+ };
14
+ function parseFiredSignals(value) {
15
+ if (value.trim().length === 0) {
16
+ return [];
17
+ }
18
+ const validSignals = new Set(SIGNALS);
19
+ return value.split(',').map((raw) => {
20
+ const signal = raw.trim();
21
+ if (!validSignals.has(signal)) {
22
+ throw new Error(`Invalid signal: ${signal}`);
23
+ }
24
+ return signal;
25
+ });
26
+ }
27
+ export function createEvaluateSignalsCommand(overrides = {}) {
28
+ const dependencies = {
29
+ ...DEFAULT_DEPENDENCIES,
30
+ ...overrides,
31
+ };
32
+ return new Command('evaluate-signals')
33
+ .description('Evaluate oat-project-split trigger signals')
34
+ .requiredOption('--fired <comma-list>', 'Comma-separated fired signals')
35
+ .action((options, command) => {
36
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
37
+ try {
38
+ context.logger.json(evaluateSignals({ fired: parseFiredSignals(options.fired) }));
39
+ process.exitCode = 0;
40
+ }
41
+ catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ context.logger.error(message);
44
+ process.exitCode = 1;
45
+ }
46
+ });
47
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createProjectSplitCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,yBAAyB,IAAI,OAAO,CAMnD"}
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import { createEvaluateSignalsCommand } from './evaluate-signals.js';
3
+ import { createProjectSplitRunCommand } from './run.js';
4
+ import { createValidateSplitPlanCommand } from './validate-plan.js';
5
+ export function createProjectSplitCommand() {
6
+ return new Command('split')
7
+ .description('Evaluate, validate, and run oat-project-split payloads')
8
+ .addCommand(createEvaluateSignalsCommand())
9
+ .addCommand(createValidateSplitPlanCommand())
10
+ .addCommand(createProjectSplitRunCommand());
11
+ }
@@ -0,0 +1,21 @@
1
+ import { appendFile, readFile, readdir, stat } from 'node:fs/promises';
2
+ import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
3
+ import { confirmAction } from '../../shared/shared.prompts.js';
4
+ import { Command } from 'commander';
5
+ interface RunSplitDependencies {
6
+ buildCommandContext: (options: GlobalOptions) => CommandContext;
7
+ resolveProjectRoot: (cwd: string) => Promise<string>;
8
+ resolveProjectsRoot: (repoRoot: string, env: NodeJS.ProcessEnv) => Promise<string>;
9
+ readFile: typeof readFile;
10
+ readdir: typeof readdir;
11
+ stat: typeof stat;
12
+ appendFile: typeof appendFile;
13
+ refreshDashboard: (options: {
14
+ repoRoot: string;
15
+ }) => Promise<void>;
16
+ confirmAction: typeof confirmAction;
17
+ processEnv: NodeJS.ProcessEnv;
18
+ }
19
+ export declare function createProjectSplitRunCommand(overrides?: Partial<RunSplitDependencies>): Command;
20
+ export {};
21
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAGvE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAKhE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,UAAU,oBAAoB;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,OAAO,EAAE,OAAO,OAAO,CAAC;IACxB,IAAI,EAAE,OAAO,IAAI,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,gBAAgB,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,aAAa,EAAE,OAAO,aAAa,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAsLD,wBAAgB,4BAA4B,CAC1C,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CAkJT"}
@@ -0,0 +1,231 @@
1
+ import { appendFile, readFile, readdir, stat } from 'node:fs/promises';
2
+ import { isAbsolute, join, relative } from 'node:path';
3
+ import { buildCommandContext, } from '../../../app/command-context.js';
4
+ import { resolveProjectsRoot } from '../../shared/oat-paths.js';
5
+ import { confirmAction } from '../../shared/shared.prompts.js';
6
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
7
+ import { generateStateDashboard } from '../../state/generate.js';
8
+ import { readOatLocalConfig } from '../../../config/oat-config.js';
9
+ import { resolveProjectRoot } from '../../../fs/paths.js';
10
+ import { Command } from 'commander';
11
+ import { validateSplitPlanDocumentShape } from '../../../projects/split/document-validation.js';
12
+ import { finalizeSplit } from '../../../projects/split/finalize.js';
13
+ import { continueSplitResume, detectPartialSplit, SplitResumeError, } from '../../../projects/split/resume.js';
14
+ import { seedChildren } from '../../../projects/split/seed-children.js';
15
+ import { validateChildPlan } from '../../../projects/split/validation.js';
16
+ import { writeCoordinationParent } from '../../../projects/split/write-parent.js';
17
+ const DEFAULT_DEPENDENCIES = {
18
+ buildCommandContext,
19
+ resolveProjectRoot,
20
+ resolveProjectsRoot,
21
+ readFile,
22
+ readdir,
23
+ stat,
24
+ appendFile,
25
+ refreshDashboard: async (options) => {
26
+ await generateStateDashboard(options);
27
+ },
28
+ confirmAction,
29
+ processEnv: process.env,
30
+ };
31
+ async function exists(path, dependencies) {
32
+ try {
33
+ await dependencies.stat(path);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ async function existingProjectSlugs(repoRoot, projectsRoot, dependencies) {
41
+ const absoluteProjectsRoot = isAbsolute(projectsRoot)
42
+ ? projectsRoot
43
+ : join(repoRoot, projectsRoot);
44
+ try {
45
+ const entries = await dependencies.readdir(absoluteProjectsRoot, {
46
+ withFileTypes: true,
47
+ });
48
+ return new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
49
+ }
50
+ catch {
51
+ return new Set();
52
+ }
53
+ }
54
+ async function recordDetectedRecommendation(repoRoot, document, dependencies) {
55
+ const localConfig = await readOatLocalConfig(repoRoot);
56
+ if (!localConfig.activeProject) {
57
+ throw new Error('Cannot record detected split recommendation without an active project');
58
+ }
59
+ const discoveryPath = join(repoRoot, localConfig.activeProject, 'discovery.md');
60
+ await dependencies.appendFile(discoveryPath, [
61
+ '',
62
+ '## Detected Split Recommendation',
63
+ '',
64
+ `Origin: ${document.origin}`,
65
+ `Proposed parent: ${document.plan.parentSlug}`,
66
+ `Proposed children: ${document.plan.children
67
+ .map((child) => child.slug)
68
+ .join(', ')}`,
69
+ '',
70
+ ].join('\n'), 'utf8');
71
+ }
72
+ function isEffectivelyNonInteractive(options, context, processEnv) {
73
+ return (options.nonInteractive === true ||
74
+ context.interactive === false ||
75
+ processEnv.OAT_NON_INTERACTIVE === '1');
76
+ }
77
+ function formatResumePreview(partial) {
78
+ return [
79
+ 'Recovered partial split plan:',
80
+ `Parent: ${partial.parentProjectPath}`,
81
+ `Children: ${partial.plan.children.map((child) => child.slug).join(', ')}`,
82
+ `Missing children: ${partial.missingChildren.length > 0 ? partial.missingChildren.join(', ') : 'none'}`,
83
+ `Dependencies: ${partial.plan.children
84
+ .map((child) => {
85
+ const dependencies = child.knownDependencies.length > 0
86
+ ? child.knownDependencies.join(', ')
87
+ : 'none';
88
+ return `${child.slug} -> ${dependencies}`;
89
+ })
90
+ .join('; ')}`,
91
+ `Active child: ${partial.plan.initialActiveChild}`,
92
+ ];
93
+ }
94
+ function isDetectedOrigin(document) {
95
+ return (document.origin === 'detected-mid-stream' ||
96
+ document.origin === 'detected-convergence');
97
+ }
98
+ function normalizeProjectPath(path) {
99
+ return path.split('\\').join('/');
100
+ }
101
+ function toRepoRelativeProjectPath(repoRoot, projectPath) {
102
+ const absoluteProjectPath = isAbsolute(projectPath)
103
+ ? projectPath
104
+ : join(repoRoot, projectPath);
105
+ return normalizeProjectPath(relative(repoRoot, absoluteProjectPath));
106
+ }
107
+ async function isActiveDetectedParentProject(repoRoot, parentPath, document) {
108
+ if (!isDetectedOrigin(document)) {
109
+ return false;
110
+ }
111
+ const localConfig = await readOatLocalConfig(repoRoot);
112
+ if (!localConfig.activeProject) {
113
+ return false;
114
+ }
115
+ return (toRepoRelativeProjectPath(repoRoot, localConfig.activeProject) ===
116
+ toRepoRelativeProjectPath(repoRoot, parentPath));
117
+ }
118
+ async function runFreshSplit(document, repoRoot, projectsRoot, dependencies, options = {}) {
119
+ const slugs = await existingProjectSlugs(repoRoot, projectsRoot, dependencies);
120
+ if (options.allowExistingParent) {
121
+ slugs.delete(document.plan.parentSlug);
122
+ }
123
+ const validation = validateChildPlan(document.plan, slugs);
124
+ if (!validation.ok) {
125
+ throw new Error(`Split plan validation failed: ${validation.errors
126
+ .map((error) => error.message)
127
+ .join('; ')}`);
128
+ }
129
+ await writeCoordinationParent(document, { repoRoot, projectsRoot });
130
+ await seedChildren(document.plan, { repoRoot, projectsRoot });
131
+ await finalizeSplit(document.plan, { repoRoot, projectsRoot });
132
+ }
133
+ export function createProjectSplitRunCommand(overrides = {}) {
134
+ const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
135
+ return new Command('run')
136
+ .description('Run an oat-project-split SplitPlanDocument end to end')
137
+ .requiredOption('--plan-file <path>', 'Path to split-plan.json')
138
+ .option('--non-interactive', 'Fail fast for detected split origins instead of writing projects')
139
+ .option('--resume', 'Confirm resuming an existing partial split without prompting')
140
+ .action(async (options, command) => {
141
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
142
+ try {
143
+ const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
144
+ const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
145
+ const raw = await dependencies.readFile(options.planFile, 'utf8');
146
+ const parsed = JSON.parse(raw);
147
+ const documentShape = validateSplitPlanDocumentShape(parsed);
148
+ if (!documentShape.ok) {
149
+ throw new Error(`Invalid SplitPlanDocument: ${documentShape.errors
150
+ .map((error) => error.message)
151
+ .join('; ')}`);
152
+ }
153
+ const document = documentShape.document;
154
+ if (isEffectivelyNonInteractive(options, context, dependencies.processEnv) &&
155
+ (document.origin === 'detected-mid-stream' ||
156
+ document.origin === 'detected-convergence')) {
157
+ await recordDetectedRecommendation(repoRoot, document, dependencies);
158
+ context.logger.error('Detected split requires interactive confirmation; recommendation recorded.');
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ const parentPath = join(projectsRoot, document.plan.parentSlug)
163
+ .split('\\')
164
+ .join('/');
165
+ const absoluteParentPath = isAbsolute(parentPath)
166
+ ? parentPath
167
+ : join(repoRoot, parentPath);
168
+ if (await exists(absoluteParentPath, dependencies)) {
169
+ let partial = null;
170
+ let convertActiveDetectedParent = false;
171
+ try {
172
+ partial = await detectPartialSplit(parentPath, {
173
+ repoRoot,
174
+ projectsRoot,
175
+ });
176
+ }
177
+ catch (error) {
178
+ if (error instanceof SplitResumeError &&
179
+ error.message === 'Split resume requires a coordination parent' &&
180
+ (await isActiveDetectedParentProject(repoRoot, parentPath, document))) {
181
+ convertActiveDetectedParent = true;
182
+ }
183
+ else {
184
+ throw error;
185
+ }
186
+ }
187
+ if (convertActiveDetectedParent) {
188
+ await runFreshSplit(document, repoRoot, projectsRoot, dependencies, {
189
+ allowExistingParent: true,
190
+ });
191
+ await dependencies.refreshDashboard({ repoRoot });
192
+ context.logger.info('Split completed.');
193
+ process.exitCode = 0;
194
+ return;
195
+ }
196
+ if (!partial) {
197
+ throw new SplitResumeError('Cannot resume split without recovered partial state.');
198
+ }
199
+ for (const line of formatResumePreview(partial)) {
200
+ context.logger.info(line);
201
+ }
202
+ const effectiveNonInteractive = isEffectivelyNonInteractive(options, context, dependencies.processEnv);
203
+ if (!options.resume) {
204
+ if (effectiveNonInteractive) {
205
+ throw new SplitResumeError('Partial split detected. Re-run with --resume to confirm resuming without an interactive prompt.');
206
+ }
207
+ const confirmed = await dependencies.confirmAction('Resume this split from the recovered plan?', { interactive: context.interactive });
208
+ if (!confirmed) {
209
+ throw new SplitResumeError('Split resume cancelled.');
210
+ }
211
+ }
212
+ await continueSplitResume(partial, { repoRoot, projectsRoot });
213
+ }
214
+ else {
215
+ await runFreshSplit(document, repoRoot, projectsRoot, dependencies);
216
+ }
217
+ await dependencies.refreshDashboard({ repoRoot });
218
+ context.logger.info('Split completed.');
219
+ process.exitCode = 0;
220
+ }
221
+ catch (error) {
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ context.logger.error(message);
224
+ if (error instanceof SplitResumeError) {
225
+ process.exitCode = 1;
226
+ return;
227
+ }
228
+ process.exitCode = 1;
229
+ }
230
+ });
231
+ }
@@ -0,0 +1,14 @@
1
+ import { readdir as defaultReaddir, readFile as defaultReadFile } from 'node:fs/promises';
2
+ import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
3
+ import { Command } from 'commander';
4
+ interface ValidatePlanDependencies {
5
+ buildCommandContext: (options: GlobalOptions) => CommandContext;
6
+ resolveProjectRoot: (cwd: string) => Promise<string>;
7
+ resolveProjectsRoot: (repoRoot: string, env: NodeJS.ProcessEnv) => Promise<string>;
8
+ readFile: typeof defaultReadFile;
9
+ readdir: typeof defaultReaddir;
10
+ processEnv: NodeJS.ProcessEnv;
11
+ }
12
+ export declare function createValidateSplitPlanCommand(overrides?: Partial<ValidatePlanDependencies>): Command;
13
+ export {};
14
+ //# sourceMappingURL=validate-plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-plan.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/split/validate-plan.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,IAAI,cAAc,EACzB,QAAQ,IAAI,eAAe,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,mBAAmB,EAAE,CACnB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,MAAM,CAAC,CAAC;IACrB,QAAQ,EAAE,OAAO,eAAe,CAAC;IACjC,OAAO,EAAE,OAAO,cAAc,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AAgCD,wBAAgB,8BAA8B,CAC5C,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAwCT"}
@@ -0,0 +1,62 @@
1
+ import { readdir as defaultReaddir, readFile as defaultReadFile, } from 'node:fs/promises';
2
+ import { isAbsolute, join } from 'node:path';
3
+ import { buildCommandContext, } from '../../../app/command-context.js';
4
+ import { resolveProjectsRoot } from '../../shared/oat-paths.js';
5
+ import { readGlobalOptions } from '../../shared/shared.utils.js';
6
+ import { resolveProjectRoot } from '../../../fs/paths.js';
7
+ import { Command } from 'commander';
8
+ import { validateSplitPlanDocumentShape } from '../../../projects/split/document-validation.js';
9
+ import { validateChildPlan } from '../../../projects/split/validation.js';
10
+ const DEFAULT_DEPENDENCIES = {
11
+ buildCommandContext,
12
+ resolveProjectRoot,
13
+ resolveProjectsRoot,
14
+ readFile: defaultReadFile,
15
+ readdir: defaultReaddir,
16
+ processEnv: process.env,
17
+ };
18
+ async function readExistingProjectSlugs(context, dependencies) {
19
+ const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
20
+ const projectsRoot = await dependencies.resolveProjectsRoot(repoRoot, dependencies.processEnv);
21
+ const absoluteProjectsRoot = isAbsolute(projectsRoot)
22
+ ? projectsRoot
23
+ : join(repoRoot, projectsRoot);
24
+ const entries = await dependencies.readdir(absoluteProjectsRoot, {
25
+ withFileTypes: true,
26
+ });
27
+ return new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
28
+ }
29
+ export function createValidateSplitPlanCommand(overrides = {}) {
30
+ const dependencies = {
31
+ ...DEFAULT_DEPENDENCIES,
32
+ ...overrides,
33
+ };
34
+ return new Command('validate-plan')
35
+ .description('Validate a persisted oat-project-split SplitPlanDocument')
36
+ .requiredOption('--plan-file <path>', 'Path to split-plan.json')
37
+ .action(async (options, command) => {
38
+ const context = dependencies.buildCommandContext(readGlobalOptions(command));
39
+ try {
40
+ const raw = await dependencies.readFile(options.planFile, 'utf8');
41
+ const parsed = JSON.parse(raw);
42
+ const shape = validateSplitPlanDocumentShape(parsed);
43
+ if (!shape.ok) {
44
+ context.logger.json({ ok: false, errors: shape.errors });
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ const existingSlugs = await readExistingProjectSlugs(context, dependencies);
49
+ const result = validateChildPlan(shape.document.plan, existingSlugs);
50
+ context.logger.json(result);
51
+ process.exitCode = result.ok ? 0 : 1;
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ context.logger.json({
56
+ ok: false,
57
+ errors: [{ code: 'read-or-parse-failed', message }],
58
+ });
59
+ process.exitCode = 1;
60
+ }
61
+ });
62
+ }
@@ -1,3 +1,12 @@
1
+ export declare const PROJECT_STATE_KINDS: readonly ["implementation", "coordination"];
2
+ export declare const PROJECT_STATE_PHASES: readonly ["discovery", "spec", "design", "plan", "implement", "decomposition"];
3
+ export declare const PROJECT_STATE_FRONTMATTER_FIELDS: readonly ["oat_kind", "oat_parent", "oat_siblings", "oat_depends_on", "oat_children", "oat_inherited_context_revalidated", "oat_phase", "oat_phase_status", "oat_status", "oat_workflow_mode", "oat_lifecycle", "oat_current_task", "oat_last_commit", "oat_blockers", "oat_hill_checkpoints", "oat_hill_completed", "oat_parallel_execution", "oat_pr_status", "oat_pr_url", "oat_project_created", "oat_project_completed", "oat_project_state_updated", "oat_docs_updated", "oat_generated", "oat_template", "oat_template_name"];
4
+ export type ProjectStateKind = (typeof PROJECT_STATE_KINDS)[number];
5
+ export type ProjectStatePhase = (typeof PROJECT_STATE_PHASES)[number];
6
+ export type ProjectStateFrontmatterField = (typeof PROJECT_STATE_FRONTMATTER_FIELDS)[number];
7
+ export declare function isProjectStateKind(value: string): value is ProjectStateKind;
8
+ export declare function isProjectStatePhase(value: string): value is ProjectStatePhase;
9
+ export declare function isProjectStateFrontmatterField(value: string): value is ProjectStateFrontmatterField;
1
10
  export declare function getFrontmatterBlock(content: string): string | null;
2
11
  export declare function getFrontmatterField(frontmatter: string, field: string): string | null;
3
12
  export declare function parseFrontmatterField(filePath: string, field: string): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/frontmatter.ts"],"names":[],"mappings":"AAGA,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAKf;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
1
+ {"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/frontmatter.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAE/E,eAAO,MAAM,oBAAoB,gFAOvB,CAAC;AAEX,eAAO,MAAM,gCAAgC,sgBA2BnC,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAKf;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}