@united-workforce/cli 0.1.0

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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/adapter-json-roundtrip.test.js +147 -0
  6. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +2 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +685 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/current-role.test.d.ts +2 -0
  12. package/dist/__tests__/current-role.test.d.ts.map +1 -0
  13. package/dist/__tests__/current-role.test.js +401 -0
  14. package/dist/__tests__/current-role.test.js.map +1 -0
  15. package/dist/__tests__/e2e-mock-agent.test.d.ts +2 -0
  16. package/dist/__tests__/e2e-mock-agent.test.d.ts.map +1 -0
  17. package/dist/__tests__/e2e-mock-agent.test.js +401 -0
  18. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -0
  19. package/dist/__tests__/include-tag.test.d.ts +2 -0
  20. package/dist/__tests__/include-tag.test.d.ts.map +1 -0
  21. package/dist/__tests__/include-tag.test.js +69 -0
  22. package/dist/__tests__/include-tag.test.js.map +1 -0
  23. package/dist/__tests__/log.test.d.ts +2 -0
  24. package/dist/__tests__/log.test.d.ts.map +1 -0
  25. package/dist/__tests__/log.test.js +161 -0
  26. package/dist/__tests__/log.test.js.map +1 -0
  27. package/dist/__tests__/moderator-evaluate.test.d.ts +2 -0
  28. package/dist/__tests__/moderator-evaluate.test.d.ts.map +1 -0
  29. package/dist/__tests__/moderator-evaluate.test.js +170 -0
  30. package/dist/__tests__/moderator-evaluate.test.js.map +1 -0
  31. package/dist/__tests__/preload.d.ts +3 -0
  32. package/dist/__tests__/preload.d.ts.map +1 -0
  33. package/dist/__tests__/preload.js +6 -0
  34. package/dist/__tests__/preload.js.map +1 -0
  35. package/dist/__tests__/prompt.test.d.ts +2 -0
  36. package/dist/__tests__/prompt.test.d.ts.map +1 -0
  37. package/dist/__tests__/prompt.test.js +111 -0
  38. package/dist/__tests__/prompt.test.js.map +1 -0
  39. package/dist/__tests__/resolve-head-hash.test.d.ts +2 -0
  40. package/dist/__tests__/resolve-head-hash.test.d.ts.map +1 -0
  41. package/dist/__tests__/resolve-head-hash.test.js +66 -0
  42. package/dist/__tests__/resolve-head-hash.test.js.map +1 -0
  43. package/dist/__tests__/setup-agent-discovery.test.d.ts +2 -0
  44. package/dist/__tests__/setup-agent-discovery.test.d.ts.map +1 -0
  45. package/dist/__tests__/setup-agent-discovery.test.js +119 -0
  46. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -0
  47. package/dist/__tests__/setup-complexity.test.d.ts +2 -0
  48. package/dist/__tests__/setup-complexity.test.d.ts.map +1 -0
  49. package/dist/__tests__/setup-complexity.test.js +314 -0
  50. package/dist/__tests__/setup-complexity.test.js.map +1 -0
  51. package/dist/__tests__/setup-validate.test.d.ts +2 -0
  52. package/dist/__tests__/setup-validate.test.d.ts.map +1 -0
  53. package/dist/__tests__/setup-validate.test.js +108 -0
  54. package/dist/__tests__/setup-validate.test.js.map +1 -0
  55. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts +2 -0
  56. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts.map +1 -0
  57. package/dist/__tests__/solve-issue-tea-worktree.test.js +107 -0
  58. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -0
  59. package/dist/__tests__/spawn-agent-json.test.d.ts +2 -0
  60. package/dist/__tests__/spawn-agent-json.test.d.ts.map +1 -0
  61. package/dist/__tests__/spawn-agent-json.test.js +79 -0
  62. package/dist/__tests__/spawn-agent-json.test.js.map +1 -0
  63. package/dist/__tests__/step-read.test.d.ts +2 -0
  64. package/dist/__tests__/step-read.test.d.ts.map +1 -0
  65. package/dist/__tests__/step-read.test.js +561 -0
  66. package/dist/__tests__/step-read.test.js.map +1 -0
  67. package/dist/__tests__/step-show-json.test.d.ts +2 -0
  68. package/dist/__tests__/step-show-json.test.d.ts.map +1 -0
  69. package/dist/__tests__/step-show-json.test.js +311 -0
  70. package/dist/__tests__/step-show-json.test.js.map +1 -0
  71. package/dist/__tests__/step-timing.test.d.ts +2 -0
  72. package/dist/__tests__/step-timing.test.d.ts.map +1 -0
  73. package/dist/__tests__/step-timing.test.js +345 -0
  74. package/dist/__tests__/step-timing.test.js.map +1 -0
  75. package/dist/__tests__/store-global-cas.test.d.ts +2 -0
  76. package/dist/__tests__/store-global-cas.test.d.ts.map +1 -0
  77. package/dist/__tests__/store-global-cas.test.js +235 -0
  78. package/dist/__tests__/store-global-cas.test.js.map +1 -0
  79. package/dist/__tests__/store-storage-root.test.d.ts +2 -0
  80. package/dist/__tests__/store-storage-root.test.d.ts.map +1 -0
  81. package/dist/__tests__/store-storage-root.test.js +43 -0
  82. package/dist/__tests__/store-storage-root.test.js.map +1 -0
  83. package/dist/__tests__/store-unified-threads.test.d.ts +2 -0
  84. package/dist/__tests__/store-unified-threads.test.d.ts.map +1 -0
  85. package/dist/__tests__/store-unified-threads.test.js +189 -0
  86. package/dist/__tests__/store-unified-threads.test.js.map +1 -0
  87. package/dist/__tests__/thread-cancel-status.test.d.ts +2 -0
  88. package/dist/__tests__/thread-cancel-status.test.d.ts.map +1 -0
  89. package/dist/__tests__/thread-cancel-status.test.js +111 -0
  90. package/dist/__tests__/thread-cancel-status.test.js.map +1 -0
  91. package/dist/__tests__/thread-list-filters.test.d.ts +2 -0
  92. package/dist/__tests__/thread-list-filters.test.d.ts.map +1 -0
  93. package/dist/__tests__/thread-list-filters.test.js +442 -0
  94. package/dist/__tests__/thread-list-filters.test.js.map +1 -0
  95. package/dist/__tests__/thread-location.test.d.ts +2 -0
  96. package/dist/__tests__/thread-location.test.d.ts.map +1 -0
  97. package/dist/__tests__/thread-location.test.js +159 -0
  98. package/dist/__tests__/thread-location.test.js.map +1 -0
  99. package/dist/__tests__/thread-read-quota.test.d.ts +2 -0
  100. package/dist/__tests__/thread-read-quota.test.d.ts.map +1 -0
  101. package/dist/__tests__/thread-read-quota.test.js +546 -0
  102. package/dist/__tests__/thread-read-quota.test.js.map +1 -0
  103. package/dist/__tests__/thread-read-xml-tags.test.d.ts +2 -0
  104. package/dist/__tests__/thread-read-xml-tags.test.d.ts.map +1 -0
  105. package/dist/__tests__/thread-read-xml-tags.test.js +610 -0
  106. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -0
  107. package/dist/__tests__/thread-resume.test.d.ts +2 -0
  108. package/dist/__tests__/thread-resume.test.d.ts.map +1 -0
  109. package/dist/__tests__/thread-resume.test.js +592 -0
  110. package/dist/__tests__/thread-resume.test.js.map +1 -0
  111. package/dist/__tests__/thread-show-status.test.d.ts +2 -0
  112. package/dist/__tests__/thread-show-status.test.d.ts.map +1 -0
  113. package/dist/__tests__/thread-show-status.test.js +267 -0
  114. package/dist/__tests__/thread-show-status.test.js.map +1 -0
  115. package/dist/__tests__/thread-start-cwd-cli.test.d.ts +2 -0
  116. package/dist/__tests__/thread-start-cwd-cli.test.d.ts.map +1 -0
  117. package/dist/__tests__/thread-start-cwd-cli.test.js +130 -0
  118. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -0
  119. package/dist/__tests__/thread-step-count.test.d.ts +2 -0
  120. package/dist/__tests__/thread-step-count.test.d.ts.map +1 -0
  121. package/dist/__tests__/thread-step-count.test.js +55 -0
  122. package/dist/__tests__/thread-step-count.test.js.map +1 -0
  123. package/dist/__tests__/thread-suspend-step.test.d.ts +2 -0
  124. package/dist/__tests__/thread-suspend-step.test.d.ts.map +1 -0
  125. package/dist/__tests__/thread-suspend-step.test.js +155 -0
  126. package/dist/__tests__/thread-suspend-step.test.js.map +1 -0
  127. package/dist/__tests__/thread-suspended-display.test.d.ts +2 -0
  128. package/dist/__tests__/thread-suspended-display.test.d.ts.map +1 -0
  129. package/dist/__tests__/thread-suspended-display.test.js +247 -0
  130. package/dist/__tests__/thread-suspended-display.test.js.map +1 -0
  131. package/dist/__tests__/thread-test-helpers.d.ts +4 -0
  132. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -0
  133. package/dist/__tests__/thread-test-helpers.js +23 -0
  134. package/dist/__tests__/thread-test-helpers.js.map +1 -0
  135. package/dist/__tests__/thread.test.d.ts +2 -0
  136. package/dist/__tests__/thread.test.d.ts.map +1 -0
  137. package/dist/__tests__/thread.test.js +883 -0
  138. package/dist/__tests__/thread.test.js.map +1 -0
  139. package/dist/__tests__/validate-semantic.test.d.ts +2 -0
  140. package/dist/__tests__/validate-semantic.test.d.ts.map +1 -0
  141. package/dist/__tests__/validate-semantic.test.js +408 -0
  142. package/dist/__tests__/validate-semantic.test.js.map +1 -0
  143. package/dist/__tests__/workflow-resolution.test.d.ts +2 -0
  144. package/dist/__tests__/workflow-resolution.test.d.ts.map +1 -0
  145. package/dist/__tests__/workflow-resolution.test.js +308 -0
  146. package/dist/__tests__/workflow-resolution.test.js.map +1 -0
  147. package/dist/background/background.d.ts +38 -0
  148. package/dist/background/background.d.ts.map +1 -0
  149. package/dist/background/background.js +123 -0
  150. package/dist/background/background.js.map +1 -0
  151. package/dist/background/index.d.ts +3 -0
  152. package/dist/background/index.d.ts.map +1 -0
  153. package/dist/background/index.js +2 -0
  154. package/dist/background/index.js.map +1 -0
  155. package/dist/background/types.d.ts +9 -0
  156. package/dist/background/types.d.ts.map +1 -0
  157. package/dist/background/types.js +2 -0
  158. package/dist/background/types.js.map +1 -0
  159. package/dist/cli.d.ts +3 -0
  160. package/dist/cli.d.ts.map +1 -0
  161. package/dist/cli.js +535 -0
  162. package/dist/cli.js.map +1 -0
  163. package/dist/commands/config.d.ts +41 -0
  164. package/dist/commands/config.d.ts.map +1 -0
  165. package/dist/commands/config.js +252 -0
  166. package/dist/commands/config.js.map +1 -0
  167. package/dist/commands/log.d.ts +26 -0
  168. package/dist/commands/log.d.ts.map +1 -0
  169. package/dist/commands/log.js +79 -0
  170. package/dist/commands/log.js.map +1 -0
  171. package/dist/commands/prompt.d.ts +6 -0
  172. package/dist/commands/prompt.d.ts.map +1 -0
  173. package/dist/commands/prompt.js +67 -0
  174. package/dist/commands/prompt.js.map +1 -0
  175. package/dist/commands/setup.d.ts +73 -0
  176. package/dist/commands/setup.d.ts.map +1 -0
  177. package/dist/commands/setup.js +522 -0
  178. package/dist/commands/setup.js.map +1 -0
  179. package/dist/commands/shared.d.ts +31 -0
  180. package/dist/commands/shared.d.ts.map +1 -0
  181. package/dist/commands/shared.js +154 -0
  182. package/dist/commands/shared.js.map +1 -0
  183. package/dist/commands/step.d.ts +18 -0
  184. package/dist/commands/step.d.ts.map +1 -0
  185. package/dist/commands/step.js +257 -0
  186. package/dist/commands/step.js.map +1 -0
  187. package/dist/commands/thread-time-parser.d.ts +6 -0
  188. package/dist/commands/thread-time-parser.d.ts.map +1 -0
  189. package/dist/commands/thread-time-parser.js +22 -0
  190. package/dist/commands/thread-time-parser.js.map +1 -0
  191. package/dist/commands/thread.d.ts +38 -0
  192. package/dist/commands/thread.d.ts.map +1 -0
  193. package/dist/commands/thread.js +1087 -0
  194. package/dist/commands/thread.js.map +1 -0
  195. package/dist/commands/workflow.d.ts +24 -0
  196. package/dist/commands/workflow.d.ts.map +1 -0
  197. package/dist/commands/workflow.js +138 -0
  198. package/dist/commands/workflow.js.map +1 -0
  199. package/dist/format.d.ts +3 -0
  200. package/dist/format.d.ts.map +1 -0
  201. package/dist/format.js +10 -0
  202. package/dist/format.js.map +1 -0
  203. package/dist/include.d.ts +12 -0
  204. package/dist/include.d.ts.map +1 -0
  205. package/dist/include.js +35 -0
  206. package/dist/include.js.map +1 -0
  207. package/dist/moderator/__tests__/evaluate.test.d.ts +2 -0
  208. package/dist/moderator/__tests__/evaluate.test.d.ts.map +1 -0
  209. package/dist/moderator/__tests__/evaluate.test.js +167 -0
  210. package/dist/moderator/__tests__/evaluate.test.js.map +1 -0
  211. package/dist/moderator/evaluate.d.ts +6 -0
  212. package/dist/moderator/evaluate.d.ts.map +1 -0
  213. package/dist/moderator/evaluate.js +65 -0
  214. package/dist/moderator/evaluate.js.map +1 -0
  215. package/dist/moderator/index.d.ts +4 -0
  216. package/dist/moderator/index.d.ts.map +1 -0
  217. package/dist/moderator/index.js +3 -0
  218. package/dist/moderator/index.js.map +1 -0
  219. package/dist/moderator/types.d.ts +25 -0
  220. package/dist/moderator/types.d.ts.map +1 -0
  221. package/dist/moderator/types.js +4 -0
  222. package/dist/moderator/types.js.map +1 -0
  223. package/dist/schemas.d.ts +16 -0
  224. package/dist/schemas.d.ts.map +1 -0
  225. package/dist/schemas.js +17 -0
  226. package/dist/schemas.js.map +1 -0
  227. package/dist/store.d.ts +77 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +392 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/validate-semantic.d.ts +7 -0
  232. package/dist/validate-semantic.d.ts.map +1 -0
  233. package/dist/validate-semantic.js +263 -0
  234. package/dist/validate-semantic.js.map +1 -0
  235. package/dist/validate.d.ts +16 -0
  236. package/dist/validate.d.ts.map +1 -0
  237. package/dist/validate.js +115 -0
  238. package/dist/validate.js.map +1 -0
  239. package/package.json +44 -0
  240. package/src/__tests__/adapter-json-roundtrip.test.ts +181 -0
  241. package/src/__tests__/config.test.ts +740 -0
  242. package/src/__tests__/current-role.test.ts +438 -0
  243. package/src/__tests__/e2e-mock-agent.test.ts +498 -0
  244. package/src/__tests__/fixtures/e2e-completed-resume.mock.yaml +15 -0
  245. package/src/__tests__/fixtures/e2e-count.mock.yaml +19 -0
  246. package/src/__tests__/fixtures/e2e-count.workflow.yaml +45 -0
  247. package/src/__tests__/fixtures/e2e-linear.mock.yaml +13 -0
  248. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +32 -0
  249. package/src/__tests__/fixtures/e2e-loop.mock.yaml +25 -0
  250. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +36 -0
  251. package/src/__tests__/fixtures/e2e-mismatch.mock.yaml +16 -0
  252. package/src/__tests__/fixtures/e2e-mustache.mock.yaml +15 -0
  253. package/src/__tests__/fixtures/e2e-mustache.workflow.yaml +34 -0
  254. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +14 -0
  255. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +24 -0
  256. package/src/__tests__/include-tag.test.ts +84 -0
  257. package/src/__tests__/log.test.ts +181 -0
  258. package/src/__tests__/moderator-evaluate.test.ts +186 -0
  259. package/src/__tests__/preload.ts +7 -0
  260. package/src/__tests__/prompt.test.ts +129 -0
  261. package/src/__tests__/resolve-head-hash.test.ts +86 -0
  262. package/src/__tests__/setup-agent-discovery.test.ts +167 -0
  263. package/src/__tests__/setup-complexity.test.ts +381 -0
  264. package/src/__tests__/setup-validate.test.ts +148 -0
  265. package/src/__tests__/solve-issue-tea-worktree.test.ts +144 -0
  266. package/src/__tests__/spawn-agent-json.test.ts +100 -0
  267. package/src/__tests__/step-read.test.ts +632 -0
  268. package/src/__tests__/step-show-json.test.ts +373 -0
  269. package/src/__tests__/step-timing.test.ts +392 -0
  270. package/src/__tests__/store-global-cas.test.ts +308 -0
  271. package/src/__tests__/store-storage-root.test.ts +49 -0
  272. package/src/__tests__/store-unified-threads.test.ts +235 -0
  273. package/src/__tests__/thread-cancel-status.test.ts +138 -0
  274. package/src/__tests__/thread-list-filters.test.ts +572 -0
  275. package/src/__tests__/thread-location.test.ts +186 -0
  276. package/src/__tests__/thread-read-quota.test.ts +613 -0
  277. package/src/__tests__/thread-read-xml-tags.test.ts +717 -0
  278. package/src/__tests__/thread-resume.test.ts +710 -0
  279. package/src/__tests__/thread-show-status.test.ts +317 -0
  280. package/src/__tests__/thread-start-cwd-cli.test.ts +164 -0
  281. package/src/__tests__/thread-step-count.test.ts +70 -0
  282. package/src/__tests__/thread-suspend-step.test.ts +181 -0
  283. package/src/__tests__/thread-suspended-display.test.ts +287 -0
  284. package/src/__tests__/thread-test-helpers.ts +37 -0
  285. package/src/__tests__/thread.test.ts +1025 -0
  286. package/src/__tests__/validate-semantic.test.ts +474 -0
  287. package/src/__tests__/workflow-resolution.test.ts +421 -0
  288. package/src/background/background.ts +147 -0
  289. package/src/background/index.ts +11 -0
  290. package/src/background/types.ts +9 -0
  291. package/src/cli.ts +692 -0
  292. package/src/commands/config.ts +304 -0
  293. package/src/commands/log.ts +116 -0
  294. package/src/commands/prompt.ts +81 -0
  295. package/src/commands/setup.ts +603 -0
  296. package/src/commands/shared.ts +227 -0
  297. package/src/commands/step.ts +343 -0
  298. package/src/commands/thread-time-parser.ts +23 -0
  299. package/src/commands/thread.ts +1575 -0
  300. package/src/commands/workflow.ts +213 -0
  301. package/src/format.ts +12 -0
  302. package/src/include.ts +37 -0
  303. package/src/moderator/__tests__/evaluate.test.ts +199 -0
  304. package/src/moderator/evaluate.ts +80 -0
  305. package/src/moderator/index.ts +7 -0
  306. package/src/moderator/types.ts +24 -0
  307. package/src/schemas.ts +26 -0
  308. package/src/store.ts +479 -0
  309. package/src/validate-semantic.ts +304 -0
  310. package/src/validate.ts +137 -0
@@ -0,0 +1,263 @@
1
+ const RESERVED_NAMES = new Set(["$START", "$END", "$SUSPEND"]);
2
+ const PSEUDO_TARGETS = new Set(["$END", "$SUSPEND"]);
3
+ /** Extract mustache variable names from a prompt string. */
4
+ function extractMustacheVars(prompt) {
5
+ const vars = [];
6
+ const re = /\{\{\{?([^}]+)\}\}\}?/g;
7
+ let m = re.exec(prompt);
8
+ while (m !== null) {
9
+ vars.push(m[1]);
10
+ m = re.exec(prompt);
11
+ }
12
+ return vars;
13
+ }
14
+ /** Check if a frontmatter schema is a oneOf (multi-exit) type. */
15
+ function isOneOfSchema(fm) {
16
+ if (typeof fm !== "object" || fm === null)
17
+ return false;
18
+ const obj = fm;
19
+ return Array.isArray(obj.oneOf);
20
+ }
21
+ /** Check if a frontmatter schema declares "$status" as an enum (the required form for user roles). */
22
+ function hasStatusEnum(fm) {
23
+ if (typeof fm !== "object" || fm === null)
24
+ return false;
25
+ const obj = fm;
26
+ const props = obj.properties;
27
+ if (!props?.$status)
28
+ return false;
29
+ return Array.isArray(props.$status.enum);
30
+ }
31
+ /** Extract status values from an enum-based $status field. */
32
+ function getEnumStatuses(fm) {
33
+ const props = fm.properties;
34
+ if (!props?.$status)
35
+ return [];
36
+ const statusDef = props.$status;
37
+ if (!Array.isArray(statusDef.enum))
38
+ return [];
39
+ return statusDef.enum;
40
+ }
41
+ /** Get property names from a schema object. */
42
+ function getPropertyNames(schema) {
43
+ const props = schema.properties;
44
+ if (typeof props !== "object" || props === null)
45
+ return new Set();
46
+ return new Set(Object.keys(props));
47
+ }
48
+ /** Extract $status const values from oneOf variants. */
49
+ function getOneOfStatuses(variants) {
50
+ const statuses = [];
51
+ for (const variant of variants) {
52
+ const props = variant.properties;
53
+ if (props?.$status) {
54
+ const statusDef = props.$status;
55
+ if (typeof statusDef.const === "string") {
56
+ statuses.push(statusDef.const);
57
+ }
58
+ }
59
+ }
60
+ return statuses;
61
+ }
62
+ /** Check reserved names and role/graph reference integrity. */
63
+ function checkRoleReferences(payload, errors) {
64
+ const roleNames = new Set(Object.keys(payload.roles));
65
+ const graphNodes = new Set(Object.keys(payload.graph));
66
+ for (const name of roleNames) {
67
+ if (RESERVED_NAMES.has(name)) {
68
+ errors.push(`reserved name "${name}" must not appear in roles`);
69
+ }
70
+ }
71
+ for (const node of graphNodes) {
72
+ if (!RESERVED_NAMES.has(node) && !roleNames.has(node)) {
73
+ errors.push(`graph references unknown role "${node}"`);
74
+ }
75
+ }
76
+ for (const name of roleNames) {
77
+ if (RESERVED_NAMES.has(name))
78
+ continue;
79
+ if (!graphNodes.has(name)) {
80
+ errors.push(`role "${name}" is defined but not referenced in graph`);
81
+ }
82
+ }
83
+ }
84
+ /** Check $START/$END constraints, edge targets, and reachability. */
85
+ function checkGraphStructure(payload, errors) {
86
+ const roleNames = new Set(Object.keys(payload.roles));
87
+ const graphNodes = new Set(Object.keys(payload.graph));
88
+ if (!graphNodes.has("$START")) {
89
+ errors.push("$START must be defined in graph");
90
+ }
91
+ else {
92
+ const startKeys = Object.keys(payload.graph.$START);
93
+ if (startKeys.length !== 1 || startKeys[0] !== "_") {
94
+ errors.push('$START must have exactly one edge with status "_"');
95
+ }
96
+ }
97
+ if (graphNodes.has("$END")) {
98
+ errors.push("$END must not have outgoing edges");
99
+ }
100
+ if (graphNodes.has("$SUSPEND")) {
101
+ errors.push("$SUSPEND must not have outgoing edges");
102
+ }
103
+ for (const [node, statusMap] of Object.entries(payload.graph)) {
104
+ for (const [status, target] of Object.entries(statusMap)) {
105
+ if (!PSEUDO_TARGETS.has(target.role) && !roleNames.has(target.role)) {
106
+ errors.push(`edge ${node}→${status}: unknown target role "${target.role}"`);
107
+ }
108
+ }
109
+ }
110
+ checkReachability(roleNames, collectReachableRoles(payload.graph), errors);
111
+ }
112
+ /** BFS to collect all roles reachable from $START. */
113
+ function collectReachableRoles(graph) {
114
+ const reachable = new Set();
115
+ const startEdges = graph.$START;
116
+ if (!startEdges)
117
+ return reachable;
118
+ const queue = [];
119
+ for (const target of Object.values(startEdges)) {
120
+ if (!PSEUDO_TARGETS.has(target.role) && !reachable.has(target.role)) {
121
+ reachable.add(target.role);
122
+ queue.push(target.role);
123
+ }
124
+ }
125
+ while (queue.length > 0) {
126
+ const current = queue.shift();
127
+ const edges = graph[current];
128
+ if (!edges)
129
+ continue;
130
+ for (const target of Object.values(edges)) {
131
+ if (!PSEUDO_TARGETS.has(target.role) && !reachable.has(target.role)) {
132
+ reachable.add(target.role);
133
+ queue.push(target.role);
134
+ }
135
+ }
136
+ }
137
+ return reachable;
138
+ }
139
+ /** Check that all defined roles are reachable from $START. */
140
+ function checkReachability(roleNames, reachable, errors) {
141
+ for (const name of roleNames) {
142
+ if (RESERVED_NAMES.has(name))
143
+ continue;
144
+ if (!reachable.has(name)) {
145
+ errors.push(`role "${name}" is not reachable from $START`);
146
+ }
147
+ }
148
+ }
149
+ /** Check oneOf discriminant validity for a role. */
150
+ function checkOneOfDiscriminant(roleName, variants, statuses, errors) {
151
+ if (statuses.length === variants.length)
152
+ return;
153
+ let foundMissing = false;
154
+ for (const variant of variants) {
155
+ const props = variant.properties;
156
+ if (!props?.$status) {
157
+ errors.push(`role "${roleName}": oneOf variants must have "$status" as const discriminant`);
158
+ foundMissing = true;
159
+ break;
160
+ }
161
+ if (typeof props.$status.const !== "string") {
162
+ errors.push(`role "${roleName}": oneOf variant $status must be a const value`);
163
+ foundMissing = true;
164
+ break;
165
+ }
166
+ }
167
+ if (!foundMissing) {
168
+ errors.push(`role "${roleName}": oneOf variant $status must be a const value`);
169
+ }
170
+ }
171
+ /** Check status-edge consistency for a user role. "_" is reserved for $START and rejected here. */
172
+ function checkStatusEdges(roleName, graphKeys, statusSet, errors) {
173
+ if (graphKeys.has("_")) {
174
+ errors.push(`role "${roleName}" must use explicit $status keys in graph, not "_"`);
175
+ return;
176
+ }
177
+ if (statusSet.has("_")) {
178
+ errors.push(`role "${roleName}" $status enum must use explicit values, not "_"`);
179
+ return;
180
+ }
181
+ const extraKeys = [...graphKeys].filter((k) => !statusSet.has(k));
182
+ const missingKeys = [...statusSet].filter((k) => !graphKeys.has(k));
183
+ if (extraKeys.length > 0) {
184
+ errors.push(`role "${roleName}" graph has extra status keys: ${extraKeys.join(", ")}`);
185
+ }
186
+ if (missingKeys.length > 0) {
187
+ errors.push(`role "${roleName}" graph is missing status keys: ${missingKeys.join(", ")}`);
188
+ }
189
+ }
190
+ /** Check mustache variables for multi-exit role. */
191
+ function checkMultiExitMustache(roleName, graphEntry, variants, errors) {
192
+ for (const [status, target] of Object.entries(graphEntry)) {
193
+ const vars = extractMustacheVars(target.prompt);
194
+ const variant = variants.find((v) => {
195
+ const props = v.properties;
196
+ return props?.$status?.const === status;
197
+ });
198
+ if (!variant)
199
+ continue;
200
+ const propNames = getPropertyNames(variant);
201
+ for (const v of vars) {
202
+ if (v === "$status")
203
+ continue;
204
+ if (!propNames.has(v)) {
205
+ errors.push(`prompt variable "${v}" not found in role "${roleName}" variant "${status}"`);
206
+ }
207
+ }
208
+ }
209
+ }
210
+ /** Check status-edge consistency and mustache for each role. */
211
+ function checkRoleConsistency(payload, errors) {
212
+ for (const [roleName, role] of Object.entries(payload.roles)) {
213
+ if (RESERVED_NAMES.has(roleName))
214
+ continue;
215
+ const graphEntry = payload.graph[roleName];
216
+ if (!graphEntry)
217
+ continue;
218
+ const fm = role.frontmatter;
219
+ const graphKeys = new Set(Object.keys(graphEntry));
220
+ if (isOneOfSchema(fm)) {
221
+ const variants = fm.oneOf;
222
+ const statuses = getOneOfStatuses(variants);
223
+ checkOneOfDiscriminant(roleName, variants, statuses, errors);
224
+ checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
225
+ checkMultiExitMustache(roleName, graphEntry, variants, errors);
226
+ }
227
+ else if (hasStatusEnum(fm)) {
228
+ const statuses = getEnumStatuses(fm);
229
+ checkStatusEdges(roleName, graphKeys, new Set(statuses), errors);
230
+ // For enum-based schemas, mustache vars come from the flat properties
231
+ checkEnumMustache(roleName, graphEntry, fm, errors);
232
+ }
233
+ else {
234
+ errors.push(`role "${roleName}" must define "$status" as an enum (or oneOf const) in frontmatter`);
235
+ }
236
+ }
237
+ }
238
+ /** Check mustache vars in all edge prompts against flat schema properties. */
239
+ function checkEnumMustache(roleName, graphEntry, fm, errors) {
240
+ const propNames = getPropertyNames(fm);
241
+ for (const [status, target] of Object.entries(graphEntry)) {
242
+ const vars = extractMustacheVars(target.prompt);
243
+ for (const v of vars) {
244
+ if (v === "$status")
245
+ continue;
246
+ if (!propNames.has(v)) {
247
+ errors.push(`prompt variable "${v}" in graph[${roleName}][${status}] not found in role "${roleName}" frontmatter`);
248
+ }
249
+ }
250
+ }
251
+ }
252
+ /**
253
+ * Validate a parsed WorkflowPayload for semantic correctness.
254
+ * Returns an array of error messages. Empty array = valid.
255
+ */
256
+ export function validateWorkflow(payload) {
257
+ const errors = [];
258
+ checkRoleReferences(payload, errors);
259
+ checkGraphStructure(payload, errors);
260
+ checkRoleConsistency(payload, errors);
261
+ return errors;
262
+ }
263
+ //# sourceMappingURL=validate-semantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-semantic.js","sourceRoot":"","sources":["../src/validate-semantic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { CasRef, WorkflowPayload } from "@united-workforce/protocol";
2
+ export declare function isCasRef(value: string): value is CasRef;
3
+ /**
4
+ * Derive the expected workflow name from a file path (stem without extension).
5
+ * Returns the stem for `.yaml` / `.yml` files.
6
+ */
7
+ export declare function workflowNameFromPath(filePath: string): string;
8
+ /**
9
+ * Check that the `name` field in a parsed payload matches the expected name
10
+ * derived from the file path. Returns an error message string on mismatch,
11
+ * or null when the names are consistent.
12
+ */
13
+ export declare function checkWorkflowFilenameConsistency(filePath: string, payload: WorkflowPayload): string | null;
14
+ /** Validate YAML-parsed workflow document shape (outputSchema may be inline JSON Schema). */
15
+ export declare function parseWorkflowPayload(raw: unknown): WorkflowPayload | null;
16
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAI1E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,MAAM,CAEvD;AAkED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAMf;AAED,6FAA6F;AAE7F,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CA4BzE"}
@@ -0,0 +1,115 @@
1
+ import { basename, dirname } from "node:path";
2
+ const CAS_REF_PATTERN = /^[0-9A-HJKMNP-TV-Z]{13}$/;
3
+ export function isCasRef(value) {
4
+ return CAS_REF_PATTERN.test(value);
5
+ }
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+ function isRoleDefinition(value) {
10
+ if (!isRecord(value)) {
11
+ return false;
12
+ }
13
+ const frontmatter = value.frontmatter;
14
+ const frontmatterOk = isRecord(frontmatter) &&
15
+ (typeof frontmatter.type === "string" || Array.isArray(frontmatter.oneOf));
16
+ const capabilities = value.capabilities;
17
+ const capabilitiesOk = Array.isArray(capabilities) && capabilities.every((c) => typeof c === "string");
18
+ return (typeof value.description === "string" &&
19
+ typeof value.goal === "string" &&
20
+ capabilitiesOk &&
21
+ typeof value.procedure === "string" &&
22
+ typeof value.output === "string" &&
23
+ frontmatterOk);
24
+ }
25
+ function isTarget(value) {
26
+ if (!isRecord(value)) {
27
+ return false;
28
+ }
29
+ const hasValidLocation = value.location === undefined || value.location === null || typeof value.location === "string";
30
+ return (typeof value.role === "string" &&
31
+ typeof value.prompt === "string" &&
32
+ value.prompt.trim() !== "" &&
33
+ hasValidLocation);
34
+ }
35
+ function isStringRecord(value, itemCheck) {
36
+ if (!isRecord(value)) {
37
+ return false;
38
+ }
39
+ return Object.values(value).every(itemCheck);
40
+ }
41
+ function isGraph(value) {
42
+ if (!isRecord(value)) {
43
+ return false;
44
+ }
45
+ return Object.entries(value).every(([node, statusMap]) => {
46
+ if (!isRecord(statusMap)) {
47
+ return false;
48
+ }
49
+ return Object.entries(statusMap).every(([status, target]) => {
50
+ // "_" is only valid as a status key for the $START entry node.
51
+ if (status === "_" && node !== "$START") {
52
+ return false;
53
+ }
54
+ return isTarget(target);
55
+ });
56
+ });
57
+ }
58
+ /**
59
+ * Derive the expected workflow name from a file path (stem without extension).
60
+ * Returns the stem for `.yaml` / `.yml` files.
61
+ */
62
+ export function workflowNameFromPath(filePath) {
63
+ const base = basename(filePath);
64
+ const stem = base.endsWith(".yaml")
65
+ ? base.slice(0, -5)
66
+ : base.endsWith(".yml")
67
+ ? base.slice(0, -4)
68
+ : base;
69
+ if (stem === "index") {
70
+ return basename(dirname(filePath));
71
+ }
72
+ return stem;
73
+ }
74
+ /**
75
+ * Check that the `name` field in a parsed payload matches the expected name
76
+ * derived from the file path. Returns an error message string on mismatch,
77
+ * or null when the names are consistent.
78
+ */
79
+ export function checkWorkflowFilenameConsistency(filePath, payload) {
80
+ const expected = workflowNameFromPath(filePath);
81
+ if (payload.name !== expected) {
82
+ return `workflow name mismatch: file "${basename(filePath)}" implies name "${expected}" but YAML declares name "${payload.name}"`;
83
+ }
84
+ return null;
85
+ }
86
+ /** Validate YAML-parsed workflow document shape (outputSchema may be inline JSON Schema). */
87
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: validation function with many field checks
88
+ export function parseWorkflowPayload(raw) {
89
+ if (!isRecord(raw)) {
90
+ return null;
91
+ }
92
+ if (typeof raw.name !== "string" || typeof raw.description !== "string") {
93
+ return null;
94
+ }
95
+ if (!isStringRecord(raw.roles, isRoleDefinition) || !isGraph(raw.graph)) {
96
+ return null;
97
+ }
98
+ // Normalize location field: undefined → null
99
+ const normalized = { ...raw };
100
+ for (const roleName of Object.keys(normalized.graph)) {
101
+ const statusMap = normalized.graph[roleName];
102
+ if (statusMap !== undefined) {
103
+ for (const status of Object.keys(statusMap)) {
104
+ const target = statusMap[status];
105
+ if (target !== undefined) {
106
+ if (target.location === undefined) {
107
+ target.location = null;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ return normalized;
114
+ }
115
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@united-workforce/cli",
3
+ "version": "0.1.0",
4
+ "files": [
5
+ "src",
6
+ "dist",
7
+ "package.json"
8
+ ],
9
+ "type": "module",
10
+ "bin": {
11
+ "uwf": "./dist/cli.js"
12
+ },
13
+ "dependencies": {
14
+ "@ocas/core": "^0.3.0",
15
+ "@ocas/fs": "^0.3.0",
16
+ "commander": "^14.0.3",
17
+ "dotenv": "^16.6.1",
18
+ "mustache": "^4.2.0",
19
+ "yaml": "^2.8.4",
20
+ "@united-workforce/util-agent": "^0.1.0",
21
+ "@united-workforce/protocol": "^0.1.0",
22
+ "@united-workforce/util": "^0.1.0"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "devDependencies": {
28
+ "@types/mustache": "^4.2.6"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://git.shazhou.work/shazhou/united-workforce.git",
33
+ "directory": "packages/cli"
34
+ },
35
+ "homepage": "https://git.shazhou.work/shazhou/united-workforce#readme",
36
+ "bugs": {
37
+ "url": "https://git.shazhou.work/shazhou/united-workforce/issues"
38
+ },
39
+ "license": "MIT",
40
+ "scripts": {
41
+ "test": "vitest run src/",
42
+ "test:ci": "vitest run src/"
43
+ }
44
+ }
@@ -0,0 +1,181 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { putSchema } from "@ocas/core";
7
+ import { openStore } from "@ocas/fs";
8
+ import type { CasRef, StepNodePayload, ThreadId } from "@united-workforce/protocol";
9
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
10
+ import { registerUwfSchemas } from "../schemas.js";
11
+ import { seedThreads } from "./thread-test-helpers.js";
12
+
13
+ // ── schemas ──────────────────────────────────────────────────────────────────
14
+
15
+ const OUTPUT_SCHEMA = {
16
+ type: "object" as const,
17
+ properties: {
18
+ $status: { type: "string" as const, enum: ["done", "failed"] },
19
+ result: { type: "string" as const },
20
+ },
21
+ required: ["$status"],
22
+ additionalProperties: false,
23
+ };
24
+
25
+ // ── fixture ──────────────────────────────────────────────────────────────────
26
+
27
+ let tmpDir: string;
28
+
29
+ beforeEach(async () => {
30
+ tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-roundtrip-test-"));
31
+ });
32
+
33
+ afterEach(async () => {
34
+ await rm(tmpDir, { recursive: true, force: true });
35
+ });
36
+
37
+ describe("C1: adapter JSON round-trip integration", () => {
38
+ test("mock agent outputs JSON, CLI parses it and updates thread head in CAS", async () => {
39
+ // 1. Set up CAS store with workflow, start node, and output schema
40
+ const casDir = join(tmpDir, "cas");
41
+ await mkdir(casDir, { recursive: true });
42
+ const store = await openStore(casDir);
43
+ const schemas = await registerUwfSchemas(store);
44
+
45
+ const outputSchemaHash = await putSchema(store, OUTPUT_SCHEMA);
46
+
47
+ const workflowHash = await store.cas.put(schemas.workflow, {
48
+ name: "test-roundtrip",
49
+ description: "roundtrip integration test",
50
+ roles: {
51
+ worker: {
52
+ description: "Worker role",
53
+ goal: "Do work",
54
+ capabilities: [],
55
+ procedure: "work",
56
+ output: "result",
57
+ frontmatter: outputSchemaHash,
58
+ },
59
+ },
60
+ graph: {
61
+ $START: { _: { role: "worker", prompt: "Do the work", location: null } },
62
+ worker: { done: { role: "$END", prompt: "completed", location: null } },
63
+ },
64
+ });
65
+
66
+ const startHash = await store.cas.put(schemas.startNode, {
67
+ workflow: workflowHash,
68
+ prompt: "Test round-trip task",
69
+ });
70
+
71
+ process.env.OCAS_HOME = casDir;
72
+
73
+ const threadId = "01ROUNDTRIPTEST0000000000" as ThreadId;
74
+ await seedThreads(tmpDir, { [threadId]: startHash });
75
+
76
+ // 2. Pre-create CAS nodes that the mock agent would produce
77
+ const outputHash = await store.cas.put(outputSchemaHash, {
78
+ $status: "done",
79
+ result: "test-ok",
80
+ });
81
+
82
+ // Use text schema for detail (simple placeholder)
83
+ const detailHash = await store.cas.put(schemas.text, "mock detail");
84
+
85
+ const startedAtMs = 1716600000000;
86
+ const completedAtMs = 1716600001500;
87
+
88
+ const stepHash = await store.cas.put(schemas.stepNode, {
89
+ start: startHash,
90
+ prev: null,
91
+ role: "worker",
92
+ output: outputHash,
93
+ detail: detailHash,
94
+ agent: "uwf-mock",
95
+ edgePrompt: "Do the work",
96
+ startedAtMs,
97
+ completedAtMs,
98
+ cwd: tmpDir,
99
+ });
100
+
101
+ // 3. Create a minimal mock agent shell script that just outputs JSON
102
+ // The step node is already in CAS — the agent just needs to print the JSON line
103
+ const mockAgentPath = join(tmpDir, "mock-agent.sh");
104
+ const adapterJson = JSON.stringify({
105
+ stepHash,
106
+ detailHash,
107
+ role: "worker",
108
+ frontmatter: { $status: "done", result: "test-ok" },
109
+ body: "",
110
+ startedAtMs,
111
+ completedAtMs,
112
+ });
113
+ await writeFile(mockAgentPath, `#!/bin/sh\necho '${adapterJson}'\n`, { mode: 0o755 });
114
+
115
+ // 4. Write config.yaml
116
+ const configPath = join(tmpDir, "config.yaml");
117
+ await writeFile(
118
+ configPath,
119
+ `defaultAgent: uwf-hermes\ndefaultModel: test-model\nagentOverrides: null\nagents: {}\nproviders: {}\nmodels: {}\n`,
120
+ );
121
+
122
+ // 5. Run CLI with agent override pointing to our mock
123
+ const cliPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "dist", "cli.js");
124
+ let stdout: string;
125
+ let stderr: string;
126
+ let exitCode: number;
127
+
128
+ try {
129
+ stdout = execFileSync(
130
+ process.execPath,
131
+ [cliPath, "thread", "exec", threadId, "--agent", mockAgentPath],
132
+ {
133
+ encoding: "utf8",
134
+ stdio: ["ignore", "pipe", "pipe"],
135
+ env: {
136
+ ...process.env,
137
+ UWF_HOME: tmpDir,
138
+ OCAS_HOME: casDir,
139
+ },
140
+ cwd: tmpDir,
141
+ timeout: 30000,
142
+ },
143
+ );
144
+ stderr = "";
145
+ exitCode = 0;
146
+ } catch (e: unknown) {
147
+ const err = e as NodeJS.ErrnoException & {
148
+ stdout?: string;
149
+ stderr?: string;
150
+ status?: number;
151
+ };
152
+ stdout = err.stdout ?? "";
153
+ stderr = err.stderr ?? "";
154
+ exitCode = err.status ?? 1;
155
+ }
156
+
157
+ // 6. Verify
158
+ if (exitCode !== 0) {
159
+ throw new Error(`CLI exited with code ${exitCode}\nstdout: ${stdout}\nstderr: ${stderr}`);
160
+ }
161
+
162
+ // Parse CLI output
163
+ const cliOutput = JSON.parse(stdout.trim());
164
+ expect(cliOutput).toHaveProperty("thread", threadId);
165
+ expect(cliOutput).toHaveProperty("head", stepHash);
166
+ expect(cliOutput.head).toMatch(/^[0-9A-HJ-NP-TV-Z]{13}$/);
167
+
168
+ // Verify the CAS step node exists and has correct metadata
169
+ const storeAfter = await openStore(casDir);
170
+ const stepNode = storeAfter.cas.get(cliOutput.head as CasRef);
171
+ expect(stepNode).not.toBeNull();
172
+
173
+ const payload = stepNode!.payload as StepNodePayload;
174
+ expect(payload.role).toBe("worker");
175
+ expect(payload.agent).toBe("uwf-mock");
176
+ expect(payload.startedAtMs).toBe(1716600000000);
177
+ expect(payload.completedAtMs).toBe(1716600001500);
178
+ expect(payload.output).toBe(outputHash);
179
+ expect(payload.detail).toBe(detailHash);
180
+ });
181
+ });