@proletariat/cli 0.3.16 → 0.3.18

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 (338) hide show
  1. package/dist/commands/action/create.d.ts +1 -0
  2. package/dist/commands/action/create.js +74 -38
  3. package/dist/commands/action/delete.d.ts +1 -0
  4. package/dist/commands/action/delete.js +23 -24
  5. package/dist/commands/action/index.d.ts +1 -0
  6. package/dist/commands/action/index.js +5 -10
  7. package/dist/commands/action/list.d.ts +1 -0
  8. package/dist/commands/action/list.js +3 -1
  9. package/dist/commands/action/run.d.ts +1 -0
  10. package/dist/commands/action/run.js +44 -32
  11. package/dist/commands/action/show.d.ts +2 -0
  12. package/dist/commands/action/update.d.ts +1 -0
  13. package/dist/commands/action/update.js +80 -39
  14. package/dist/commands/agent/auth.d.ts +2 -0
  15. package/dist/commands/agent/auth.js +44 -3
  16. package/dist/commands/agent/discover.d.ts +2 -0
  17. package/dist/commands/agent/discover.js +35 -3
  18. package/dist/commands/agent/index.d.ts +1 -0
  19. package/dist/commands/agent/index.js +25 -45
  20. package/dist/commands/agent/list.d.ts +8 -3
  21. package/dist/commands/agent/list.js +16 -29
  22. package/dist/commands/agent/login.d.ts +1 -0
  23. package/dist/commands/agent/login.js +14 -32
  24. package/dist/commands/agent/rebuild.d.ts +1 -0
  25. package/dist/commands/agent/rebuild.js +2 -2
  26. package/dist/commands/agent/remove.d.ts +17 -0
  27. package/dist/commands/agent/remove.js +144 -0
  28. package/dist/commands/agent/restart.d.ts +1 -0
  29. package/dist/commands/agent/restart.js +2 -2
  30. package/dist/commands/agent/shell.d.ts +1 -0
  31. package/dist/commands/agent/shell.js +63 -76
  32. package/dist/commands/agent/staff/add.d.ts +1 -0
  33. package/dist/commands/agent/staff/add.js +7 -1
  34. package/dist/commands/agent/staff/index.d.ts +1 -0
  35. package/dist/commands/agent/staff/index.js +5 -4
  36. package/dist/commands/agent/staff/remove.d.ts +1 -0
  37. package/dist/commands/agent/status.d.ts +1 -0
  38. package/dist/commands/agent/status.js +11 -23
  39. package/dist/commands/agent/temp/cleanup.d.ts +1 -0
  40. package/dist/commands/agent/temp/index.d.ts +1 -0
  41. package/dist/commands/agent/temp/index.js +4 -3
  42. package/dist/commands/agent/themes/index.d.ts +1 -0
  43. package/dist/commands/agent/themes/index.js +9 -3
  44. package/dist/commands/agent/themes/set.d.ts +1 -0
  45. package/dist/commands/agent/themes/set.js +7 -1
  46. package/dist/commands/agent/visit.d.ts +1 -0
  47. package/dist/commands/agent/visit.js +11 -23
  48. package/dist/commands/autocomplete/setup.d.ts +11 -0
  49. package/dist/commands/autocomplete/setup.js +113 -8
  50. package/dist/commands/board/index.d.ts +4 -0
  51. package/dist/commands/board/index.js +32 -30
  52. package/dist/commands/board/watch.d.ts +2 -0
  53. package/dist/commands/branch/create.d.ts +1 -0
  54. package/dist/commands/branch/create.js +33 -41
  55. package/dist/commands/branch/index.d.ts +1 -0
  56. package/dist/commands/branch/list.d.ts +2 -0
  57. package/dist/commands/branch/validate.d.ts +2 -0
  58. package/dist/commands/branch/where.d.ts +1 -0
  59. package/dist/commands/claude.d.ts +6 -0
  60. package/dist/commands/claude.js +166 -116
  61. package/dist/commands/commit.d.ts +6 -0
  62. package/dist/commands/commit.js +68 -73
  63. package/dist/commands/config/index.d.ts +13 -0
  64. package/dist/commands/config/index.js +142 -98
  65. package/dist/commands/docker/clean.d.ts +2 -1
  66. package/dist/commands/docker/clean.js +20 -29
  67. package/dist/commands/docker/index.d.ts +1 -0
  68. package/dist/commands/docker/index.js +37 -41
  69. package/dist/commands/docker/prune.d.ts +2 -1
  70. package/dist/commands/docker/prune.js +20 -27
  71. package/dist/commands/docker/restart.d.ts +2 -1
  72. package/dist/commands/docker/restart.js +20 -29
  73. package/dist/commands/docker/stop.d.ts +2 -1
  74. package/dist/commands/docker/stop.js +20 -29
  75. package/dist/commands/epic/activate.d.ts +1 -0
  76. package/dist/commands/epic/archive.d.ts +1 -0
  77. package/dist/commands/epic/create.d.ts +1 -0
  78. package/dist/commands/epic/index.d.ts +1 -0
  79. package/dist/commands/epic/link/block.d.ts +1 -0
  80. package/dist/commands/epic/link/duplicates.d.ts +1 -0
  81. package/dist/commands/epic/link/index.d.ts +1 -0
  82. package/dist/commands/epic/link/relates.d.ts +1 -0
  83. package/dist/commands/epic/link/remove.d.ts +1 -0
  84. package/dist/commands/epic/list.d.ts +2 -0
  85. package/dist/commands/epic/move.d.ts +1 -0
  86. package/dist/commands/epic/progress.d.ts +1 -0
  87. package/dist/commands/epic/project.d.ts +1 -0
  88. package/dist/commands/epic/reorder.d.ts +1 -0
  89. package/dist/commands/epic/spec.d.ts +1 -0
  90. package/dist/commands/epic/ticket.d.ts +1 -0
  91. package/dist/commands/epic/view.d.ts +1 -0
  92. package/dist/commands/execution/index.d.ts +1 -0
  93. package/dist/commands/execution/index.js +9 -25
  94. package/dist/commands/execution/list.d.ts +2 -0
  95. package/dist/commands/execution/logs.d.ts +1 -0
  96. package/dist/commands/execution/logs.js +6 -16
  97. package/dist/commands/execution/stop.d.ts +1 -0
  98. package/dist/commands/execution/stop.js +4 -15
  99. package/dist/commands/gh/index.d.ts +1 -0
  100. package/dist/commands/gh/index.js +27 -27
  101. package/dist/commands/gh/login.d.ts +4 -0
  102. package/dist/commands/gh/login.js +31 -0
  103. package/dist/commands/gh/status.d.ts +4 -0
  104. package/dist/commands/gh/status.js +27 -4
  105. package/dist/commands/gh/token.d.ts +4 -0
  106. package/dist/commands/gh/token.js +49 -5
  107. package/dist/commands/phase/create.d.ts +1 -1
  108. package/dist/commands/phase/create.js +116 -74
  109. package/dist/commands/phase/delete.d.ts +1 -0
  110. package/dist/commands/phase/delete.js +23 -22
  111. package/dist/commands/phase/list.d.ts +1 -0
  112. package/dist/commands/phase/list.js +3 -5
  113. package/dist/commands/phase/move.d.ts +1 -0
  114. package/dist/commands/phase/move.js +39 -39
  115. package/dist/commands/phase/template/apply.d.ts +1 -0
  116. package/dist/commands/phase/template/create.d.ts +2 -0
  117. package/dist/commands/phase/template/delete.d.ts +1 -0
  118. package/dist/commands/phase/template/index.d.ts +1 -0
  119. package/dist/commands/phase/template/list.d.ts +1 -0
  120. package/dist/commands/phase/template/update.d.ts +2 -0
  121. package/dist/commands/phase/update.d.ts +1 -1
  122. package/dist/commands/phase/update.js +89 -55
  123. package/dist/commands/pmo/init.d.ts +2 -0
  124. package/dist/commands/pmo/init.js +84 -22
  125. package/dist/commands/pr/create.d.ts +12 -3
  126. package/dist/commands/pr/create.js +130 -147
  127. package/dist/commands/pr/index.d.ts +6 -3
  128. package/dist/commands/pr/index.js +41 -39
  129. package/dist/commands/pr/link.d.ts +7 -3
  130. package/dist/commands/pr/link.js +126 -150
  131. package/dist/commands/pr/status.d.ts +6 -3
  132. package/dist/commands/pr/status.js +101 -126
  133. package/dist/commands/project/archive.d.ts +1 -0
  134. package/dist/commands/project/archive.js +15 -20
  135. package/dist/commands/project/create.d.ts +1 -0
  136. package/dist/commands/project/create.js +13 -5
  137. package/dist/commands/project/delete.d.ts +1 -0
  138. package/dist/commands/project/delete.js +14 -28
  139. package/dist/commands/project/index.d.ts +1 -0
  140. package/dist/commands/project/index.js +0 -5
  141. package/dist/commands/project/list.d.ts +2 -0
  142. package/dist/commands/project/list.js +21 -3
  143. package/dist/commands/project/spec.d.ts +1 -0
  144. package/dist/commands/project/spec.js +17 -23
  145. package/dist/commands/project/unarchive.d.ts +2 -0
  146. package/dist/commands/project/unarchive.js +21 -2
  147. package/dist/commands/project/view.d.ts +1 -0
  148. package/dist/commands/project/view.js +34 -22
  149. package/dist/commands/repo/add.d.ts +2 -0
  150. package/dist/commands/repo/add.js +44 -1
  151. package/dist/commands/repo/index.d.ts +1 -0
  152. package/dist/commands/repo/index.js +20 -38
  153. package/dist/commands/repo/list.d.ts +2 -0
  154. package/dist/commands/repo/remove.d.ts +1 -0
  155. package/dist/commands/repo/remove.js +45 -63
  156. package/dist/commands/repo/view.d.ts +2 -0
  157. package/dist/commands/repo/view.js +30 -5
  158. package/dist/commands/roadmap/add-project.d.ts +1 -0
  159. package/dist/commands/roadmap/create.d.ts +1 -0
  160. package/dist/commands/roadmap/delete.d.ts +1 -0
  161. package/dist/commands/roadmap/generate.d.ts +1 -0
  162. package/dist/commands/roadmap/index.d.ts +1 -0
  163. package/dist/commands/roadmap/list.d.ts +2 -0
  164. package/dist/commands/roadmap/remove-project.d.ts +1 -0
  165. package/dist/commands/roadmap/reorder.d.ts +1 -0
  166. package/dist/commands/roadmap/update.d.ts +1 -0
  167. package/dist/commands/roadmap/view.d.ts +1 -0
  168. package/dist/commands/session/attach.d.ts +1 -0
  169. package/dist/commands/session/index.d.ts +1 -0
  170. package/dist/commands/session/index.js +8 -25
  171. package/dist/commands/session/list.d.ts +2 -0
  172. package/dist/commands/spec/create.d.ts +1 -1
  173. package/dist/commands/spec/create.js +64 -65
  174. package/dist/commands/spec/index.d.ts +1 -0
  175. package/dist/commands/spec/index.js +36 -22
  176. package/dist/commands/spec/link/depends.d.ts +1 -0
  177. package/dist/commands/spec/link/depends.js +6 -6
  178. package/dist/commands/spec/link/duplicates.d.ts +1 -0
  179. package/dist/commands/spec/link/duplicates.js +6 -6
  180. package/dist/commands/spec/link/index.d.ts +2 -1
  181. package/dist/commands/spec/link/index.js +0 -4
  182. package/dist/commands/spec/link/relates.d.ts +1 -0
  183. package/dist/commands/spec/link/relates.js +6 -6
  184. package/dist/commands/spec/link/remove.d.ts +2 -1
  185. package/dist/commands/spec/link/remove.js +6 -6
  186. package/dist/commands/spec/list.d.ts +2 -0
  187. package/dist/commands/spec/list.js +25 -0
  188. package/dist/commands/spec/plan.d.ts +2 -1
  189. package/dist/commands/spec/plan.js +19 -26
  190. package/dist/commands/spec/ticket.d.ts +2 -1
  191. package/dist/commands/spec/ticket.js +48 -34
  192. package/dist/commands/spec/view.d.ts +2 -1
  193. package/dist/commands/spec/view.js +25 -16
  194. package/dist/commands/status/create.d.ts +1 -1
  195. package/dist/commands/status/create.js +80 -64
  196. package/dist/commands/status/delete.d.ts +2 -1
  197. package/dist/commands/status/delete.js +26 -22
  198. package/dist/commands/status/index.d.ts +1 -0
  199. package/dist/commands/status/index.js +26 -19
  200. package/dist/commands/status/list.d.ts +1 -0
  201. package/dist/commands/status/list.js +12 -7
  202. package/dist/commands/status/move.d.ts +2 -1
  203. package/dist/commands/status/move.js +62 -61
  204. package/dist/commands/status/update.d.ts +2 -2
  205. package/dist/commands/status/update.js +110 -77
  206. package/dist/commands/template/delete.d.ts +1 -0
  207. package/dist/commands/template/delete.js +47 -48
  208. package/dist/commands/template/index.d.ts +1 -0
  209. package/dist/commands/template/index.js +26 -33
  210. package/dist/commands/template/list.d.ts +1 -0
  211. package/dist/commands/template/phase/create.d.ts +1 -0
  212. package/dist/commands/template/phase/create.js +6 -0
  213. package/dist/commands/template/phase/index.d.ts +1 -0
  214. package/dist/commands/template/phase/index.js +27 -26
  215. package/dist/commands/template/phase/update.d.ts +1 -0
  216. package/dist/commands/template/phase/update.js +6 -0
  217. package/dist/commands/template/ticket/index.d.ts +1 -0
  218. package/dist/commands/template/ticket/index.js +27 -26
  219. package/dist/commands/template/ticket/save.d.ts +1 -0
  220. package/dist/commands/template/ticket/save.js +6 -0
  221. package/dist/commands/terminal/title.d.ts +26 -0
  222. package/dist/commands/terminal/title.js +37 -3
  223. package/dist/commands/ticket/bulk.d.ts +1 -0
  224. package/dist/commands/ticket/complete.d.ts +1 -0
  225. package/dist/commands/ticket/complete.js +18 -14
  226. package/dist/commands/ticket/create.d.ts +1 -0
  227. package/dist/commands/ticket/create.js +45 -41
  228. package/dist/commands/ticket/delete.d.ts +1 -0
  229. package/dist/commands/ticket/delete.js +1 -1
  230. package/dist/commands/ticket/edit.d.ts +1 -0
  231. package/dist/commands/ticket/edit.js +1 -1
  232. package/dist/commands/ticket/epic.d.ts +1 -0
  233. package/dist/commands/ticket/epic.js +2 -2
  234. package/dist/commands/ticket/index.d.ts +1 -0
  235. package/dist/commands/ticket/link/block.d.ts +1 -0
  236. package/dist/commands/ticket/link/block.js +1 -1
  237. package/dist/commands/ticket/link/duplicates.d.ts +1 -0
  238. package/dist/commands/ticket/link/duplicates.js +1 -1
  239. package/dist/commands/ticket/link/index.d.ts +1 -0
  240. package/dist/commands/ticket/link/index.js +9 -8
  241. package/dist/commands/ticket/link/relates.d.ts +1 -0
  242. package/dist/commands/ticket/link/relates.js +1 -1
  243. package/dist/commands/ticket/link/remove.d.ts +1 -0
  244. package/dist/commands/ticket/link/remove.js +1 -1
  245. package/dist/commands/ticket/list.d.ts +2 -0
  246. package/dist/commands/ticket/move.d.ts +1 -0
  247. package/dist/commands/ticket/move.js +27 -19
  248. package/dist/commands/ticket/project.d.ts +1 -0
  249. package/dist/commands/ticket/project.js +3 -3
  250. package/dist/commands/ticket/reassign.d.ts +1 -0
  251. package/dist/commands/ticket/reassign.js +1 -1
  252. package/dist/commands/ticket/spec.d.ts +1 -0
  253. package/dist/commands/ticket/spec.js +3 -3
  254. package/dist/commands/ticket/status.d.ts +1 -0
  255. package/dist/commands/ticket/status.js +1 -1
  256. package/dist/commands/ticket/template/apply.d.ts +1 -0
  257. package/dist/commands/ticket/template/create.d.ts +2 -0
  258. package/dist/commands/ticket/template/delete.d.ts +1 -0
  259. package/dist/commands/ticket/template/index.d.ts +1 -0
  260. package/dist/commands/ticket/template/list.d.ts +1 -0
  261. package/dist/commands/ticket/template/save.d.ts +2 -0
  262. package/dist/commands/ticket/update.d.ts +1 -0
  263. package/dist/commands/ticket/update.js +1 -1
  264. package/dist/commands/ticket/view.d.ts +1 -0
  265. package/dist/commands/ticket/view.js +1 -1
  266. package/dist/commands/work/complete.d.ts +1 -0
  267. package/dist/commands/work/index.d.ts +1 -0
  268. package/dist/commands/work/ready.d.ts +1 -0
  269. package/dist/commands/work/revise.d.ts +1 -0
  270. package/dist/commands/work/spawn-all.d.ts +2 -0
  271. package/dist/commands/work/spawn-all.js +11 -4
  272. package/dist/commands/work/spawn.d.ts +1 -0
  273. package/dist/commands/work/spawn.js +261 -166
  274. package/dist/commands/work/start.d.ts +1 -0
  275. package/dist/commands/work/start.js +270 -189
  276. package/dist/commands/work/watch.d.ts +1 -0
  277. package/dist/commands/work/watch.js +63 -58
  278. package/dist/commands/workflow/create.d.ts +1 -0
  279. package/dist/commands/workflow/create.js +2 -4
  280. package/dist/commands/workflow/delete.d.ts +1 -0
  281. package/dist/commands/workflow/delete.js +21 -33
  282. package/dist/commands/workflow/index.d.ts +1 -0
  283. package/dist/commands/workflow/list.d.ts +1 -0
  284. package/dist/commands/workflow/list.js +3 -6
  285. package/dist/commands/workflow/switch.d.ts +2 -0
  286. package/dist/commands/workflow/switch.js +46 -21
  287. package/dist/commands/workflow/view.d.ts +1 -0
  288. package/dist/commands/workflow/view.js +18 -27
  289. package/dist/commands/workspace/remove.d.ts +2 -2
  290. package/dist/commands/workspace/remove.js +16 -21
  291. package/dist/commands/workspace/use.d.ts +2 -2
  292. package/dist/commands/workspace/use.js +12 -18
  293. package/dist/lib/agents/commands.d.ts +1 -1
  294. package/dist/lib/agents/commands.js +4 -4
  295. package/dist/lib/database/drizzle-schema.d.ts +5009 -0
  296. package/dist/lib/database/drizzle-schema.js +699 -0
  297. package/dist/lib/database/drizzle.d.ts +29 -0
  298. package/dist/lib/database/drizzle.js +37 -0
  299. package/dist/lib/database/index.d.ts +1 -0
  300. package/dist/lib/database/index.js +1 -1
  301. package/dist/lib/execution/config.d.ts +6 -0
  302. package/dist/lib/execution/config.js +31 -13
  303. package/dist/lib/execution/devcontainer.js +13 -7
  304. package/dist/lib/execution/runners.js +24 -7
  305. package/dist/lib/execution/spawner.js +19 -0
  306. package/dist/lib/flags/index.d.ts +4 -0
  307. package/dist/lib/flags/index.js +4 -0
  308. package/dist/lib/flags/resolver.d.ts +224 -0
  309. package/dist/lib/flags/resolver.js +313 -0
  310. package/dist/lib/pmo/base-command.d.ts +53 -3
  311. package/dist/lib/pmo/base-command.js +92 -13
  312. package/dist/lib/pmo/find-pmo.d.ts +1 -1
  313. package/dist/lib/pmo/find-pmo.js +4 -4
  314. package/dist/lib/pmo/index.d.ts +1 -1
  315. package/dist/lib/pmo/index.js +1 -1
  316. package/dist/lib/pmo/storage/helpers.js +2 -1
  317. package/dist/lib/pmo/storage/index.d.ts +9 -0
  318. package/dist/lib/pmo/storage/index.js +14 -0
  319. package/dist/lib/pmo/storage/projects.d.ts +28 -13
  320. package/dist/lib/pmo/storage/projects.js +110 -34
  321. package/dist/lib/pmo/storage/roadmaps.d.ts +2 -0
  322. package/dist/lib/pmo/storage/roadmaps.js +182 -111
  323. package/dist/lib/pmo/storage/specs.js +13 -16
  324. package/dist/lib/pmo/storage/subtasks.js +17 -2
  325. package/dist/lib/pmo/storage/tickets.d.ts +12 -2
  326. package/dist/lib/pmo/storage/tickets.js +63 -5
  327. package/dist/lib/pmo/storage/types.d.ts +7 -3
  328. package/dist/lib/pmo/storage/views.d.ts +12 -1
  329. package/dist/lib/pmo/storage/views.js +61 -6
  330. package/dist/lib/prompt-command.d.ts +90 -0
  331. package/dist/lib/prompt-command.js +102 -0
  332. package/dist/lib/prompt-json.d.ts +34 -4
  333. package/dist/lib/prompt-json.js +35 -3
  334. package/dist/lib/repos/index.js +15 -15
  335. package/dist/lib/workspace.d.ts +4 -3
  336. package/dist/lib/workspace.js +3 -3
  337. package/oclif.manifest.json +4610 -2997
  338. package/package.json +13 -5
@@ -13,6 +13,55 @@ export class TicketStorage {
13
13
  constructor(ctx) {
14
14
  this.ctx = ctx;
15
15
  }
16
+ /**
17
+ * Resolve a project identifier to its actual ID.
18
+ * Tries multiple strategies:
19
+ * 1. Exact ID match
20
+ * 2. Case-insensitive ID match
21
+ * 3. Exact name match
22
+ * 4. Case-insensitive name match
23
+ * 5. Slugified name match
24
+ */
25
+ resolveProjectId(identifier) {
26
+ if (!identifier)
27
+ return null;
28
+ // 1. Exact ID match
29
+ const exactMatch = this.ctx.db.prepare(`
30
+ SELECT id FROM ${T.projects} WHERE id = ?
31
+ `).get(identifier);
32
+ if (exactMatch)
33
+ return exactMatch.id;
34
+ // 2. Case-insensitive ID match
35
+ const caseInsensitiveId = this.ctx.db.prepare(`
36
+ SELECT id FROM ${T.projects} WHERE LOWER(id) = LOWER(?)
37
+ `).get(identifier);
38
+ if (caseInsensitiveId)
39
+ return caseInsensitiveId.id;
40
+ // 3. Exact name match
41
+ const nameMatch = this.ctx.db.prepare(`
42
+ SELECT id FROM ${T.projects} WHERE name = ?
43
+ `).get(identifier);
44
+ if (nameMatch)
45
+ return nameMatch.id;
46
+ // 4. Case-insensitive name match
47
+ const caseInsensitiveName = this.ctx.db.prepare(`
48
+ SELECT id FROM ${T.projects} WHERE LOWER(name) = LOWER(?)
49
+ `).get(identifier);
50
+ if (caseInsensitiveName)
51
+ return caseInsensitiveName.id;
52
+ // 5. Slugified name match
53
+ const allProjects = this.ctx.db.prepare(`
54
+ SELECT id, name FROM ${T.projects}
55
+ `).all();
56
+ const identifierLower = identifier.toLowerCase();
57
+ for (const project of allProjects) {
58
+ const projectSlug = slugify(project.name);
59
+ if (projectSlug === identifierLower || projectSlug === identifier) {
60
+ return project.id;
61
+ }
62
+ }
63
+ return null;
64
+ }
16
65
  /**
17
66
  * Create a new ticket.
18
67
  * Gets default status from the project's workflow.
@@ -298,11 +347,20 @@ export class TicketStorage {
298
347
  }
299
348
  /**
300
349
  * List tickets with optional filters.
301
- * @param projectId - The project to filter by. Pass undefined to list all tickets across all projects.
350
+ * @param projectIdOrName - The project ID, name, or slug to filter by. Pass undefined to list all tickets across all projects.
302
351
  * @param filter - Additional filters to apply.
303
352
  */
304
- async listTickets(projectId, filter) {
353
+ async listTickets(projectIdOrName, filter) {
305
354
  const params = [];
355
+ // Resolve project identifier to actual ID if provided
356
+ let resolvedProjectId;
357
+ if (projectIdOrName !== undefined) {
358
+ resolvedProjectId = this.resolveProjectId(projectIdOrName) || undefined;
359
+ // If resolution fails, use original value to allow normal "not found" behavior
360
+ if (!resolvedProjectId) {
361
+ resolvedProjectId = projectIdOrName;
362
+ }
363
+ }
306
364
  // Build the base query using workflow_statuses
307
365
  let query = `
308
366
  SELECT t.*,
@@ -316,9 +374,9 @@ export class TicketStorage {
316
374
  WHERE 1=1
317
375
  `;
318
376
  // Apply project scoping
319
- if (projectId !== undefined) {
377
+ if (resolvedProjectId !== undefined) {
320
378
  query += ' AND t.project_id = ?';
321
- params.push(projectId);
379
+ params.push(resolvedProjectId);
322
380
  }
323
381
  // If projectId is undefined, list all tickets across all projects
324
382
  if (filter?.statusId) {
@@ -363,7 +421,7 @@ export class TicketStorage {
363
421
  params.push(filter.column);
364
422
  }
365
423
  // Order by project, then status position, then priority, then created_at
366
- if (projectId === undefined) {
424
+ if (projectIdOrName === undefined) {
367
425
  query += ` ORDER BY p.name, ws.position,
368
426
  CASE t.priority
369
427
  WHEN 'P0' THEN 0
@@ -3,13 +3,17 @@
3
3
  * These types are shared between storage modules but not exported publicly.
4
4
  */
5
5
  import Database from 'better-sqlite3';
6
+ import { DrizzleDB } from '../../database/drizzle.js';
6
7
  /**
7
8
  * Base context passed to all storage modules.
8
- * Contains the database connection only - project ID is passed explicitly to operations.
9
+ * Contains both raw SQLite and Drizzle connections for gradual migration.
10
+ * Project ID is passed explicitly to operations.
9
11
  */
10
12
  export interface StorageContext {
11
- /** Database connection */
13
+ /** Raw better-sqlite3 database connection (for legacy queries) */
12
14
  db: Database.Database;
15
+ /** Drizzle ORM database connection (for type-safe queries) */
16
+ drizzle: DrizzleDB;
13
17
  /** Update the board timestamp for a project */
14
18
  updateBoardTimestamp: (projectId: string) => void;
15
19
  }
@@ -57,7 +61,7 @@ export interface SpecRow {
57
61
  status: string;
58
62
  type: string | null;
59
63
  tags: string | null;
60
- depends_on: string | null;
64
+ depends_on?: string | null;
61
65
  problem: string | null;
62
66
  solution: string | null;
63
67
  decisions: string | null;
@@ -6,6 +6,16 @@ import { StorageContext } from './types.js';
6
6
  export declare class ViewStorage {
7
7
  private ctx;
8
8
  constructor(ctx: StorageContext);
9
+ /**
10
+ * Resolve a project identifier to its actual ID.
11
+ * Tries multiple strategies:
12
+ * 1. Exact ID match
13
+ * 2. Case-insensitive ID match
14
+ * 3. Exact name match
15
+ * 4. Case-insensitive name match
16
+ * 5. Slugified name match
17
+ */
18
+ private resolveProjectId;
9
19
  /**
10
20
  * List board views.
11
21
  */
@@ -32,8 +42,9 @@ export declare class ViewStorage {
32
42
  getDefaultBoardView(projectId: string): Promise<BoardView | null>;
33
43
  /**
34
44
  * Get board with optional filters applied.
45
+ * @param projectIdOrName - Project ID, name, or slug. Will be resolved to actual ID.
35
46
  */
36
- getBoardWithView(projectId: string, viewId?: string, filters?: BoardViewFilters): Promise<Board>;
47
+ getBoardWithView(projectIdOrName: string, viewId?: string, filters?: BoardViewFilters): Promise<Board>;
37
48
  /**
38
49
  * Get tickets for a column (workflow status) with filters applied.
39
50
  */
@@ -11,6 +11,55 @@ export class ViewStorage {
11
11
  constructor(ctx) {
12
12
  this.ctx = ctx;
13
13
  }
14
+ /**
15
+ * Resolve a project identifier to its actual ID.
16
+ * Tries multiple strategies:
17
+ * 1. Exact ID match
18
+ * 2. Case-insensitive ID match
19
+ * 3. Exact name match
20
+ * 4. Case-insensitive name match
21
+ * 5. Slugified name match
22
+ */
23
+ resolveProjectId(identifier) {
24
+ if (!identifier)
25
+ return null;
26
+ // 1. Exact ID match
27
+ const exactMatch = this.ctx.db.prepare(`
28
+ SELECT id FROM ${T.projects} WHERE id = ?
29
+ `).get(identifier);
30
+ if (exactMatch)
31
+ return exactMatch.id;
32
+ // 2. Case-insensitive ID match
33
+ const caseInsensitiveId = this.ctx.db.prepare(`
34
+ SELECT id FROM ${T.projects} WHERE LOWER(id) = LOWER(?)
35
+ `).get(identifier);
36
+ if (caseInsensitiveId)
37
+ return caseInsensitiveId.id;
38
+ // 3. Exact name match
39
+ const nameMatch = this.ctx.db.prepare(`
40
+ SELECT id FROM ${T.projects} WHERE name = ?
41
+ `).get(identifier);
42
+ if (nameMatch)
43
+ return nameMatch.id;
44
+ // 4. Case-insensitive name match
45
+ const caseInsensitiveName = this.ctx.db.prepare(`
46
+ SELECT id FROM ${T.projects} WHERE LOWER(name) = LOWER(?)
47
+ `).get(identifier);
48
+ if (caseInsensitiveName)
49
+ return caseInsensitiveName.id;
50
+ // 5. Slugified name match
51
+ const allProjects = this.ctx.db.prepare(`
52
+ SELECT id, name FROM ${T.projects}
53
+ `).all();
54
+ const identifierLower = identifier.toLowerCase();
55
+ for (const project of allProjects) {
56
+ const projectSlug = slugify(project.name);
57
+ if (projectSlug === identifierLower || projectSlug === identifier) {
58
+ return project.id;
59
+ }
60
+ }
61
+ return null;
62
+ }
14
63
  /**
15
64
  * List board views.
16
65
  */
@@ -138,8 +187,14 @@ export class ViewStorage {
138
187
  }
139
188
  /**
140
189
  * Get board with optional filters applied.
190
+ * @param projectIdOrName - Project ID, name, or slug. Will be resolved to actual ID.
141
191
  */
142
- async getBoardWithView(projectId, viewId, filters) {
192
+ async getBoardWithView(projectIdOrName, viewId, filters) {
193
+ // Resolve project identifier to actual ID
194
+ const resolvedId = this.resolveProjectId(projectIdOrName);
195
+ if (!resolvedId) {
196
+ throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}. Run init() first.`);
197
+ }
143
198
  let viewFilters = {};
144
199
  let viewSortBy;
145
200
  // Load view if specified
@@ -152,15 +207,15 @@ export class ViewStorage {
152
207
  }
153
208
  // Override with explicit filters if provided
154
209
  const effectiveFilters = { ...viewFilters, ...filters };
155
- // Get project metadata
156
- const projectRow = this.ctx.db.prepare(`SELECT * FROM ${T.projects} WHERE id = ?`).get(projectId);
210
+ // Get project metadata using resolved ID
211
+ const projectRow = this.ctx.db.prepare(`SELECT * FROM ${T.projects} WHERE id = ?`).get(resolvedId);
157
212
  if (!projectRow) {
158
- throw new PMOError('NOT_FOUND', `Project not found: ${projectId}. Run init() first.`);
213
+ throw new PMOError('NOT_FOUND', `Project not found: ${projectIdOrName}. Run init() first.`);
159
214
  }
160
215
  // Get the project's workflow
161
216
  const projectMeta = this.ctx.db.prepare(`
162
217
  SELECT workflow_id FROM ${T.projects} WHERE id = ?
163
- `).get(projectId);
218
+ `).get(resolvedId);
164
219
  const workflowId = projectMeta?.workflow_id || 'default';
165
220
  // Get workflow statuses as columns
166
221
  const columnRows = this.ctx.db.prepare(`
@@ -175,7 +230,7 @@ export class ViewStorage {
175
230
  : columnRows;
176
231
  // Get tickets with filters applied
177
232
  const columns = await Promise.all(filteredColumnRows.map(async (col) => {
178
- const tickets = await this.getTicketsForColumnWithFilters(col.id, projectId, effectiveFilters);
233
+ const tickets = await this.getTicketsForColumnWithFilters(col.id, resolvedId, effectiveFilters);
179
234
  // Apply sorting if specified
180
235
  const sortedTickets = viewSortBy ? this.sortTickets(tickets, viewSortBy) : tickets;
181
236
  return {
@@ -0,0 +1,90 @@
1
+ import { Command } from '@oclif/core';
2
+ import { type JsonFlags } from './prompt-json.js';
3
+ /**
4
+ * Lightweight base command with prompt() method for JSON mode support.
5
+ *
6
+ * Extends oclif Command with a drop-in replacement for inquirer.prompt()
7
+ * that works in both interactive and JSON/agent modes. Use this as the
8
+ * base class for commands that need interactive prompts but don't require
9
+ * PMO database context (for PMO commands, use PMOCommand which extends this).
10
+ *
11
+ * Usage:
12
+ * ```typescript
13
+ * import { PromptCommand } from '../../lib/prompt-command.js';
14
+ *
15
+ * export default class MyCommand extends PromptCommand {
16
+ * async run(): Promise<void> {
17
+ * const { flags } = await this.parse(MyCommand);
18
+ * const { selection } = await this.prompt([{
19
+ * type: 'list',
20
+ * name: 'selection',
21
+ * message: 'Pick one:',
22
+ * choices: items.map(i => ({
23
+ * name: i.name,
24
+ * value: i.id,
25
+ * command: `prlt my-command --selection "${i.id}" --json`,
26
+ * })),
27
+ * }], { flags, commandName: 'my-command' });
28
+ * }
29
+ * }
30
+ * ```
31
+ */
32
+ export declare abstract class PromptCommand extends Command {
33
+ /**
34
+ * Prompt wrapper - drop-in replacement for inquirer.prompt
35
+ *
36
+ * Works in BOTH modes:
37
+ * - Interactive mode: calls inquirer.prompt normally (human sees menu)
38
+ * - JSON/Agent mode: outputs prompt as structured JSON and exits
39
+ *
40
+ * This is the simplest way to make any inquirer.prompt call work for both humans and agents.
41
+ * Just replace `await inquirer.prompt(questions)` with `await this.prompt(questions, jsonModeConfig)`
42
+ *
43
+ * @param questions - Inquirer question config(s)
44
+ * @param jsonModeConfig - JSON mode configuration (null to disable JSON mode handling)
45
+ * @returns Answers object (only in interactive mode)
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // Before (breaks in JSON mode):
50
+ * const { column } = await inquirer.prompt([{
51
+ * type: 'list',
52
+ * name: 'column',
53
+ * message: 'Select column:',
54
+ * choices: columns.map(c => ({ name: c, value: c })),
55
+ * }]);
56
+ *
57
+ * // After (works in both modes):
58
+ * const { column } = await this.prompt([{
59
+ * type: 'list',
60
+ * name: 'column',
61
+ * message: 'Select column:',
62
+ * choices: columns.map(c => ({
63
+ * name: c,
64
+ * value: c,
65
+ * command: `prlt ticket move --column "${c}" --json`,
66
+ * })),
67
+ * }], {
68
+ * flags,
69
+ * commandName: 'ticket move',
70
+ * });
71
+ * ```
72
+ */
73
+ protected prompt<T extends Record<string, unknown>>(questions: Array<{
74
+ type: string;
75
+ name: string;
76
+ message: string;
77
+ choices?: Array<string | {
78
+ name: string;
79
+ value: unknown;
80
+ disabled?: boolean | string;
81
+ command?: string;
82
+ } | unknown>;
83
+ default?: unknown;
84
+ validate?: (input: unknown) => boolean | string;
85
+ when?: boolean | ((answers: Record<string, unknown>) => boolean);
86
+ }>, jsonModeConfig?: {
87
+ flags: JsonFlags & Record<string, unknown>;
88
+ commandName: string;
89
+ } | null): Promise<T>;
90
+ }
@@ -0,0 +1,102 @@
1
+ import { Command } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { isAgentMode, outputPromptAsJson, createMetadata, normalizeChoices, } from './prompt-json.js';
4
+ /**
5
+ * Lightweight base command with prompt() method for JSON mode support.
6
+ *
7
+ * Extends oclif Command with a drop-in replacement for inquirer.prompt()
8
+ * that works in both interactive and JSON/agent modes. Use this as the
9
+ * base class for commands that need interactive prompts but don't require
10
+ * PMO database context (for PMO commands, use PMOCommand which extends this).
11
+ *
12
+ * Usage:
13
+ * ```typescript
14
+ * import { PromptCommand } from '../../lib/prompt-command.js';
15
+ *
16
+ * export default class MyCommand extends PromptCommand {
17
+ * async run(): Promise<void> {
18
+ * const { flags } = await this.parse(MyCommand);
19
+ * const { selection } = await this.prompt([{
20
+ * type: 'list',
21
+ * name: 'selection',
22
+ * message: 'Pick one:',
23
+ * choices: items.map(i => ({
24
+ * name: i.name,
25
+ * value: i.id,
26
+ * command: `prlt my-command --selection "${i.id}" --json`,
27
+ * })),
28
+ * }], { flags, commandName: 'my-command' });
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+ export class PromptCommand extends Command {
34
+ /**
35
+ * Prompt wrapper - drop-in replacement for inquirer.prompt
36
+ *
37
+ * Works in BOTH modes:
38
+ * - Interactive mode: calls inquirer.prompt normally (human sees menu)
39
+ * - JSON/Agent mode: outputs prompt as structured JSON and exits
40
+ *
41
+ * This is the simplest way to make any inquirer.prompt call work for both humans and agents.
42
+ * Just replace `await inquirer.prompt(questions)` with `await this.prompt(questions, jsonModeConfig)`
43
+ *
44
+ * @param questions - Inquirer question config(s)
45
+ * @param jsonModeConfig - JSON mode configuration (null to disable JSON mode handling)
46
+ * @returns Answers object (only in interactive mode)
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // Before (breaks in JSON mode):
51
+ * const { column } = await inquirer.prompt([{
52
+ * type: 'list',
53
+ * name: 'column',
54
+ * message: 'Select column:',
55
+ * choices: columns.map(c => ({ name: c, value: c })),
56
+ * }]);
57
+ *
58
+ * // After (works in both modes):
59
+ * const { column } = await this.prompt([{
60
+ * type: 'list',
61
+ * name: 'column',
62
+ * message: 'Select column:',
63
+ * choices: columns.map(c => ({
64
+ * name: c,
65
+ * value: c,
66
+ * command: `prlt ticket move --column "${c}" --json`,
67
+ * })),
68
+ * }], {
69
+ * flags,
70
+ * commandName: 'ticket move',
71
+ * });
72
+ * ```
73
+ */
74
+ async prompt(questions, jsonModeConfig) {
75
+ // Auto-detect non-TTY: switch to JSON mode when no TTY present
76
+ if (!jsonModeConfig && !process.stdin.isTTY) {
77
+ jsonModeConfig = { flags: { json: true }, commandName: this.id ?? 'unknown' };
78
+ }
79
+ // Check for JSON/agent mode
80
+ if (jsonModeConfig && isAgentMode(jsonModeConfig.flags)) {
81
+ // Find first question that should be shown (respecting 'when' conditions)
82
+ const firstQuestion = questions[0];
83
+ if (firstQuestion) {
84
+ // Convert choices to agent-compatible format
85
+ const choices = firstQuestion.choices
86
+ ? normalizeChoices(firstQuestion.choices)
87
+ : undefined;
88
+ outputPromptAsJson({
89
+ type: firstQuestion.type,
90
+ name: firstQuestion.name,
91
+ message: firstQuestion.message,
92
+ choices,
93
+ default: firstQuestion.default,
94
+ }, createMetadata(jsonModeConfig.commandName, jsonModeConfig.flags));
95
+ // outputPromptAsJson calls process.exit, never returns
96
+ }
97
+ return {};
98
+ }
99
+ // Interactive mode: just call inquirer
100
+ return inquirer.prompt(questions);
101
+ }
102
+ }
@@ -128,11 +128,22 @@ export interface ErrorJsonOutput {
128
128
  */
129
129
  export type JsonOutput = PromptJsonOutput | SuccessJsonOutput | ErrorJsonOutput;
130
130
  /**
131
- * Flags interface for shouldOutputJson
131
+ * Flags interface for JSON mode detection (legacy)
132
+ * @deprecated Use MachineOutputFlags instead
132
133
  */
133
134
  export interface JsonFlags {
134
135
  json?: boolean;
135
136
  }
137
+ /**
138
+ * Flags interface for machine-readable output mode detection
139
+ * Supports both new --machine format and legacy --json boolean
140
+ */
141
+ export interface MachineOutputFlags {
142
+ /** New format: --machine (enables machine-readable JSON output for AI agents) */
143
+ machine?: boolean;
144
+ /** Legacy format: --json (deprecated, use --machine instead) */
145
+ json?: boolean;
146
+ }
136
147
  /**
137
148
  * Check if the current environment is non-TTY (piped output)
138
149
  *
@@ -140,16 +151,35 @@ export interface JsonFlags {
140
151
  */
141
152
  export declare function isNonTTY(): boolean;
142
153
  /**
143
- * Determine if JSON output should be used
154
+ * Determine if JSON output mode is active (for AI agents)
144
155
  *
145
156
  * Returns true if:
157
+ * - The --machine flag is explicitly set
146
158
  * - The --json flag is explicitly set
147
159
  * - The environment is non-TTY (piped output)
148
160
  *
149
161
  * @param flags - Command flags object
150
- * @returns true if JSON output should be used
162
+ * @returns true if JSON mode should be used
163
+ */
164
+ export declare function shouldOutputJson(flags: JsonFlags & {
165
+ machine?: boolean;
166
+ }): boolean;
167
+ /**
168
+ * Alias for shouldOutputJson - clearer name for agent-focused code
169
+ */
170
+ export declare const isAgentMode: typeof shouldOutputJson;
171
+ /**
172
+ * Determine if machine-readable output mode is active (for AI agents/scripts)
173
+ *
174
+ * Returns true if:
175
+ * - The --machine flag is set
176
+ * - The --json flag is explicitly set (legacy support)
177
+ * - The environment is non-TTY (piped output)
178
+ *
179
+ * @param flags - Command flags object
180
+ * @returns true if machine-readable output mode should be used
151
181
  */
152
- export declare function shouldOutputJson(flags: JsonFlags): boolean;
182
+ export declare function isMachineOutput(flags: MachineOutputFlags): boolean;
153
183
  /**
154
184
  * Create metadata object for JSON output
155
185
  *
@@ -38,17 +38,49 @@ export function isNonTTY() {
38
38
  return !process.stdout.isTTY;
39
39
  }
40
40
  /**
41
- * Determine if JSON output should be used
41
+ * Determine if JSON output mode is active (for AI agents)
42
42
  *
43
43
  * Returns true if:
44
+ * - The --machine flag is explicitly set
44
45
  * - The --json flag is explicitly set
45
46
  * - The environment is non-TTY (piped output)
46
47
  *
47
48
  * @param flags - Command flags object
48
- * @returns true if JSON output should be used
49
+ * @returns true if JSON mode should be used
49
50
  */
50
51
  export function shouldOutputJson(flags) {
51
- // Explicit flag takes precedence
52
+ // New --machine flag
53
+ if (flags.machine === true) {
54
+ return true;
55
+ }
56
+ // Legacy --json flag
57
+ if (flags.json === true) {
58
+ return true;
59
+ }
60
+ // Automatic detection for non-TTY environments
61
+ return isNonTTY();
62
+ }
63
+ /**
64
+ * Alias for shouldOutputJson - clearer name for agent-focused code
65
+ */
66
+ export const isAgentMode = shouldOutputJson;
67
+ /**
68
+ * Determine if machine-readable output mode is active (for AI agents/scripts)
69
+ *
70
+ * Returns true if:
71
+ * - The --machine flag is set
72
+ * - The --json flag is explicitly set (legacy support)
73
+ * - The environment is non-TTY (piped output)
74
+ *
75
+ * @param flags - Command flags object
76
+ * @returns true if machine-readable output mode should be used
77
+ */
78
+ export function isMachineOutput(flags) {
79
+ // New --machine flag takes precedence
80
+ if (flags.machine === true) {
81
+ return true;
82
+ }
83
+ // Legacy --json flag support
52
84
  if (flags.json === true) {
53
85
  return true;
54
86
  }
@@ -497,21 +497,21 @@ export async function removeRepository(hqPath, repoName, keepFiles = false) {
497
497
  */
498
498
  async function createWorktreesForRepo(hqPath, repoName, repoPath) {
499
499
  const db = openWorkspaceDatabase(hqPath);
500
- // Get workspace config for theme info
501
- const workspace = db.prepare('SELECT theme FROM workspace').get();
502
- const theme = db.prepare('SELECT workspace_dir FROM themes WHERE name = ?').get(workspace.theme);
503
- // Get all agents
504
- const agents = db.prepare('SELECT name FROM agents').all();
500
+ // Get all agents with their worktree paths
501
+ const agents = db.prepare('SELECT name, worktree_path FROM agents WHERE status = \'active\' OR status IS NULL').all();
505
502
  if (agents.length === 0) {
506
503
  db.close();
507
504
  return;
508
505
  }
509
- const agentsBasePath = path.join(hqPath, 'agents', theme.workspace_dir);
510
506
  for (const agent of agents) {
507
+ // Skip agents without a worktree path
508
+ if (!agent.worktree_path) {
509
+ continue;
510
+ }
511
511
  // Name worktree directory as {repoName}-{agentName} so git creates unique worktree entries
512
512
  // e.g., proletariat-branson instead of proletariat (which causes proletariat1, proletariat2, etc.)
513
513
  const worktreeDirName = `${repoName}-${agent.name}`;
514
- const agentRepoPath = path.join(agentsBasePath, agent.name, worktreeDirName);
514
+ const agentRepoPath = path.join(hqPath, agent.worktree_path, worktreeDirName);
515
515
  const branchName = `agent-${agent.name}`;
516
516
  try {
517
517
  // Create worktree
@@ -524,7 +524,7 @@ async function createWorktreesForRepo(hqPath, repoName, repoPath) {
524
524
  INSERT OR REPLACE INTO agent_worktrees
525
525
  (agent_name, repo_name, worktree_path, branch, created_at, commits_ahead, is_clean)
526
526
  VALUES (?, ?, ?, ?, ?, 0, 1)
527
- `).run(agent.name, repoName, `agents/${theme.workspace_dir}/${agent.name}/${worktreeDirName}`, branchName, new Date().toISOString());
527
+ `).run(agent.name, repoName, `${agent.worktree_path}/${worktreeDirName}`, branchName, new Date().toISOString());
528
528
  }
529
529
  catch {
530
530
  console.log(chalk.yellow(`Warning: Could not create worktree for ${agent.name}/${repoName}`));
@@ -537,15 +537,15 @@ async function createWorktreesForRepo(hqPath, repoName, repoPath) {
537
537
  */
538
538
  async function removeWorktreesForRepo(hqPath, repoName) {
539
539
  const db = openWorkspaceDatabase(hqPath);
540
- // Get workspace config for theme info
541
- const workspace = db.prepare('SELECT theme FROM workspace').get();
542
- const theme = db.prepare('SELECT workspace_dir FROM themes WHERE name = ?').get(workspace.theme);
543
- // Get all agents
544
- const agents = db.prepare('SELECT name FROM agents').all();
540
+ // Get all agents with their worktree paths
541
+ const agents = db.prepare('SELECT name, worktree_path FROM agents').all();
545
542
  const repoPath = path.join(hqPath, 'repos', repoName);
546
- const agentsBasePath = path.join(hqPath, 'agents', theme.workspace_dir);
547
543
  for (const agent of agents) {
548
- const agentRepoPath = path.join(agentsBasePath, agent.name, repoName);
544
+ // Skip agents without a worktree path
545
+ if (!agent.worktree_path) {
546
+ continue;
547
+ }
548
+ const agentRepoPath = path.join(hqPath, agent.worktree_path, repoName);
549
549
  try {
550
550
  // Remove worktree from git
551
551
  if (fs.existsSync(repoPath)) {
@@ -2,7 +2,7 @@
2
2
  * Central headquarters resolution utilities.
3
3
  *
4
4
  * Search priority:
5
- * 1. PRLT_HQ_PATH env var (ONLY when DEVCONTAINER=true - for devcontainer mounts)
5
+ * 1. PRLT_HQ_PATH env var (ONLY when DEVCONTAINER=true or PRLT_TEST_ENV=true)
6
6
  * 2. Walk up directory tree looking for .proletariat/config.json with type='hq'
7
7
  * - If found but not registered, emit warning to register
8
8
  * 3. ~/.proletariat/config.json activeHeadquarters (fallback when NOT in any HQ)
@@ -12,8 +12,9 @@
12
12
  * working in different HQs simultaneously. Each agent uses the HQ
13
13
  * they're physically in, not a global env var that could cause conflicts.
14
14
  *
15
- * For testing HQ isolation, see TKT-400.
16
- * For CI/CD HQ isolation, see TKT-401.
15
+ * Environment variables:
16
+ * - DEVCONTAINER=true: Enables PRLT_HQ_PATH (for devcontainer mounts)
17
+ * - PRLT_TEST_ENV=true: Enables PRLT_HQ_PATH (for E2E test isolation)
17
18
  */
18
19
  export interface WorkspaceLocation {
19
20
  path: string;
@@ -22,11 +22,11 @@ export function findHQRoot(startDir = process.cwd()) {
22
22
  * Useful for debugging and logging where the workspace was resolved from.
23
23
  */
24
24
  export function findHQRootWithSource(startDir = process.cwd()) {
25
- // 1. Check PRLT_HQ_PATH environment variable (only in devcontainers)
25
+ // 1. Check PRLT_HQ_PATH environment variable (only in devcontainers or test environments)
26
26
  // On host machines, we use directory walk to support multiple workspaces/agents
27
27
  const envHqPath = process.env.PRLT_HQ_PATH;
28
- const isDevcontainer = process.env.DEVCONTAINER === 'true';
29
- if (envHqPath && isDevcontainer) {
28
+ const allowEnvHqPath = process.env.DEVCONTAINER === 'true' || process.env.PRLT_TEST_ENV === 'true';
29
+ if (envHqPath && allowEnvHqPath) {
30
30
  const resolvedPath = path.resolve(envHqPath);
31
31
  // Validate it's actually an HQ
32
32
  if (isValidHQ(resolvedPath)) {