@open-agent-toolkit/cli 0.1.4 → 0.1.6

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 (79) hide show
  1. package/assets/docs/workflows/projects/artifacts.md +17 -0
  2. package/assets/docs/workflows/projects/implementation-execution.md +2 -2
  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 +8 -4
  10. package/assets/skills/oat-project-quick-start/SKILL.md +14 -5
  11. package/assets/skills/oat-project-split/SKILL.md +82 -0
  12. package/assets/templates/state.md +6 -1
  13. package/dist/__tests__/skills/split-flow-fixtures.d.ts +50 -0
  14. package/dist/__tests__/skills/split-flow-fixtures.d.ts.map +1 -0
  15. package/dist/__tests__/skills/split-flow-fixtures.js +161 -0
  16. package/dist/commands/init/tools/shared/skill-manifest.d.ts +1 -1
  17. package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
  18. package/dist/commands/init/tools/shared/skill-manifest.js +1 -0
  19. package/dist/commands/project/complete-discovery/index.d.ts +16 -0
  20. package/dist/commands/project/complete-discovery/index.d.ts.map +1 -0
  21. package/dist/commands/project/complete-discovery/index.js +123 -0
  22. package/dist/commands/project/complete-state/index.d.ts.map +1 -1
  23. package/dist/commands/project/complete-state/index.js +5 -0
  24. package/dist/commands/project/index.d.ts.map +1 -1
  25. package/dist/commands/project/index.js +4 -0
  26. package/dist/commands/project/list.d.ts +6 -0
  27. package/dist/commands/project/list.d.ts.map +1 -1
  28. package/dist/commands/project/list.js +37 -4
  29. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  30. package/dist/commands/project/new/scaffold.js +4 -0
  31. package/dist/commands/project/open/index.d.ts.map +1 -1
  32. package/dist/commands/project/open/index.js +9 -3
  33. package/dist/commands/project/pause/index.d.ts.map +1 -1
  34. package/dist/commands/project/pause/index.js +7 -1
  35. package/dist/commands/project/split/evaluate-signals.d.ts +8 -0
  36. package/dist/commands/project/split/evaluate-signals.d.ts.map +1 -0
  37. package/dist/commands/project/split/evaluate-signals.js +47 -0
  38. package/dist/commands/project/split/index.d.ts +3 -0
  39. package/dist/commands/project/split/index.d.ts.map +1 -0
  40. package/dist/commands/project/split/index.js +11 -0
  41. package/dist/commands/project/split/run.d.ts +21 -0
  42. package/dist/commands/project/split/run.d.ts.map +1 -0
  43. package/dist/commands/project/split/run.js +231 -0
  44. package/dist/commands/project/split/validate-plan.d.ts +14 -0
  45. package/dist/commands/project/split/validate-plan.d.ts.map +1 -0
  46. package/dist/commands/project/split/validate-plan.js +62 -0
  47. package/dist/commands/shared/frontmatter.d.ts +9 -0
  48. package/dist/commands/shared/frontmatter.d.ts.map +1 -1
  49. package/dist/commands/shared/frontmatter.js +46 -0
  50. package/dist/commands/state/generate.d.ts.map +1 -1
  51. package/dist/commands/state/generate.js +38 -6
  52. package/dist/projects/split/child-plan.d.ts +46 -0
  53. package/dist/projects/split/child-plan.d.ts.map +1 -0
  54. package/dist/projects/split/child-plan.js +107 -0
  55. package/dist/projects/split/document-validation.d.ts +14 -0
  56. package/dist/projects/split/document-validation.d.ts.map +1 -0
  57. package/dist/projects/split/document-validation.js +106 -0
  58. package/dist/projects/split/finalize.d.ts +7 -0
  59. package/dist/projects/split/finalize.d.ts.map +1 -0
  60. package/dist/projects/split/finalize.js +32 -0
  61. package/dist/projects/split/resume.d.ts +19 -0
  62. package/dist/projects/split/resume.d.ts.map +1 -0
  63. package/dist/projects/split/resume.js +107 -0
  64. package/dist/projects/split/seed-children.d.ts +9 -0
  65. package/dist/projects/split/seed-children.d.ts.map +1 -0
  66. package/dist/projects/split/seed-children.js +122 -0
  67. package/dist/projects/split/signals.d.ts +10 -0
  68. package/dist/projects/split/signals.d.ts.map +1 -0
  69. package/dist/projects/split/signals.js +18 -0
  70. package/dist/projects/split/validation.d.ts +14 -0
  71. package/dist/projects/split/validation.d.ts.map +1 -0
  72. package/dist/projects/split/validation.js +104 -0
  73. package/dist/projects/split/write-parent.d.ts +16 -0
  74. package/dist/projects/split/write-parent.d.ts.map +1 -0
  75. package/dist/projects/split/write-parent.js +176 -0
  76. package/dist/validation/project-state.d.ts +50 -0
  77. package/dist/validation/project-state.d.ts.map +1 -0
  78. package/dist/validation/project-state.js +279 -0
  79. package/package.json +2 -2
@@ -0,0 +1,107 @@
1
+ function deriveSlugFromPath(path) {
2
+ const normalized = path.trim().replace(/\/+$/, '');
3
+ const slug = normalized.split('/').at(-1);
4
+ if (!slug) {
5
+ throw new Error('Unable to derive parent slug from priorDiscovery.path');
6
+ }
7
+ return slug;
8
+ }
9
+ function resolveParentSlug(payload) {
10
+ if (payload.parentSlug) {
11
+ return payload.parentSlug;
12
+ }
13
+ if (payload.priorDiscovery?.parentSlug) {
14
+ return payload.priorDiscovery.parentSlug;
15
+ }
16
+ if (payload.priorDiscovery?.path) {
17
+ return deriveSlugFromPath(payload.priorDiscovery.path);
18
+ }
19
+ throw new Error('SplitPayload requires parentSlug or priorDiscovery.path');
20
+ }
21
+ function resolveChildren(payload) {
22
+ const children = payload.declaredChildren ??
23
+ payload.inferredChildren ??
24
+ payload.priorDiscovery?.children;
25
+ if (!children || children.length === 0) {
26
+ throw new Error('SplitPayload requires at least one child');
27
+ }
28
+ return children;
29
+ }
30
+ function resolveFoundationChild(payload, children) {
31
+ return (payload.foundationChild ?? children.find((child) => child.foundation)?.slug);
32
+ }
33
+ function orderChildren(children, foundationChild) {
34
+ const childSlugs = new Set(children.map((child) => child.slug));
35
+ const bySlug = new Map(children.map((child) => [child.slug, child]));
36
+ const inputOrder = new Map(children.map((child, index) => [child.slug, index]));
37
+ const indegree = new Map(children.map((child) => [child.slug, 0]));
38
+ const dependents = new Map();
39
+ for (const child of children) {
40
+ for (const dependency of child.knownDependencies ?? []) {
41
+ if (!childSlugs.has(dependency)) {
42
+ continue;
43
+ }
44
+ indegree.set(child.slug, (indegree.get(child.slug) ?? 0) + 1);
45
+ const existing = dependents.get(dependency) ?? [];
46
+ existing.push(child.slug);
47
+ dependents.set(dependency, existing);
48
+ }
49
+ }
50
+ const priority = (slug) => slug === foundationChild ? -1 : (inputOrder.get(slug) ?? 0);
51
+ const ready = children
52
+ .filter((child) => indegree.get(child.slug) === 0)
53
+ .map((child) => child.slug)
54
+ .sort((left, right) => priority(left) - priority(right));
55
+ const ordered = [];
56
+ while (ready.length > 0) {
57
+ const slug = ready.shift();
58
+ const child = bySlug.get(slug);
59
+ if (child) {
60
+ ordered.push(child);
61
+ }
62
+ for (const dependent of dependents.get(slug) ?? []) {
63
+ indegree.set(dependent, (indegree.get(dependent) ?? 0) - 1);
64
+ if (indegree.get(dependent) === 0) {
65
+ ready.push(dependent);
66
+ ready.sort((left, right) => priority(left) - priority(right));
67
+ }
68
+ }
69
+ }
70
+ if (ordered.length !== children.length) {
71
+ const orderedSlugs = new Set(ordered.map((child) => child.slug));
72
+ ordered.push(...children
73
+ .filter((child) => !orderedSlugs.has(child.slug))
74
+ .sort((left, right) => priority(left.slug) - priority(right.slug)));
75
+ }
76
+ return ordered;
77
+ }
78
+ export function buildSplitPlanDocument(payload) {
79
+ const parentSlug = resolveParentSlug(payload);
80
+ const children = resolveChildren(payload);
81
+ const foundationChild = resolveFoundationChild(payload, children);
82
+ const orderedChildren = orderChildren(children, foundationChild);
83
+ const firstChild = orderedChildren[0]?.slug;
84
+ if (!firstChild) {
85
+ throw new Error('SplitPayload requires at least one child');
86
+ }
87
+ return {
88
+ origin: payload.origin,
89
+ interactive: payload.interactive,
90
+ plan: {
91
+ parentSlug,
92
+ children: orderedChildren.map((child, index) => ({
93
+ slug: child.slug,
94
+ description: child.description,
95
+ inheritedContext: child.inheritedContext ??
96
+ payload.priorDiscovery?.inheritedContext ??
97
+ child.description ??
98
+ '',
99
+ knownDependencies: [...(child.knownDependencies ?? [])],
100
+ order: index + 1,
101
+ })),
102
+ foundationChild,
103
+ integrationSketch: payload.integrationSketch ?? payload.priorDiscovery?.integrationSketch,
104
+ initialActiveChild: payload.initialActiveChild ?? foundationChild ?? firstChild,
105
+ },
106
+ };
107
+ }
@@ -0,0 +1,14 @@
1
+ import type { ChildPlan, SplitPlanDocument } from './child-plan.js';
2
+ export interface DocumentValidationError {
3
+ code: string;
4
+ message: string;
5
+ }
6
+ export declare function normalizePlanForValidation(plan: ChildPlan): ChildPlan;
7
+ export declare function validateSplitPlanDocumentShape(value: unknown): {
8
+ ok: true;
9
+ document: SplitPlanDocument;
10
+ } | {
11
+ ok: false;
12
+ errors: DocumentValidationError[];
13
+ };
14
+ //# sourceMappingURL=document-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document-validation.d.ts","sourceRoot":"","sources":["../../../src/projects/split/document-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAQrE;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GACzD;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,GACzC;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,uBAAuB,EAAE,CAAC;CACnC,CAgGJ"}
@@ -0,0 +1,106 @@
1
+ const SPLIT_ORIGINS = [
2
+ 'declared',
3
+ 'detected-mid-stream',
4
+ 'detected-convergence',
5
+ 'brainstorm-picker',
6
+ ];
7
+ function isObject(value) {
8
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
9
+ }
10
+ export function normalizePlanForValidation(plan) {
11
+ return {
12
+ ...plan,
13
+ children: plan.children.map((child) => ({
14
+ ...child,
15
+ knownDependencies: child.knownDependencies ?? [],
16
+ })),
17
+ };
18
+ }
19
+ export function validateSplitPlanDocumentShape(value) {
20
+ const errors = [];
21
+ if (!isObject(value)) {
22
+ return {
23
+ ok: false,
24
+ errors: [{ code: 'invalid-document', message: 'Expected JSON object' }],
25
+ };
26
+ }
27
+ if (typeof value.origin !== 'string' ||
28
+ !SPLIT_ORIGINS.includes(value.origin)) {
29
+ errors.push({
30
+ code: 'invalid-origin',
31
+ message: 'SplitPlanDocument origin is required',
32
+ });
33
+ }
34
+ if (typeof value.interactive !== 'boolean') {
35
+ errors.push({
36
+ code: 'invalid-interactive',
37
+ message: 'SplitPlanDocument interactive boolean is required',
38
+ });
39
+ }
40
+ if (!isObject(value.plan)) {
41
+ errors.push({
42
+ code: 'invalid-plan',
43
+ message: 'SplitPlanDocument plan object is required',
44
+ });
45
+ }
46
+ else {
47
+ if (typeof value.plan.parentSlug !== 'string') {
48
+ errors.push({
49
+ code: 'invalid-parent-slug',
50
+ message: 'ChildPlan parentSlug is required',
51
+ });
52
+ }
53
+ if (!Array.isArray(value.plan.children)) {
54
+ errors.push({
55
+ code: 'invalid-children',
56
+ message: 'ChildPlan children array is required',
57
+ });
58
+ }
59
+ else {
60
+ value.plan.children.forEach((child, index) => {
61
+ if (!isObject(child)) {
62
+ errors.push({
63
+ code: 'invalid-child',
64
+ message: `ChildPlan child ${index + 1} must be an object`,
65
+ });
66
+ return;
67
+ }
68
+ if (typeof child.slug !== 'string') {
69
+ errors.push({
70
+ code: 'invalid-child-slug',
71
+ message: `ChildPlan child ${index + 1} slug is required`,
72
+ });
73
+ }
74
+ if (child.knownDependencies !== undefined &&
75
+ !Array.isArray(child.knownDependencies)) {
76
+ errors.push({
77
+ code: 'invalid-known-dependencies',
78
+ message: `ChildPlan child ${index + 1} knownDependencies must be an array`,
79
+ });
80
+ }
81
+ if (typeof child.order !== 'number') {
82
+ errors.push({
83
+ code: 'invalid-child-order',
84
+ message: `ChildPlan child ${index + 1} order is required`,
85
+ });
86
+ }
87
+ });
88
+ }
89
+ if (typeof value.plan.initialActiveChild !== 'string') {
90
+ errors.push({
91
+ code: 'invalid-initial-active-child',
92
+ message: 'ChildPlan initialActiveChild is required',
93
+ });
94
+ }
95
+ }
96
+ if (errors.length > 0) {
97
+ return { ok: false, errors };
98
+ }
99
+ return {
100
+ ok: true,
101
+ document: {
102
+ ...value,
103
+ plan: normalizePlanForValidation(value.plan),
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,7 @@
1
+ import type { ChildPlan } from './child-plan.js';
2
+ import type { SplitProjectContext } from './write-parent.js';
3
+ export interface FinalizeSplitResult {
4
+ activeProjectPath: string;
5
+ }
6
+ export declare function finalizeSplit(plan: ChildPlan, context: SplitProjectContext): Promise<FinalizeSplitResult>;
7
+ //# sourceMappingURL=finalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../../src/projects/split/finalize.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAiB1D,MAAM,WAAW,mBAAmB;IAClC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,aAAa,CACjC,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAoB9B"}
@@ -0,0 +1,32 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { getFrontmatterBlock } from '../../commands/shared/frontmatter.js';
4
+ import { replaceFrontmatter } from '../../commands/shared/frontmatter-write.js';
5
+ import { setActiveProject } from '../../config/oat-config.js';
6
+ import YAML from 'yaml';
7
+ function readObjectFrontmatter(content, filePath) {
8
+ const block = getFrontmatterBlock(content);
9
+ if (!block) {
10
+ throw new Error(`${filePath} is missing frontmatter`);
11
+ }
12
+ const parsed = YAML.parse(block);
13
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
14
+ throw new Error(`${filePath} frontmatter must be an object`);
15
+ }
16
+ return parsed;
17
+ }
18
+ export async function finalizeSplit(plan, context) {
19
+ const projectsRoot = context.projectsRoot ?? '.oat/projects/shared';
20
+ const parentPath = join(projectsRoot, plan.parentSlug).split('\\').join('/');
21
+ const statePath = join(context.repoRoot, parentPath, 'state.md');
22
+ const stateContent = await readFile(statePath, 'utf8');
23
+ const frontmatter = readObjectFrontmatter(stateContent, statePath);
24
+ frontmatter['oat_phase'] = 'decomposition';
25
+ frontmatter['oat_phase_status'] = 'complete';
26
+ await writeFile(statePath, replaceFrontmatter(stateContent, YAML.stringify(frontmatter).trimEnd()), 'utf8');
27
+ const activeProjectPath = join(projectsRoot, plan.initialActiveChild)
28
+ .split('\\')
29
+ .join('/');
30
+ await setActiveProject(context.repoRoot, activeProjectPath);
31
+ return { activeProjectPath };
32
+ }
@@ -0,0 +1,19 @@
1
+ import type { ChildPlan, SplitPlanDocument } from './child-plan.js';
2
+ import type { SplitProjectContext } from './write-parent.js';
3
+ export declare class SplitResumeError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ export interface PartialSplit {
7
+ document: SplitPlanDocument;
8
+ plan: ChildPlan;
9
+ missingChildren: string[];
10
+ parentProjectPath: string;
11
+ projectsRoot: string;
12
+ }
13
+ export interface ResumeSplitOptions {
14
+ confirmed?: boolean;
15
+ }
16
+ export declare function detectPartialSplit(parentPath: string, context: SplitProjectContext): Promise<PartialSplit>;
17
+ export declare function resumeSplit(parentPath: string, context: SplitProjectContext, options?: ResumeSplitOptions): Promise<PartialSplit>;
18
+ export declare function continueSplitResume(partial: PartialSplit, context: SplitProjectContext): Promise<PartialSplit>;
19
+ //# sourceMappingURL=resume.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../../src/projects/split/resume.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAKjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA6CD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CA8DvB;AAED,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,YAAY,CAAC,CAQvB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,YAAY,EACrB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CAavB"}
@@ -0,0 +1,107 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { isAbsolute, join, relative } from 'node:path';
3
+ import { getFrontmatterBlock } from '../../commands/shared/frontmatter.js';
4
+ import YAML from 'yaml';
5
+ import { validateSplitPlanDocumentShape } from './document-validation.js';
6
+ import { finalizeSplit } from './finalize.js';
7
+ import { seedChildren } from './seed-children.js';
8
+ import { validateChildPlan } from './validation.js';
9
+ export class SplitResumeError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'SplitResumeError';
13
+ }
14
+ }
15
+ function readObjectFrontmatter(content, filePath) {
16
+ const block = getFrontmatterBlock(content);
17
+ if (!block) {
18
+ throw new SplitResumeError(`${filePath} is missing frontmatter`);
19
+ }
20
+ const parsed = YAML.parse(block);
21
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
22
+ throw new SplitResumeError(`${filePath} frontmatter must be an object`);
23
+ }
24
+ return parsed;
25
+ }
26
+ async function exists(path) {
27
+ try {
28
+ await stat(path);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ function resolveParentPaths(parentPath, context) {
36
+ const absoluteParentPath = isAbsolute(parentPath)
37
+ ? parentPath
38
+ : join(context.repoRoot, parentPath);
39
+ const parentProjectPath = relative(context.repoRoot, absoluteParentPath)
40
+ .split('\\')
41
+ .join('/');
42
+ const segments = parentProjectPath.split('/');
43
+ const projectsRoot = segments.slice(0, -1).join('/');
44
+ return { absoluteParentPath, parentProjectPath, projectsRoot };
45
+ }
46
+ export async function detectPartialSplit(parentPath, context) {
47
+ const { absoluteParentPath, parentProjectPath, projectsRoot } = resolveParentPaths(parentPath, context);
48
+ const statePath = join(absoluteParentPath, 'state.md');
49
+ const state = readObjectFrontmatter(await readFile(statePath, 'utf8'), statePath);
50
+ if (state['oat_kind'] !== 'coordination') {
51
+ throw new SplitResumeError('Split resume requires a coordination parent');
52
+ }
53
+ if (state['oat_phase_status'] === 'complete') {
54
+ throw new SplitResumeError('Cannot resume a completed split parent');
55
+ }
56
+ let parsed;
57
+ try {
58
+ parsed = JSON.parse(await readFile(join(absoluteParentPath, 'references', 'split-plan.json'), 'utf8'));
59
+ }
60
+ catch (error) {
61
+ const message = error instanceof Error ? error.message : String(error);
62
+ throw new SplitResumeError(`Cannot resume split without valid references/split-plan.json: ${message}`);
63
+ }
64
+ const documentShape = validateSplitPlanDocumentShape(parsed);
65
+ if (!documentShape.ok) {
66
+ throw new SplitResumeError(`Cannot resume split without valid references/split-plan.json: ${documentShape.errors
67
+ .map((error) => error.message)
68
+ .join('; ')}`);
69
+ }
70
+ const document = documentShape.document;
71
+ const validation = validateChildPlan(document.plan, new Set());
72
+ if (!validation.ok) {
73
+ throw new SplitResumeError(`Cannot resume split with invalid references/split-plan.json: ${validation.errors
74
+ .map((error) => error.message)
75
+ .join('; ')}`);
76
+ }
77
+ const missingChildren = [];
78
+ for (const child of document.plan.children) {
79
+ if (!(await exists(join(context.repoRoot, projectsRoot, child.slug)))) {
80
+ missingChildren.push(child.slug);
81
+ }
82
+ }
83
+ return {
84
+ document,
85
+ plan: document.plan,
86
+ missingChildren,
87
+ parentProjectPath,
88
+ projectsRoot,
89
+ };
90
+ }
91
+ export async function resumeSplit(parentPath, context, options = {}) {
92
+ const partial = await detectPartialSplit(parentPath, context);
93
+ if (options.confirmed !== true) {
94
+ throw new SplitResumeError('Split resume requires explicit confirmation before writing missing children.');
95
+ }
96
+ return continueSplitResume(partial, context);
97
+ }
98
+ export async function continueSplitResume(partial, context) {
99
+ if (partial.missingChildren.length > 0) {
100
+ await seedChildren(partial.plan, { ...context, projectsRoot: partial.projectsRoot }, new Set(partial.missingChildren));
101
+ }
102
+ await finalizeSplit(partial.plan, {
103
+ ...context,
104
+ projectsRoot: partial.projectsRoot,
105
+ });
106
+ return partial;
107
+ }
@@ -0,0 +1,9 @@
1
+ import type { ChildPlan } from './child-plan.js';
2
+ import type { SplitProjectContext } from './write-parent.js';
3
+ declare const SEEDED_SECTIONS: readonly ["Origin", "Inherited Context", "Child Scope", "Known Dependencies", "Assumptions To Revalidate", "Likely Workflow Mode", "Sibling Projects"];
4
+ export interface SeedChildrenResult {
5
+ childProjectPaths: string[];
6
+ }
7
+ export declare function seedChildren(plan: ChildPlan, context: SplitProjectContext, onlySlugs?: Set<string>): Promise<SeedChildrenResult>;
8
+ export { SEEDED_SECTIONS };
9
+ //# sourceMappingURL=seed-children.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-children.d.ts","sourceRoot":"","sources":["../../../src/projects/split/seed-children.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,QAAA,MAAM,eAAe,wJAQX,CAAC;AAoFX,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,mBAAmB,EAC5B,SAAS,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,OAAO,CAAC,kBAAkB,CAAC,CA4C7B;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,122 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { scaffoldProject as defaultScaffoldProject } from '../../commands/project/new/scaffold.js';
4
+ import { getFrontmatterBlock } from '../../commands/shared/frontmatter.js';
5
+ import { replaceFrontmatter } from '../../commands/shared/frontmatter-write.js';
6
+ import YAML from 'yaml';
7
+ const SEEDED_SECTIONS = [
8
+ 'Origin',
9
+ 'Inherited Context',
10
+ 'Child Scope',
11
+ 'Known Dependencies',
12
+ 'Assumptions To Revalidate',
13
+ 'Likely Workflow Mode',
14
+ 'Sibling Projects',
15
+ ];
16
+ function readObjectFrontmatter(content, filePath) {
17
+ const block = getFrontmatterBlock(content);
18
+ if (!block) {
19
+ if (content.startsWith('---\n---')) {
20
+ return {};
21
+ }
22
+ throw new Error(`${filePath} is missing frontmatter`);
23
+ }
24
+ if (block.trim().length === 0) {
25
+ return {};
26
+ }
27
+ const parsed = YAML.parse(block);
28
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
29
+ throw new Error(`${filePath} frontmatter must be an object`);
30
+ }
31
+ return parsed;
32
+ }
33
+ async function updateFrontmatter(filePath, updates) {
34
+ const content = await readFile(filePath, 'utf8');
35
+ const frontmatter = readObjectFrontmatter(content, filePath);
36
+ for (const [key, value] of Object.entries(updates)) {
37
+ frontmatter[key] = value;
38
+ }
39
+ const nextBlock = YAML.stringify(frontmatter).trimEnd();
40
+ const nextContent = getFrontmatterBlock(content)
41
+ ? replaceFrontmatter(content, nextBlock)
42
+ : content.replace(/^---\n---/, `---\n${nextBlock}\n---`);
43
+ await writeFile(filePath, nextContent, 'utf8');
44
+ }
45
+ function bulletList(items) {
46
+ if (items.length === 0) {
47
+ return '- None.';
48
+ }
49
+ return items.map((item) => `- ${item}`).join('\n');
50
+ }
51
+ function renderSeededDiscovery(plan, child) {
52
+ const siblingSlugs = plan.children
53
+ .map((candidate) => candidate.slug)
54
+ .filter((slug) => slug !== child.slug);
55
+ const sections = [
56
+ ['Origin', `Split from coordination parent \`${plan.parentSlug}\`.`],
57
+ [
58
+ 'Inherited Context',
59
+ child.inheritedContext || 'No inherited context provided.',
60
+ ],
61
+ ['Child Scope', child.description ?? child.slug],
62
+ ['Known Dependencies', bulletList(child.knownDependencies)],
63
+ [
64
+ 'Assumptions To Revalidate',
65
+ '- Revalidate inherited context before completing discovery.',
66
+ ],
67
+ ['Likely Workflow Mode', 'quick'],
68
+ ['Sibling Projects', bulletList(siblingSlugs)],
69
+ ];
70
+ return [
71
+ '---',
72
+ 'oat_status: in_progress',
73
+ 'oat_ready_for: null',
74
+ 'oat_blockers: []',
75
+ 'oat_inherited_context_revalidated: false',
76
+ 'oat_generated: false',
77
+ '---',
78
+ '',
79
+ `# Discovery: ${child.slug}`,
80
+ '',
81
+ ...sections.flatMap(([heading, body]) => [`## ${heading}`, '', body, '']),
82
+ ].join('\n');
83
+ }
84
+ export async function seedChildren(plan, context, onlySlugs) {
85
+ const scaffoldProject = context.scaffoldProject ?? defaultScaffoldProject;
86
+ const childProjectPaths = [];
87
+ const orderedChildren = plan.children
88
+ .slice()
89
+ .sort((left, right) => left.order - right.order)
90
+ .filter((child) => !onlySlugs || onlySlugs.has(child.slug));
91
+ for (const child of orderedChildren) {
92
+ const scaffold = await scaffoldProject({
93
+ repoRoot: context.repoRoot,
94
+ projectName: child.slug,
95
+ mode: 'quick',
96
+ setActive: false,
97
+ refreshDashboard: false,
98
+ env: context.env,
99
+ today: context.today,
100
+ nowUtc: context.nowUtc,
101
+ });
102
+ const childRoot = join(context.repoRoot, scaffold.projectPath);
103
+ const siblings = plan.children
104
+ .map((candidate) => candidate.slug)
105
+ .filter((slug) => slug !== child.slug);
106
+ await updateFrontmatter(join(childRoot, 'state.md'), {
107
+ oat_phase: 'discovery',
108
+ oat_workflow_mode: 'quick',
109
+ oat_hill_checkpoints: [],
110
+ oat_parent: plan.parentSlug,
111
+ oat_siblings: siblings,
112
+ oat_depends_on: child.knownDependencies,
113
+ });
114
+ await updateFrontmatter(join(childRoot, 'plan.md'), {
115
+ oat_plan_source: 'quick',
116
+ });
117
+ await writeFile(join(childRoot, 'discovery.md'), renderSeededDiscovery(plan, child), 'utf8');
118
+ childProjectPaths.push(scaffold.projectPath);
119
+ }
120
+ return { childProjectPaths };
121
+ }
122
+ export { SEEDED_SECTIONS };
@@ -0,0 +1,10 @@
1
+ export type Signal = 'independently-shippable' | 'no-shared-design-surface' | 'expect-separate-prs' | 'distinct-subsystems';
2
+ export interface SignalEvaluation {
3
+ fired: Signal[];
4
+ triggered: boolean;
5
+ confidence: 'high' | 'soft' | 'below';
6
+ }
7
+ export declare function evaluateSignals(input: {
8
+ fired: Signal[];
9
+ }): SignalEvaluation;
10
+ //# sourceMappingURL=signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../../../src/projects/split/signals.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GACd,yBAAyB,GACzB,0BAA0B,GAC1B,qBAAqB,GACrB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CACvC;AAOD,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,gBAAgB,CAiB5E"}
@@ -0,0 +1,18 @@
1
+ const LOAD_BEARING = new Set([
2
+ 'independently-shippable',
3
+ 'no-shared-design-surface',
4
+ ]);
5
+ export function evaluateSignals(input) {
6
+ const fired = [...new Set(input.fired)];
7
+ const triggered = fired.length >= 2;
8
+ const loadBearingCount = fired.filter((signal) => LOAD_BEARING.has(signal)).length;
9
+ let confidence = 'below';
10
+ if (triggered) {
11
+ confidence = loadBearingCount === LOAD_BEARING.size ? 'high' : 'soft';
12
+ }
13
+ return {
14
+ fired,
15
+ triggered,
16
+ confidence,
17
+ };
18
+ }
@@ -0,0 +1,14 @@
1
+ import type { ChildPlan } from './child-plan.js';
2
+ export interface ValidationError {
3
+ code: 'slug-collision-existing' | 'duplicate-child-slug' | 'parent-child-slug-collision' | 'unknown-dependency' | 'dependency-cycle' | 'unknown-foundation-child' | 'unknown-initial-active-child';
4
+ message: string;
5
+ slug?: string;
6
+ }
7
+ export type ValidationResult = {
8
+ ok: true;
9
+ } | {
10
+ ok: false;
11
+ errors: ValidationError[];
12
+ };
13
+ export declare function validateChildPlan(plan: ChildPlan, existingSlugs: Set<string>): ValidationResult;
14
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/projects/split/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,IAAI,EACA,yBAAyB,GACzB,sBAAsB,GACtB,6BAA6B,GAC7B,oBAAoB,GACpB,kBAAkB,GAClB,0BAA0B,GAC1B,8BAA8B,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,EAAE,CAAA;CAAE,CAAC;AAgD7C,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,SAAS,EACf,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GACzB,gBAAgB,CA2ElB"}