@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
@@ -5,6 +5,7 @@ import { describe, expect, test } from "vitest";
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
 
8
+ import { generateCliReference } from "@united-workforce/util";
8
9
  import {
9
10
  cmdPromptAdapterDeveloping,
10
11
  cmdPromptBootstrap,
@@ -42,6 +43,24 @@ describe("prompt commands", () => {
42
43
  expect(result.length).toBeGreaterThan(500);
43
44
  });
44
45
 
46
+ test("prompt usage describes .workflows/ auto-discovery", () => {
47
+ const result = cmdPromptUsage();
48
+ expect(result).toContain(".workflows/");
49
+ expect(result).toContain("uwf thread start solve-issue");
50
+ expect(result.toLowerCase()).toContain("auto-discover");
51
+ expect(result.toLowerCase()).toContain("recommended");
52
+ });
53
+
54
+ test("prompt cli-reference describes .workflows/ auto-discovery", () => {
55
+ const ref = generateCliReference();
56
+ expect(ref).toContain(".workflows/");
57
+ expect(ref.toLowerCase()).toContain("cwd upward");
58
+ expect(ref).toContain("workflow list");
59
+ expect(ref).toMatch(/CAS hash/i);
60
+ expect(ref).toMatch(/file path/i);
61
+ expect(ref).toMatch(/registry/i);
62
+ });
63
+
45
64
  test("prompt workflow-authoring returns non-empty markdown string with frontmatter", () => {
46
65
  const result = cmdPromptWorkflowAuthoring();
47
66
  expect(typeof result).toBe("string");
@@ -56,6 +75,41 @@ describe("prompt commands", () => {
56
75
  expect(result.length).toBeGreaterThan(500);
57
76
  });
58
77
 
78
+ test("prompt workflow-authoring documents .workflows/ Placement section", () => {
79
+ const result = cmdPromptWorkflowAuthoring();
80
+ expect(result).toContain("## Placement");
81
+ expect(result).toContain(".workflows/");
82
+ expect(result).toContain("solve-issue.yaml");
83
+ expect(result.toLowerCase()).toContain("auto-discover");
84
+ expect(result.toLowerCase()).toContain("no workflow add");
85
+ // Placement must appear before Self-Testing
86
+ expect(result.indexOf("## Placement")).toBeLessThan(result.indexOf("## Self-Testing"));
87
+ });
88
+
89
+ test("prompt workflow-authoring mentions .workflow/ as legacy fallback", () => {
90
+ const result = cmdPromptWorkflowAuthoring();
91
+ expect(result).toContain(".workflow/");
92
+ expect(result.toLowerCase()).toContain("legacy");
93
+ });
94
+
95
+ test("prompt workflow-authoring documents Liquid filters with join example", () => {
96
+ const result = cmdPromptWorkflowAuthoring();
97
+ expect(result).toContain("| join");
98
+ expect(result.toLowerCase()).toContain("filter");
99
+ });
100
+
101
+ test("prompt workflow-authoring documents Liquid loops with for example", () => {
102
+ const result = cmdPromptWorkflowAuthoring();
103
+ expect(result).toContain("{% for");
104
+ expect(result).toContain("{% endfor %}");
105
+ });
106
+
107
+ test("prompt workflow-authoring uses Liquid terminology with no Mustache remnants", () => {
108
+ const result = cmdPromptWorkflowAuthoring();
109
+ expect(result.toLowerCase()).not.toContain("mustache");
110
+ expect(result.toLowerCase()).toContain("liquid");
111
+ });
112
+
59
113
  test("prompt adapter-developing returns non-empty markdown string with frontmatter", () => {
60
114
  const result = cmdPromptAdapterDeveloping();
61
115
  expect(typeof result).toBe("string");
@@ -81,8 +135,6 @@ describe("prompt commands", () => {
81
135
  // Fresh install scenario
82
136
  expect(result).toContain("Fresh Install");
83
137
  expect(result).toContain("uwf setup");
84
- expect(result).toContain("--provider");
85
- expect(result).toContain("--api-key");
86
138
  expect(result).toContain("agent adapter");
87
139
  // Upgrade scenario
88
140
  expect(result).toContain("Upgrade");
@@ -93,6 +145,49 @@ describe("prompt commands", () => {
93
145
  expect(result.length).toBeGreaterThan(100);
94
146
  });
95
147
 
148
+ // Skip: pure documentation content assertions on bootstrap prompt text.
149
+ test.skip("prompt bootstrap has no LLM provider/model references", () => {
150
+ const result = cmdPromptBootstrap();
151
+ // Must NOT contain provider/model flags
152
+ expect(result).not.toContain("--provider");
153
+ expect(result).not.toContain("--base-url");
154
+ expect(result).not.toContain("--api-key");
155
+ expect(result).not.toContain("--model");
156
+ // Must NOT contain old Step 2 about provider config
157
+ expect(result).not.toContain("Configure provider and model");
158
+ // Must NOT contain preset providers table
159
+ expect(result).not.toContain("openrouter");
160
+ expect(result).not.toContain("OpenRouter");
161
+ expect(result).not.toContain("xAI");
162
+ expect(result).not.toContain("dashscope");
163
+ // Must NOT show provider/model config keys
164
+ expect(result).not.toContain("providers:");
165
+ expect(result).not.toContain("defaultModel");
166
+ expect(result).not.toContain("models:");
167
+ // Setup step must show only --agent
168
+ expect(result).toContain("uwf setup --agent");
169
+ // Config example must show only agents, defaultAgent, agentOverrides
170
+ expect(result).toContain("agents:");
171
+ expect(result).toContain("defaultAgent:");
172
+ // Must mention per-adapter LLM config
173
+ expect(result).toMatch(/~\/\.uwf\/agents\//);
174
+ });
175
+
176
+ // Skip: pure documentation content assertions on bootstrap prompt text.
177
+ test.skip("prompt bootstrap step numbering has no gaps after removing old Step 2", () => {
178
+ const result = cmdPromptBootstrap();
179
+ // Extract only the Fresh Install section
180
+ const freshStart = result.indexOf("## Scenario A: Fresh Install");
181
+ const freshEnd = result.indexOf("## Scenario B:");
182
+ const freshSection = result.slice(freshStart, freshEnd);
183
+ const stepHeaders = freshSection.match(/### Step \d+/g) ?? [];
184
+ const stepNumbers = stepHeaders.map((h) => Number.parseInt(h.replace("### Step ", ""), 10));
185
+ // Verify sequential numbering (0, 1, 2, 3, ...)
186
+ for (let i = 0; i < stepNumbers.length; i++) {
187
+ expect(stepNumbers[i]).toBe(i);
188
+ }
189
+ });
190
+
96
191
  test("prompt help subcommand is suppressed", { timeout: 30_000 }, () => {
97
192
  const cliPath = join(__dirname, "..", "..", "dist", "cli.js");
98
193
  const output = execFileSync("node", [cliPath, "prompt", "--help"], {
@@ -110,3 +205,349 @@ describe("prompt commands", () => {
110
205
  expect(output).not.toContain("usage-reference");
111
206
  });
112
207
  });
208
+
209
+ // Skip: pure documentation content assertions — text changes break these without
210
+ // indicating real bugs. Verified by human review instead. See #299 discussion.
211
+ describe.skip("prompt adapter-developing — issue #214 v0.4 contract", () => {
212
+ const text = cmdPromptAdapterDeveloping();
213
+ const lower = text.toLowerCase();
214
+
215
+ // ── Item 1 — AgentOptions includes fork and cleanup ─────────────────
216
+ test("AgentOptions documents fork field with AgentForkFn | null", () => {
217
+ expect(text).toContain("AgentOptions");
218
+ expect(text).toMatch(/fork\s*:\s*AgentForkFn\s*\|\s*null/);
219
+ expect(text).toContain("AgentForkFn");
220
+ });
221
+
222
+ test("AgentOptions documents cleanup field with AgentCleanupFn | null", () => {
223
+ expect(text).toMatch(/cleanup\s*:\s*AgentCleanupFn\s*\|\s*null/);
224
+ expect(text).toContain("AgentCleanupFn");
225
+ });
226
+
227
+ test("explains that fork=null is acceptable for adapters that do not implement step ask", () => {
228
+ expect(lower).toMatch(/fork.*null.*(do(es)? not|no).*step ask|step ask.*fork.*null/);
229
+ });
230
+
231
+ test("explains that cleanup runs after the agent completes (success or failure)", () => {
232
+ expect(lower).toMatch(
233
+ /cleanup.*(after|completes|invoked).*(release|i\/?o|resources|subprocess)/,
234
+ );
235
+ });
236
+
237
+ // ── Item 2 — Public helpers table is complete ───────────────────────
238
+ test("helpers table lists buildRolePrompt", () => {
239
+ expect(text).toContain("buildRolePrompt");
240
+ });
241
+
242
+ test("helpers table lists buildContinuationPrompt", () => {
243
+ expect(text).toContain("buildContinuationPrompt");
244
+ });
245
+
246
+ test("helpers table lists buildThreadProgress", () => {
247
+ expect(text).toContain("buildThreadProgress");
248
+ });
249
+
250
+ test("helpers table lists buildOutputFormatInstruction", () => {
251
+ expect(text).toContain("buildOutputFormatInstruction");
252
+ });
253
+
254
+ test("helpers table lists buildSuspendOutput", () => {
255
+ expect(text).toContain("buildSuspendOutput");
256
+ });
257
+
258
+ test("helpers table lists buildFrontmatterRetryPrompt", () => {
259
+ expect(text).toContain("buildFrontmatterRetryPrompt");
260
+ });
261
+
262
+ test("helpers table lists session-cache helpers", () => {
263
+ expect(text).toContain("getCachedSessionId");
264
+ expect(text).toContain("setCachedSessionId");
265
+ expect(text).toContain("getAskSessionId");
266
+ expect(text).toContain("setAskSessionId");
267
+ });
268
+
269
+ // ── Item 3 — $SUSPEND coroutine yield ───────────────────────────────
270
+ test("documents $SUSPEND as coroutine yield with reason", () => {
271
+ expect(text).toContain("$SUSPEND");
272
+ expect(lower).toContain("coroutine");
273
+ expect(lower).toMatch(/reason/);
274
+ });
275
+
276
+ test("documents buildSuspendOutput helper to emit a $SUSPEND output", () => {
277
+ expect(text).toContain("buildSuspendOutput");
278
+ expect(text).toMatch(/buildSuspendOutput\s*\(/);
279
+ });
280
+
281
+ test("documents trySuspendFastPath round-trip and SUSPEND_OUTPUT_SCHEMA", () => {
282
+ expect(text).toContain("trySuspendFastPath");
283
+ expect(text).toMatch(/SUSPEND_OUTPUT_SCHEMA|suspendOutput/);
284
+ });
285
+
286
+ test("explains engine intercepts $SUSPEND before the moderator", () => {
287
+ expect(lower).toMatch(/intercept.*moderator|before the moderator|engine.*suspend/);
288
+ expect(lower).toMatch(/(thread|state).*suspend/);
289
+ });
290
+
291
+ test("notes that $SUSPEND is reserved and may be emitted by any role regardless of declared output", () => {
292
+ expect(lower).toMatch(/(any role|every role|regardless).*\$?suspend|reserved/);
293
+ });
294
+
295
+ // ── Item 4 — step ask adapter contract ──────────────────────────────
296
+ test("documents step ask --mode fork CLI contract for adapters", () => {
297
+ expect(text).toContain("--mode fork");
298
+ expect(text).toContain("--session");
299
+ expect(lower).toMatch(/fork.*(stdout|prints|return).*session/);
300
+ });
301
+
302
+ test("documents step ask --mode ask CLI contract", () => {
303
+ expect(text).toContain("--mode ask");
304
+ expect(text).toContain("--prompt");
305
+ });
306
+
307
+ test("explains that fork: null adapters do not need to handle --mode fork/ask", () => {
308
+ expect(lower).toMatch(/fork\s*:\s*null.*(do(es)? not|no|not required).*(--mode|step ask)/);
309
+ });
310
+
311
+ test("documents the per-stepHash ask-session cache key", () => {
312
+ expect(text).toContain("getAskSessionId");
313
+ expect(lower).toMatch(/(<step ?hash>|stephash).*:ask|ask.*cache|forked.*session.*step/);
314
+ });
315
+
316
+ // ── Item 5 — Adapter-owned LLM config ───────────────────────────────
317
+ test("explains engine config.yaml is LLM-free (no providers/models)", () => {
318
+ expect(lower).toMatch(/engine.*(config|llm-free|llm free)/);
319
+ expect(lower).toMatch(/no.*(provider|model|api[- ]?key)/);
320
+ });
321
+
322
+ test("shows the adapter-owned config path convention ~/.uwf/agents/<name>.yaml", () => {
323
+ expect(text).toMatch(/~\/?\.uwf\/agents\/.+\.yaml/);
324
+ });
325
+
326
+ test("shows a concrete example with provider.baseUrl, provider.apiKey, model", () => {
327
+ expect(text).toContain("baseUrl");
328
+ expect(text).toContain("apiKey");
329
+ expect(text).toMatch(/^\s*model\s*:/m);
330
+ });
331
+
332
+ test("references storageRoot from AgentContext as the way to resolve the adapter config path", () => {
333
+ expect(text).toContain("storageRoot");
334
+ expect(lower).toMatch(/(storageroot|ctx\.storageroot).*(agents\/|config|yaml)/);
335
+ });
336
+
337
+ // ── Item 6 — previousAttempts and $status: error ────────────────────
338
+ test("documents the failed-step retry path with $status: error", () => {
339
+ expect(text).toMatch(/\$status\s*:\s*["']?error["']?/);
340
+ expect(text).toContain("ErrorOutputPayload");
341
+ });
342
+
343
+ test("documents previousAttempts as CAS refs to prior failed StepNodes", () => {
344
+ expect(text).toContain("previousAttempts");
345
+ expect(lower).toMatch(/previousattempts.*(failed|prior|retry).*step/);
346
+ });
347
+
348
+ test("explains thread head is NOT advanced on isError=true", () => {
349
+ expect(lower).toMatch(/(head|thread).*not.*advance|advance.*not|isError.*true/);
350
+ });
351
+
352
+ test("documents the @uwf/thread-failed variable for tracking failed attempts across runs", () => {
353
+ expect(text).toContain("@uwf/thread-failed/");
354
+ });
355
+
356
+ test("explains MAX_FRONTMATTER_RETRIES (2) before persisting the error step", () => {
357
+ expect(text).toMatch(/2\s*(retries?|attempts?|frontmatter)/i);
358
+ });
359
+
360
+ // ── Item 7 — Realistic run() skeleton ───────────────────────────────
361
+ test("Quick Start run() builds prompt via helpers (not empty comments)", () => {
362
+ expect(text).toMatch(/buildRolePrompt|buildContinuationPrompt|buildThreadProgress/);
363
+ });
364
+
365
+ test("Quick Start run() returns all 5 AgentRunResult fields", () => {
366
+ expect(text).toContain("assembledPrompt");
367
+ expect(text).toContain("usage");
368
+ expect(text).toContain("detailHash");
369
+ expect(text).toContain("sessionId");
370
+ });
371
+
372
+ test("documents Usage type fields turns/inputTokens/outputTokens/duration", () => {
373
+ expect(text).toContain("inputTokens");
374
+ expect(text).toContain("outputTokens");
375
+ expect(text).toMatch(/turns/);
376
+ expect(text).toContain("duration");
377
+ });
378
+
379
+ test("Quick Start example does NOT contain the placeholder stub `// 1. Build your prompt from ctx`", () => {
380
+ expect(text).not.toMatch(/\/\/\s*1\.\s*Build your prompt from ctx\b/);
381
+ });
382
+
383
+ // ── Item 8 — isFirstVisit ───────────────────────────────────────────
384
+ test("explains isFirstVisit semantics", () => {
385
+ expect(text).toContain("isFirstVisit");
386
+ expect(lower).toMatch(/isfirstvisit.*(true|false).*(role|appeared|run|history)/);
387
+ });
388
+
389
+ test("explains the first-visit / re-entry branching pattern", () => {
390
+ expect(lower).toMatch(/(first[- ]?visit|isfirstvisit)[\s\S]*(re-?entry|resume)/);
391
+ });
392
+
393
+ // ── Item 9 — Fast path jargon explained ─────────────────────────────
394
+ test("introduces frontmatter extraction concept before the symbol name", () => {
395
+ const idxConcept = lower.search(
396
+ /frontmatter extraction|extract.*frontmatter|parse.*frontmatter/,
397
+ );
398
+ const idxSymbol = text.indexOf("tryFrontmatterFastPath");
399
+ if (idxSymbol !== -1) {
400
+ expect(idxConcept).toBeGreaterThanOrEqual(0);
401
+ expect(idxConcept).toBeLessThan(idxSymbol);
402
+ }
403
+ });
404
+
405
+ test("does not use the bare term 'fast path' without an explanation in the surrounding 200 chars", () => {
406
+ const re = /fast[- ]?path/gi;
407
+ let m: RegExpExecArray | null = re.exec(text);
408
+ while (m !== null) {
409
+ const window = text.slice(Math.max(0, m.index - 200), m.index + 200).toLowerCase();
410
+ expect(window).toMatch(/extract|parse|attempt|try|interpret/);
411
+ m = re.exec(text);
412
+ }
413
+ });
414
+
415
+ // ── Item 10 — No undefined schema variables ─────────────────────────
416
+ test("does not reference an undefined `textSchema` in the code samples", () => {
417
+ const idx = text.indexOf("textSchema");
418
+ if (idx !== -1) {
419
+ const window = text.slice(Math.max(0, idx - 200), idx + 200);
420
+ expect(window).toMatch(/registerAgentSchemas|schemas\.text|putSchema|TEXT_SCHEMA/);
421
+ }
422
+ });
423
+
424
+ test("does not reference an undefined `detailSchema` in the code samples", () => {
425
+ const idx = text.indexOf("detailSchema");
426
+ if (idx !== -1) {
427
+ const window = text.slice(Math.max(0, idx - 200), idx + 200);
428
+ expect(window).toMatch(/registerAgentSchemas|schemas|putSchema/);
429
+ }
430
+ });
431
+
432
+ test("Storing Session Detail section uses real APIs (storeBuiltinDetail / storeClaudeCodeDetail or store.cas.put with a registered schema)", () => {
433
+ expect(text).toMatch(
434
+ /store\.cas\.put|storeBuiltinDetail|storeClaudeCodeDetail|registerAgentSchemas/,
435
+ );
436
+ });
437
+
438
+ // ── Cross-cutting structural tests ──────────────────────────────────
439
+ test("AdapterOutput JSON envelope (not just step hash) is documented as the stdout contract", () => {
440
+ expect(text).toContain("AdapterOutput");
441
+ expect(lower).toMatch(/json.*stdout|stdout.*json/);
442
+ expect(text).toContain("isError");
443
+ expect(text).toContain("errorMessage");
444
+ });
445
+
446
+ test("documents AgentContext storageRoot and casDir fields", () => {
447
+ expect(text).toContain("storageRoot");
448
+ expect(text).toContain("casDir");
449
+ });
450
+
451
+ test("documents UWF_HOME / OCAS_HOME env propagation from CLI to adapter", () => {
452
+ expect(text).toContain("UWF_HOME");
453
+ expect(text).toContain("OCAS_HOME");
454
+ });
455
+
456
+ test("Existing Adapters table still lists hermes, builtin, claude-code", () => {
457
+ expect(text).toContain("uwf-hermes");
458
+ expect(text).toContain("uwf-builtin");
459
+ expect(text).toContain("uwf-claude-code");
460
+ });
461
+
462
+ test("Checklist now includes fork, cleanup, $SUSPEND, and adapter-owned LLM config items", () => {
463
+ const checklistIdx = text.search(/##\s+Checklist/);
464
+ expect(checklistIdx).toBeGreaterThan(-1);
465
+ const checklist = text.slice(checklistIdx);
466
+ expect(checklist).toContain("fork");
467
+ expect(checklist).toContain("cleanup");
468
+ expect(checklist).toContain("$SUSPEND");
469
+ expect(checklist.toLowerCase()).toMatch(/llm config|agents\/.+\.yaml|adapter-owned/);
470
+ });
471
+ });
472
+
473
+ // Skip: pure documentation content assertions on reference text.
474
+ describe.skip("prompt workflow-authoring — issue #226 edge location field", () => {
475
+ const text = cmdPromptWorkflowAuthoring();
476
+ const lower = text.toLowerCase();
477
+
478
+ // ── Group 1 — Field documentation ───────────────────────────────────
479
+ test("documents the location field on graph edges", () => {
480
+ expect(text).toMatch(/^\s*\|\s*`?location`?\s*\|/m);
481
+ expect(text).toMatch(/location[\s\S]{0,200}(working directory|cwd)/i);
482
+ });
483
+
484
+ test("documents location as optional with null fallback", () => {
485
+ expect(lower).toMatch(/location[\s\S]{0,300}(null|omitted|optional|default|fall(s| ?back))/i);
486
+ expect(lower).toMatch(/(thread.*cwd|start.*cwd|creation cwd|thread['']?s cwd)/);
487
+ });
488
+
489
+ test("documents Liquid template support for location", () => {
490
+ expect(text).toMatch(/location[\s\S]{0,400}\{\{\s*[a-zA-Z_]\w*\s*\}\}/);
491
+ expect(lower).toMatch(/location[\s\S]{0,300}liquid/);
492
+ });
493
+
494
+ // ── Group 2 — Inheritance chain ─────────────────────────────────────
495
+ test("documents the cwd inheritance chain end-to-end", () => {
496
+ expect(text).toContain("--cwd");
497
+ expect(text).toMatch(/StartNodePayload\.cwd|start(\.|node\.)?cwd|thread start cwd/i);
498
+ expect(text).toMatch(/Target\.location|edge\s+location|location\s+(field|override)/i);
499
+ expect(text).toMatch(/StepRecord\.cwd|StepNodePayload\.cwd|step(\.|node\.)?cwd|step.*cwd/i);
500
+ const flagIdx = text.indexOf("--cwd");
501
+ const startIdx = text.search(/StartNodePayload\.cwd|start(\.|node\.)?cwd|thread start cwd/i);
502
+ const locIdx = text.search(/Target\.location|edge\s+location|location\s+(field|override)/i);
503
+ const stepIdx = text.search(/StepRecord\.cwd|StepNodePayload\.cwd|step(\.|node\.)?cwd/i);
504
+ expect(flagIdx).toBeGreaterThanOrEqual(0);
505
+ expect(startIdx).toBeGreaterThan(flagIdx);
506
+ expect(locIdx).toBeGreaterThan(startIdx);
507
+ expect(stepIdx).toBeGreaterThan(locIdx);
508
+ });
509
+
510
+ test("explains location override is per-step (not per-thread)", () => {
511
+ expect(lower).toMatch(/(each|per).?step|override.*per.*step|step['']?s (working|cwd)/);
512
+ });
513
+
514
+ // ── Group 3 — Realistic cross-cwd example ───────────────────────────
515
+ test("includes a YAML example showing location on an edge", () => {
516
+ const yamlBlocks = text.match(/```yaml[\s\S]*?```/g) ?? [];
517
+ const hasLocationEdge = yamlBlocks.some(
518
+ (b) => /graph\s*:/.test(b) && /^\s*location\s*:/m.test(b),
519
+ );
520
+ expect(hasLocationEdge).toBe(true);
521
+ });
522
+
523
+ test("example demonstrates cross-cwd execution with a Liquid-templated path", () => {
524
+ const yamlBlocks = text.match(/```yaml[\s\S]*?```/g) ?? [];
525
+ const hasCrossCwdExample = yamlBlocks.some((b) =>
526
+ /location\s*:\s*['"]?\{\{\s*[a-zA-Z_]\w*\s*\}\}/m.test(b),
527
+ );
528
+ expect(hasCrossCwdExample).toBe(true);
529
+ });
530
+
531
+ test("example narrates a realistic scenario", () => {
532
+ expect(lower).toMatch(
533
+ /(clone|checkout|dispatch|cross[- ]repo|different (repo|directory|working directory|cwd))/,
534
+ );
535
+ });
536
+
537
+ // ── Group 4 — Structural placement ──────────────────────────────────
538
+ test("location documentation appears under the Graph Routing section", () => {
539
+ const graphIdx = text.indexOf("## Graph Routing");
540
+ expect(graphIdx).toBeGreaterThanOrEqual(0);
541
+ const after = text.slice(graphIdx);
542
+ const localLocIdx = after.search(/\blocation\b/i);
543
+ expect(localLocIdx).toBeGreaterThanOrEqual(0);
544
+ const nextHeadingIdx = after.slice(1).search(/\n## /);
545
+ expect(localLocIdx).toBeLessThan(nextHeadingIdx === -1 ? after.length : nextHeadingIdx + 1);
546
+ });
547
+
548
+ test("Target field table still includes role and prompt alongside location", () => {
549
+ expect(text).toMatch(/\|\s*`?role`?\s*\|/m);
550
+ expect(text).toMatch(/\|\s*`?prompt`?\s*\|/m);
551
+ expect(text).toMatch(/\|\s*`?location`?\s*\|/m);
552
+ });
553
+ });
@@ -7,8 +7,10 @@ import { resolveHeadHash } from "../commands/shared.js";
7
7
  import { completeThread, createUwfStore, setThread } from "../store.js";
8
8
 
9
9
  let tmpDir: string;
10
+ let savedOcasHome: string | undefined;
10
11
 
11
12
  beforeEach(async () => {
13
+ savedOcasHome = process.env.OCAS_HOME;
12
14
  tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-resolve-head-"));
13
15
  const casDir = join(tmpDir, "cas");
14
16
  await mkdir(casDir, { recursive: true });
@@ -16,6 +18,11 @@ beforeEach(async () => {
16
18
  });
17
19
 
18
20
  afterEach(async () => {
21
+ if (savedOcasHome === undefined) {
22
+ delete process.env.OCAS_HOME;
23
+ } else {
24
+ process.env.OCAS_HOME = savedOcasHome;
25
+ }
19
26
  await rm(tmpDir, { recursive: true, force: true });
20
27
  });
21
28
 
@@ -37,7 +44,7 @@ describe("resolveHeadHash", () => {
37
44
  const uwf = await createUwfStore(tmpDir);
38
45
  const headHash = (await uwf.store.cas.put(uwf.schemas.text, "completed-head")) as CasRef;
39
46
  setThread(uwf.varStore, threadId, createThreadIndexEntry(headHash));
40
- completeThread(uwf.varStore, threadId, "completed");
47
+ completeThread(uwf.varStore, threadId, "end");
41
48
 
42
49
  const result = await resolveHeadHash(tmpDir, threadId);
43
50
 
@@ -71,13 +78,13 @@ describe("resolveHeadHash", () => {
71
78
  const hash3 = (await uwf.store.cas.put(uwf.schemas.text, "hash-thread3")) as CasRef;
72
79
 
73
80
  setThread(uwf.varStore, threadId1, createThreadIndexEntry(hash1));
74
- completeThread(uwf.varStore, threadId1, "completed");
81
+ completeThread(uwf.varStore, threadId1, "end");
75
82
 
76
83
  setThread(uwf.varStore, threadId2, createThreadIndexEntry(hash2));
77
- completeThread(uwf.varStore, threadId2, "completed");
84
+ completeThread(uwf.varStore, threadId2, "end");
78
85
 
79
86
  setThread(uwf.varStore, threadId3, createThreadIndexEntry(hash3));
80
- completeThread(uwf.varStore, threadId3, "completed");
87
+ completeThread(uwf.varStore, threadId3, "end");
81
88
 
82
89
  const result = await resolveHeadHash(tmpDir, threadId2);
83
90
 
@@ -59,7 +59,7 @@ describe("_printAgentMenu", () => {
59
59
 
60
60
  // ─── cmdSetup agent config ───────────────────────────────────────────────────
61
61
 
62
- describe("cmdSetup agent configuration", () => {
62
+ describe("cmdSetup agent configuration (engine config is LLM-free, issue #143)", () => {
63
63
  let storageRoot: string;
64
64
 
65
65
  beforeEach(async () => {
@@ -71,33 +71,8 @@ describe("cmdSetup agent configuration", () => {
71
71
  await rm(storageRoot, { recursive: true, force: true });
72
72
  });
73
73
 
74
- const baseArgs = () => ({
75
- provider: "testprovider",
76
- baseUrl: "https://api.test.com/v1",
77
- apiKey: "sk-test",
78
- model: "test-model",
79
- storageRoot,
80
- });
81
-
82
- test("defaults to hermes agent when no agent specified", async () => {
83
- vi.spyOn(globalThis, "fetch").mockResolvedValue(
84
- new Response(JSON.stringify({}), { status: 200 }),
85
- );
86
-
87
- const result = await cmdSetup(baseArgs());
88
-
89
- expect(result.defaultAgent).toBe("hermes");
90
- const config = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
91
- expect(config.agents.hermes).toEqual({ command: "uwf-hermes", args: [] });
92
- expect(config.defaultAgent).toBe("hermes");
93
- });
94
-
95
74
  test("writes specified agent as default", async () => {
96
- vi.spyOn(globalThis, "fetch").mockResolvedValue(
97
- new Response(JSON.stringify({}), { status: 200 }),
98
- );
99
-
100
- const result = await cmdSetup({ ...baseArgs(), agent: "claude-code" });
75
+ const result = await cmdSetup({ agent: "claude-code", storageRoot });
101
76
 
102
77
  expect(result.defaultAgent).toBe("claude-code");
103
78
  const config = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
@@ -106,14 +81,8 @@ describe("cmdSetup agent configuration", () => {
106
81
  });
107
82
 
108
83
  test("preserves existing agents when adding new one", async () => {
109
- vi.spyOn(globalThis, "fetch").mockResolvedValue(
110
- new Response(JSON.stringify({}), { status: 200 }),
111
- );
112
-
113
- // First setup with hermes
114
- await cmdSetup(baseArgs());
115
- // Second setup with claude-code
116
- await cmdSetup({ ...baseArgs(), agent: "claude-code" });
84
+ await cmdSetup({ agent: "hermes", storageRoot });
85
+ await cmdSetup({ agent: "claude-code", storageRoot });
117
86
 
118
87
  const config = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
119
88
  expect(config.agents.hermes).toBeDefined();
@@ -122,25 +91,17 @@ describe("cmdSetup agent configuration", () => {
122
91
  });
123
92
 
124
93
  test("updates defaultAgent on re-run with different agent", async () => {
125
- vi.spyOn(globalThis, "fetch").mockResolvedValue(
126
- new Response(JSON.stringify({}), { status: 200 }),
127
- );
128
-
129
- await cmdSetup(baseArgs());
94
+ await cmdSetup({ agent: "hermes", storageRoot });
130
95
  const config1 = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
131
96
  expect(config1.defaultAgent).toBe("hermes");
132
97
 
133
- await cmdSetup({ ...baseArgs(), agent: "builtin" });
98
+ await cmdSetup({ agent: "builtin", storageRoot });
134
99
  const config2 = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
135
100
  expect(config2.defaultAgent).toBe("builtin");
136
101
  });
137
102
 
138
103
  test("normalizes agent name with uwf- prefix to bare name", async () => {
139
- vi.spyOn(globalThis, "fetch").mockResolvedValue(
140
- new Response(JSON.stringify({}), { status: 200 }),
141
- );
142
-
143
- const result = await cmdSetup({ ...baseArgs(), agent: "uwf-hermes" });
104
+ const result = await cmdSetup({ agent: "uwf-hermes", storageRoot });
144
105
 
145
106
  expect(result.defaultAgent).toBe("hermes");
146
107
  const config = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
@@ -151,11 +112,7 @@ describe("cmdSetup agent configuration", () => {
151
112
  });
152
113
 
153
114
  test("normalizes uwf-claude-code to claude-code", async () => {
154
- vi.spyOn(globalThis, "fetch").mockResolvedValue(
155
- new Response(JSON.stringify({}), { status: 200 }),
156
- );
157
-
158
- const result = await cmdSetup({ ...baseArgs(), agent: "uwf-claude-code" });
115
+ const result = await cmdSetup({ agent: "uwf-claude-code", storageRoot });
159
116
 
160
117
  expect(result.defaultAgent).toBe("claude-code");
161
118
  const config = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
@@ -164,4 +121,22 @@ describe("cmdSetup agent configuration", () => {
164
121
  // Verify no duplicate uwf- prefix
165
122
  expect(config.agents["uwf-claude-code"]).toBeUndefined();
166
123
  });
124
+
125
+ test("rewrite drops legacy provider/model fields from existing config", async () => {
126
+ // First create a config that contains legacy LLM fields
127
+ const { writeFileSync, mkdirSync } = await import("node:fs");
128
+ mkdirSync(storageRoot, { recursive: true });
129
+ writeFileSync(
130
+ join(storageRoot, "config.yaml"),
131
+ "providers:\n openai: { baseUrl: x, apiKey: y }\nmodels:\n default: { provider: openai, name: gpt-4o }\ndefaultModel: default\nagents:\n hermes: { command: uwf-hermes, args: [] }\ndefaultAgent: hermes\n",
132
+ "utf8",
133
+ );
134
+ await cmdSetup({ agent: "hermes", storageRoot });
135
+ const config = parse(readFileSync(join(storageRoot, "config.yaml"), "utf8"));
136
+ expect(config.providers).toBeUndefined();
137
+ expect(config.models).toBeUndefined();
138
+ expect(config.defaultModel).toBeUndefined();
139
+ expect(config.agents.hermes).toEqual({ command: "uwf-hermes", args: [] });
140
+ expect(config.defaultAgent).toBe("hermes");
141
+ });
167
142
  });