@open-agent-toolkit/cli 0.2.40 → 0.2.45

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 (213) hide show
  1. package/assets/agents/oat-codebase-mapper.md +15 -3
  2. package/assets/agents/oat-phase-implementer.md +34 -9
  3. package/assets/agents/oat-reviewer.md +2 -2
  4. package/assets/bundle-metadata.json +1 -1
  5. package/assets/docs/cli-utilities/configuration.md +11 -1
  6. package/assets/docs/cli-utilities/tool-packs.md +8 -0
  7. package/assets/docs/contributing/skills.md +67 -14
  8. package/assets/docs/quickstart.md +9 -0
  9. package/assets/docs/reference/cli-reference.md +56 -1
  10. package/assets/docs/reference/file-locations.md +18 -1
  11. package/assets/docs/reference/oat-directory-structure.md +20 -4
  12. package/assets/docs/workflows/projects/artifacts.md +27 -1
  13. package/assets/docs/workflows/projects/implementation-execution.md +22 -0
  14. package/assets/docs/workflows/projects/index.md +6 -0
  15. package/assets/docs/workflows/projects/lifecycle.md +19 -5
  16. package/assets/docs/workflows/projects/picking-up-projects.md +75 -0
  17. package/assets/docs/workflows/projects/pr-flow.md +22 -1
  18. package/assets/docs/workflows/projects/reviewing-oat-prs.md +60 -0
  19. package/assets/public-package-versions.json +4 -4
  20. package/assets/skills/analyze/SKILL.md +13 -5
  21. package/assets/skills/compare/SKILL.md +11 -2
  22. package/assets/skills/oat-brainstorm/SKILL.md +135 -32
  23. package/assets/skills/oat-brainstorm/references/destinations.md +9 -6
  24. package/assets/skills/oat-cursor-cloud-projects/SKILL.md +11 -1
  25. package/assets/skills/oat-dispatch-subagents/SKILL.md +19 -6
  26. package/assets/skills/oat-doctor/SKILL.md +8 -1
  27. package/assets/skills/oat-project-autonomous/SKILL.md +45 -1
  28. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +8 -8
  29. package/assets/skills/oat-project-capture/SKILL.md +29 -9
  30. package/assets/skills/oat-project-complete/SKILL.md +610 -38
  31. package/assets/skills/oat-project-complete/scripts/parse-completion-retry-fields.mjs +265 -0
  32. package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +764 -0
  33. package/assets/skills/oat-project-complete/scripts/resolve-completion-retry.mjs +134 -0
  34. package/assets/skills/oat-project-complete/scripts/validate-nonarchive-lifecycle-receipt.mjs +160 -0
  35. package/assets/skills/oat-project-design/SKILL.md +33 -9
  36. package/assets/skills/oat-project-discover/SKILL.md +16 -3
  37. package/assets/skills/oat-project-document/SKILL.md +10 -4
  38. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +8 -8
  39. package/assets/skills/oat-project-implement/SKILL.md +14 -3
  40. package/assets/skills/oat-project-implement/references/completion-and-closeout.md +18 -6
  41. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +8 -8
  42. package/assets/skills/oat-project-implement/references/phase-execution.md +9 -3
  43. package/assets/skills/oat-project-implement/references/plan-and-resume.md +3 -0
  44. package/assets/skills/oat-project-import-plan/SKILL.md +15 -9
  45. package/assets/skills/oat-project-new/SKILL.md +7 -5
  46. package/assets/skills/oat-project-next/SKILL.md +32 -10
  47. package/assets/skills/oat-project-plan/SKILL.md +18 -3
  48. package/assets/skills/oat-project-pr-final/SKILL.md +23 -2
  49. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +8 -8
  50. package/assets/skills/oat-project-pr-progress/SKILL.md +23 -2
  51. package/assets/skills/oat-project-progress/SKILL.md +23 -9
  52. package/assets/skills/oat-project-promote-spec-driven/SKILL.md +20 -2
  53. package/assets/skills/oat-project-quick-start/SKILL.md +48 -18
  54. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +8 -8
  55. package/assets/skills/oat-project-reconcile/SKILL.md +14 -10
  56. package/assets/skills/oat-project-retro/SKILL.md +3 -3
  57. package/assets/skills/oat-project-retro/references/apply-procedure.md +102 -6
  58. package/assets/skills/oat-project-retro-file/SKILL.md +57 -6
  59. package/assets/skills/oat-project-review-provide/SKILL.md +56 -6
  60. package/assets/skills/oat-project-review-receive/SKILL.md +16 -7
  61. package/assets/skills/oat-project-review-receive-remote/SKILL.md +9 -3
  62. package/assets/skills/oat-project-revise/SKILL.md +12 -3
  63. package/assets/skills/oat-project-spec/SKILL.md +16 -3
  64. package/assets/skills/oat-project-summary/SKILL.md +113 -13
  65. package/assets/skills/oat-repo-improve/SKILL.md +18 -7
  66. package/assets/skills/oat-review-provide-remote/SKILL.md +11 -2
  67. package/assets/skills/oat-wave-execute/SKILL.md +34 -2
  68. package/assets/skills/oat-worktree-bootstrap/SKILL.md +13 -1
  69. package/assets/skills/oat-worktree-bootstrap-auto/SKILL.md +13 -1
  70. package/dist/commands/config/index.d.ts +1 -0
  71. package/dist/commands/config/index.d.ts.map +1 -1
  72. package/dist/commands/config/index.js +30 -2
  73. package/dist/commands/doctor/index.d.ts +2 -0
  74. package/dist/commands/doctor/index.d.ts.map +1 -1
  75. package/dist/commands/doctor/index.js +27 -3
  76. package/dist/commands/doctor/synced-projects.d.ts +12 -0
  77. package/dist/commands/doctor/synced-projects.d.ts.map +1 -0
  78. package/dist/commands/doctor/synced-projects.js +208 -0
  79. package/dist/commands/init/gitattributes.d.ts +4 -0
  80. package/dist/commands/init/gitattributes.d.ts.map +1 -0
  81. package/dist/commands/init/gitattributes.js +12 -0
  82. package/dist/commands/init/gitignore.d.ts +8 -1
  83. package/dist/commands/init/gitignore.d.ts.map +1 -1
  84. package/dist/commands/init/gitignore.js +146 -41
  85. package/dist/commands/init/index.d.ts +2 -0
  86. package/dist/commands/init/index.d.ts.map +1 -1
  87. package/dist/commands/init/index.js +3 -0
  88. package/dist/commands/init/managed-block.d.ts +10 -0
  89. package/dist/commands/init/managed-block.d.ts.map +1 -0
  90. package/dist/commands/init/managed-block.js +40 -0
  91. package/dist/commands/init/tools/workflows/install-workflows.js +1 -1
  92. package/dist/commands/local/index.d.ts.map +1 -1
  93. package/dist/commands/local/index.js +5 -1
  94. package/dist/commands/local/sync.d.ts +4 -0
  95. package/dist/commands/local/sync.d.ts.map +1 -1
  96. package/dist/commands/local/sync.js +123 -2
  97. package/dist/commands/pjm/adoption.d.ts +2 -0
  98. package/dist/commands/pjm/adoption.d.ts.map +1 -1
  99. package/dist/commands/pjm/adoption.js +1 -1
  100. package/dist/commands/project/archive/archive-utils.d.ts +24 -0
  101. package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
  102. package/dist/commands/project/archive/archive-utils.js +349 -22
  103. package/dist/commands/project/archive/index.d.ts.map +1 -1
  104. package/dist/commands/project/archive/index.js +1 -0
  105. package/dist/commands/project/archive/push-runner.d.ts +5 -1
  106. package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
  107. package/dist/commands/project/archive/push-runner.js +33 -4
  108. package/dist/commands/project/complete-state/index.d.ts.map +1 -1
  109. package/dist/commands/project/complete-state/index.js +3 -1
  110. package/dist/commands/project/index.d.ts.map +1 -1
  111. package/dist/commands/project/index.js +12 -0
  112. package/dist/commands/project/links/compute.d.ts +9 -0
  113. package/dist/commands/project/links/compute.d.ts.map +1 -0
  114. package/dist/commands/project/links/compute.js +27 -0
  115. package/dist/commands/project/links/index.d.ts +17 -0
  116. package/dist/commands/project/links/index.d.ts.map +1 -0
  117. package/dist/commands/project/links/index.js +72 -0
  118. package/dist/commands/project/links/refresh.d.ts +26 -0
  119. package/dist/commands/project/links/refresh.d.ts.map +1 -0
  120. package/dist/commands/project/links/refresh.js +92 -0
  121. package/dist/commands/project/links/render.d.ts +24 -0
  122. package/dist/commands/project/links/render.d.ts.map +1 -0
  123. package/dist/commands/project/links/render.js +68 -0
  124. package/dist/commands/project/list.d.ts +5 -0
  125. package/dist/commands/project/list.d.ts.map +1 -1
  126. package/dist/commands/project/list.js +182 -9
  127. package/dist/commands/project/migrate/index.d.ts +22 -0
  128. package/dist/commands/project/migrate/index.d.ts.map +1 -0
  129. package/dist/commands/project/migrate/index.js +79 -0
  130. package/dist/commands/project/new/index.d.ts +2 -0
  131. package/dist/commands/project/new/index.d.ts.map +1 -1
  132. package/dist/commands/project/new/index.js +14 -2
  133. package/dist/commands/project/new/scaffold.d.ts +22 -1
  134. package/dist/commands/project/new/scaffold.d.ts.map +1 -1
  135. package/dist/commands/project/new/scaffold.js +287 -69
  136. package/dist/commands/project/open/index.d.ts +16 -0
  137. package/dist/commands/project/open/index.d.ts.map +1 -1
  138. package/dist/commands/project/open/index.js +151 -19
  139. package/dist/commands/project/pause/index.d.ts +8 -0
  140. package/dist/commands/project/pause/index.d.ts.map +1 -1
  141. package/dist/commands/project/pause/index.js +129 -19
  142. package/dist/commands/project/prune/index.d.ts +21 -0
  143. package/dist/commands/project/prune/index.d.ts.map +1 -0
  144. package/dist/commands/project/prune/index.js +95 -0
  145. package/dist/commands/project/pull/index.d.ts +28 -0
  146. package/dist/commands/project/pull/index.d.ts.map +1 -0
  147. package/dist/commands/project/pull/index.js +157 -0
  148. package/dist/commands/project/push/index.d.ts +22 -0
  149. package/dist/commands/project/push/index.d.ts.map +1 -0
  150. package/dist/commands/project/push/index.js +100 -0
  151. package/dist/commands/project/scope/index.d.ts +17 -0
  152. package/dist/commands/project/scope/index.d.ts.map +1 -0
  153. package/dist/commands/project/scope/index.js +107 -0
  154. package/dist/commands/project/split/run.d.ts +4 -0
  155. package/dist/commands/project/split/run.d.ts.map +1 -1
  156. package/dist/commands/project/split/run.js +48 -10
  157. package/dist/commands/project/sync/git.d.ts +18 -0
  158. package/dist/commands/project/sync/git.d.ts.map +1 -0
  159. package/dist/commands/project/sync/git.js +60 -0
  160. package/dist/commands/project/sync/record.d.ts +22 -0
  161. package/dist/commands/project/sync/record.d.ts.map +1 -0
  162. package/dist/commands/project/sync/record.js +133 -0
  163. package/dist/commands/project/sync/ref-sync.d.ts +120 -0
  164. package/dist/commands/project/sync/ref-sync.d.ts.map +1 -0
  165. package/dist/commands/project/sync/ref-sync.js +962 -0
  166. package/dist/commands/project/sync/resolve-target.d.ts +27 -0
  167. package/dist/commands/project/sync/resolve-target.d.ts.map +1 -0
  168. package/dist/commands/project/sync/resolve-target.js +159 -0
  169. package/dist/commands/shared/oat-paths.d.ts +4 -1
  170. package/dist/commands/shared/oat-paths.d.ts.map +1 -1
  171. package/dist/commands/shared/oat-paths.js +2 -2
  172. package/dist/commands/shared/project-scope.d.ts +23 -0
  173. package/dist/commands/shared/project-scope.d.ts.map +1 -0
  174. package/dist/commands/shared/project-scope.js +106 -0
  175. package/dist/commands/tools/migrate/index.js +1 -1
  176. package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
  177. package/dist/commands/tools/shared/pack-inventory.js +11 -0
  178. package/dist/commands/tools/shared/pack-lifecycle.js +1 -1
  179. package/dist/commands/tools/shared/pack-reconcile.d.ts.map +1 -1
  180. package/dist/commands/tools/shared/pack-reconcile.js +4 -1
  181. package/dist/commands/tools/update/index.d.ts.map +1 -1
  182. package/dist/commands/tools/update/index.js +20 -10
  183. package/dist/commands/tools/update/update-tools.d.ts +2 -0
  184. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  185. package/dist/config/oat-config.d.ts +6 -3
  186. package/dist/config/oat-config.d.ts.map +1 -1
  187. package/dist/config/oat-config.js +41 -7
  188. package/dist/config/resolve.d.ts.map +1 -1
  189. package/dist/config/resolve.js +2 -1
  190. package/dist/fs/io.d.ts +2 -1
  191. package/dist/fs/io.d.ts.map +1 -1
  192. package/dist/fs/io.js +5 -2
  193. package/dist/projects/split/finalize.d.ts.map +1 -1
  194. package/dist/projects/split/finalize.js +55 -2
  195. package/dist/projects/split/resume.d.ts +3 -0
  196. package/dist/projects/split/resume.d.ts.map +1 -1
  197. package/dist/projects/split/resume.js +34 -7
  198. package/dist/projects/split/seed-children.d.ts.map +1 -1
  199. package/dist/projects/split/seed-children.js +6 -0
  200. package/dist/projects/split/write-parent.d.ts +7 -0
  201. package/dist/projects/split/write-parent.d.ts.map +1 -1
  202. package/dist/projects/split/write-parent.js +7 -4
  203. package/dist/release/public-package-contract.d.ts.map +1 -1
  204. package/dist/release/public-package-contract.js +1 -0
  205. package/dist/validation/skills.d.ts.map +1 -1
  206. package/dist/validation/skills.js +434 -0
  207. package/package.json +4 -4
  208. package/dist/__tests__/skills/split-flow-fixtures.d.ts +0 -50
  209. package/dist/__tests__/skills/split-flow-fixtures.d.ts.map +0 -1
  210. package/dist/__tests__/skills/split-flow-fixtures.js +0 -161
  211. package/dist/commands/__tests__/helpers.d.ts +0 -12
  212. package/dist/commands/__tests__/helpers.d.ts.map +0 -1
  213. package/dist/commands/__tests__/helpers.js +0 -36
@@ -13,6 +13,439 @@ async function isDirectory(path) {
13
13
  return false;
14
14
  }
15
15
  }
16
+ async function isFile(path) {
17
+ try {
18
+ const s = await stat(path);
19
+ return s.isFile();
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ async function listMarkdownFiles(root) {
26
+ if (!(await isDirectory(root))) {
27
+ return [];
28
+ }
29
+ const files = [];
30
+ for (const entry of await readdir(root, { withFileTypes: true })) {
31
+ const path = join(root, entry.name);
32
+ if (entry.isDirectory()) {
33
+ files.push(...(await listMarkdownFiles(path)));
34
+ }
35
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
36
+ files.push(path);
37
+ }
38
+ }
39
+ return files.sort();
40
+ }
41
+ function countOccurrences(content, anchor) {
42
+ if (anchor.length === 0) {
43
+ return 0;
44
+ }
45
+ let count = 0;
46
+ let offset = 0;
47
+ while ((offset = content.indexOf(anchor, offset)) !== -1) {
48
+ count += 1;
49
+ offset += anchor.length;
50
+ }
51
+ return count;
52
+ }
53
+ function isLifecycleSafetyFile(file) {
54
+ return (/\/\.agents\/skills\/(?:oat-project-[^/]+|oat-worktree-[^/]+|oat-brainstorm|oat-wave-execute)\//.test(file) || file.endsWith('/.agents/agents/oat-phase-implementer.md'));
55
+ }
56
+ function referencesProjectArtifactVariable(line, artifactArrays = new Set()) {
57
+ if (/\$PROJECT_PATH|\$\{PROJECT_PATH\}|\{PROJECT_PATH\}|\$ARTIFACT_PATH|\$\{ARTIFACT_PATH\}|\$ACTIVE_PROJECT(?!_PATH)|\$\{ACTIVE_PROJECT\}|\$REVIEW_PATH|\$\{REVIEW_PATH\}|\$\{(?:PROJECT|RETRO)_[A-Z0-9_]*(?:PATHS|FILES)\[@\]\}/.test(line)) {
58
+ return true;
59
+ }
60
+ return [...artifactArrays].some((name) => line.includes(`\${${name}[@]}`));
61
+ }
62
+ function projectArtifactArrays(content) {
63
+ const arrays = new Set();
64
+ let changed = true;
65
+ while (changed) {
66
+ changed = false;
67
+ for (const { content: line } of logicalLines(content)) {
68
+ const assignment = /^\s*([A-Z][A-Z0-9_]*)(?:\+)?=\((.*)\)\s*$/.exec(line);
69
+ const name = assignment?.[1];
70
+ const value = assignment?.[2];
71
+ if (name &&
72
+ value &&
73
+ !arrays.has(name) &&
74
+ referencesProjectArtifactVariable(value, arrays)) {
75
+ arrays.add(name);
76
+ changed = true;
77
+ }
78
+ }
79
+ }
80
+ return arrays;
81
+ }
82
+ function executesCommand(line, command) {
83
+ const prefix = '(?:(?:&&|\\|\\||;|\\bthen\\b|\\belse\\b|\\bdo\\b|\\bif\\b)\\s+)';
84
+ const modifiers = '(?:!\\s+)?(?:command\\s+)?(?:env\\s+)?(?:[A-Z][A-Z0-9_]*=\\S+\\s+)*';
85
+ return new RegExp(`^\\s*(?:(?:[A-Z][A-Z0-9_]*)=\\$\\()?${command}\\b|${prefix}${modifiers}${command}\\b`).test(line);
86
+ }
87
+ function executesGitCommand(line) {
88
+ return executesCommand(line, 'git');
89
+ }
90
+ function logicalLines(content) {
91
+ const physicalLines = content.split('\n');
92
+ const logical = [];
93
+ let offset = 0;
94
+ for (let index = 0; index < physicalLines.length; index += 1) {
95
+ const startLine = index;
96
+ const startOffset = offset;
97
+ let combined = physicalLines[index] ?? '';
98
+ offset += combined.length + 1;
99
+ while (/\\\s*$/.test(combined) && index + 1 < physicalLines.length) {
100
+ combined = combined.replace(/\\\s*$/, ' ');
101
+ index += 1;
102
+ const continuation = physicalLines[index] ?? '';
103
+ combined += continuation.trimStart();
104
+ offset += continuation.length + 1;
105
+ }
106
+ logical.push({
107
+ content: combined,
108
+ endLine: index,
109
+ offset: startOffset,
110
+ startLine,
111
+ });
112
+ }
113
+ return logical;
114
+ }
115
+ function isProjectArtifactWriterLine(line, artifactArrays = new Set()) {
116
+ const trimmed = line.trim();
117
+ const executesGitOrOat = executesGitCommand(trimmed) || executesCommand(trimmed, 'oat');
118
+ const writesProjectArtifact = executesGitOrOat &&
119
+ /\bgit\s+(?:add|commit)\b|\boat\s+project\s+push\b/.test(trimmed) &&
120
+ referencesProjectArtifactVariable(line, artifactArrays);
121
+ const writesActiveProject = executesGitOrOat && /\boat\s+config\s+set\s+activeProject\b/.test(trimmed);
122
+ return writesProjectArtifact || writesActiveProject;
123
+ }
124
+ function fencedContentSites(content) {
125
+ const lines = content.split('\n');
126
+ const sites = [];
127
+ let offset = 0;
128
+ let opening = null;
129
+ for (const line of lines) {
130
+ const lineEnd = offset + line.length;
131
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})(?:[^`~]*)$/);
132
+ if (fenceMatch) {
133
+ const marker = fenceMatch[1] ?? '';
134
+ if (opening === null) {
135
+ opening = { marker, start: offset };
136
+ }
137
+ else if (marker[0] === opening.marker[0] &&
138
+ marker.length >= opening.marker.length &&
139
+ /^\s*[`~]+\s*$/.test(line)) {
140
+ const end = lineEnd;
141
+ sites.push({
142
+ start: opening.start,
143
+ end,
144
+ content: content.slice(opening.start, end),
145
+ });
146
+ opening = null;
147
+ }
148
+ }
149
+ offset = lineEnd + 1;
150
+ }
151
+ return sites;
152
+ }
153
+ function contentSiteAt(content, offset, fencedSites) {
154
+ const fenced = fencedSites.find((site) => offset >= site.start && offset < site.end);
155
+ if (fenced)
156
+ return fenced;
157
+ const preceding = content.slice(0, offset);
158
+ const headings = [...preceding.matchAll(/^([#]{1,6})\s+.+$/gm)];
159
+ const heading = headings.at(-1);
160
+ if (heading?.index !== undefined) {
161
+ const level = heading[1]?.length ?? 6;
162
+ const following = content.slice(offset);
163
+ const nextHeading = [...following.matchAll(/^([#]{1,6})\s+.+$/gm)].find((candidate) => (candidate[1]?.length ?? 7) <= level);
164
+ const end = nextHeading?.index === undefined
165
+ ? content.length
166
+ : offset + nextHeading.index;
167
+ return {
168
+ start: heading.index,
169
+ end,
170
+ content: content.slice(heading.index, end),
171
+ };
172
+ }
173
+ const start = content.lastIndexOf('\n', Math.max(0, offset - 1)) + 1;
174
+ const nextNewline = content.indexOf('\n', offset);
175
+ const end = nextNewline === -1 ? content.length : nextNewline;
176
+ return { start, end, content: content.slice(start, end) };
177
+ }
178
+ function projectArtifactWriterSites(content) {
179
+ const fencedSites = fencedContentSites(content);
180
+ const sites = new Map();
181
+ const artifactArrays = projectArtifactArrays(content);
182
+ for (const line of logicalLines(content)) {
183
+ if (isProjectArtifactWriterLine(line.content, artifactArrays)) {
184
+ const site = contentSiteAt(content, line.offset, fencedSites);
185
+ sites.set(`${site.start}:${site.end}`, site);
186
+ }
187
+ }
188
+ return [...sites.values()];
189
+ }
190
+ function collectSyncedContentFindings(file, content, findings) {
191
+ for (const match of content.matchAll(/PROJECT_SCOPE=\$\(oat project scope[^\n]+\) \|\| exit 1\n\s*\[\s*"?\$PROJECT_SCOPE"?\s*=\s*"?synced"?\s*\]\s*&&\s*oat\s+project\s+pull\b/g)) {
192
+ const lineNumber = content.slice(0, match.index).split('\n').length + 1;
193
+ findings.push({
194
+ file,
195
+ message: `Line ${lineNumber}: Synced arrival pull must use an if block so shared and local arrival exits successfully`,
196
+ });
197
+ }
198
+ const logical = logicalLines(content);
199
+ const artifactArrays = projectArtifactArrays(content);
200
+ for (const [logicalIndex, site] of logical.entries()) {
201
+ const line = site.content;
202
+ const lineNumber = site.startLine + 1;
203
+ if (/\bgit\s+add\b[^\n]*\.oat\/projects\/synced(?:\/|\b)/.test(line)) {
204
+ findings.push({
205
+ file,
206
+ message: `Line ${lineNumber}: Never stage a path under .oat/projects/synced/; use oat project push`,
207
+ });
208
+ }
209
+ if (/\boat\s+project\s+scope\b/.test(line) &&
210
+ (/--json\b/.test(line) || /\|[^\n]*\bjq\b/.test(line))) {
211
+ findings.push({
212
+ file,
213
+ message: `Line ${lineNumber}: Resolve project scope with --format value; do not parse --json or pipe into jq`,
214
+ });
215
+ }
216
+ if (isLifecycleSafetyFile(file) &&
217
+ executesGitCommand(line) &&
218
+ /\bgit\s+add\s+-A(?:\s*$|\s*[;&|])/.test(line)) {
219
+ findings.push({
220
+ file,
221
+ message: `Line ${lineNumber}: Project-artifact staging must use exact pathspecs; broad git add -A is forbidden`,
222
+ });
223
+ }
224
+ if (isLifecycleSafetyFile(file) &&
225
+ executesGitCommand(line) &&
226
+ /\bgit\s+add\b/.test(line) &&
227
+ /(?:\$PROJECT_PATH|\$\{PROJECT_PATH\})(?:\/?(?:\*|\*\*)?)?(?:["'}\s]|$)/.test(line) &&
228
+ !/\$PROJECT_PATH\/[A-Za-z0-9_.-]+|\$\{PROJECT_PATH\}\/[A-Za-z0-9_.-]+/.test(line)) {
229
+ findings.push({
230
+ file,
231
+ message: `Line ${lineNumber}: Project-artifact staging must name exact files, not the project directory or a glob`,
232
+ });
233
+ }
234
+ const pushReceipt = /^\s*([A-Z][A-Z0-9_]*)=\$\(oat project push\b[^\n]*--json\b/.exec(line);
235
+ if (pushReceipt) {
236
+ const outputVariable = pushReceipt[1] ?? '';
237
+ const validationLines = logical
238
+ .slice(logicalIndex + 1, logicalIndex + 5)
239
+ .map((candidate) => candidate.content);
240
+ const parserNeedle = `parse_synced_push_receipt "$${outputVariable}"`;
241
+ const parserOffset = validationLines.findIndex((candidate) => candidate.includes(parserNeedle));
242
+ if (parserOffset === -1) {
243
+ findings.push({
244
+ file,
245
+ message: `Line ${lineNumber}: Synced push JSON receipt must validate status as pushed or up-to-date and require a full SHA before use`,
246
+ });
247
+ }
248
+ else {
249
+ for (const [offset, candidate] of validationLines
250
+ .slice(0, parserOffset)
251
+ .entries()) {
252
+ if (candidate.includes(`$${outputVariable}`) &&
253
+ /(?:\.sha\b|\[['"]sha['"]\])/.test(candidate)) {
254
+ findings.push({
255
+ file,
256
+ message: `Line ${lineNumber + offset + 1}: Synced push JSON receipt must not extract or use .sha before parse_synced_push_receipt validation`,
257
+ });
258
+ }
259
+ }
260
+ }
261
+ }
262
+ if (/\boat\s+project\s+scope\b/.test(line) &&
263
+ /\|\|\s*echo\s+["']?shared\b/.test(line)) {
264
+ findings.push({
265
+ file,
266
+ message: `Line ${lineNumber}: Project scope resolution must fail closed; do not fall back to shared`,
267
+ });
268
+ }
269
+ const isPushCommand = executesCommand(line, 'oat') && /\boat\s+project\s+push\b/.test(line);
270
+ const pushFailureHandled = /\|\|\s*(?:\{|exit\b)/.test(line) ||
271
+ ((line.match(/"/g)?.length ?? 0) % 2 === 1 &&
272
+ logical
273
+ .slice(logicalIndex + 1, logicalIndex + 16)
274
+ .some((candidate) => /"\s*\|\|\s*(?:\{|exit\b)/.test(candidate.content)));
275
+ if (isPushCommand && !pushFailureHandled) {
276
+ findings.push({
277
+ file,
278
+ message: `Line ${lineNumber}: Project push must handle a nonzero exit explicitly and stop bookkeeping`,
279
+ });
280
+ }
281
+ }
282
+ const lines = content.split('\n');
283
+ const logicalByStart = new Map(logical.map((line) => [line.startLine, line.content]));
284
+ const continuedLines = new Set(logical.flatMap((line) => Array.from({ length: line.endLine - line.startLine }, (_, offset) => line.startLine + offset + 1)));
285
+ let fenceMarker = null;
286
+ let scopeGuardSeen = false;
287
+ for (const [index, line] of lines.entries()) {
288
+ const lineNumber = index + 1;
289
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})(?:[^`~]*)$/);
290
+ if (fenceMatch) {
291
+ const marker = fenceMatch[1] ?? '';
292
+ if (fenceMarker === null) {
293
+ fenceMarker = marker;
294
+ scopeGuardSeen = false;
295
+ }
296
+ else if (marker[0] === fenceMarker[0] &&
297
+ marker.length >= fenceMarker.length &&
298
+ /^\s*[`~]+\s*$/.test(line)) {
299
+ fenceMarker = null;
300
+ scopeGuardSeen = false;
301
+ }
302
+ continue;
303
+ }
304
+ if (continuedLines.has(index))
305
+ continue;
306
+ const inspectedLine = logicalByStart.get(index) ?? line;
307
+ if (fenceMarker === null || !isLifecycleSafetyFile(file)) {
308
+ continue;
309
+ }
310
+ if (/\boat\s+project\s+scope\b/.test(inspectedLine) &&
311
+ /--format\s+value\b/.test(inspectedLine)) {
312
+ scopeGuardSeen = true;
313
+ }
314
+ if (/\bPROJECT_SCOPE\b/.test(inspectedLine) &&
315
+ /\bsynced\b/.test(inspectedLine)) {
316
+ scopeGuardSeen = true;
317
+ }
318
+ if (/\bgit\s+(?:add|commit)\b/.test(inspectedLine) &&
319
+ referencesProjectArtifactVariable(inspectedLine, artifactArrays) &&
320
+ !scopeGuardSeen) {
321
+ findings.push({
322
+ file,
323
+ message: `Line ${lineNumber}: Project-artifact git writes require an oat project scope --format value guard earlier in the same fenced block`,
324
+ });
325
+ }
326
+ }
327
+ }
328
+ async function collectSyncedBookkeepingInventoryFindings(repoRoot, safetyFiles, findings) {
329
+ const inventoryPath = join(repoRoot, 'packages', 'cli', 'src', 'validation', 'synced-bookkeeping-sites.json');
330
+ if (!(await isFile(inventoryPath))) {
331
+ return;
332
+ }
333
+ let sites;
334
+ try {
335
+ const parsed = JSON.parse(await readFile(inventoryPath, 'utf8'));
336
+ if (!Array.isArray(parsed)) {
337
+ throw new Error('expected a JSON array');
338
+ }
339
+ sites = parsed;
340
+ }
341
+ catch (error) {
342
+ findings.push({
343
+ file: inventoryPath,
344
+ message: `Invalid synced-bookkeeping inventory: ${error instanceof Error ? error.message : String(error)}`,
345
+ });
346
+ return;
347
+ }
348
+ const inventoriedWriterSites = new Map();
349
+ for (const site of sites) {
350
+ if (typeof site?.file !== 'string' ||
351
+ typeof site?.anchor !== 'string' ||
352
+ typeof site?.guard !== 'string' ||
353
+ !['resolve', 'arrival', 'write'].includes(site?.kind)) {
354
+ findings.push({
355
+ file: inventoryPath,
356
+ message: 'Each synced-bookkeeping inventory entry requires file, unique anchor, companion guard, and kind (resolve | arrival | write)',
357
+ });
358
+ continue;
359
+ }
360
+ const sitePath = join(repoRoot, site.file);
361
+ let content;
362
+ try {
363
+ content = await readFile(sitePath, 'utf8');
364
+ }
365
+ catch {
366
+ findings.push({
367
+ file: inventoryPath,
368
+ message: `Missing synced-bookkeeping inventory file: ${sitePath}`,
369
+ });
370
+ continue;
371
+ }
372
+ const occurrences = countOccurrences(content, site.anchor);
373
+ if (occurrences === 0) {
374
+ findings.push({
375
+ file: inventoryPath,
376
+ message: `Stale synced-bookkeeping inventory anchor in ${sitePath}: ${site.anchor}`,
377
+ });
378
+ }
379
+ else if (occurrences > 1) {
380
+ findings.push({
381
+ file: inventoryPath,
382
+ message: `Synced-bookkeeping inventory anchor is not unique in ${sitePath}: ${site.anchor}`,
383
+ });
384
+ }
385
+ if (occurrences === 1) {
386
+ const fencedSites = fencedContentSites(content);
387
+ const anchorOffset = content.indexOf(site.anchor);
388
+ const anchorSite = contentSiteAt(content, anchorOffset, fencedSites);
389
+ const companionGuardPresent = site.kind === 'write' && site.guard === 'project-scope'
390
+ ? /\boat\s+project\s+scope\b[^\n]*--format\s+value\b|\bPROJECT_SCOPE\b[^\n]*\bsynced\b/.test(anchorSite.content)
391
+ : site.kind === 'arrival' && site.guard === 'materialized-arrival'
392
+ ? /\boat\s+project\s+pull\b|pulled and materialized/i.test(anchorSite.content)
393
+ : site.kind === 'resolve' && site.guard === 'canonical-resolution'
394
+ ? /\bresolve(?:Project|NamedProject|SyncedTarget|ProjectScope|ActiveProject)\b|\bprojectScope\s*===\s*['"]synced['"]|options\.remote[^\n]*synced|\boat\s+project\s+new\b[^\n]*--scope\b|project:synced_tracked_artifacts|\boat\s+config\s+get\s+activeProject\b/.test(anchorSite.content)
395
+ : anchorSite.content.includes(site.guard);
396
+ if (!companionGuardPresent) {
397
+ findings.push({
398
+ file: inventoryPath,
399
+ message: `Synced-bookkeeping ${site.kind} site lacks its companion guard in the same function or fenced block: ${sitePath}: ${site.anchor}`,
400
+ });
401
+ }
402
+ if (site.kind === 'write') {
403
+ const fileSites = inventoriedWriterSites.get(sitePath) ?? [];
404
+ fileSites.push(anchorSite);
405
+ inventoriedWriterSites.set(sitePath, fileSites);
406
+ }
407
+ }
408
+ }
409
+ for (const file of safetyFiles) {
410
+ const content = await readFile(file, 'utf8');
411
+ if (!isLifecycleSafetyFile(file))
412
+ continue;
413
+ const inventoriedSites = inventoriedWriterSites.get(file) ?? [];
414
+ const writerSites = projectArtifactWriterSites(content);
415
+ const artifactArrays = projectArtifactArrays(content);
416
+ if (writerSites.length > 0 && inventoriedSites.length === 0) {
417
+ findings.push({
418
+ file: inventoryPath,
419
+ message: `Lifecycle project-artifact writer is missing from synced-bookkeeping inventory: ${file}`,
420
+ });
421
+ continue;
422
+ }
423
+ for (const writerSite of writerSites) {
424
+ if (inventoriedSites.some((site) => site.start === writerSite.start && site.end === writerSite.end)) {
425
+ continue;
426
+ }
427
+ const writer = writerSite.content
428
+ .split('\n')
429
+ .find((line) => isProjectArtifactWriterLine(line, artifactArrays))
430
+ ?.trim();
431
+ findings.push({
432
+ file: inventoryPath,
433
+ message: `Lifecycle project-artifact writer site is missing from synced-bookkeeping inventory: ${file}: ${writer ?? '(unknown writer)'}`,
434
+ });
435
+ }
436
+ }
437
+ }
438
+ async function collectSyncedSafetyFindings(repoRoot, oatSkillDirs, findings) {
439
+ const skillFiles = (await Promise.all(oatSkillDirs.map((dir) => listMarkdownFiles(join(repoRoot, '.agents', 'skills', dir))))).flat();
440
+ const phaseImplementerPath = join(repoRoot, '.agents', 'agents', 'oat-phase-implementer.md');
441
+ const safetyFiles = (await isFile(phaseImplementerPath))
442
+ ? [...skillFiles, phaseImplementerPath]
443
+ : skillFiles;
444
+ for (const file of safetyFiles) {
445
+ collectSyncedContentFindings(file, await readFile(file, 'utf8'), findings);
446
+ }
447
+ await collectSyncedBookkeepingInventoryFindings(repoRoot, safetyFiles, findings);
448
+ }
16
449
  function frontmatterHasKey(frontmatter, key) {
17
450
  const re = new RegExp(`^${key}:`, 'm');
18
451
  return re.test(frontmatter);
@@ -299,6 +732,7 @@ export async function validateOatSkills(repoRoot, options = {}, dependencies = {
299
732
  validateQuickStartSemantics(skillPath, content, findings);
300
733
  }
301
734
  }
735
+ await collectSyncedSafetyFindings(repoRoot, oatSkillDirs, findings);
302
736
  await collectGateabilityFindings(skillsRoot, options.gateSkillNames, findings);
303
737
  if (options.baseRef) {
304
738
  const changedSkillFiles = await listChangedSkillFiles(repoRoot, options.baseRef, dependencies);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.2.40",
3
+ "version": "0.2.45",
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",
@@ -34,7 +34,7 @@
34
34
  "ora": "^9.0.0",
35
35
  "yaml": "2.8.2",
36
36
  "zod": "^3.25.76",
37
- "@open-agent-toolkit/control-plane": "0.2.40"
37
+ "@open-agent-toolkit/control-plane": "0.2.45"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.10.0",
@@ -48,7 +48,7 @@
48
48
  "node": ">=22.17.0"
49
49
  },
50
50
  "scripts": {
51
- "build": "bash scripts/bundle-assets.sh && tsc && tsc-alias && chmod +x dist/index.js && touch dist/index.js",
51
+ "build": "bash scripts/bundle-assets.sh && { [ ! -d dist ] || find dist -type d -name __tests__ -prune -exec rm -rf {} +; } && tsc && tsc-alias && chmod +x dist/index.js && touch dist/index.js",
52
52
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
53
53
  "dev": "tsx watch src/index.ts",
54
54
  "check": "oxlint . && oxlint --type-aware -A all -D typescript/no-floating-promises -D typescript/no-misused-promises --ignore-pattern '**/*.test.ts' --ignore-pattern '**/*.spec.ts' . && oxfmt --check .",
@@ -60,6 +60,6 @@
60
60
  "test": "vitest run",
61
61
  "test:watch": "vitest",
62
62
  "test:coverage": "vitest run --coverage",
63
- "type-check": "tsc --noEmit"
63
+ "type-check": "tsc --noEmit && tsc -p tsconfig.test-support.json"
64
64
  }
65
65
  }
@@ -1,50 +0,0 @@
1
- import { type SplitChildInput, type SplitPayload, type SplitPlanDocument } from '../../projects/split/child-plan.js';
2
- import { type Signal } from '../../projects/split/signals.js';
3
- export type TranscriptSpeaker = 'assistant' | 'user';
4
- export interface TranscriptTurn {
5
- speaker: TranscriptSpeaker;
6
- text: string;
7
- }
8
- export type StubbedUserChoice = 'broad-first' | 'children-known' | 'decompose-children' | 'inline-only' | 'keep-one-project' | 'promote-n-projects' | 'split-now';
9
- export interface StubbedQuestion {
10
- id: string;
11
- prompt: string;
12
- options: string[];
13
- response: StubbedUserChoice;
14
- }
15
- export interface TranscriptFixture {
16
- transcript: TranscriptTurn[];
17
- parentSlug: string;
18
- children: SplitChildInput[];
19
- discoveryPath?: string;
20
- inheritedContext?: string;
21
- integrationSketch?: string;
22
- }
23
- export declare const SPLIT_HANDOFF_TARGET: {
24
- readonly skill: 'oat-project-split';
25
- readonly hookCreatesProjects: false;
26
- readonly responsibilities: readonly ['normalize-split-plan', 'write-coordination-parent', 'scaffold-children', 'activate-initial-child'];
27
- };
28
- export declare function inferSplitSignals(transcript: TranscriptTurn[]): Signal[];
29
- export declare function runDiscoverDetectionFixture(fixture: TranscriptFixture, responses: Record<string, StubbedUserChoice>): {
30
- asked: StubbedQuestion[];
31
- confidence: 'below' | 'high' | 'soft';
32
- decision?: StubbedUserChoice;
33
- document?: SplitPlanDocument;
34
- payload?: SplitPayload;
35
- triggered: boolean;
36
- };
37
- export declare function runDeclaredBrainstormFixture(fixture: TranscriptFixture, responses: Record<string, StubbedUserChoice>): {
38
- asked: StubbedQuestion[];
39
- document?: SplitPlanDocument;
40
- handoffTarget: typeof SPLIT_HANDOFF_TARGET;
41
- payload?: SplitPayload;
42
- };
43
- export declare function runBrainstormPickerFixture(fixture: TranscriptFixture, responses: Record<string, StubbedUserChoice>): {
44
- asked: StubbedQuestion[];
45
- document?: SplitPlanDocument;
46
- handoffTarget: typeof SPLIT_HANDOFF_TARGET;
47
- options: string[];
48
- payload?: SplitPayload;
49
- };
50
- //# sourceMappingURL=split-flow-fixtures.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"split-flow-fixtures.d.ts","sourceRoot":"","sources":["../../../src/__tests__/skills/split-flow-fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,gBAAgB,GAChB,oBAAoB,GACpB,aAAa,GACb,kBAAkB,GAClB,oBAAoB,GACpB,WAAW,CAAC;AAEhB,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,oBAAoB;aAC/B,KAAK,EAAE,mBAAmB;aAC1B,mBAAmB;aACnB,gBAAgB,YACd,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,EACnB,wBAAwB;CAElB,CAAC;AAsBX,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,EAAE,CA+BxE;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC3C;IACD,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACtC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB,CAqDA;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC3C;IACD,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,aAAa,EAAE,OAAO,oBAAoB,CAAC;IAC3C,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAmCA;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,iBAAiB,EAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC3C;IACD,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,aAAa,EAAE,OAAO,oBAAoB,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CA4CA"}