@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,381 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, test, vi } from "vitest";
5
+ import {
6
+ _discoverAgents,
7
+ _isBackspace,
8
+ _isTerminator,
9
+ _parseWhichOutput,
10
+ _printModelMenu,
11
+ _printProviderMenu,
12
+ _printValidationResult,
13
+ _resolveModelChoice,
14
+ _resolveProviderChoice,
15
+ _searchPathDirs,
16
+ } from "../commands/setup.js";
17
+
18
+ // ──────────────────────────────────────────────────────────────────────────────
19
+ // 1a. _searchPathDirs
20
+ // ──────────────────────────────────────────────────────────────────────────────
21
+
22
+ describe("_searchPathDirs", () => {
23
+ test("returns empty array for empty PATH", async () => {
24
+ const result = await _searchPathDirs("");
25
+ expect(result).toEqual([]);
26
+ });
27
+
28
+ test("finds uwf-hermes in a single dir", async () => {
29
+ const dir = mkdirSync(join(tmpdir(), `uwf-test-${Date.now()}`), { recursive: true }) as
30
+ | string
31
+ | undefined;
32
+ const actualDir = dir ?? join(tmpdir(), `uwf-test-${Date.now()}`);
33
+ mkdirSync(actualDir, { recursive: true });
34
+ const filePath = join(actualDir, "uwf-hermes");
35
+ writeFileSync(filePath, "#!/bin/sh\n", { mode: 0o755 });
36
+ const result = await _searchPathDirs(actualDir);
37
+ expect(result).toContain("uwf-hermes");
38
+ });
39
+
40
+ test("skips non-uwf- prefixed binaries", async () => {
41
+ const dir = join(tmpdir(), `uwf-test-${Date.now()}-2`);
42
+ mkdirSync(dir, { recursive: true });
43
+ writeFileSync(join(dir, "hermes"), "#!/bin/sh\n", { mode: 0o755 });
44
+ writeFileSync(join(dir, "uwf-hermes"), "#!/bin/sh\n", { mode: 0o755 });
45
+ const result = await _searchPathDirs(dir);
46
+ expect(result).toEqual(["uwf-hermes"]);
47
+ });
48
+
49
+ test("skips entry named exactly 'uwf'", async () => {
50
+ const dir = join(tmpdir(), `uwf-test-${Date.now()}-3`);
51
+ mkdirSync(dir, { recursive: true });
52
+ writeFileSync(join(dir, "uwf"), "#!/bin/sh\n", { mode: 0o755 });
53
+ writeFileSync(join(dir, "uwf-hermes"), "#!/bin/sh\n", { mode: 0o755 });
54
+ const result = await _searchPathDirs(dir);
55
+ expect(result).toEqual(["uwf-hermes"]);
56
+ });
57
+
58
+ test("skips non-executable files", async () => {
59
+ const dir = join(tmpdir(), `uwf-test-${Date.now()}-4`);
60
+ mkdirSync(dir, { recursive: true });
61
+ writeFileSync(join(dir, "uwf-foo"), "#!/bin/sh\n", { mode: 0o644 });
62
+ const result = await _searchPathDirs(dir);
63
+ expect(result).toEqual([]);
64
+ });
65
+
66
+ test("deduplicates across PATH dirs", async () => {
67
+ const dir1 = join(tmpdir(), `uwf-test-${Date.now()}-5a`);
68
+ const dir2 = join(tmpdir(), `uwf-test-${Date.now()}-5b`);
69
+ mkdirSync(dir1, { recursive: true });
70
+ mkdirSync(dir2, { recursive: true });
71
+ writeFileSync(join(dir1, "uwf-hermes"), "#!/bin/sh\n", { mode: 0o755 });
72
+ writeFileSync(join(dir2, "uwf-hermes"), "#!/bin/sh\n", { mode: 0o755 });
73
+ const result = await _searchPathDirs(`${dir1}:${dir2}`);
74
+ expect(result).toEqual(["uwf-hermes"]);
75
+ });
76
+
77
+ test("returns sorted array", async () => {
78
+ const dir = join(tmpdir(), `uwf-test-${Date.now()}-6`);
79
+ mkdirSync(dir, { recursive: true });
80
+ writeFileSync(join(dir, "uwf-zoo"), "#!/bin/sh\n", { mode: 0o755 });
81
+ writeFileSync(join(dir, "uwf-alpha"), "#!/bin/sh\n", { mode: 0o755 });
82
+ writeFileSync(join(dir, "uwf-mid"), "#!/bin/sh\n", { mode: 0o755 });
83
+ const result = await _searchPathDirs(dir);
84
+ expect(result).toEqual(["uwf-alpha", "uwf-mid", "uwf-zoo"]);
85
+ });
86
+
87
+ test("skips inaccessible/nonexistent directories silently", async () => {
88
+ const result = await _searchPathDirs("/nonexistent-dir-xyz-abc-12345");
89
+ expect(result).toEqual([]);
90
+ });
91
+ });
92
+
93
+ // ──────────────────────────────────────────────────────────────────────────────
94
+ // 1b. _parseWhichOutput
95
+ // ──────────────────────────────────────────────────────────────────────────────
96
+
97
+ describe("_parseWhichOutput", () => {
98
+ test("returns empty array for empty string", () => {
99
+ expect(_parseWhichOutput("")).toEqual([]);
100
+ });
101
+
102
+ test("parses single path", () => {
103
+ expect(_parseWhichOutput("/usr/local/bin/uwf-hermes")).toEqual(["uwf-hermes"]);
104
+ });
105
+
106
+ test("parses multiple paths", () => {
107
+ expect(_parseWhichOutput("/usr/local/bin/uwf-hermes\n/usr/bin/uwf-claude-code")).toEqual([
108
+ "uwf-claude-code",
109
+ "uwf-hermes",
110
+ ]);
111
+ });
112
+
113
+ test("deduplicates identical basenames from different dirs", () => {
114
+ expect(_parseWhichOutput("/a/uwf-hermes\n/b/uwf-hermes")).toEqual(["uwf-hermes"]);
115
+ });
116
+
117
+ test("skips blank lines", () => {
118
+ expect(_parseWhichOutput("/a/uwf-hermes\n\n/b/uwf-cursor")).toEqual([
119
+ "uwf-cursor",
120
+ "uwf-hermes",
121
+ ]);
122
+ });
123
+
124
+ test("skips entry named exactly 'uwf'", () => {
125
+ expect(_parseWhichOutput("/usr/bin/uwf")).toEqual([]);
126
+ });
127
+
128
+ test("skips basenames not starting with uwf-", () => {
129
+ expect(_parseWhichOutput("/usr/bin/node")).toEqual([]);
130
+ });
131
+
132
+ test("returns sorted array", () => {
133
+ expect(_parseWhichOutput("/a/uwf-zoo\n/a/uwf-alpha")).toEqual(["uwf-alpha", "uwf-zoo"]);
134
+ });
135
+ });
136
+
137
+ // ──────────────────────────────────────────────────────────────────────────────
138
+ // 2a. _isTerminator
139
+ // ──────────────────────────────────────────────────────────────────────────────
140
+
141
+ describe("_isTerminator", () => {
142
+ test("\\n is a terminator", () => {
143
+ expect(_isTerminator("\n")).toBe(true);
144
+ });
145
+ test("\\r is a terminator", () => {
146
+ expect(_isTerminator("\r")).toBe(true);
147
+ });
148
+ test("\\u0004 (EOT) is a terminator", () => {
149
+ expect(_isTerminator("")).toBe(true);
150
+ });
151
+ test("regular char is not a terminator", () => {
152
+ expect(_isTerminator("a")).toBe(false);
153
+ });
154
+ test("empty string is not a terminator", () => {
155
+ expect(_isTerminator("")).toBe(false);
156
+ });
157
+ });
158
+
159
+ // ──────────────────────────────────────────────────────────────────────────────
160
+ // 2b. _isBackspace
161
+ // ──────────────────────────────────────────────────────────────────────────────
162
+
163
+ describe("_isBackspace", () => {
164
+ test("\\u007F is a backspace", () => {
165
+ expect(_isBackspace("")).toBe(true);
166
+ });
167
+ test("\\b is a backspace", () => {
168
+ expect(_isBackspace("\b")).toBe(true);
169
+ });
170
+ test("regular char is not a backspace", () => {
171
+ expect(_isBackspace("x")).toBe(false);
172
+ });
173
+ });
174
+
175
+ // ──────────────────────────────────────────────────────────────────────────────
176
+ // 3a. _printProviderMenu
177
+ // ──────────────────────────────────────────────────────────────────────────────
178
+
179
+ describe("_printProviderMenu", () => {
180
+ afterEach(() => {
181
+ vi.restoreAllMocks();
182
+ });
183
+
184
+ const providers = [
185
+ { name: "openai", label: "OpenAI", baseUrl: "https://api.openai.com/v1" },
186
+ { name: "xai", label: "xAI", baseUrl: "https://api.x.ai/v1" },
187
+ ] as const;
188
+
189
+ test("prints correct number of lines (one per provider + custom)", () => {
190
+ const lines: string[] = [];
191
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
192
+ lines.push(msg);
193
+ });
194
+ _printProviderMenu(providers);
195
+ // 2 providers + 1 custom = 3 lines
196
+ expect(lines.length).toBe(3);
197
+ });
198
+
199
+ test("custom option number = providers.length + 1", () => {
200
+ const lines: string[] = [];
201
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
202
+ lines.push(msg);
203
+ });
204
+ _printProviderMenu(providers);
205
+ const lastLine = lines[lines.length - 1] ?? "";
206
+ expect(lastLine).toMatch(/3\)/);
207
+ });
208
+
209
+ test("each provider line contains its label and baseUrl", () => {
210
+ const lines: string[] = [];
211
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
212
+ lines.push(msg);
213
+ });
214
+ _printProviderMenu(providers);
215
+ expect(lines[0]).toContain("OpenAI");
216
+ expect(lines[0]).toContain("https://api.openai.com/v1");
217
+ expect(lines[1]).toContain("xAI");
218
+ expect(lines[1]).toContain("https://api.x.ai/v1");
219
+ });
220
+ });
221
+
222
+ // ──────────────────────────────────────────────────────────────────────────────
223
+ // 3b. _resolveProviderChoice
224
+ // ──────────────────────────────────────────────────────────────────────────────
225
+
226
+ describe("_resolveProviderChoice", () => {
227
+ const providers = [
228
+ { name: "openai", label: "OpenAI", baseUrl: "https://api.openai.com/v1" },
229
+ { name: "xai", label: "xAI", baseUrl: "https://api.x.ai/v1" },
230
+ { name: "deepseek", label: "DeepSeek", baseUrl: "https://api.deepseek.com/v1" },
231
+ ] as const;
232
+
233
+ test("valid index 1 returns first provider", () => {
234
+ const result = _resolveProviderChoice("1", providers);
235
+ expect(result).toEqual({ providerName: "openai", baseUrl: "https://api.openai.com/v1" });
236
+ });
237
+
238
+ test("valid index N (last preset) returns last provider", () => {
239
+ const result = _resolveProviderChoice("3", providers);
240
+ expect(result).toEqual({ providerName: "deepseek", baseUrl: "https://api.deepseek.com/v1" });
241
+ });
242
+
243
+ test("index providers.length+1 (custom) returns null", () => {
244
+ const result = _resolveProviderChoice("4", providers);
245
+ expect(result).toBeNull();
246
+ });
247
+
248
+ test("non-numeric string returns null", () => {
249
+ expect(_resolveProviderChoice("abc", providers)).toBeNull();
250
+ });
251
+
252
+ test("0 returns null (out of range)", () => {
253
+ expect(_resolveProviderChoice("0", providers)).toBeNull();
254
+ });
255
+
256
+ test("N+2 returns null (out of range)", () => {
257
+ expect(_resolveProviderChoice("5", providers)).toBeNull();
258
+ });
259
+
260
+ test("negative number returns null", () => {
261
+ expect(_resolveProviderChoice("-1", providers)).toBeNull();
262
+ });
263
+ });
264
+
265
+ // ──────────────────────────────────────────────────────────────────────────────
266
+ // 3c. _resolveModelChoice
267
+ // ──────────────────────────────────────────────────────────────────────────────
268
+
269
+ describe("_resolveModelChoice", () => {
270
+ test("numeric input within range returns model at that index", () => {
271
+ expect(_resolveModelChoice("2", ["a", "b", "c"])).toBe("b");
272
+ });
273
+
274
+ test("numeric input out of range returns input as-is", () => {
275
+ expect(_resolveModelChoice("5", ["a"])).toBe("5");
276
+ });
277
+
278
+ test("non-numeric input returns input as-is", () => {
279
+ expect(_resolveModelChoice("gpt-4o", ["a", "b"])).toBe("gpt-4o");
280
+ });
281
+
282
+ test("numeric input 1 returns first model", () => {
283
+ expect(_resolveModelChoice("1", ["alpha", "beta"])).toBe("alpha");
284
+ });
285
+
286
+ test("empty models list with numeric input returns input as-is", () => {
287
+ expect(_resolveModelChoice("1", [])).toBe("1");
288
+ });
289
+ });
290
+
291
+ // ──────────────────────────────────────────────────────────────────────────────
292
+ // 3d. _printModelMenu
293
+ // ──────────────────────────────────────────────────────────────────────────────
294
+
295
+ describe("_printModelMenu", () => {
296
+ afterEach(() => {
297
+ vi.restoreAllMocks();
298
+ });
299
+
300
+ test("prints all models — each model name appears in output", () => {
301
+ const output: string[] = [];
302
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
303
+ output.push(msg);
304
+ });
305
+ const models = ["model-a", "model-b", "model-c"];
306
+ _printModelMenu(models, 100);
307
+ const combined = output.join("\n");
308
+ for (const m of models) {
309
+ expect(combined).toContain(m);
310
+ }
311
+ });
312
+
313
+ test("single column when termCols is very small", () => {
314
+ const output: string[] = [];
315
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
316
+ output.push(msg);
317
+ });
318
+ _printModelMenu(["a", "b", "c"], 1);
319
+ // Each model on its own row → 3 lines
320
+ expect(output.length).toBe(3);
321
+ });
322
+
323
+ test("wide terminal fits multiple columns", () => {
324
+ const output: string[] = [];
325
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
326
+ output.push(msg);
327
+ });
328
+ const models = Array.from({ length: 6 }, (_, i) => `m${i}`);
329
+ _printModelMenu(models, 200);
330
+ // With wide terminal and short names, should fit in fewer than 6 rows
331
+ expect(output.length).toBeLessThan(6);
332
+ });
333
+ });
334
+
335
+ // ──────────────────────────────────────────────────────────────────────────────
336
+ // 3e. _printValidationResult
337
+ // ──────────────────────────────────────────────────────────────────────────────
338
+
339
+ describe("_printValidationResult", () => {
340
+ afterEach(() => {
341
+ vi.restoreAllMocks();
342
+ });
343
+
344
+ test("ok=true prints success message containing '✓'", () => {
345
+ const lines: string[] = [];
346
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
347
+ lines.push(msg);
348
+ });
349
+ _printValidationResult({ ok: true, error: null });
350
+ expect(lines.join("\n")).toContain("✓");
351
+ });
352
+
353
+ test("ok=false prints warning message containing '⚠'", () => {
354
+ const lines: string[] = [];
355
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
356
+ lines.push(msg);
357
+ });
358
+ _printValidationResult({ ok: false, error: "HTTP 401" });
359
+ expect(lines.join("\n")).toContain("⚠");
360
+ });
361
+
362
+ test("ok=false includes the error string in output", () => {
363
+ const lines: string[] = [];
364
+ vi.spyOn(console, "log").mockImplementation((msg: string) => {
365
+ lines.push(msg);
366
+ });
367
+ _printValidationResult({ ok: false, error: "HTTP 401" });
368
+ expect(lines.join("\n")).toContain("HTTP 401");
369
+ });
370
+ });
371
+
372
+ // ──────────────────────────────────────────────────────────────────────────────
373
+ // 4. Regression
374
+ // ──────────────────────────────────────────────────────────────────────────────
375
+
376
+ describe("_discoverAgents regression", () => {
377
+ test("returns an array (may be empty) — never throws", async () => {
378
+ const result = await _discoverAgents();
379
+ expect(Array.isArray(result)).toBe(true);
380
+ });
381
+ });
@@ -0,0 +1,148 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
5
+ import { cmdSetup, validateModel } from "../commands/setup.js";
6
+
7
+ describe("validateModel", () => {
8
+ const BASE_URL = "https://api.example.com/v1";
9
+ const API_KEY = "sk-test-key";
10
+ const MODEL = "test-model";
11
+
12
+ afterEach(() => {
13
+ vi.restoreAllMocks();
14
+ });
15
+
16
+ test("success path — returns ok on 200", async () => {
17
+ const mockFetch = vi
18
+ .spyOn(globalThis, "fetch")
19
+ .mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
20
+
21
+ const result = await validateModel(BASE_URL, API_KEY, MODEL);
22
+
23
+ expect(result).toEqual({ ok: true, value: undefined });
24
+ expect(mockFetch).toHaveBeenCalledTimes(1);
25
+
26
+ const [url, opts] = mockFetch.mock.calls[0]!;
27
+ expect(url).toBe(`${BASE_URL}/chat/completions`);
28
+ expect((opts as RequestInit).headers).toEqual(
29
+ expect.objectContaining({ Authorization: `Bearer ${API_KEY}` }),
30
+ );
31
+ const body = JSON.parse((opts as RequestInit).body as string);
32
+ expect(body).toEqual({
33
+ model: MODEL,
34
+ messages: [{ role: "user", content: "hi" }],
35
+ max_tokens: 1,
36
+ });
37
+ });
38
+
39
+ test("HTTP 401 — returns error containing 401", async () => {
40
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(
41
+ new Response("Unauthorized", { status: 401, statusText: "Unauthorized" }),
42
+ );
43
+
44
+ const result = await validateModel(BASE_URL, API_KEY, MODEL);
45
+
46
+ expect(result.ok).toBe(false);
47
+ if (!result.ok) {
48
+ expect(result.error).toContain("401");
49
+ }
50
+ });
51
+
52
+ test("HTTP 404 — returns error containing 404", async () => {
53
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(
54
+ new Response("Not Found", { status: 404, statusText: "Not Found" }),
55
+ );
56
+
57
+ const result = await validateModel(BASE_URL, API_KEY, MODEL);
58
+
59
+ expect(result.ok).toBe(false);
60
+ if (!result.ok) {
61
+ expect(result.error).toContain("404");
62
+ }
63
+ });
64
+
65
+ test("network timeout — returns error mentioning timeout", async () => {
66
+ const err = new DOMException("signal timed out", "AbortError");
67
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(err);
68
+
69
+ const result = await validateModel(BASE_URL, API_KEY, MODEL);
70
+
71
+ expect(result.ok).toBe(false);
72
+ if (!result.ok) {
73
+ expect(result.error.toLowerCase()).toMatch(/timeout|timed out/);
74
+ }
75
+ });
76
+
77
+ test("network error (DNS/connection) — returns error mentioning connectivity", async () => {
78
+ vi.spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("fetch failed"));
79
+
80
+ const result = await validateModel(BASE_URL, API_KEY, MODEL);
81
+
82
+ expect(result.ok).toBe(false);
83
+ if (!result.ok) {
84
+ expect(result.error.toLowerCase()).toMatch(/connect|reach|network/);
85
+ }
86
+ });
87
+
88
+ test("request body correctness", async () => {
89
+ const mockFetch = vi
90
+ .spyOn(globalThis, "fetch")
91
+ .mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));
92
+
93
+ await validateModel(BASE_URL, API_KEY, "my-special-model");
94
+
95
+ const body = JSON.parse((mockFetch.mock.calls[0]![1] as RequestInit).body as string);
96
+ expect(body).toEqual({
97
+ model: "my-special-model",
98
+ messages: [{ role: "user", content: "hi" }],
99
+ max_tokens: 1,
100
+ });
101
+ });
102
+ });
103
+
104
+ describe("cmdSetup with validation", () => {
105
+ let storageRoot: string;
106
+
107
+ beforeEach(async () => {
108
+ storageRoot = await mkdtemp(join(tmpdir(), "uwf-setup-validate-"));
109
+ });
110
+
111
+ afterEach(async () => {
112
+ vi.restoreAllMocks();
113
+ await rm(storageRoot, { recursive: true, force: true });
114
+ });
115
+
116
+ const setupArgs = () => ({
117
+ provider: "testprovider",
118
+ baseUrl: "https://api.test.com/v1",
119
+ apiKey: "sk-test",
120
+ model: "test-model",
121
+ storageRoot,
122
+ });
123
+
124
+ test("includes validation result on success", async () => {
125
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(
126
+ new Response(JSON.stringify({}), { status: 200 }),
127
+ );
128
+
129
+ const result = await cmdSetup(setupArgs());
130
+
131
+ expect(result.validation).toEqual({ ok: true, value: undefined });
132
+ // Config file should still be written
133
+ expect(result.configPath).toBeTruthy();
134
+ });
135
+
136
+ test("includes validation failure — config still saved", async () => {
137
+ vi.spyOn(globalThis, "fetch").mockResolvedValue(
138
+ new Response("Unauthorized", { status: 401, statusText: "Unauthorized" }),
139
+ );
140
+
141
+ const result = await cmdSetup(setupArgs());
142
+
143
+ expect(result.validation).toBeDefined();
144
+ expect((result.validation as { ok: boolean }).ok).toBe(false);
145
+ // Config file should still be written despite validation failure
146
+ expect(result.configPath).toBeTruthy();
147
+ });
148
+ });
@@ -0,0 +1,144 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { WorkflowPayload } from "@united-workforce/protocol";
5
+ import { describe, expect, test } from "vitest";
6
+ import { parse } from "yaml";
7
+
8
+ /**
9
+ * Test: Issue #474 - tea pr create fails in git worktree directories
10
+ *
11
+ * This test verifies that the solve-issue workflow's committer role
12
+ * uses direct Gitea API calls via curl instead of tea pr create,
13
+ * which fixes the "path segment [0] is empty" error in worktree directories.
14
+ */
15
+
16
+ describe("solve-issue workflow: Gitea API PR creation", () => {
17
+ // Navigate up from packages/cli/src/__tests__ to repo root
18
+ const workflowPath = join(
19
+ dirname(fileURLToPath(import.meta.url)),
20
+ "..",
21
+ "..",
22
+ "..",
23
+ "..",
24
+ ".workflows",
25
+ "solve-issue.yaml",
26
+ );
27
+
28
+ test("committer procedure should use curl API instead of tea pr create", async () => {
29
+ const yamlContent = await readFile(workflowPath, "utf-8");
30
+ const workflow = parse(yamlContent) as WorkflowPayload;
31
+
32
+ expect(workflow.roles.committer).toBeDefined();
33
+ const committerProcedure = workflow.roles.committer?.procedure;
34
+ expect(committerProcedure).toBeDefined();
35
+
36
+ // Verify the procedure uses curl API, not tea pr create
37
+ expect(committerProcedure).toContain("curl");
38
+ expect(committerProcedure).toContain("api/v1/repos");
39
+ expect(committerProcedure).toContain("/pulls");
40
+
41
+ // Verify it explicitly warns against tea pr create
42
+ expect(committerProcedure).toMatch(/do NOT use.*tea pr create/i);
43
+ });
44
+
45
+ test("committer procedure should reference repoRemote from task prompt", async () => {
46
+ const yamlContent = await readFile(workflowPath, "utf-8");
47
+ const workflow = parse(yamlContent) as WorkflowPayload;
48
+
49
+ const committerProcedure = workflow.roles.committer?.procedure;
50
+ expect(committerProcedure).toBeDefined();
51
+
52
+ // Verify the procedure mentions repoRemote is provided in task prompt
53
+ expect(committerProcedure).toMatch(/repo remote.*provided.*task prompt/i);
54
+ expect(committerProcedure).toMatch(/owner\/repo/i);
55
+ });
56
+
57
+ test("committer procedure should include error handling for curl failures", async () => {
58
+ const yamlContent = await readFile(workflowPath, "utf-8");
59
+ const workflow = parse(yamlContent) as WorkflowPayload;
60
+
61
+ const committerProcedure = workflow.roles.committer?.procedure;
62
+ expect(committerProcedure).toBeDefined();
63
+
64
+ // Verify the procedure includes error handling guidance for curl
65
+ // This ensures we capture failures and provide actionable output
66
+ expect(committerProcedure).toMatch(/error|fail/i);
67
+ expect(committerProcedure).toContain("hook_failed");
68
+ });
69
+
70
+ test("workflow should be parseable as valid WorkflowPayload", async () => {
71
+ const yamlContent = await readFile(workflowPath, "utf-8");
72
+ const workflow = parse(yamlContent) as WorkflowPayload;
73
+
74
+ // Basic structure validation
75
+ expect(workflow.name).toBe("solve-issue");
76
+ expect(workflow.roles).toBeDefined();
77
+ expect(workflow.graph).toBeDefined();
78
+
79
+ // Verify committer role exists with required fields
80
+ expect(workflow.roles.committer).toBeDefined();
81
+ expect(workflow.roles.committer?.description).toBeDefined();
82
+ expect(workflow.roles.committer?.goal).toBeDefined();
83
+ expect(workflow.roles.committer?.procedure).toBeDefined();
84
+ expect(workflow.roles.committer?.output).toBeDefined();
85
+ expect(workflow.roles.committer?.frontmatter).toBeDefined();
86
+ });
87
+
88
+ test("committer frontmatter schema should be oneOf with $status discriminant", async () => {
89
+ const yamlContent = await readFile(workflowPath, "utf-8");
90
+ // Parse as any to access the raw YAML structure (frontmatter is inline JSON Schema in YAML)
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ const workflow = parse(yamlContent) as any;
93
+ const frontmatter = workflow.roles.committer?.frontmatter;
94
+ expect(frontmatter).toBeDefined();
95
+ expect(frontmatter?.oneOf).toBeDefined();
96
+ const committedVariant = frontmatter.oneOf.find(
97
+ (v: any) => v.properties?.$status?.const === "committed",
98
+ );
99
+ expect(committedVariant).toBeDefined();
100
+ expect(committedVariant.required).toContain("$status");
101
+ });
102
+
103
+ test("developer procedure should include mandatory verification step", async () => {
104
+ const yamlContent = await readFile(workflowPath, "utf-8");
105
+ const workflow = parse(yamlContent) as WorkflowPayload;
106
+
107
+ const developerProcedure = workflow.roles.developer?.procedure;
108
+ expect(developerProcedure).toBeDefined();
109
+
110
+ // Verify the procedure includes mandatory verification step
111
+ expect(developerProcedure).toContain("MANDATORY VERIFICATION");
112
+ expect(developerProcedure).toContain("git branch --show-current");
113
+ expect(developerProcedure).toContain("git status");
114
+ expect(developerProcedure).toMatch(/ls -la|verify.*exist/i);
115
+ });
116
+
117
+ test("reviewer procedure should enforce worktree path verification", async () => {
118
+ const yamlContent = await readFile(workflowPath, "utf-8");
119
+ const workflow = parse(yamlContent) as WorkflowPayload;
120
+
121
+ const reviewerProcedure = workflow.roles.reviewer?.procedure;
122
+ expect(reviewerProcedure).toBeDefined();
123
+
124
+ // Verify the procedure includes critical enforcement
125
+ expect(reviewerProcedure).toContain("CRITICAL");
126
+ expect(reviewerProcedure).toMatch(/cd.*pwd/);
127
+ expect(reviewerProcedure).toContain(
128
+ "Do NOT report results without running the actual commands",
129
+ );
130
+ });
131
+
132
+ test("developer procedure should include test debugging escalation", async () => {
133
+ const yamlContent = await readFile(workflowPath, "utf-8");
134
+ const workflow = parse(yamlContent) as WorkflowPayload;
135
+
136
+ const developerProcedure = workflow.roles.developer?.procedure;
137
+ expect(developerProcedure).toBeDefined();
138
+
139
+ // Verify the procedure includes test failure guidance
140
+ expect(developerProcedure).toMatch(/tests fail.*first run/i);
141
+ expect(developerProcedure).toMatch(/3 test cycles|after 3 attempts/i);
142
+ expect(developerProcedure).toContain("$status=failed");
143
+ });
144
+ });