@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
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Roadmap operations for PMO.
3
3
  * Roadmaps are curated collections of projects for documentation/visualization.
4
+ *
5
+ * This module uses Drizzle ORM for type-safe database queries.
4
6
  */
5
- import { PMO_TABLES } from '../schema.js';
7
+ import { eq, and, like, or, desc, asc, sql, gte, gt, lt, lte } from 'drizzle-orm';
8
+ import { pmoRoadmaps, pmoRoadmapProjects, pmoProjects, } from '../../database/drizzle-schema.js';
6
9
  import { PMOError } from '../types.js';
7
- const T = PMO_TABLES;
8
10
  export class RoadmapStorage {
9
11
  ctx;
10
12
  constructor(ctx) {
@@ -16,15 +18,22 @@ export class RoadmapStorage {
16
18
  */
17
19
  async createRoadmap(roadmap) {
18
20
  const id = roadmap.id || `roadmap-${Date.now()}`;
19
- const now = Date.now();
21
+ const now = new Date().toISOString();
20
22
  // If setting as default, unset other defaults first
21
23
  if (roadmap.isDefault) {
22
- this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET is_default = 0`).run();
24
+ this.ctx.drizzle
25
+ .update(pmoRoadmaps)
26
+ .set({ isDefault: false })
27
+ .run();
23
28
  }
24
- this.ctx.db.prepare(`
25
- INSERT INTO ${T.roadmaps} (id, name, description, is_default, created_at, updated_at)
26
- VALUES (?, ?, ?, ?, ?, ?)
27
- `).run(id, roadmap.name, roadmap.description || null, roadmap.isDefault ? 1 : 0, now, now);
29
+ this.ctx.drizzle.insert(pmoRoadmaps).values({
30
+ id,
31
+ name: roadmap.name,
32
+ description: roadmap.description || null,
33
+ isDefault: roadmap.isDefault || false,
34
+ createdAt: now,
35
+ updatedAt: now,
36
+ }).run();
28
37
  return {
29
38
  id,
30
39
  name: roadmap.name,
@@ -38,9 +47,11 @@ export class RoadmapStorage {
38
47
  * Get a roadmap by ID.
39
48
  */
40
49
  async getRoadmap(id) {
41
- const row = this.ctx.db.prepare(`
42
- SELECT * FROM ${T.roadmaps} WHERE id = ?
43
- `).get(id);
50
+ const row = this.ctx.drizzle
51
+ .select()
52
+ .from(pmoRoadmaps)
53
+ .where(eq(pmoRoadmaps.id, id))
54
+ .get();
44
55
  if (!row)
45
56
  return null;
46
57
  return this.rowToRoadmap(row);
@@ -49,18 +60,23 @@ export class RoadmapStorage {
49
60
  * List roadmaps with optional filters.
50
61
  */
51
62
  async listRoadmaps(filter) {
52
- let query = `SELECT * FROM ${T.roadmaps} WHERE 1=1`;
53
- const params = [];
63
+ let query = this.ctx.drizzle
64
+ .select()
65
+ .from(pmoRoadmaps)
66
+ .$dynamic();
67
+ const conditions = [];
54
68
  if (filter?.search) {
55
- query += ' AND (name LIKE ? OR description LIKE ?)';
56
- params.push(`%${filter.search}%`, `%${filter.search}%`);
69
+ conditions.push(or(like(pmoRoadmaps.name, `%${filter.search}%`), like(pmoRoadmaps.description, `%${filter.search}%`)));
57
70
  }
58
71
  if (filter?.isDefault !== undefined) {
59
- query += ' AND is_default = ?';
60
- params.push(filter.isDefault ? 1 : 0);
72
+ conditions.push(eq(pmoRoadmaps.isDefault, filter.isDefault));
73
+ }
74
+ if (conditions.length > 0) {
75
+ query = query.where(and(...conditions));
61
76
  }
62
- query += ' ORDER BY is_default DESC, name ASC';
63
- const rows = this.ctx.db.prepare(query).all(...params);
77
+ const rows = query
78
+ .orderBy(desc(pmoRoadmaps.isDefault), asc(pmoRoadmaps.name))
79
+ .all();
64
80
  return rows.map(row => this.rowToRoadmap(row));
65
81
  }
66
82
  /**
@@ -71,29 +87,30 @@ export class RoadmapStorage {
71
87
  if (!roadmap) {
72
88
  throw new PMOError('NOT_FOUND', `Roadmap not found: ${id}`);
73
89
  }
74
- const updates = [];
75
- const params = [];
90
+ const updates = {};
76
91
  if (changes.name !== undefined) {
77
- updates.push('name = ?');
78
- params.push(changes.name);
92
+ updates.name = changes.name;
79
93
  }
80
94
  if (changes.description !== undefined) {
81
- updates.push('description = ?');
82
- params.push(changes.description || null);
95
+ updates.description = changes.description || null;
83
96
  }
84
97
  if (changes.isDefault !== undefined) {
85
98
  // If setting as default, unset other defaults first
86
99
  if (changes.isDefault) {
87
- this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET is_default = 0`).run();
100
+ this.ctx.drizzle
101
+ .update(pmoRoadmaps)
102
+ .set({ isDefault: false })
103
+ .run();
88
104
  }
89
- updates.push('is_default = ?');
90
- params.push(changes.isDefault ? 1 : 0);
105
+ updates.isDefault = changes.isDefault;
91
106
  }
92
- if (updates.length > 0) {
93
- updates.push('updated_at = ?');
94
- params.push(Date.now());
95
- params.push(id);
96
- this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET ${updates.join(', ')} WHERE id = ?`).run(...params);
107
+ if (Object.keys(updates).length > 0) {
108
+ updates.updatedAt = new Date().toISOString();
109
+ this.ctx.drizzle
110
+ .update(pmoRoadmaps)
111
+ .set(updates)
112
+ .where(eq(pmoRoadmaps.id, id))
113
+ .run();
97
114
  }
98
115
  return (await this.getRoadmap(id));
99
116
  }
@@ -106,15 +123,21 @@ export class RoadmapStorage {
106
123
  throw new PMOError('NOT_FOUND', `Roadmap not found: ${id}`);
107
124
  }
108
125
  // Delete roadmap (cascades to roadmap_projects)
109
- this.ctx.db.prepare(`DELETE FROM ${T.roadmaps} WHERE id = ?`).run(id);
126
+ this.ctx.drizzle
127
+ .delete(pmoRoadmaps)
128
+ .where(eq(pmoRoadmaps.id, id))
129
+ .run();
110
130
  }
111
131
  /**
112
132
  * Get the default roadmap.
113
133
  */
114
134
  async getDefaultRoadmap() {
115
- const row = this.ctx.db.prepare(`
116
- SELECT * FROM ${T.roadmaps} WHERE is_default = 1 LIMIT 1
117
- `).get();
135
+ const row = this.ctx.drizzle
136
+ .select()
137
+ .from(pmoRoadmaps)
138
+ .where(eq(pmoRoadmaps.isDefault, true))
139
+ .limit(1)
140
+ .get();
118
141
  if (!row)
119
142
  return null;
120
143
  return this.rowToRoadmap(row);
@@ -130,12 +153,26 @@ export class RoadmapStorage {
130
153
  * List projects in a roadmap, ordered by position.
131
154
  */
132
155
  async listRoadmapProjects(roadmapId) {
133
- const rows = this.ctx.db.prepare(`
134
- SELECT p.* FROM ${T.projects} p
135
- JOIN ${T.roadmap_projects} rp ON p.id = rp.project_id
136
- WHERE rp.roadmap_id = ?
137
- ORDER BY rp.position ASC
138
- `).all(roadmapId);
156
+ const rows = this.ctx.drizzle
157
+ .select({
158
+ id: pmoProjects.id,
159
+ name: pmoProjects.name,
160
+ template: pmoProjects.template,
161
+ description: pmoProjects.description,
162
+ status: pmoProjects.status,
163
+ phaseId: pmoProjects.phaseId,
164
+ workflowId: pmoProjects.workflowId,
165
+ isArchived: pmoProjects.isArchived,
166
+ targetDate: pmoProjects.targetDate,
167
+ initiativeId: pmoProjects.initiativeId,
168
+ createdAt: pmoProjects.createdAt,
169
+ updatedAt: pmoProjects.updatedAt,
170
+ })
171
+ .from(pmoProjects)
172
+ .innerJoin(pmoRoadmapProjects, eq(pmoProjects.id, pmoRoadmapProjects.projectId))
173
+ .where(eq(pmoRoadmapProjects.roadmapId, roadmapId))
174
+ .orderBy(asc(pmoRoadmapProjects.position))
175
+ .all();
139
176
  return rows.map(row => this.rowToProject(row));
140
177
  }
141
178
  /**
@@ -148,34 +185,44 @@ export class RoadmapStorage {
148
185
  throw new PMOError('NOT_FOUND', `Roadmap not found: ${roadmapId}`);
149
186
  }
150
187
  // Check if project is already in roadmap
151
- const existing = this.ctx.db.prepare(`
152
- SELECT * FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
153
- `).get(roadmapId, projectId);
188
+ const existing = this.ctx.drizzle
189
+ .select()
190
+ .from(pmoRoadmapProjects)
191
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), eq(pmoRoadmapProjects.projectId, projectId)))
192
+ .get();
154
193
  if (existing) {
155
194
  throw new PMOError('CONFLICT', `Project ${projectId} is already in roadmap ${roadmapId}`);
156
195
  }
157
196
  // Get next position if not provided
158
197
  if (position === undefined) {
159
- const maxPos = this.ctx.db.prepare(`
160
- SELECT COALESCE(MAX(position), -1) as max_pos FROM ${T.roadmap_projects} WHERE roadmap_id = ?
161
- `).get(roadmapId);
162
- position = maxPos.max_pos + 1;
198
+ const maxPosResult = this.ctx.drizzle
199
+ .select({ maxPos: sql `COALESCE(MAX(${pmoRoadmapProjects.position}), -1)` })
200
+ .from(pmoRoadmapProjects)
201
+ .where(eq(pmoRoadmapProjects.roadmapId, roadmapId))
202
+ .get();
203
+ position = (maxPosResult?.maxPos ?? -1) + 1;
163
204
  }
164
205
  else {
165
206
  // Shift existing projects at or after this position
166
- this.ctx.db.prepare(`
167
- UPDATE ${T.roadmap_projects}
168
- SET position = position + 1
169
- WHERE roadmap_id = ? AND position >= ?
170
- `).run(roadmapId, position);
207
+ this.ctx.drizzle
208
+ .update(pmoRoadmapProjects)
209
+ .set({ position: sql `${pmoRoadmapProjects.position} + 1` })
210
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), gte(pmoRoadmapProjects.position, position)))
211
+ .run();
171
212
  }
172
- const now = Date.now();
173
- this.ctx.db.prepare(`
174
- INSERT INTO ${T.roadmap_projects} (roadmap_id, project_id, position, created_at)
175
- VALUES (?, ?, ?, ?)
176
- `).run(roadmapId, projectId, position, now);
213
+ const now = new Date().toISOString();
214
+ this.ctx.drizzle.insert(pmoRoadmapProjects).values({
215
+ roadmapId,
216
+ projectId,
217
+ position,
218
+ createdAt: now,
219
+ }).run();
177
220
  // Update roadmap timestamp
178
- this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET updated_at = ? WHERE id = ?`).run(now, roadmapId);
221
+ this.ctx.drizzle
222
+ .update(pmoRoadmaps)
223
+ .set({ updatedAt: now })
224
+ .where(eq(pmoRoadmaps.id, roadmapId))
225
+ .run();
179
226
  return {
180
227
  roadmapId,
181
228
  projectId,
@@ -188,33 +235,45 @@ export class RoadmapStorage {
188
235
  */
189
236
  async removeProjectFromRoadmap(roadmapId, projectId) {
190
237
  // Get current position for reordering
191
- const current = this.ctx.db.prepare(`
192
- SELECT position FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
193
- `).get(roadmapId, projectId);
238
+ const current = this.ctx.drizzle
239
+ .select({ position: pmoRoadmapProjects.position })
240
+ .from(pmoRoadmapProjects)
241
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), eq(pmoRoadmapProjects.projectId, projectId)))
242
+ .get();
194
243
  if (!current) {
195
244
  throw new PMOError('NOT_FOUND', `Project ${projectId} not in roadmap ${roadmapId}`);
196
245
  }
197
246
  // Delete the association
198
- this.ctx.db.prepare(`
199
- DELETE FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
200
- `).run(roadmapId, projectId);
247
+ this.ctx.drizzle
248
+ .delete(pmoRoadmapProjects)
249
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), eq(pmoRoadmapProjects.projectId, projectId)))
250
+ .run();
201
251
  // Reorder remaining projects to fill the gap
202
- this.ctx.db.prepare(`
203
- UPDATE ${T.roadmap_projects}
204
- SET position = position - 1
205
- WHERE roadmap_id = ? AND position > ?
206
- `).run(roadmapId, current.position);
252
+ this.ctx.drizzle
253
+ .update(pmoRoadmapProjects)
254
+ .set({ position: sql `${pmoRoadmapProjects.position} - 1` })
255
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), gt(pmoRoadmapProjects.position, current.position)))
256
+ .run();
207
257
  // Update roadmap timestamp
208
- this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET updated_at = ? WHERE id = ?`).run(Date.now(), roadmapId);
258
+ this.ctx.drizzle
259
+ .update(pmoRoadmaps)
260
+ .set({ updatedAt: new Date().toISOString() })
261
+ .where(eq(pmoRoadmaps.id, roadmapId))
262
+ .run();
209
263
  }
210
264
  /**
211
265
  * Reorder a project within a roadmap.
212
266
  */
213
267
  async reorderRoadmapProject(roadmapId, projectId, newPosition) {
214
268
  // Get current position
215
- const current = this.ctx.db.prepare(`
216
- SELECT position, created_at FROM ${T.roadmap_projects} WHERE roadmap_id = ? AND project_id = ?
217
- `).get(roadmapId, projectId);
269
+ const current = this.ctx.drizzle
270
+ .select({
271
+ position: pmoRoadmapProjects.position,
272
+ createdAt: pmoRoadmapProjects.createdAt,
273
+ })
274
+ .from(pmoRoadmapProjects)
275
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), eq(pmoRoadmapProjects.projectId, projectId)))
276
+ .get();
218
277
  if (!current) {
219
278
  throw new PMOError('NOT_FOUND', `Project ${projectId} not in roadmap ${roadmapId}`);
220
279
  }
@@ -224,51 +283,63 @@ export class RoadmapStorage {
224
283
  roadmapId,
225
284
  projectId,
226
285
  position: newPosition,
227
- createdAt: new Date(current.created_at),
286
+ createdAt: new Date(current.createdAt),
228
287
  };
229
288
  }
230
- // Shift positions (same pattern as EpicStorage.reorderEpic)
289
+ // Shift positions
231
290
  if (newPosition < oldPosition) {
232
291
  // Moving up: increment positions in between
233
- this.ctx.db.prepare(`
234
- UPDATE ${T.roadmap_projects}
235
- SET position = position + 1
236
- WHERE roadmap_id = ? AND position >= ? AND position < ?
237
- `).run(roadmapId, newPosition, oldPosition);
292
+ this.ctx.drizzle
293
+ .update(pmoRoadmapProjects)
294
+ .set({ position: sql `${pmoRoadmapProjects.position} + 1` })
295
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), gte(pmoRoadmapProjects.position, newPosition), lt(pmoRoadmapProjects.position, oldPosition)))
296
+ .run();
238
297
  }
239
298
  else {
240
299
  // Moving down: decrement positions in between
241
- this.ctx.db.prepare(`
242
- UPDATE ${T.roadmap_projects}
243
- SET position = position - 1
244
- WHERE roadmap_id = ? AND position > ? AND position <= ?
245
- `).run(roadmapId, oldPosition, newPosition);
300
+ this.ctx.drizzle
301
+ .update(pmoRoadmapProjects)
302
+ .set({ position: sql `${pmoRoadmapProjects.position} - 1` })
303
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), gt(pmoRoadmapProjects.position, oldPosition), lte(pmoRoadmapProjects.position, newPosition)))
304
+ .run();
246
305
  }
247
306
  // Update the target project's position
248
- this.ctx.db.prepare(`
249
- UPDATE ${T.roadmap_projects}
250
- SET position = ?
251
- WHERE roadmap_id = ? AND project_id = ?
252
- `).run(newPosition, roadmapId, projectId);
307
+ this.ctx.drizzle
308
+ .update(pmoRoadmapProjects)
309
+ .set({ position: newPosition })
310
+ .where(and(eq(pmoRoadmapProjects.roadmapId, roadmapId), eq(pmoRoadmapProjects.projectId, projectId)))
311
+ .run();
253
312
  // Update roadmap timestamp
254
- this.ctx.db.prepare(`UPDATE ${T.roadmaps} SET updated_at = ? WHERE id = ?`).run(Date.now(), roadmapId);
313
+ this.ctx.drizzle
314
+ .update(pmoRoadmaps)
315
+ .set({ updatedAt: new Date().toISOString() })
316
+ .where(eq(pmoRoadmaps.id, roadmapId))
317
+ .run();
255
318
  return {
256
319
  roadmapId,
257
320
  projectId,
258
321
  position: newPosition,
259
- createdAt: new Date(current.created_at),
322
+ createdAt: new Date(current.createdAt),
260
323
  };
261
324
  }
262
325
  /**
263
326
  * Get all roadmaps that contain a project.
264
327
  */
265
328
  async getRoadmapsForProject(projectId) {
266
- const rows = this.ctx.db.prepare(`
267
- SELECT r.* FROM ${T.roadmaps} r
268
- JOIN ${T.roadmap_projects} rp ON r.id = rp.roadmap_id
269
- WHERE rp.project_id = ?
270
- ORDER BY r.name ASC
271
- `).all(projectId);
329
+ const rows = this.ctx.drizzle
330
+ .select({
331
+ id: pmoRoadmaps.id,
332
+ name: pmoRoadmaps.name,
333
+ description: pmoRoadmaps.description,
334
+ isDefault: pmoRoadmaps.isDefault,
335
+ createdAt: pmoRoadmaps.createdAt,
336
+ updatedAt: pmoRoadmaps.updatedAt,
337
+ })
338
+ .from(pmoRoadmaps)
339
+ .innerJoin(pmoRoadmapProjects, eq(pmoRoadmaps.id, pmoRoadmapProjects.roadmapId))
340
+ .where(eq(pmoRoadmapProjects.projectId, projectId))
341
+ .orderBy(asc(pmoRoadmaps.name))
342
+ .all();
272
343
  return rows.map(row => this.rowToRoadmap(row));
273
344
  }
274
345
  // ===== Helpers =====
@@ -277,9 +348,9 @@ export class RoadmapStorage {
277
348
  id: row.id,
278
349
  name: row.name,
279
350
  description: row.description || undefined,
280
- isDefault: row.is_default === 1,
281
- createdAt: new Date(row.created_at),
282
- updatedAt: new Date(row.updated_at),
351
+ isDefault: row.isDefault ?? false,
352
+ createdAt: new Date(row.createdAt || Date.now()),
353
+ updatedAt: new Date(row.updatedAt || Date.now()),
283
354
  };
284
355
  }
285
356
  rowToProject(row) {
@@ -289,13 +360,13 @@ export class RoadmapStorage {
289
360
  template: row.template || undefined,
290
361
  description: row.description || undefined,
291
362
  status: (row.status || 'active'),
292
- phaseId: row.phase_id || undefined,
293
- workflowId: row.workflow_id || undefined,
294
- isArchived: row.is_archived === 1,
295
- targetDate: row.target_date ? new Date(row.target_date) : undefined,
296
- initiativeId: row.initiative_id || undefined,
297
- createdAt: new Date(row.created_at),
298
- updatedAt: new Date(row.updated_at),
363
+ phaseId: row.phaseId || undefined,
364
+ workflowId: row.workflowId || undefined,
365
+ isArchived: row.isArchived ?? false,
366
+ targetDate: row.targetDate ? new Date(row.targetDate) : undefined,
367
+ initiativeId: row.initiativeId || undefined,
368
+ createdAt: new Date(row.createdAt || Date.now()),
369
+ updatedAt: new Date(row.updatedAt || Date.now()),
299
370
  };
300
371
  }
301
372
  }
@@ -19,21 +19,22 @@ export class SpecStorage {
19
19
  const now = Date.now();
20
20
  this.ctx.db.prepare(`
21
21
  INSERT INTO ${T.specs} (
22
- id, title, status, type, tags, depends_on,
22
+ id, title, status, type, tags,
23
23
  problem, solution, decisions, not_now, ui_ux,
24
24
  acceptance_criteria, open_questions,
25
25
  requirements_functional, requirements_technical,
26
26
  context, created_at, updated_at
27
27
  )
28
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
- `).run(id, spec.title || 'Untitled Spec', spec.status || 'draft', spec.type || null, spec.tags ? JSON.stringify(spec.tags) : null, spec.dependsOn ? JSON.stringify(spec.dependsOn) : null, spec.problem || null, spec.solution || null, spec.decisions || null, spec.notNow || null, spec.uiUx || null, spec.acceptanceCriteria || null, spec.openQuestions || null, spec.requirementsFunctional || null, spec.requirementsTechnical || null, spec.context || null, now, now);
28
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
+ `).run(id, spec.title || 'Untitled Spec', spec.status || 'draft', spec.type || null, spec.tags ? JSON.stringify(spec.tags) : null, spec.problem || null, spec.solution || null, spec.decisions || null, spec.notNow || null, spec.uiUx || null, spec.acceptanceCriteria || null, spec.openQuestions || null, spec.requirementsFunctional || null, spec.requirementsTechnical || null, spec.context || null, now, now);
30
30
  return {
31
31
  id,
32
32
  title: spec.title || 'Untitled Spec',
33
33
  status: spec.status || 'draft',
34
34
  type: spec.type,
35
35
  tags: spec.tags,
36
- dependsOn: spec.dependsOn,
36
+ // Note: dependsOn is now handled via spec_dependencies table
37
+ dependsOn: undefined,
37
38
  problem: spec.problem,
38
39
  solution: spec.solution,
39
40
  decisions: spec.decisions,
@@ -53,7 +54,7 @@ export class SpecStorage {
53
54
  */
54
55
  async getSpec(id) {
55
56
  const row = this.ctx.db.prepare(`
56
- SELECT id, title, status, type, tags, depends_on,
57
+ SELECT id, title, status, type, tags,
57
58
  problem, solution, decisions, not_now, ui_ux,
58
59
  acceptance_criteria, open_questions,
59
60
  requirements_functional, requirements_technical,
@@ -69,7 +70,7 @@ export class SpecStorage {
69
70
  */
70
71
  async listSpecs(filter) {
71
72
  let query = `
72
- SELECT id, title, status, type, tags, depends_on,
73
+ SELECT id, title, status, type, tags,
73
74
  problem, solution, decisions, not_now, ui_ux,
74
75
  acceptance_criteria, open_questions,
75
76
  requirements_functional, requirements_technical,
@@ -119,10 +120,6 @@ export class SpecStorage {
119
120
  updates.push('tags = ?');
120
121
  params.push(JSON.stringify(changes.tags));
121
122
  }
122
- if (changes.dependsOn !== undefined) {
123
- updates.push('depends_on = ?');
124
- params.push(JSON.stringify(changes.dependsOn));
125
- }
126
123
  if (changes.problem !== undefined) {
127
124
  updates.push('problem = ?');
128
125
  params.push(changes.problem);
@@ -272,7 +269,7 @@ export class SpecStorage {
272
269
  throw new PMOError('NOT_FOUND', `Spec not found: ${dependsOnId}`);
273
270
  }
274
271
  this.ctx.db.prepare(`
275
- INSERT OR IGNORE INTO ${T.spec_dependencies} (spec_id, depends_on, created_at)
272
+ INSERT OR IGNORE INTO ${T.spec_dependencies} (spec_id, depends_on_spec_id, created_at)
276
273
  VALUES (?, ?, ?)
277
274
  `).run(specId, dependsOnId, Date.now());
278
275
  }
@@ -282,7 +279,7 @@ export class SpecStorage {
282
279
  async removeSpecDependency(specId, dependsOnId) {
283
280
  this.ctx.db.prepare(`
284
281
  DELETE FROM ${T.spec_dependencies}
285
- WHERE spec_id = ? AND depends_on = ?
282
+ WHERE spec_id = ? AND depends_on_spec_id = ?
286
283
  `).run(specId, dependsOnId);
287
284
  }
288
285
  /**
@@ -290,12 +287,12 @@ export class SpecStorage {
290
287
  */
291
288
  async getSpecDependencies(specId) {
292
289
  const rows = this.ctx.db.prepare(`
293
- SELECT depends_on FROM ${T.spec_dependencies} WHERE spec_id = ?
290
+ SELECT depends_on_spec_id FROM ${T.spec_dependencies} WHERE spec_id = ?
294
291
  `).all(specId);
295
292
  const specs = [];
296
293
  for (const row of rows) {
297
294
  // eslint-disable-next-line no-await-in-loop -- Sequential lookup for relationship chain
298
- const spec = await this.getSpec(row.depends_on);
295
+ const spec = await this.getSpec(row.depends_on_spec_id);
299
296
  if (spec)
300
297
  specs.push(spec);
301
298
  }
@@ -306,7 +303,7 @@ export class SpecStorage {
306
303
  */
307
304
  async getSpecDependents(specId) {
308
305
  const rows = this.ctx.db.prepare(`
309
- SELECT spec_id FROM ${T.spec_dependencies} WHERE depends_on = ?
306
+ SELECT spec_id FROM ${T.spec_dependencies} WHERE depends_on_spec_id = ?
310
307
  `).all(specId);
311
308
  const specs = [];
312
309
  for (const row of rows) {
@@ -352,7 +349,7 @@ export class SpecStorage {
352
349
  */
353
350
  async getSpecsForProject(projectId) {
354
351
  const rows = this.ctx.db.prepare(`
355
- SELECT s.id, s.title, s.status, s.type, s.tags, s.depends_on,
352
+ SELECT s.id, s.title, s.status, s.type, s.tags,
356
353
  s.problem, s.solution, s.decisions, s.not_now, s.ui_ux,
357
354
  s.acceptance_criteria, s.open_questions,
358
355
  s.requirements_functional, s.requirements_technical,
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Subtask operations for tickets.
3
3
  */
4
+ import { randomUUID } from 'node:crypto';
4
5
  import { PMO_TABLES } from '../schema.js';
5
6
  import { PMOError } from '../types.js';
6
7
  import { slugify } from '../utils.js';
@@ -26,7 +27,20 @@ export class SubtaskStorage {
26
27
  SELECT COALESCE(MAX(position), -1) as max_pos
27
28
  FROM ${T.subtasks} WHERE ticket_id = ?
28
29
  `).get(ticketId);
29
- const id = slugify(title);
30
+ // Generate unique ID - start with slugified title, append counter if collision
31
+ const baseId = slugify(title);
32
+ let id = baseId;
33
+ let counter = 1;
34
+ // Check for existing subtask with same ID and append counter if needed
35
+ while (true) {
36
+ const existing = this.ctx.db.prepare(`
37
+ SELECT 1 FROM ${T.subtasks} WHERE ticket_id = ? AND id = ?
38
+ `).get(ticketId, id);
39
+ if (!existing)
40
+ break;
41
+ counter++;
42
+ id = `${baseId}-${counter}`;
43
+ }
30
44
  const position = maxPos.max_pos + 1;
31
45
  this.ctx.db.prepare(`
32
46
  INSERT INTO ${T.subtasks} (id, ticket_id, title, done, position)
@@ -122,7 +136,8 @@ export class AcceptanceCriteriaStorage {
122
136
  SELECT COALESCE(MAX(position), -1) as max_pos
123
137
  FROM ${T.ticket_acceptance_criteria} WHERE ticket_id = ?
124
138
  `).get(ticketId);
125
- const id = `ac-${Date.now()}`;
139
+ // Use UUID to guarantee uniqueness even when multiple ACs are added in the same millisecond
140
+ const id = `ac-${randomUUID()}`;
126
141
  const position = maxPos.max_pos + 1;
127
142
  this.ctx.db.prepare(`
128
143
  INSERT INTO ${T.ticket_acceptance_criteria} (id, ticket_id, criterion, verifiable, verified, position)
@@ -8,6 +8,16 @@ import { StorageContext } from './types.js';
8
8
  export declare class TicketStorage {
9
9
  private ctx;
10
10
  constructor(ctx: StorageContext);
11
+ /**
12
+ * Resolve a project identifier to its actual ID.
13
+ * Tries multiple strategies:
14
+ * 1. Exact ID match
15
+ * 2. Case-insensitive ID match
16
+ * 3. Exact name match
17
+ * 4. Case-insensitive name match
18
+ * 5. Slugified name match
19
+ */
20
+ private resolveProjectId;
11
21
  /**
12
22
  * Create a new ticket.
13
23
  * Gets default status from the project's workflow.
@@ -45,10 +55,10 @@ export declare class TicketStorage {
45
55
  deleteTicket(id: string): Promise<void>;
46
56
  /**
47
57
  * List tickets with optional filters.
48
- * @param projectId - The project to filter by. Pass undefined to list all tickets across all projects.
58
+ * @param projectIdOrName - The project ID, name, or slug to filter by. Pass undefined to list all tickets across all projects.
49
59
  * @param filter - Additional filters to apply.
50
60
  */
51
- listTickets(projectId: string | undefined, filter?: TicketFilter): Promise<Ticket[]>;
61
+ listTickets(projectIdOrName: string | undefined, filter?: TicketFilter): Promise<Ticket[]>;
52
62
  /**
53
63
  * Move a ticket to a different project.
54
64
  * The ticket will get the default status from the target project's workflow.