@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
@@ -0,0 +1,279 @@
1
+ import { readdir as defaultReaddir, readFile as defaultReadFile, } from 'node:fs/promises';
2
+ import { basename, join } from 'node:path';
3
+ import { getFrontmatterBlock, isProjectStateKind, isProjectStatePhase, } from '../commands/shared/frontmatter.js';
4
+ import YAML from 'yaml';
5
+ function readStringField(frontmatter, key) {
6
+ const value = frontmatter[key];
7
+ return typeof value === 'string' ? value : undefined;
8
+ }
9
+ function readStringArrayField(frontmatter, key, errors) {
10
+ const value = frontmatter[key];
11
+ if (value === undefined || value === null) {
12
+ return [];
13
+ }
14
+ if (Array.isArray(value) && value.every((item) => typeof item === 'string')) {
15
+ return [...value];
16
+ }
17
+ errors.push({
18
+ code: 'invalid-string-array',
19
+ message: `${key} must be an array of strings`,
20
+ });
21
+ return [];
22
+ }
23
+ function readKind(frontmatter) {
24
+ const rawKind = readStringField(frontmatter, 'oat_kind');
25
+ return rawKind && isProjectStateKind(rawKind) ? rawKind : 'implementation';
26
+ }
27
+ function sameStringSet(left, right) {
28
+ if (left.length !== right.length) {
29
+ return false;
30
+ }
31
+ const sortedLeft = [...left].sort();
32
+ const sortedRight = [...right].sort();
33
+ return sortedLeft.every((value, index) => value === sortedRight[index]);
34
+ }
35
+ function graphHasCycle(graph) {
36
+ const visiting = new Set();
37
+ const visited = new Set();
38
+ function visit(slug) {
39
+ if (visiting.has(slug)) {
40
+ return true;
41
+ }
42
+ if (visited.has(slug)) {
43
+ return false;
44
+ }
45
+ visiting.add(slug);
46
+ for (const dependency of graph.get(slug) ?? []) {
47
+ if (graph.has(dependency) && visit(dependency)) {
48
+ return true;
49
+ }
50
+ }
51
+ visiting.delete(slug);
52
+ visited.add(slug);
53
+ return false;
54
+ }
55
+ return [...graph.keys()].some((slug) => visit(slug));
56
+ }
57
+ function validateChildLinkage(input, state, errors) {
58
+ if (!state.oat_parent) {
59
+ return;
60
+ }
61
+ const relatedProjects = input.relatedProjects ?? [];
62
+ const parent = relatedProjects.find((project) => project.slug === state.oat_parent);
63
+ if (!parent && input.relatedProjects !== undefined) {
64
+ errors.push({
65
+ code: 'parent-missing',
66
+ message: `oat_parent ${state.oat_parent} must reference an existing project`,
67
+ });
68
+ }
69
+ if (parent && readKind(parent.frontmatter) !== 'coordination') {
70
+ errors.push({
71
+ code: 'parent-not-coordination',
72
+ message: `oat_parent ${state.oat_parent} must reference a coordination project`,
73
+ });
74
+ }
75
+ for (const dependency of state.oat_depends_on) {
76
+ if (!state.oat_siblings.includes(dependency)) {
77
+ errors.push({
78
+ code: 'depends-on-non-sibling',
79
+ message: `oat_depends_on entry ${dependency} must be listed in oat_siblings`,
80
+ });
81
+ }
82
+ }
83
+ if (input.slug && parent) {
84
+ const parentChildren = readStringArrayField(parent.frontmatter, 'oat_children', errors);
85
+ if (parentChildren.length > 0) {
86
+ const expectedSiblings = parentChildren.filter((childSlug) => childSlug !== input.slug);
87
+ if (!sameStringSet(state.oat_siblings, expectedSiblings)) {
88
+ errors.push({
89
+ code: 'siblings-must-match-parent-children',
90
+ message: 'oat_siblings must equal parent oat_children minus the current child',
91
+ });
92
+ }
93
+ }
94
+ }
95
+ const graph = new Map();
96
+ if (input.slug) {
97
+ graph.set(input.slug, state.oat_depends_on);
98
+ }
99
+ for (const project of relatedProjects) {
100
+ if (project.slug === input.slug || project.slug === state.oat_parent) {
101
+ continue;
102
+ }
103
+ if (readStringField(project.frontmatter, 'oat_parent') !== state.oat_parent) {
104
+ continue;
105
+ }
106
+ graph.set(project.slug, readStringArrayField(project.frontmatter, 'oat_depends_on', errors));
107
+ }
108
+ if (graphHasCycle(graph)) {
109
+ errors.push({
110
+ code: 'sibling-dependency-cycle',
111
+ message: 'oat_depends_on across sibling projects must be acyclic',
112
+ });
113
+ }
114
+ }
115
+ function validateInheritedContextGate(frontmatter, state, errors) {
116
+ if (!state.oat_parent) {
117
+ return;
118
+ }
119
+ if (readStringField(frontmatter, 'oat_status') === 'complete' &&
120
+ frontmatter['oat_inherited_context_revalidated'] === false) {
121
+ errors.push({
122
+ code: 'inherited-context-revalidation-required',
123
+ message: 'child discovery cannot complete until oat_inherited_context_revalidated is true',
124
+ });
125
+ }
126
+ }
127
+ function validateCoordinationParent(state, errors) {
128
+ if (state.oat_phase !== 'decomposition') {
129
+ return;
130
+ }
131
+ if (state.oat_children.length === 0) {
132
+ errors.push({
133
+ code: 'decomposition-requires-children',
134
+ message: 'oat_phase: decomposition requires non-empty oat_children',
135
+ });
136
+ }
137
+ }
138
+ function parseFrontmatterObject(content, filePath) {
139
+ const frontmatter = getFrontmatterBlock(content);
140
+ if (!frontmatter) {
141
+ throw new Error(`${filePath} is missing frontmatter`);
142
+ }
143
+ const parsed = YAML.parse(frontmatter);
144
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
145
+ throw new Error(`${filePath} frontmatter must be a YAML object`);
146
+ }
147
+ return parsed;
148
+ }
149
+ export function validateProjectState(input) {
150
+ const errors = [];
151
+ const rawKind = readStringField(input.frontmatter, 'oat_kind');
152
+ const rawPhase = readStringField(input.frontmatter, 'oat_phase');
153
+ const rawPhaseStatus = readStringField(input.frontmatter, 'oat_phase_status');
154
+ const rawParent = readStringField(input.frontmatter, 'oat_parent');
155
+ let kind = 'implementation';
156
+ if (rawKind) {
157
+ if (isProjectStateKind(rawKind)) {
158
+ kind = rawKind;
159
+ }
160
+ else {
161
+ errors.push({
162
+ code: 'invalid-oat-kind',
163
+ message: `Invalid oat_kind: ${rawKind}`,
164
+ });
165
+ }
166
+ }
167
+ let phase;
168
+ if (rawPhase) {
169
+ if (isProjectStatePhase(rawPhase)) {
170
+ phase = rawPhase;
171
+ }
172
+ else {
173
+ errors.push({
174
+ code: 'invalid-oat-phase',
175
+ message: `Invalid oat_phase: ${rawPhase}`,
176
+ });
177
+ }
178
+ }
179
+ if (phase === 'decomposition' && kind !== 'coordination') {
180
+ errors.push({
181
+ code: 'decomposition-requires-coordination',
182
+ message: 'oat_phase: decomposition requires oat_kind: coordination',
183
+ });
184
+ }
185
+ const state = {
186
+ oat_kind: kind,
187
+ oat_phase: phase,
188
+ oat_phase_status: rawPhaseStatus,
189
+ oat_parent: rawParent,
190
+ oat_siblings: readStringArrayField(input.frontmatter, 'oat_siblings', errors),
191
+ oat_depends_on: readStringArrayField(input.frontmatter, 'oat_depends_on', errors),
192
+ oat_children: readStringArrayField(input.frontmatter, 'oat_children', errors),
193
+ };
194
+ validateChildLinkage(input, state, errors);
195
+ validateInheritedContextGate(input.frontmatter, state, errors);
196
+ validateCoordinationParent(state, errors);
197
+ return {
198
+ ok: errors.length === 0,
199
+ state,
200
+ errors,
201
+ };
202
+ }
203
+ export function assertValidProjectStateContent(content, options = {}) {
204
+ const filePath = options.filePath ?? 'state.md';
205
+ const frontmatter = parseFrontmatterObject(content, filePath);
206
+ const result = validateProjectState({
207
+ frontmatter,
208
+ slug: options.slug,
209
+ relatedProjects: options.relatedProjects,
210
+ });
211
+ if (!result.ok) {
212
+ throw new Error(result.errors.map((error) => error.message).join('; '));
213
+ }
214
+ }
215
+ export async function readRelatedProjectStateSnapshots(options) {
216
+ const readdir = options.readdir ?? defaultReaddir;
217
+ const readFile = options.readFile ?? defaultReadFile;
218
+ const snapshots = [];
219
+ const entries = await readdir(options.projectsRoot, { withFileTypes: true });
220
+ for (const entry of entries) {
221
+ if (!entry.isDirectory() || entry.name === options.currentProjectSlug) {
222
+ continue;
223
+ }
224
+ const statePath = join(options.projectsRoot, entry.name, 'state.md');
225
+ try {
226
+ const content = await readFile(statePath, 'utf8');
227
+ snapshots.push({
228
+ slug: entry.name,
229
+ frontmatter: parseFrontmatterObject(content, statePath),
230
+ });
231
+ }
232
+ catch {
233
+ continue;
234
+ }
235
+ }
236
+ return snapshots;
237
+ }
238
+ export async function assertValidProjectStateFilesystemContent(content, options) {
239
+ const slug = options.slug ?? basename(options.projectPath);
240
+ const relatedProjects = await readRelatedProjectStateSnapshots({
241
+ projectsRoot: options.projectsRoot ?? join(options.projectPath, '..'),
242
+ currentProjectSlug: slug,
243
+ readdir: options.readdir,
244
+ readFile: options.readFile,
245
+ });
246
+ const frontmatter = parseFrontmatterObject(content, options.filePath ?? 'state.md');
247
+ const result = validateProjectState({
248
+ frontmatter,
249
+ slug,
250
+ relatedProjects,
251
+ });
252
+ const errors = [...result.errors];
253
+ if (result.state.oat_kind === 'coordination' ||
254
+ result.state.oat_phase === 'decomposition') {
255
+ const readdir = options.readdir ?? defaultReaddir;
256
+ const entries = await readdir(options.projectPath, {
257
+ withFileTypes: true,
258
+ });
259
+ const executableArtifacts = new Set([
260
+ 'spec.md',
261
+ 'design.md',
262
+ 'plan.md',
263
+ 'implementation.md',
264
+ ]);
265
+ const presentExecutableArtifacts = entries
266
+ .filter((entry) => entry.isFile() && executableArtifacts.has(entry.name))
267
+ .map((entry) => entry.name)
268
+ .sort();
269
+ if (presentExecutableArtifacts.length > 0) {
270
+ errors.push({
271
+ code: 'coordination-parent-no-executable-artifacts',
272
+ message: `coordination projects must not contain executable phase artifacts: ${presentExecutableArtifacts.join(', ')}`,
273
+ });
274
+ }
275
+ }
276
+ if (errors.length > 0) {
277
+ throw new Error(errors.map((error) => error.message).join('; '));
278
+ }
279
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "private": false,
5
5
  "description": "Open Agent Toolkit CLI",
6
6
  "homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
@@ -33,7 +33,7 @@
33
33
  "ora": "^9.0.0",
34
34
  "yaml": "2.8.2",
35
35
  "zod": "^3.25.76",
36
- "@open-agent-toolkit/control-plane": "0.1.5"
36
+ "@open-agent-toolkit/control-plane": "0.1.7"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.10.0",