@united-workforce/cli 0.3.0 → 0.5.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 (319) hide show
  1. package/README.md +45 -11
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.js +17 -7
  4. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
  5. package/dist/__tests__/agent-resolution-llm-free.test.d.ts +2 -0
  6. package/dist/__tests__/agent-resolution-llm-free.test.d.ts.map +1 -0
  7. package/dist/__tests__/agent-resolution-llm-free.test.js +30 -0
  8. package/dist/__tests__/agent-resolution-llm-free.test.js.map +1 -0
  9. package/dist/__tests__/build-step-entry.test.d.ts +2 -0
  10. package/dist/__tests__/build-step-entry.test.d.ts.map +1 -0
  11. package/dist/__tests__/build-step-entry.test.js +173 -0
  12. package/dist/__tests__/build-step-entry.test.js.map +1 -0
  13. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts +2 -0
  14. package/dist/__tests__/clear-thread-failed-attempts.test.d.ts.map +1 -0
  15. package/dist/__tests__/clear-thread-failed-attempts.test.js +93 -0
  16. package/dist/__tests__/clear-thread-failed-attempts.test.js.map +1 -0
  17. package/dist/__tests__/concurrency.test.d.ts +2 -0
  18. package/dist/__tests__/concurrency.test.d.ts.map +1 -0
  19. package/dist/__tests__/concurrency.test.js +196 -0
  20. package/dist/__tests__/concurrency.test.js.map +1 -0
  21. package/dist/__tests__/config.test.js +26 -302
  22. package/dist/__tests__/config.test.js.map +1 -1
  23. package/dist/__tests__/current-role.test.js +7 -6
  24. package/dist/__tests__/current-role.test.js.map +1 -1
  25. package/dist/__tests__/e2e-mock-agent.test.js +43 -30
  26. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  27. package/dist/__tests__/format-text-default.test.d.ts +2 -0
  28. package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
  29. package/dist/__tests__/format-text-default.test.js +43 -0
  30. package/dist/__tests__/format-text-default.test.js.map +1 -0
  31. package/dist/__tests__/format-text-registry.test.d.ts +2 -0
  32. package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
  33. package/dist/__tests__/format-text-registry.test.js +158 -0
  34. package/dist/__tests__/format-text-registry.test.js.map +1 -0
  35. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts +2 -0
  36. package/dist/__tests__/issue-180-workflow-ref-removed.test.d.ts.map +1 -0
  37. package/dist/__tests__/issue-180-workflow-ref-removed.test.js +40 -0
  38. package/dist/__tests__/issue-180-workflow-ref-removed.test.js.map +1 -0
  39. package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
  40. package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
  41. package/dist/__tests__/log-text-renderer.test.js +265 -0
  42. package/dist/__tests__/log-text-renderer.test.js.map +1 -0
  43. package/dist/__tests__/moderator-evaluate.test.js +9 -50
  44. package/dist/__tests__/moderator-evaluate.test.js.map +1 -1
  45. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
  46. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
  47. package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
  48. package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
  49. package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
  50. package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
  51. package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
  52. package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
  53. package/dist/__tests__/pid-recycling.test.d.ts +2 -0
  54. package/dist/__tests__/pid-recycling.test.d.ts.map +1 -0
  55. package/dist/__tests__/pid-recycling.test.js +273 -0
  56. package/dist/__tests__/pid-recycling.test.js.map +1 -0
  57. package/dist/__tests__/prompt.test.js +365 -2
  58. package/dist/__tests__/prompt.test.js.map +1 -1
  59. package/dist/__tests__/resolve-head-hash.test.js +12 -4
  60. package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
  61. package/dist/__tests__/setup-agent-discovery.test.js +21 -30
  62. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -1
  63. package/dist/__tests__/setup-complexity.test.js +2 -168
  64. package/dist/__tests__/setup-complexity.test.js.map +1 -1
  65. package/dist/__tests__/setup-no-llm.test.d.ts +2 -0
  66. package/dist/__tests__/setup-no-llm.test.d.ts.map +1 -0
  67. package/dist/__tests__/setup-no-llm.test.js +52 -0
  68. package/dist/__tests__/setup-no-llm.test.js.map +1 -0
  69. package/dist/__tests__/solve-issue-tea-worktree.test.js +27 -28
  70. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
  71. package/dist/__tests__/step-ask.test.d.ts +2 -0
  72. package/dist/__tests__/step-ask.test.d.ts.map +1 -0
  73. package/dist/__tests__/step-ask.test.js +507 -0
  74. package/dist/__tests__/step-ask.test.js.map +1 -0
  75. package/dist/__tests__/step-show-json.test.js +1 -0
  76. package/dist/__tests__/step-show-json.test.js.map +1 -1
  77. package/dist/__tests__/step-timing.test.js +2 -0
  78. package/dist/__tests__/step-timing.test.js.map +1 -1
  79. package/dist/__tests__/store-global-cas.test.js +2 -2
  80. package/dist/__tests__/store-global-cas.test.js.map +1 -1
  81. package/dist/__tests__/store-unified-threads.test.js +28 -26
  82. package/dist/__tests__/store-unified-threads.test.js.map +1 -1
  83. package/dist/__tests__/thread-cancel-status.test.js +25 -19
  84. package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
  85. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
  86. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
  87. package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
  88. package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
  89. package/dist/__tests__/thread-list-filters.test.js +354 -17
  90. package/dist/__tests__/thread-list-filters.test.js.map +1 -1
  91. package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
  92. package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
  93. package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
  94. package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
  95. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
  96. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
  97. package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
  98. package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
  99. package/dist/__tests__/thread-poke.test.d.ts +2 -0
  100. package/dist/__tests__/thread-poke.test.d.ts.map +1 -0
  101. package/dist/__tests__/thread-poke.test.js +422 -0
  102. package/dist/__tests__/thread-poke.test.js.map +1 -0
  103. package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
  104. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
  105. package/dist/__tests__/thread-resume.test.js +21 -15
  106. package/dist/__tests__/thread-resume.test.js.map +1 -1
  107. package/dist/__tests__/thread-show-status.test.js +17 -28
  108. package/dist/__tests__/thread-show-status.test.js.map +1 -1
  109. package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
  110. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
  111. package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
  112. package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
  113. package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
  114. package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
  115. package/dist/__tests__/thread-suspend-step.test.js +13 -16
  116. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  117. package/dist/__tests__/thread-suspended-display.test.js +10 -22
  118. package/dist/__tests__/thread-suspended-display.test.js.map +1 -1
  119. package/dist/__tests__/thread-test-helpers.d.ts +7 -0
  120. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
  121. package/dist/__tests__/thread-test-helpers.js +13 -0
  122. package/dist/__tests__/thread-test-helpers.js.map +1 -1
  123. package/dist/__tests__/thread.test.js +15 -13
  124. package/dist/__tests__/thread.test.js.map +1 -1
  125. package/dist/__tests__/validate-semantic.test.js +105 -23
  126. package/dist/__tests__/validate-semantic.test.js.map +1 -1
  127. package/dist/__tests__/workflow-list-recursive.test.d.ts +2 -0
  128. package/dist/__tests__/workflow-list-recursive.test.d.ts.map +1 -0
  129. package/dist/__tests__/workflow-list-recursive.test.js +286 -0
  130. package/dist/__tests__/workflow-list-recursive.test.js.map +1 -0
  131. package/dist/__tests__/workflow-resolution.test.js +46 -28
  132. package/dist/__tests__/workflow-resolution.test.js.map +1 -1
  133. package/dist/__tests__/workflow-show-resolution.test.d.ts +2 -0
  134. package/dist/__tests__/workflow-show-resolution.test.d.ts.map +1 -0
  135. package/dist/__tests__/workflow-show-resolution.test.js +213 -0
  136. package/dist/__tests__/workflow-show-resolution.test.js.map +1 -0
  137. package/dist/__tests__/workflow-validate.test.d.ts +2 -0
  138. package/dist/__tests__/workflow-validate.test.d.ts.map +1 -0
  139. package/dist/__tests__/workflow-validate.test.js +707 -0
  140. package/dist/__tests__/workflow-validate.test.js.map +1 -0
  141. package/dist/__tests__/write-envelope.test.d.ts +2 -0
  142. package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
  143. package/dist/__tests__/write-envelope.test.js +201 -0
  144. package/dist/__tests__/write-envelope.test.js.map +1 -0
  145. package/dist/background/background.d.ts +22 -1
  146. package/dist/background/background.d.ts.map +1 -1
  147. package/dist/background/background.js +83 -6
  148. package/dist/background/background.js.map +1 -1
  149. package/dist/background/index.d.ts +1 -1
  150. package/dist/background/index.d.ts.map +1 -1
  151. package/dist/background/index.js +1 -1
  152. package/dist/background/index.js.map +1 -1
  153. package/dist/background/types.d.ts +1 -0
  154. package/dist/background/types.d.ts.map +1 -1
  155. package/dist/cli.js +120 -62
  156. package/dist/cli.js.map +1 -1
  157. package/dist/commands/config.d.ts +3 -1
  158. package/dist/commands/config.d.ts.map +1 -1
  159. package/dist/commands/config.js +17 -31
  160. package/dist/commands/config.js.map +1 -1
  161. package/dist/commands/prompt.d.ts.map +1 -1
  162. package/dist/commands/prompt.js +57 -31
  163. package/dist/commands/prompt.js.map +1 -1
  164. package/dist/commands/setup.d.ts +12 -39
  165. package/dist/commands/setup.d.ts.map +1 -1
  166. package/dist/commands/setup.js +72 -303
  167. package/dist/commands/setup.js.map +1 -1
  168. package/dist/commands/step.d.ts +44 -1
  169. package/dist/commands/step.d.ts.map +1 -1
  170. package/dist/commands/step.js +255 -11
  171. package/dist/commands/step.js.map +1 -1
  172. package/dist/commands/thread.d.ts +16 -3
  173. package/dist/commands/thread.d.ts.map +1 -1
  174. package/dist/commands/thread.js +423 -142
  175. package/dist/commands/thread.js.map +1 -1
  176. package/dist/commands/workflow.d.ts +9 -1
  177. package/dist/commands/workflow.d.ts.map +1 -1
  178. package/dist/commands/workflow.js +126 -6
  179. package/dist/commands/workflow.js.map +1 -1
  180. package/dist/concurrency/concurrency.d.ts +34 -0
  181. package/dist/concurrency/concurrency.d.ts.map +1 -0
  182. package/dist/concurrency/concurrency.js +216 -0
  183. package/dist/concurrency/concurrency.js.map +1 -0
  184. package/dist/concurrency/index.d.ts +3 -0
  185. package/dist/concurrency/index.d.ts.map +1 -0
  186. package/dist/concurrency/index.js +2 -0
  187. package/dist/concurrency/index.js.map +1 -0
  188. package/dist/concurrency/types.d.ts +19 -0
  189. package/dist/concurrency/types.d.ts.map +1 -0
  190. package/dist/concurrency/types.js +2 -0
  191. package/dist/concurrency/types.js.map +1 -0
  192. package/dist/format.d.ts +69 -2
  193. package/dist/format.d.ts.map +1 -1
  194. package/dist/format.js +198 -1
  195. package/dist/format.js.map +1 -1
  196. package/dist/moderator/__tests__/evaluate.test.js +31 -17
  197. package/dist/moderator/__tests__/evaluate.test.js.map +1 -1
  198. package/dist/moderator/evaluate.d.ts.map +1 -1
  199. package/dist/moderator/evaluate.js +4 -16
  200. package/dist/moderator/evaluate.js.map +1 -1
  201. package/dist/moderator/index.d.ts +1 -2
  202. package/dist/moderator/index.d.ts.map +1 -1
  203. package/dist/moderator/index.js +0 -1
  204. package/dist/moderator/index.js.map +1 -1
  205. package/dist/moderator/types.d.ts +6 -10
  206. package/dist/moderator/types.d.ts.map +1 -1
  207. package/dist/moderator/types.js +1 -3
  208. package/dist/moderator/types.js.map +1 -1
  209. package/dist/output-mappers.d.ts +122 -0
  210. package/dist/output-mappers.d.ts.map +1 -0
  211. package/dist/output-mappers.js +134 -0
  212. package/dist/output-mappers.js.map +1 -0
  213. package/dist/schemas.d.ts +6 -1
  214. package/dist/schemas.d.ts.map +1 -1
  215. package/dist/schemas.js +34 -5
  216. package/dist/schemas.js.map +1 -1
  217. package/dist/store.d.ts +28 -9
  218. package/dist/store.d.ts.map +1 -1
  219. package/dist/store.js +75 -16
  220. package/dist/store.js.map +1 -1
  221. package/dist/text-renderers.d.ts +30 -0
  222. package/dist/text-renderers.d.ts.map +1 -0
  223. package/dist/text-renderers.js +251 -0
  224. package/dist/text-renderers.js.map +1 -0
  225. package/dist/validate-semantic.d.ts.map +1 -1
  226. package/dist/validate-semantic.js +95 -61
  227. package/dist/validate-semantic.js.map +1 -1
  228. package/dist/validate.d.ts +6 -0
  229. package/dist/validate.d.ts.map +1 -1
  230. package/dist/validate.js +24 -0
  231. package/dist/validate.js.map +1 -1
  232. package/examples/brainstorm.yaml +130 -0
  233. package/examples/debate.yaml +169 -0
  234. package/examples/socratic-questioning.yaml +112 -0
  235. package/package.json +9 -10
  236. package/src/__tests__/adapter-json-roundtrip.test.ts +16 -7
  237. package/src/__tests__/agent-resolution-llm-free.test.ts +39 -0
  238. package/src/__tests__/build-step-entry.test.ts +203 -0
  239. package/src/__tests__/clear-thread-failed-attempts.test.ts +122 -0
  240. package/src/__tests__/concurrency.test.ts +266 -0
  241. package/src/__tests__/config.test.ts +33 -321
  242. package/src/__tests__/current-role.test.ts +7 -6
  243. package/src/__tests__/e2e-mock-agent.test.ts +65 -30
  244. package/src/__tests__/fixtures/e2e-count.workflow.yaml +1 -0
  245. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +1 -0
  246. package/src/__tests__/fixtures/{e2e-mustache.workflow.yaml → e2e-liquid.workflow.yaml} +3 -2
  247. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +1 -0
  248. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +2 -2
  249. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +6 -10
  250. package/src/__tests__/format-text-default.test.ts +49 -0
  251. package/src/__tests__/format-text-registry.test.ts +173 -0
  252. package/src/__tests__/issue-180-workflow-ref-removed.test.ts +43 -0
  253. package/src/__tests__/log-text-renderer.test.ts +294 -0
  254. package/src/__tests__/moderator-evaluate.test.ts +9 -52
  255. package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
  256. package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
  257. package/src/__tests__/pid-recycling.test.ts +329 -0
  258. package/src/__tests__/prompt.test.ts +443 -2
  259. package/src/__tests__/resolve-head-hash.test.ts +11 -4
  260. package/src/__tests__/setup-agent-discovery.test.ts +26 -51
  261. package/src/__tests__/setup-complexity.test.ts +1 -203
  262. package/src/__tests__/setup-no-llm.test.ts +68 -0
  263. package/src/__tests__/solve-issue-tea-worktree.test.ts +27 -31
  264. package/src/__tests__/step-ask.test.ts +677 -0
  265. package/src/__tests__/step-show-json.test.ts +1 -0
  266. package/src/__tests__/step-timing.test.ts +2 -0
  267. package/src/__tests__/store-global-cas.test.ts +2 -2
  268. package/src/__tests__/store-unified-threads.test.ts +30 -27
  269. package/src/__tests__/thread-cancel-status.test.ts +27 -20
  270. package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
  271. package/src/__tests__/thread-list-filters.test.ts +443 -17
  272. package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
  273. package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
  274. package/src/__tests__/thread-poke.test.ts +554 -0
  275. package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
  276. package/src/__tests__/thread-resume.test.ts +20 -15
  277. package/src/__tests__/thread-show-status.test.ts +17 -29
  278. package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
  279. package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
  280. package/src/__tests__/thread-suspend-step.test.ts +13 -16
  281. package/src/__tests__/thread-suspended-display.test.ts +10 -22
  282. package/src/__tests__/thread-test-helpers.ts +15 -1
  283. package/src/__tests__/thread.test.ts +14 -14
  284. package/src/__tests__/validate-semantic.test.ts +118 -33
  285. package/src/__tests__/workflow-list-recursive.test.ts +370 -0
  286. package/src/__tests__/workflow-resolution.test.ts +48 -29
  287. package/src/__tests__/workflow-show-resolution.test.ts +286 -0
  288. package/src/__tests__/workflow-validate.test.ts +828 -0
  289. package/src/__tests__/write-envelope.test.ts +257 -0
  290. package/src/background/background.ts +88 -6
  291. package/src/background/index.ts +2 -0
  292. package/src/background/types.ts +1 -0
  293. package/src/cli.ts +184 -77
  294. package/src/commands/config.ts +16 -33
  295. package/src/commands/prompt.ts +57 -31
  296. package/src/commands/setup.ts +80 -358
  297. package/src/commands/step.ts +339 -12
  298. package/src/commands/thread.ts +511 -171
  299. package/src/commands/workflow.ts +155 -4
  300. package/src/concurrency/concurrency.ts +245 -0
  301. package/src/concurrency/index.ts +10 -0
  302. package/src/concurrency/types.ts +19 -0
  303. package/src/format.ts +282 -2
  304. package/src/moderator/__tests__/evaluate.test.ts +34 -17
  305. package/src/moderator/evaluate.ts +5 -17
  306. package/src/moderator/index.ts +1 -6
  307. package/src/moderator/types.ts +6 -14
  308. package/src/output-mappers.ts +254 -0
  309. package/src/schemas.ts +51 -5
  310. package/src/store.ts +86 -20
  311. package/src/text-renderers.ts +355 -0
  312. package/src/validate-semantic.ts +125 -73
  313. package/src/validate.ts +27 -0
  314. package/dist/__tests__/setup-validate.test.d.ts +0 -2
  315. package/dist/__tests__/setup-validate.test.d.ts.map +0 -1
  316. package/dist/__tests__/setup-validate.test.js +0 -108
  317. package/dist/__tests__/setup-validate.test.js.map +0 -1
  318. package/src/__tests__/setup-validate.test.ts +0 -148
  319. /package/src/__tests__/fixtures/{e2e-mustache.mock.yaml → e2e-liquid.mock.yaml} +0 -0
@@ -6,19 +6,14 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
6
6
  import { stringify } from "yaml";
7
7
  import { cmdThreadStart } from "../commands/thread.js";
8
8
  import type { UwfStore } from "../store.js";
9
- import { createUwfStore, saveWorkflowRegistry } from "../store.js";
9
+ import { saveWorkflowRegistry } from "../store.js";
10
+ import { makeUwfStore } from "./thread-test-helpers.js";
10
11
 
11
12
  // ── helpers ───────────────────────────────────────────────────────────────────
12
13
 
13
- async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
14
- const casDir = join(storageRoot, "cas");
15
- await mkdir(casDir, { recursive: true });
16
- process.env.OCAS_HOME = casDir;
17
- return createUwfStore(storageRoot);
18
- }
19
-
20
14
  function makeMinimalPayload(name: string, description: string): WorkflowPayload {
21
15
  return {
16
+ version: 1,
22
17
  name,
23
18
  description,
24
19
  roles: {
@@ -66,8 +61,10 @@ async function createWorkflowYaml(name: string, version: string | null = null):
66
61
  let tmpDir: string;
67
62
  let storageRoot: string;
68
63
  let projectRoot: string;
64
+ let savedOcasHome: string | undefined;
69
65
 
70
66
  beforeEach(async () => {
67
+ savedOcasHome = process.env.OCAS_HOME;
71
68
  tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wf-resolve-test-"));
72
69
  storageRoot = join(tmpDir, "storage");
73
70
  projectRoot = join(tmpDir, "project");
@@ -76,6 +73,11 @@ beforeEach(async () => {
76
73
  });
77
74
 
78
75
  afterEach(async () => {
76
+ if (savedOcasHome === undefined) {
77
+ delete process.env.OCAS_HOME;
78
+ } else {
79
+ process.env.OCAS_HOME = savedOcasHome;
80
+ }
79
81
  await rm(tmpDir, { recursive: true, force: true });
80
82
  });
81
83
 
@@ -180,9 +182,9 @@ describe("Strategy 2: File Path Resolution", () => {
180
182
  // ── Strategy 3: Local Discovery (Parent Traversal) ────────────────────────────
181
183
 
182
184
  describe("Strategy 3: Local Discovery", () => {
183
- test("should find workflow in current directory .workflow/", async () => {
185
+ test("should find workflow in current directory .workflows/", async () => {
184
186
  await makeUwfStore(storageRoot);
185
- const workflowDir = join(projectRoot, ".workflow");
187
+ const workflowDir = join(projectRoot, ".workflows");
186
188
  await mkdir(workflowDir, { recursive: true });
187
189
  await writeFile(join(workflowDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
188
190
 
@@ -197,9 +199,9 @@ describe("Strategy 3: Local Discovery", () => {
197
199
  }
198
200
  });
199
201
 
200
- test("should find workflow in parent directory .workflow/", async () => {
202
+ test("should find workflow in parent directory .workflows/", async () => {
201
203
  await makeUwfStore(storageRoot);
202
- const workflowDir = join(projectRoot, ".workflow");
204
+ const workflowDir = join(projectRoot, ".workflows");
203
205
  await mkdir(workflowDir, { recursive: true });
204
206
  await writeFile(join(workflowDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
205
207
 
@@ -219,19 +221,19 @@ describe("Strategy 3: Local Discovery", () => {
219
221
  await expect(cmdThreadStart(storageRoot, "nonexistent", "prompt", deepPath)).rejects.toThrow();
220
222
  });
221
223
 
222
- test("should prefer .workflow/ over .workflows/ directory", async () => {
224
+ test("should prefer .workflows/ over .workflow/ directory", async () => {
223
225
  await makeUwfStore(storageRoot);
224
- const workflowDir = join(projectRoot, ".workflow");
225
- const workflowsDir = join(projectRoot, ".workflows");
226
- await mkdir(workflowDir, { recursive: true });
227
- await mkdir(workflowsDir, { recursive: true });
226
+ const primaryDir = join(projectRoot, ".workflows");
227
+ const legacyDir = join(projectRoot, ".workflow");
228
+ await mkdir(primaryDir, { recursive: true });
229
+ await mkdir(legacyDir, { recursive: true });
228
230
 
229
231
  await writeFile(
230
- join(workflowDir, "solve-issue.yaml"),
232
+ join(primaryDir, "solve-issue.yaml"),
231
233
  await createWorkflowYaml("solve-issue", "1"),
232
234
  );
233
235
  await writeFile(
234
- join(workflowsDir, "solve-issue.yaml"),
236
+ join(legacyDir, "solve-issue.yaml"),
235
237
  await createWorkflowYaml("solve-issue", "2"),
236
238
  );
237
239
 
@@ -245,9 +247,9 @@ describe("Strategy 3: Local Discovery", () => {
245
247
  }
246
248
  });
247
249
 
248
- test("should support .yml extension in local discovery", async () => {
250
+ test("should support .yml extension in local discovery under .workflows/", async () => {
249
251
  await makeUwfStore(storageRoot);
250
- const workflowDir = join(projectRoot, ".workflow");
252
+ const workflowDir = join(projectRoot, ".workflows");
251
253
  await mkdir(workflowDir, { recursive: true });
252
254
  await writeFile(join(workflowDir, "solve-issue.yml"), await createWorkflowYaml("solve-issue"));
253
255
 
@@ -256,9 +258,9 @@ describe("Strategy 3: Local Discovery", () => {
256
258
  expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
257
259
  });
258
260
 
259
- test("should find workflow in folder-based layout (name/index.yaml)", async () => {
261
+ test("should find workflow in folder-based layout (.workflows/<name>/index.yaml)", async () => {
260
262
  await makeUwfStore(storageRoot);
261
- const workflowDir = join(projectRoot, ".workflow", "solve-issue");
263
+ const workflowDir = join(projectRoot, ".workflows", "solve-issue");
262
264
  await mkdir(workflowDir, { recursive: true });
263
265
  await writeFile(join(workflowDir, "index.yaml"), await createWorkflowYaml("solve-issue"));
264
266
 
@@ -273,9 +275,9 @@ describe("Strategy 3: Local Discovery", () => {
273
275
  }
274
276
  });
275
277
 
276
- test("should prefer flat file over folder-based layout", async () => {
278
+ test("should prefer flat file over folder-based layout under .workflows/", async () => {
277
279
  await makeUwfStore(storageRoot);
278
- const workflowDir = join(projectRoot, ".workflow");
280
+ const workflowDir = join(projectRoot, ".workflows");
279
281
  await mkdir(workflowDir, { recursive: true });
280
282
  await writeFile(
281
283
  join(workflowDir, "solve-issue.yaml"),
@@ -298,6 +300,23 @@ describe("Strategy 3: Local Discovery", () => {
298
300
  expect((node.payload as WorkflowPayload).description).toBe("Test workflow (flat)");
299
301
  }
300
302
  });
303
+
304
+ test("should resolve from legacy .workflow/ when .workflows/ is absent", async () => {
305
+ await makeUwfStore(storageRoot);
306
+ const legacyDir = join(projectRoot, ".workflow");
307
+ await mkdir(legacyDir, { recursive: true });
308
+ await writeFile(join(legacyDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
309
+
310
+ const result = await cmdThreadStart(storageRoot, "solve-issue", "prompt", projectRoot);
311
+
312
+ expect(result.workflow).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
313
+ const uwf = await makeUwfStore(storageRoot);
314
+ const node = uwf.store.cas.get(result.workflow);
315
+ expect(node).not.toBeNull();
316
+ if (node !== null) {
317
+ expect((node.payload as WorkflowPayload).name).toBe("solve-issue");
318
+ }
319
+ });
301
320
  });
302
321
 
303
322
  // ── Strategy 4: Global Registry Fallback ──────────────────────────────────────
@@ -329,8 +348,8 @@ describe("Resolution Priority", () => {
329
348
  test("should use explicit file path over local discovery", async () => {
330
349
  await makeUwfStore(storageRoot);
331
350
 
332
- // Setup: Create workflow in .workflow/ AND as explicit file
333
- const workflowDir = join(projectRoot, ".workflow");
351
+ // Setup: Create workflow in .workflows/ AND as explicit file
352
+ const workflowDir = join(projectRoot, ".workflows");
334
353
  await mkdir(workflowDir, { recursive: true });
335
354
  await writeFile(
336
355
  join(workflowDir, "solve-issue.yaml"),
@@ -358,8 +377,8 @@ describe("Resolution Priority", () => {
358
377
  const globalHash = await storeWorkflow(uwf, "solve-issue");
359
378
  saveWorkflowRegistry(uwf.varStore, "solve-issue", globalHash);
360
379
 
361
- // Setup: Create local .workflow/
362
- const workflowDir = join(projectRoot, ".workflow");
380
+ // Setup: Create local .workflows/
381
+ const workflowDir = join(projectRoot, ".workflows");
363
382
  await mkdir(workflowDir, { recursive: true });
364
383
  const localYaml = await createWorkflowYaml("solve-issue", "local");
365
384
  await writeFile(join(workflowDir, "solve-issue.yaml"), localYaml);
@@ -0,0 +1,286 @@
1
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { CasRef, WorkflowPayload } from "@united-workforce/protocol";
5
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
6
+ import { stringify } from "yaml";
7
+ import { cmdWorkflowShow } from "../commands/workflow.js";
8
+ import type { UwfStore } from "../store.js";
9
+ import { saveWorkflowRegistry } from "../store.js";
10
+ import { makeUwfStore } from "./thread-test-helpers.js";
11
+
12
+ // ── helpers ───────────────────────────────────────────────────────────────────
13
+
14
+ function makeMinimalPayload(name: string, description: string): WorkflowPayload {
15
+ return {
16
+ version: 1,
17
+ name,
18
+ description,
19
+ roles: {
20
+ worker: {
21
+ description: "worker role",
22
+ goal: "do work",
23
+ capabilities: [],
24
+ procedure: "",
25
+ output: "",
26
+ frontmatter: {
27
+ type: "object",
28
+ properties: {
29
+ $status: { const: "done" },
30
+ },
31
+ required: ["$status"],
32
+ } as unknown as CasRef,
33
+ },
34
+ },
35
+ graph: {
36
+ $START: {
37
+ new: { role: "worker", prompt: "start working", location: null },
38
+ resume: { role: "worker", prompt: "resume working", location: null },
39
+ },
40
+ worker: { done: { role: "$END", prompt: "done", location: null } },
41
+ },
42
+ };
43
+ }
44
+
45
+ async function storeWorkflow(uwf: UwfStore, name: string): Promise<CasRef> {
46
+ const payload = makeMinimalPayload(name, "Test workflow");
47
+ return await uwf.store.cas.put(uwf.schemas.workflow, payload);
48
+ }
49
+
50
+ async function createWorkflowYaml(name: string, version: string | null = null): Promise<string> {
51
+ const payload = makeMinimalPayload(
52
+ name,
53
+ version !== null ? `Test workflow (${version})` : "Test workflow",
54
+ );
55
+ const yaml = stringify(payload);
56
+ return yaml;
57
+ }
58
+
59
+ // ── fixture ───────────────────────────────────────────────────────────────────
60
+
61
+ let tmpDir: string;
62
+ let storageRoot: string;
63
+ let projectRoot: string;
64
+ let savedOcasHome: string | undefined;
65
+
66
+ beforeEach(async () => {
67
+ savedOcasHome = process.env.OCAS_HOME;
68
+ tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-wf-show-test-"));
69
+ storageRoot = join(tmpDir, "storage");
70
+ projectRoot = join(tmpDir, "project");
71
+ await mkdir(storageRoot, { recursive: true });
72
+ await mkdir(projectRoot, { recursive: true });
73
+ });
74
+
75
+ afterEach(async () => {
76
+ if (savedOcasHome === undefined) {
77
+ delete process.env.OCAS_HOME;
78
+ } else {
79
+ process.env.OCAS_HOME = savedOcasHome;
80
+ }
81
+ await rm(tmpDir, { recursive: true, force: true });
82
+ });
83
+
84
+ // ── Strategy 1: CAS Hash Resolution ───────────────────────────────────────────
85
+
86
+ describe("Strategy 1: CAS Hash Resolution", () => {
87
+ test("should resolve valid 13-char Crockford Base32 hash", async () => {
88
+ const uwf = await makeUwfStore(storageRoot);
89
+ const hash = await storeWorkflow(uwf, "test-workflow");
90
+
91
+ const result = await cmdWorkflowShow(storageRoot, hash, projectRoot);
92
+
93
+ expect(result.hash).toBe(hash);
94
+ expect(result.payload.name).toBe("test-workflow");
95
+ expect(result.name).toBeNull(); // not in registry
96
+ });
97
+
98
+ test("should fail on invalid hash format (non-Crockford characters)", async () => {
99
+ await makeUwfStore(storageRoot);
100
+
101
+ await expect(cmdWorkflowShow(storageRoot, "123456789ABCD", projectRoot)).rejects.toThrow();
102
+ });
103
+
104
+ test("should fail on valid-format hash not present in CAS", async () => {
105
+ await makeUwfStore(storageRoot);
106
+ const fakeHash = "0000000000000"; // valid format, doesn't exist
107
+
108
+ await expect(cmdWorkflowShow(storageRoot, fakeHash, projectRoot)).rejects.toThrow();
109
+ });
110
+ });
111
+
112
+ // ── Strategy 2: File Path Resolution ──────────────────────────────────────────
113
+
114
+ describe("Strategy 2: File Path Resolution", () => {
115
+ test("should load workflow from absolute file path", async () => {
116
+ await makeUwfStore(storageRoot);
117
+ const yamlPath = join(tmpDir, "test-workflow.yaml");
118
+ await writeFile(yamlPath, await createWorkflowYaml("test-workflow"));
119
+
120
+ const result = await cmdWorkflowShow(storageRoot, yamlPath, projectRoot);
121
+
122
+ expect(result.hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
123
+ expect(result.payload.name).toBe("test-workflow");
124
+ expect(result.payload.description).toBe("Test workflow");
125
+ });
126
+
127
+ test("should load workflow from relative file path", async () => {
128
+ await makeUwfStore(storageRoot);
129
+ const yamlPath = "test-workflow.yaml";
130
+ await writeFile(join(projectRoot, yamlPath), await createWorkflowYaml("test-workflow"));
131
+
132
+ const result = await cmdWorkflowShow(storageRoot, yamlPath, projectRoot);
133
+
134
+ expect(result.hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
135
+ expect(result.payload.name).toBe("test-workflow");
136
+ });
137
+
138
+ test("should fail when file path does not exist", async () => {
139
+ await makeUwfStore(storageRoot);
140
+
141
+ await expect(cmdWorkflowShow(storageRoot, "./nonexistent.yaml", projectRoot)).rejects.toThrow();
142
+ });
143
+ });
144
+
145
+ // ── Strategy 3: Local Discovery (Parent Traversal) ────────────────────────────
146
+
147
+ describe("Strategy 3: Local Discovery", () => {
148
+ test("should find workflow in current directory .workflows/", async () => {
149
+ await makeUwfStore(storageRoot);
150
+ const workflowDir = join(projectRoot, ".workflows");
151
+ await mkdir(workflowDir, { recursive: true });
152
+ await writeFile(join(workflowDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
153
+
154
+ const result = await cmdWorkflowShow(storageRoot, "solve-issue", projectRoot);
155
+
156
+ expect(result.hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
157
+ expect(result.payload.name).toBe("solve-issue");
158
+ });
159
+
160
+ test("should find workflow in parent directory .workflows/", async () => {
161
+ await makeUwfStore(storageRoot);
162
+ const workflowDir = join(projectRoot, ".workflows");
163
+ await mkdir(workflowDir, { recursive: true });
164
+ await writeFile(join(workflowDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
165
+
166
+ const subdir = join(projectRoot, "packages", "cli", "src");
167
+ await mkdir(subdir, { recursive: true });
168
+
169
+ const result = await cmdWorkflowShow(storageRoot, "solve-issue", subdir);
170
+
171
+ expect(result.hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
172
+ expect(result.payload.name).toBe("solve-issue");
173
+ });
174
+
175
+ test("should prefer .workflows/ over .workflow/ directory", async () => {
176
+ await makeUwfStore(storageRoot);
177
+ const primaryDir = join(projectRoot, ".workflows");
178
+ const legacyDir = join(projectRoot, ".workflow");
179
+ await mkdir(primaryDir, { recursive: true });
180
+ await mkdir(legacyDir, { recursive: true });
181
+
182
+ await writeFile(
183
+ join(primaryDir, "solve-issue.yaml"),
184
+ await createWorkflowYaml("solve-issue", "primary"),
185
+ );
186
+ await writeFile(
187
+ join(legacyDir, "solve-issue.yaml"),
188
+ await createWorkflowYaml("solve-issue", "legacy"),
189
+ );
190
+
191
+ const result = await cmdWorkflowShow(storageRoot, "solve-issue", projectRoot);
192
+
193
+ expect(result.payload.description).toBe("Test workflow (primary)");
194
+ });
195
+
196
+ test("should find workflow in folder-based layout (.workflows/<name>/index.yaml)", async () => {
197
+ await makeUwfStore(storageRoot);
198
+ const workflowDir = join(projectRoot, ".workflows", "solve-issue");
199
+ await mkdir(workflowDir, { recursive: true });
200
+ await writeFile(join(workflowDir, "index.yaml"), await createWorkflowYaml("solve-issue"));
201
+
202
+ const result = await cmdWorkflowShow(storageRoot, "solve-issue", projectRoot);
203
+
204
+ expect(result.hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
205
+ expect(result.payload.name).toBe("solve-issue");
206
+ });
207
+
208
+ test("should resolve from legacy .workflow/ when .workflows/ is absent", async () => {
209
+ await makeUwfStore(storageRoot);
210
+ const legacyDir = join(projectRoot, ".workflow");
211
+ await mkdir(legacyDir, { recursive: true });
212
+ await writeFile(join(legacyDir, "solve-issue.yaml"), await createWorkflowYaml("solve-issue"));
213
+
214
+ const result = await cmdWorkflowShow(storageRoot, "solve-issue", projectRoot);
215
+
216
+ expect(result.hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/);
217
+ expect(result.payload.name).toBe("solve-issue");
218
+ });
219
+ });
220
+
221
+ // ── Strategy 4: Global Registry Fallback ──────────────────────────────────────
222
+
223
+ describe("Strategy 4: Global Registry Resolution", () => {
224
+ test("should resolve workflow from global registry when not found locally", async () => {
225
+ const uwf = await makeUwfStore(storageRoot);
226
+ const hash = await storeWorkflow(uwf, "deploy-pipeline");
227
+ saveWorkflowRegistry(uwf.varStore, "deploy-pipeline", hash);
228
+
229
+ const isolatedRoot = join(tmpDir, "isolated");
230
+ await mkdir(isolatedRoot, { recursive: true });
231
+
232
+ const result = await cmdWorkflowShow(storageRoot, "deploy-pipeline", isolatedRoot);
233
+
234
+ expect(result.hash).toBe(hash);
235
+ expect(result.name).toBe("deploy-pipeline"); // found in registry
236
+ });
237
+
238
+ test("should fail when workflow not found in any strategy", async () => {
239
+ await makeUwfStore(storageRoot);
240
+
241
+ await expect(cmdWorkflowShow(storageRoot, "nonexistent", tmpDir)).rejects.toThrow();
242
+ });
243
+ });
244
+
245
+ // ── Strategy Priority Order ───────────────────────────────────────────────────
246
+
247
+ describe("Resolution Priority", () => {
248
+ test("should use explicit file path over local discovery", async () => {
249
+ await makeUwfStore(storageRoot);
250
+
251
+ // Setup: Create workflow in .workflows/ AND as explicit file
252
+ const workflowDir = join(projectRoot, ".workflows");
253
+ await mkdir(workflowDir, { recursive: true });
254
+ await writeFile(
255
+ join(workflowDir, "solve-issue.yaml"),
256
+ await createWorkflowYaml("solve-issue", "discovery"),
257
+ );
258
+
259
+ const explicitPath = join(projectRoot, "custom-solve-issue.yaml");
260
+ await writeFile(explicitPath, await createWorkflowYaml("custom-solve-issue", "explicit"));
261
+
262
+ // Execute with explicit path
263
+ const result = await cmdWorkflowShow(storageRoot, explicitPath, projectRoot);
264
+
265
+ expect(result.payload.description).toBe("Test workflow (explicit)");
266
+ });
267
+
268
+ test("should use local discovery over global registry", async () => {
269
+ const uwf = await makeUwfStore(storageRoot);
270
+
271
+ // Setup: Register globally
272
+ const payload = makeMinimalPayload("solve-issue", "Test workflow (global)");
273
+ const globalHash = await uwf.store.cas.put(uwf.schemas.workflow, payload);
274
+ saveWorkflowRegistry(uwf.varStore, "solve-issue", globalHash);
275
+
276
+ // Setup: Create local .workflows/
277
+ const workflowDir = join(projectRoot, ".workflows");
278
+ await mkdir(workflowDir, { recursive: true });
279
+ const localYaml = await createWorkflowYaml("solve-issue", "local");
280
+ await writeFile(join(workflowDir, "solve-issue.yaml"), localYaml);
281
+
282
+ const result = await cmdWorkflowShow(storageRoot, "solve-issue", projectRoot);
283
+
284
+ expect(result.payload.description).toBe("Test workflow (local)");
285
+ });
286
+ });