@starlove/orbit 1.4.0 → 2.0.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 (329) hide show
  1. package/Dockerfile +10 -8
  2. package/README.md +146 -164
  3. package/dist/full/csp.txt +1 -0
  4. package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
  5. package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
  6. package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
  7. package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
  8. package/dist/full/fonts/Mona_Sans/README.txt +10 -0
  9. package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
  10. package/dist/full/fonts/Newsreader/README.txt +8 -0
  11. package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
  12. package/dist/full/index.html +36 -653
  13. package/dist/full/js/app.BmuponPJ.js +32 -0
  14. package/dist/full/js/app.BmuponPJ.js.map +1 -0
  15. package/dist/full/js/chunk.1fJGQGbW.js +2 -0
  16. package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
  17. package/dist/full/js/chunk.5D7JO2zp.js +2 -0
  18. package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
  19. package/dist/full/js/chunk.BrNn_D79.js +2 -0
  20. package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
  21. package/dist/full/js/chunk.CokJXIqM.js +2 -0
  22. package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
  23. package/dist/full/js/chunk.D_Haqy6R.js +2 -0
  24. package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
  25. package/dist/full/js/chunk.RO0jsH6I.js +15 -0
  26. package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
  27. package/dist/full/js/chunk.dqWOEtBM.js +2 -0
  28. package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
  29. package/dist/full/js/warm.f260bbbc.js +15 -0
  30. package/dist/full/orbits-favicon.png +0 -0
  31. package/dist/full/orbits-logo-bg-wide.png +0 -0
  32. package/dist/full/orbits-logo-bg.png +0 -0
  33. package/dist/full/orbits-logo.png +0 -0
  34. package/dist/full/styles.2b154ffe.css +1 -0
  35. package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
  36. package/dist/full/version.json +2 -2
  37. package/docs/AGENT_GUIDE.md +114 -0
  38. package/docs/UPDATING.md +249 -64
  39. package/lib/adapters/indexeddb/blobs.d.ts +3 -0
  40. package/lib/adapters/indexeddb/blobs.js +54 -0
  41. package/lib/adapters/indexeddb/database.d.ts +60 -0
  42. package/lib/adapters/indexeddb/database.js +126 -0
  43. package/lib/adapters/indexeddb/index.d.ts +5 -0
  44. package/lib/adapters/indexeddb/index.js +3 -0
  45. package/lib/adapters/indexeddb/locks.d.ts +14 -0
  46. package/lib/adapters/indexeddb/locks.js +40 -0
  47. package/lib/adapters/indexeddb/store.d.ts +40 -0
  48. package/lib/adapters/indexeddb/store.js +424 -0
  49. package/lib/adapters/indexeddb/tables.d.ts +43 -0
  50. package/lib/adapters/indexeddb/tables.js +127 -0
  51. package/lib/adapters/memory/blobs.d.ts +3 -0
  52. package/lib/adapters/memory/blobs.js +44 -0
  53. package/lib/adapters/memory/board.d.ts +41 -0
  54. package/lib/adapters/memory/board.js +521 -0
  55. package/lib/adapters/memory/index.d.ts +2 -0
  56. package/lib/adapters/memory/index.js +2 -0
  57. package/lib/adapters/memory/search.d.ts +6 -0
  58. package/lib/adapters/memory/search.js +30 -0
  59. package/lib/adapters/memory/store.d.ts +34 -0
  60. package/lib/adapters/memory/store.js +266 -0
  61. package/lib/adapters/sqlite/backups.d.ts +53 -0
  62. package/lib/adapters/sqlite/backups.js +232 -0
  63. package/lib/adapters/sqlite/blobs.d.ts +14 -0
  64. package/lib/adapters/sqlite/blobs.js +109 -0
  65. package/lib/adapters/sqlite/board.d.ts +77 -0
  66. package/lib/adapters/sqlite/board.js +328 -0
  67. package/lib/adapters/sqlite/database.d.ts +67 -0
  68. package/lib/adapters/sqlite/database.js +285 -0
  69. package/lib/adapters/sqlite/index.d.ts +3 -0
  70. package/lib/adapters/sqlite/index.js +2 -0
  71. package/lib/adapters/sqlite/lock.d.ts +11 -0
  72. package/lib/adapters/sqlite/lock.js +34 -0
  73. package/lib/adapters/sqlite/records.d.ts +54 -0
  74. package/lib/adapters/sqlite/records.js +546 -0
  75. package/lib/adapters/sqlite/schema.d.ts +54 -0
  76. package/lib/adapters/sqlite/schema.js +276 -0
  77. package/lib/adapters/sqlite/store.d.ts +39 -0
  78. package/lib/adapters/sqlite/store.js +385 -0
  79. package/lib/browser/in-page.d.ts +31 -0
  80. package/lib/browser/in-page.js +46 -0
  81. package/lib/browser/old-preview.d.ts +13 -0
  82. package/lib/browser/old-preview.js +80 -0
  83. package/lib/browser/preview-board.d.ts +8 -0
  84. package/lib/browser/preview-board.js +69 -0
  85. package/lib/cli/main.d.ts +7 -0
  86. package/lib/cli/main.js +343 -0
  87. package/lib/cli/orbit.d.ts +8 -0
  88. package/lib/cli/orbit.js +25 -0
  89. package/lib/client/http.d.ts +38 -0
  90. package/lib/client/http.js +334 -0
  91. package/lib/client/local.d.ts +26 -0
  92. package/lib/client/local.js +190 -0
  93. package/lib/client/object-url.d.ts +8 -0
  94. package/lib/client/object-url.js +21 -0
  95. package/lib/client/types.d.ts +268 -0
  96. package/lib/client/types.js +8 -0
  97. package/lib/commands/agent-tools.d.ts +31 -0
  98. package/lib/commands/agent-tools.js +100 -0
  99. package/lib/commands/index.d.ts +56 -0
  100. package/lib/commands/index.js +298 -0
  101. package/lib/commands/parse.d.ts +73 -0
  102. package/lib/commands/parse.js +231 -0
  103. package/lib/commands/present.d.ts +134 -0
  104. package/lib/commands/present.js +198 -0
  105. package/lib/commands/session.d.ts +85 -0
  106. package/lib/commands/session.js +138 -0
  107. package/lib/commands/verbs.d.ts +35 -0
  108. package/lib/commands/verbs.js +842 -0
  109. package/lib/core/actors.d.ts +18 -0
  110. package/lib/core/actors.js +45 -0
  111. package/lib/core/agent.d.ts +128 -0
  112. package/lib/core/agent.js +323 -0
  113. package/lib/core/boards.d.ts +113 -0
  114. package/lib/core/boards.js +288 -0
  115. package/lib/core/bootstrap.d.ts +47 -0
  116. package/lib/core/bootstrap.js +100 -0
  117. package/lib/core/bytes.d.ts +10 -0
  118. package/lib/core/bytes.js +52 -0
  119. package/lib/core/card-paths.d.ts +25 -0
  120. package/lib/core/card-paths.js +44 -0
  121. package/lib/core/context.d.ts +60 -0
  122. package/lib/core/context.js +42 -0
  123. package/lib/core/cross-board.d.ts +81 -0
  124. package/lib/core/cross-board.js +228 -0
  125. package/lib/core/describe.d.ts +21 -0
  126. package/lib/core/describe.js +81 -0
  127. package/lib/core/errors.d.ts +159 -0
  128. package/lib/core/errors.js +160 -0
  129. package/lib/core/files.d.ts +79 -0
  130. package/lib/core/files.js +262 -0
  131. package/lib/core/history.d.ts +20 -0
  132. package/lib/core/history.js +53 -0
  133. package/lib/core/image-types.d.ts +16 -0
  134. package/lib/core/image-types.js +36 -0
  135. package/lib/core/model.d.ts +263 -0
  136. package/lib/core/model.js +34 -0
  137. package/lib/core/nebula-link.d.ts +14 -0
  138. package/lib/core/nebula-link.js +39 -0
  139. package/lib/core/relations.d.ts +58 -0
  140. package/lib/core/relations.js +112 -0
  141. package/lib/core/search.d.ts +38 -0
  142. package/lib/core/search.js +92 -0
  143. package/lib/core/sessions.d.ts +44 -0
  144. package/lib/core/sessions.js +158 -0
  145. package/lib/core/snapshot-schema.d.ts +295 -0
  146. package/lib/core/snapshot-schema.js +82 -0
  147. package/lib/core/snapshots.d.ts +75 -0
  148. package/lib/core/snapshots.js +409 -0
  149. package/lib/core/states.d.ts +25 -0
  150. package/lib/core/states.js +126 -0
  151. package/lib/core/text.d.ts +40 -0
  152. package/lib/core/text.js +113 -0
  153. package/lib/core/tickets.d.ts +69 -0
  154. package/lib/core/tickets.js +311 -0
  155. package/lib/core/trello.d.ts +17 -0
  156. package/lib/core/trello.js +269 -0
  157. package/lib/core/verdicts.d.ts +28 -0
  158. package/lib/core/verdicts.js +92 -0
  159. package/lib/core/views.d.ts +95 -0
  160. package/lib/core/views.js +186 -0
  161. package/lib/http/app.d.ts +46 -0
  162. package/lib/http/app.js +491 -0
  163. package/lib/mcp-server.d.ts +10 -0
  164. package/lib/mcp-server.js +120 -0
  165. package/lib/node/assets.d.ts +7 -0
  166. package/lib/node/assets.js +68 -0
  167. package/lib/node/data-folder.d.ts +21 -0
  168. package/lib/node/data-folder.js +34 -0
  169. package/lib/node/env.d.ts +38 -0
  170. package/lib/node/env.js +49 -0
  171. package/lib/node/host-check.d.ts +24 -0
  172. package/lib/node/host-check.js +53 -0
  173. package/lib/node/host-services.d.ts +9 -0
  174. package/lib/node/host-services.js +150 -0
  175. package/lib/node/local.d.ts +22 -0
  176. package/lib/node/local.js +61 -0
  177. package/lib/node/migrate.d.ts +53 -0
  178. package/lib/node/migrate.js +635 -0
  179. package/lib/node/package.d.ts +7 -0
  180. package/lib/node/package.js +14 -0
  181. package/lib/node/repo-board.d.ts +49 -0
  182. package/lib/node/repo-board.js +245 -0
  183. package/lib/node/serve.d.ts +8 -0
  184. package/lib/node/serve.js +106 -0
  185. package/lib/orbit.d.ts +47 -0
  186. package/lib/orbit.js +44 -0
  187. package/lib/quiet-sqlite-warning.d.ts +1 -0
  188. package/lib/quiet-sqlite-warning.js +18 -0
  189. package/lib/store/blob-path.d.ts +6 -0
  190. package/lib/store/blob-path.js +12 -0
  191. package/lib/store/conformance.d.ts +52 -0
  192. package/lib/store/conformance.js +1449 -0
  193. package/lib/store/search-words.d.ts +10 -0
  194. package/lib/store/search-words.js +19 -0
  195. package/lib/store/types.d.ts +452 -0
  196. package/lib/store/types.js +34 -0
  197. package/llms.txt +63 -0
  198. package/package.json +109 -25
  199. package/src/cli/orbit.js +11 -658
  200. package/src/mcp-server.js +16 -876
  201. package/SKILL-ORBIT.md +0 -151
  202. package/build.mjs +0 -210
  203. package/dist/full/js/app.62905fcb.js +0 -952
  204. package/dist/full/js/app.62905fcb.js.map +0 -7
  205. package/dist/full/styles.4e60d824.css +0 -1
  206. package/docs/AGENT_PROTOCOL.md +0 -424
  207. package/docs/MCP_SETUP.md +0 -54
  208. package/docs/ORBIT_DISPATCH.md +0 -280
  209. package/docs/SS-Orbit-Dark.png +0 -0
  210. package/docs/SS-Orbit-Light.png +0 -0
  211. package/docs/mcp-client.example.json +0 -13
  212. package/docs/setup/orbit-init-wizard.md +0 -174
  213. package/public/apple-touch-icon.png +0 -0
  214. package/public/cursors/live_arrow.cur +0 -0
  215. package/public/cursors/live_link.cur +0 -0
  216. package/public/cursors/live_move.cur +0 -0
  217. package/public/cursors/live_select.cur +0 -0
  218. package/public/cursors/live_unavail.cur +0 -0
  219. package/public/favicon-32x32.png +0 -0
  220. package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
  221. package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
  222. package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
  223. package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
  224. package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
  225. package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
  226. package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
  227. package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
  228. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
  229. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
  230. package/public/fonts/Chakra_Petch/OFL.txt +0 -93
  231. package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
  232. package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
  233. package/public/fonts/Inter/OFL.txt +0 -93
  234. package/public/fonts/Inter/README.txt +0 -118
  235. package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  236. package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
  237. package/public/fonts/JetBrains_Mono/README.txt +0 -79
  238. package/public/fonts/OpenRing-Regular.ttf +0 -0
  239. package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  240. package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  241. package/public/fonts/Open_Sans/README.txt +0 -100
  242. package/public/fonts/Orbitron/OFL.txt +0 -93
  243. package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
  244. package/public/fonts/Orbitron/README.txt +0 -68
  245. package/public/fonts/Rajdhani/OFL.txt +0 -93
  246. package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
  247. package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
  248. package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
  249. package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
  250. package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
  251. package/public/fonts/runescape-uf.ttf +0 -0
  252. package/public/index.html +0 -769
  253. package/public/js/actor-labels.js +0 -65
  254. package/public/js/api.js +0 -39
  255. package/public/js/app.js +0 -45
  256. package/public/js/artifact-drawer.js +0 -79
  257. package/public/js/board-export.js +0 -161
  258. package/public/js/board-menu.js +0 -374
  259. package/public/js/card-actions.js +0 -545
  260. package/public/js/config.js +0 -20
  261. package/public/js/create-card.js +0 -190
  262. package/public/js/dom.js +0 -19
  263. package/public/js/drawer.js +0 -273
  264. package/public/js/font-preference.js +0 -229
  265. package/public/js/format.js +0 -433
  266. package/public/js/help-menu.js +0 -31
  267. package/public/js/icons.js +0 -16
  268. package/public/js/kanban.js +0 -694
  269. package/public/js/local-backend.js +0 -1015
  270. package/public/js/main.js +0 -161
  271. package/public/js/modal.js +0 -33
  272. package/public/js/motion-preference.js +0 -64
  273. package/public/js/panel.js +0 -58
  274. package/public/js/router.js +0 -134
  275. package/public/js/search.js +0 -56
  276. package/public/js/settings-tabs.js +0 -7
  277. package/public/js/settings.js +0 -1634
  278. package/public/js/sse.js +0 -159
  279. package/public/js/starscape.js +0 -332
  280. package/public/js/state.js +0 -129
  281. package/public/js/text-editing.js +0 -128
  282. package/public/js/theme-preference.js +0 -95
  283. package/public/js/ticket-detail.js +0 -1516
  284. package/public/js/ticket-typeahead.js +0 -49
  285. package/public/js/toast.js +0 -89
  286. package/public/js/topbar-notes.js +0 -155
  287. package/public/js/transport.js +0 -22
  288. package/public/js/typeahead.js +0 -206
  289. package/public/js/unread.js +0 -121
  290. package/public/js/update-check.js +0 -58
  291. package/public/js/url-routes.js +0 -94
  292. package/public/orbits-favicon.png +0 -0
  293. package/public/orbits-logo-bg-wide.png +0 -0
  294. package/public/orbits-logo-bg.png +0 -0
  295. package/public/orbits-logo.png +0 -0
  296. package/public/styles.css +0 -7709
  297. package/src/core/agent.js +0 -387
  298. package/src/core/agents-md.js +0 -109
  299. package/src/core/artifacts.js +0 -149
  300. package/src/core/attachments.js +0 -238
  301. package/src/core/auth.js +0 -85
  302. package/src/core/backups.js +0 -142
  303. package/src/core/board.js +0 -148
  304. package/src/core/boards.js +0 -409
  305. package/src/core/bootstrap.js +0 -170
  306. package/src/core/css-bundle.js +0 -30
  307. package/src/core/db.js +0 -315
  308. package/src/core/delete-board.js +0 -163
  309. package/src/core/dispatch.js +0 -1007
  310. package/src/core/events.js +0 -314
  311. package/src/core/http.js +0 -115
  312. package/src/core/migrate.js +0 -198
  313. package/src/core/paths.js +0 -55
  314. package/src/core/provision-repo-board.js +0 -119
  315. package/src/core/queries.js +0 -246
  316. package/src/core/registry.js +0 -211
  317. package/src/core/relations.js +0 -163
  318. package/src/core/review-verdicts.js +0 -138
  319. package/src/core/router.js +0 -556
  320. package/src/core/search.js +0 -183
  321. package/src/core/seed.js +0 -234
  322. package/src/core/snapshots.js +0 -692
  323. package/src/core/states.js +0 -167
  324. package/src/core/system-picker.js +0 -109
  325. package/src/core/tickets.js +0 -400
  326. package/src/core/util.js +0 -106
  327. package/src/core/version-check.js +0 -154
  328. package/src/mcp/orbit-client.js +0 -287
  329. package/src/server.js +0 -115
@@ -1,163 +0,0 @@
1
- import { boardById, childTicketsFor, relationRows, ticketById, unresolvedBlockers } from "./queries.js";
2
- import { tx } from "./db.js";
3
- import { requireBoardAccess, requirePermission } from "./auth.js";
4
- import { recordEvent } from "./events.js";
5
- import { httpError, id, now, requiredString } from "./util.js";
6
-
7
- export function createRelation(body, ctx) {
8
- const { db, board, actor } = ctx;
9
- const sourceId = requiredString(body.source_ticket_id, "source_ticket_id");
10
- const source = ticketById(db, sourceId);
11
- if (!source || source.board_id !== board.id) throw httpError(404, "ticket_not_found");
12
-
13
- const target = ticketById(db, requiredString(body.target_ticket_id, "target_ticket_id"));
14
- // Relations are only meaningful within a single board's DB. Cross-board
15
- // relations are not supported by the schema (foreign keys live in this DB
16
- // only) and would invent silent invariants.
17
- if (!target || target.board_id !== board.id) throw httpError(404, "ticket_not_found");
18
-
19
- requireBoardAccess(actor, boardById(db, source.board_id));
20
- requirePermission(actor, "write");
21
-
22
- if (source.id === target.id) throw httpError(400, "relation_self");
23
- if (source.parent_ticket_id === target.id || target.parent_ticket_id === source.id) {
24
- throw httpError(400, "relation_redundant_with_parent");
25
- }
26
-
27
- const relationType = body.type || "relates_to";
28
- // Hierarchy is stored on the ticket row (parent_ticket_id), not in the
29
- // relations table. The `child_of` / `parent_of` types only appear as
30
- // read-only synthetic entries in getTicketRelations output — pointing
31
- // callers at the right write path keeps the two models disjoint.
32
- if (relationType === "child_of" || relationType === "parent_of") {
33
- throw httpError(400, "hierarchy_uses_parent_ticket_id");
34
- }
35
- // Blocker relations are meaningless against a target that's already in a
36
- // done lane — the row would be a stale blocker on creation. Reject so
37
- // callers fix the data instead of silently creating dead relations.
38
- if (relationType === "blocked_by" && target.state_role === "done") {
39
- throw httpError(400, "blocker_target_already_done");
40
- }
41
- if (relationType === "blocks" && source.state_role === "done") {
42
- throw httpError(400, "blocker_source_already_done");
43
- }
44
- const existing = db
45
- .prepare("SELECT * FROM relations WHERE source_ticket_id = ? AND target_ticket_id = ? AND type = ?")
46
- .get(source.id, target.id, relationType);
47
- // Don't silently 201 on a no-op: an INSERT OR IGNORE that hit the UNIQUE
48
- // constraint would have returned the pre-existing row, hiding from the
49
- // caller that nothing was created.
50
- if (existing) throw httpError(409, "relation_exists");
51
-
52
- const relationId = id();
53
- return tx(db, () => {
54
- db.prepare(
55
- "INSERT INTO relations (id, source_ticket_id, target_ticket_id, type, created_at) VALUES (?, ?, ?, ?, ?)"
56
- ).run(relationId, source.id, target.id, relationType, now());
57
- recordEvent(db, source.board_id, "relation_created", source.id, actor.name, {
58
- target_ticket_id: target.id,
59
- type: relationType
60
- });
61
- return db.prepare("SELECT * FROM relations WHERE id = ?").get(relationId);
62
- });
63
- }
64
-
65
- export function deleteRelation(relationId, ctx) {
66
- const { db, board, actor } = ctx;
67
- const row = db.prepare("SELECT * FROM relations WHERE id = ?").get(relationId);
68
- if (!row) throw httpError(404, "relation_not_found");
69
- const source = ticketById(db, row.source_ticket_id);
70
- const target = ticketById(db, row.target_ticket_id);
71
- // Relation must live in the resolved board (sanity check — same DB by
72
- // definition since router resolved this board, but keep the assertion).
73
- if (source && source.board_id !== board.id) throw httpError(404, "relation_not_found");
74
- if (source) requireBoardAccess(actor, boardById(db, source.board_id));
75
- if (target) requireBoardAccess(actor, boardById(db, target.board_id));
76
- requirePermission(actor, "write");
77
- return tx(db, () => {
78
- db.prepare("DELETE FROM relations WHERE id = ?").run(relationId);
79
- recordEvent(db, source?.board_id || board.id, "relation_deleted", row.source_ticket_id, actor.name, {
80
- target_ticket_id: row.target_ticket_id,
81
- type: row.type
82
- });
83
- return { ok: true };
84
- });
85
- }
86
-
87
- export function getTicketRelations(ticketId, ctx) {
88
- const { db, board, actor } = ctx;
89
- const ticket = ticketById(db, ticketId);
90
- if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
91
- requireBoardAccess(actor, boardById(db, ticket.board_id));
92
- const compactOther = (other) => other && {
93
- id: other.id,
94
- number: other.number,
95
- title: other.title,
96
- type: other.type,
97
- state_id: other.state_id,
98
- state_name: other.state_name,
99
- state_role: other.state_role,
100
- parent_ticket_id: other.parent_ticket_id
101
- };
102
-
103
- const relations = relationRows(db, ticketId).map((row) => ({
104
- id: row.id,
105
- type: row.type,
106
- direction: row.direction,
107
- source: "relation",
108
- other_ticket: compactOther(row.other_ticket)
109
- }));
110
-
111
- // Hierarchy is the other relationship model in the system. We don't store
112
- // it in the `relations` table (parent_ticket_id is a column on the ticket
113
- // row, kept disjoint from peer relations), but agents asking "what's linked
114
- // to this ticket?" want a single answer. Synthesize read-only `child_of` /
115
- // `parent_of` entries here. Marked source='hierarchy' so callers know they
116
- // can't delete via DELETE /api/relations/:id; mutate via PATCH on the
117
- // ticket's parent_ticket_id instead.
118
- if (ticket.parent_ticket_id) {
119
- const parent = ticketById(db, ticket.parent_ticket_id);
120
- if (parent && !parent.archived_at) {
121
- relations.push({
122
- id: null,
123
- type: "child_of",
124
- direction: "outgoing",
125
- source: "hierarchy",
126
- other_ticket: compactOther(parent)
127
- });
128
- }
129
- }
130
- for (const child of childTicketsFor(db, ticketId)) {
131
- relations.push({
132
- id: null,
133
- type: "parent_of",
134
- direction: "outgoing",
135
- source: "hierarchy",
136
- other_ticket: compactOther(child)
137
- });
138
- }
139
-
140
- return { ticket_id: ticket.id, relations };
141
- }
142
-
143
- export function getTicketBlockers(ticketId, ctx) {
144
- const { db, board, actor } = ctx;
145
- const ticket = ticketById(db, ticketId);
146
- if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
147
- requireBoardAccess(actor, boardById(db, ticket.board_id));
148
- const blockers = unresolvedBlockers(db, ticketId).map((row) => ({
149
- id: row.id,
150
- number: row.number,
151
- title: row.title,
152
- type: row.type,
153
- state_id: row.state_id,
154
- state_name: row.state_name,
155
- state_role: row.state_role,
156
- via_epic_id: row.via_epic_id || null
157
- }));
158
- return {
159
- ticket_id: ticket.id,
160
- can_start: blockers.length === 0,
161
- blockers
162
- };
163
- }
@@ -1,138 +0,0 @@
1
- import { boardById, ticketById } from "./queries.js";
2
- import { requireBoardAccess, requirePermission } from "./auth.js";
3
- import { tx } from "./db.js";
4
- import { recordEvent } from "./events.js";
5
- import { bumpTicketUpdatedAt } from "./tickets.js";
6
- import { httpError, id, now, requiredString } from "./util.js";
7
-
8
- const VERDICTS = new Set(["PASS", "BLOCK", "QUESTION"]);
9
- const JSON_ARRAY_FIELDS = ["blocking_findings", "optional_findings", "evidence_commands"];
10
-
11
- export function normalizeReviewVerdict(value) {
12
- const verdict = String(value || "").trim().toUpperCase();
13
- if (!VERDICTS.has(verdict)) throw httpError(400, "invalid_review_verdict");
14
- return verdict;
15
- }
16
-
17
- function normalizeJsonArray(value, field) {
18
- if (value === undefined || value === null) return [];
19
- if (!Array.isArray(value)) throw httpError(400, `invalid_${field}`);
20
- return value;
21
- }
22
-
23
- function optionalString(value) {
24
- if (value === undefined || value === null) return "";
25
- return String(value).trim();
26
- }
27
-
28
- function hydrateReviewVerdict(row) {
29
- if (!row) return null;
30
- const hydrated = { ...row };
31
- for (const field of JSON_ARRAY_FIELDS) {
32
- const jsonField = `${field}_json`;
33
- hydrated[field] = JSON.parse(row[jsonField] || "[]");
34
- delete hydrated[jsonField];
35
- }
36
- hydrated.supersedes_prior_review_id = hydrated.supersedes_prior_review_id || null;
37
- return hydrated;
38
- }
39
-
40
- function reviewVerdictById(db, reviewId) {
41
- return hydrateReviewVerdict(db.prepare("SELECT * FROM review_verdicts WHERE id = ?").get(reviewId));
42
- }
43
-
44
- function requireTicketForReview(ticketId, ctx) {
45
- const { db, board, actor } = ctx;
46
- const ticket = ticketById(db, ticketId);
47
- if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
48
- requireBoardAccess(actor, boardById(db, ticket.board_id));
49
- return ticket;
50
- }
51
-
52
- export function createReviewVerdict(ticketId, body = {}, ctx) {
53
- const { db, actor } = ctx;
54
- const ticket = requireTicketForReview(ticketId, ctx);
55
- requirePermission(actor, "write");
56
-
57
- const verdict = normalizeReviewVerdict(requiredString(body.verdict, "verdict"));
58
- const blockingFindings = normalizeJsonArray(body.blocking_findings, "blocking_findings");
59
- const optionalFindings = normalizeJsonArray(body.optional_findings, "optional_findings");
60
- const evidenceCommands = normalizeJsonArray(body.evidence_commands, "evidence_commands");
61
- const supersedesPriorReviewId = optionalString(body.supersedes_prior_review_id) || null;
62
-
63
- if (supersedesPriorReviewId) {
64
- const prior = reviewVerdictById(db, supersedesPriorReviewId);
65
- if (!prior) throw httpError(400, "invalid_supersedes_prior_review_id");
66
- if (prior.ticket_id !== ticket.id) throw httpError(400, "superseded_review_ticket_mismatch");
67
- }
68
-
69
- const reviewId = id();
70
- return tx(db, () => {
71
- const time = now();
72
- db.prepare(
73
- `INSERT INTO review_verdicts
74
- (id, ticket_id, verdict, blocking_findings_json, optional_findings_json,
75
- evidence_commands_json, reviewer_profile, reviewer_session_id,
76
- reviewed_commit_sha, dispatch_run_id, supersedes_prior_review_id,
77
- created_by, created_at)
78
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
79
- ).run(
80
- reviewId,
81
- ticket.id,
82
- verdict,
83
- JSON.stringify(blockingFindings),
84
- JSON.stringify(optionalFindings),
85
- JSON.stringify(evidenceCommands),
86
- optionalString(body.reviewer_profile),
87
- optionalString(body.reviewer_session_id),
88
- optionalString(body.reviewed_commit_sha),
89
- optionalString(body.dispatch_run_id),
90
- supersedesPriorReviewId,
91
- actor.name,
92
- time
93
- );
94
- db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticket.id);
95
- recordEvent(db, ticket.board_id, "review_verdict_created", ticket.id, actor.name, {
96
- review_id: reviewId,
97
- verdict,
98
- supersedes_prior_review_id: supersedesPriorReviewId,
99
- reviewed_commit_sha: optionalString(body.reviewed_commit_sha),
100
- dispatch_run_id: optionalString(body.dispatch_run_id),
101
- actor_type: actor.type,
102
- actor_id: actor.id
103
- });
104
- bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
105
- return getReviewVerdict(reviewId, ctx);
106
- });
107
- }
108
-
109
- export function getReviewVerdict(reviewId, ctx) {
110
- const { db, board, actor } = ctx;
111
- const review = reviewVerdictById(db, reviewId);
112
- if (!review) throw httpError(404, "review_verdict_not_found");
113
- const ticket = ticketById(db, review.ticket_id);
114
- if (!ticket || ticket.board_id !== board.id) throw httpError(404, "review_verdict_not_found");
115
- requireBoardAccess(actor, boardById(db, ticket.board_id));
116
- return review;
117
- }
118
-
119
- export function listReviewVerdicts(ticketId, ctx, options = {}) {
120
- const { db } = ctx;
121
- const ticket = requireTicketForReview(ticketId, ctx);
122
- const limit = normalizeLimit(options.limit);
123
- return db
124
- .prepare(
125
- `SELECT * FROM review_verdicts
126
- WHERE ticket_id = ?
127
- ORDER BY created_at DESC, rowid DESC
128
- LIMIT ?`
129
- )
130
- .all(ticket.id, limit)
131
- .map(hydrateReviewVerdict);
132
- }
133
-
134
- function normalizeLimit(value) {
135
- const n = Number(value ?? 50);
136
- if (!Number.isInteger(n) || n <= 0) return 50;
137
- return Math.min(n, 200);
138
- }