@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,119 +0,0 @@
1
- // Side-effect-free workspace provisioning for CLI and server startup.
2
- // Do not import board.js from here (that module runs full startup at import time).
3
-
4
- import { existsSync, mkdirSync } from "node:fs";
5
- import { basename, dirname } from "node:path";
6
- import { createBoardSchema, openConnection } from "./db.js";
7
- import {
8
- computeNewBoardDbPath,
9
- getBoardByRepoPath,
10
- insertBoard,
11
- syncRegistryFromBoardDb
12
- } from "./registry.js";
13
- import { seedIfEmpty } from "./seed.js";
14
- import { normalizePath, now, slugify } from "./util.js";
15
-
16
- /**
17
- * Create or repair the **first** Orbit board for a repo root: `.orbit/board.db`,
18
- * full seed when empty, and a matching registry row.
19
- *
20
- * Idempotent when a registry row already exists for `repo_path`.
21
- *
22
- * @param {string} repoPathRaw Absolute or normalized repo directory
23
- * @returns {{ ok: true, status: "skipped"|"created"|"repaired", registryRow: object|null, message?: string }}
24
- */
25
- export function provisionRepoBoard(repoPathRaw, options = {}) {
26
- const repoRoot = normalizePath(repoPathRaw);
27
- const existingReg = getBoardByRepoPath(repoRoot);
28
- if (existingReg) {
29
- return {
30
- ok: true,
31
- status: "skipped",
32
- registryRow: existingReg,
33
- message: "A board is already registered for this repo path."
34
- };
35
- }
36
-
37
- const slugHint = slugify(basename(repoRoot) || "board");
38
- const dbPath = computeNewBoardDbPath(repoRoot, slugHint);
39
- mkdirSync(dirname(dbPath), { recursive: true });
40
-
41
- const hadExistingFile = existsSync(dbPath);
42
- if (hadExistingFile) {
43
- return provisionExistingDbFile(repoRoot, dbPath, options);
44
- }
45
-
46
- const db = openConnection(dbPath);
47
- createBoardSchema(db);
48
- const seeded = seedIfEmpty(db, repoRoot, {
49
- enableAi: options.enableAi,
50
- includeExamples: options.includeExamples
51
- });
52
- if (!seeded) {
53
- const boardMeta = readBoardMetaFromSqlite(db);
54
- if (!boardMeta) {
55
- throw new Error("provisionRepoBoard: database file exists but contains no board row.");
56
- }
57
- return registerOrphanBoardDb(repoRoot, dbPath, db, boardMeta);
58
- }
59
-
60
- return insertAfterSeed(repoRoot, dbPath, db, seeded, "created");
61
- }
62
-
63
- function provisionExistingDbFile(repoRoot, dbPath, options = {}) {
64
- const db = openConnection(dbPath);
65
- createBoardSchema(db);
66
- const seeded = seedIfEmpty(db, repoRoot, {
67
- enableAi: options.enableAi,
68
- includeExamples: options.includeExamples
69
- });
70
- if (seeded) {
71
- return insertAfterSeed(repoRoot, dbPath, db, seeded, "repaired");
72
- }
73
-
74
- const boardMeta = readBoardMetaFromSqlite(db);
75
- if (!boardMeta) {
76
- throw new Error(
77
- "provisionRepoBoard: .orbit/board.db exists but has no board row — remove or repair the file manually."
78
- );
79
- }
80
- return registerOrphanBoardDb(repoRoot, dbPath, db, boardMeta);
81
- }
82
-
83
- function readBoardMetaFromSqlite(db) {
84
- return db.prepare("SELECT * FROM boards LIMIT 1").get() || null;
85
- }
86
-
87
- /**
88
- * @param {"created"|"repaired"} outcome
89
- */
90
- function insertAfterSeed(repoRoot, dbPath, db, seeded, outcome) {
91
- const t = now();
92
- insertBoard({
93
- id: seeded.id,
94
- slug: seeded.slug,
95
- name: seeded.name,
96
- repo_path: repoRoot,
97
- db_path: dbPath,
98
- repo_url: seeded.repo_url || "",
99
- default_branch: seeded.default_branch || "main",
100
- last_active_at: t,
101
- created_at: t,
102
- updated_at: t
103
- });
104
- const fresh = getBoardByRepoPath(repoRoot);
105
- if (fresh) syncRegistryFromBoardDb(fresh, db);
106
- return { ok: true, status: outcome, registryRow: fresh, message: undefined };
107
- }
108
-
109
- function registerOrphanBoardDb(repoRoot, dbPath, db, b) {
110
- const boardRow = { id: b.id, repo_path: repoRoot, db_path: dbPath };
111
- syncRegistryFromBoardDb(boardRow, db);
112
- const fresh = getBoardByRepoPath(repoRoot);
113
- return {
114
- ok: true,
115
- status: "repaired",
116
- registryRow: fresh,
117
- message: "Registered existing .orbit/board.db with the registry."
118
- };
119
- }
@@ -1,246 +0,0 @@
1
- // Read/write helpers that all take an explicit `db` connection. No hidden
2
- // "active board" state — callers pass in the connection for the board they
3
- // resolved at the request boundary.
4
-
5
- import { httpError, id, now, requiredString } from "./util.js";
6
-
7
- export function ticketByNumber(db, boardId, number) {
8
- return db
9
- .prepare(
10
- `SELECT t.*, s.name AS state_name, s.role AS state_role,
11
- b.slug AS board_slug, b.name AS board_name
12
- FROM tickets t
13
- JOIN states s ON s.id = t.state_id
14
- JOIN boards b ON b.id = t.board_id
15
- WHERE t.board_id = ? AND t.number = ?`
16
- )
17
- .get(boardId, number);
18
- }
19
-
20
- export function ticketByTitle(db, boardId, title) {
21
- return db
22
- .prepare(
23
- `SELECT t.*, s.name AS state_name, s.role AS state_role,
24
- b.slug AS board_slug, b.name AS board_name
25
- FROM tickets t
26
- JOIN states s ON s.id = t.state_id
27
- JOIN boards b ON b.id = t.board_id
28
- WHERE t.board_id = ? AND lower(t.title) = lower(?)
29
- ORDER BY t.archived_at IS NULL DESC, t.number DESC
30
- LIMIT 1`
31
- )
32
- .get(boardId, title);
33
- }
34
-
35
- export function ticketById(db, ticketId) {
36
- return db
37
- .prepare(
38
- `SELECT t.*, s.name AS state_name, s.role AS state_role,
39
- b.slug AS board_slug, b.name AS board_name
40
- FROM tickets t
41
- JOIN states s ON s.id = t.state_id
42
- JOIN boards b ON b.id = t.board_id
43
- WHERE t.id = ?`
44
- )
45
- .get(ticketId);
46
- }
47
-
48
- export function boardById(db, boardId) {
49
- return db.prepare("SELECT * FROM boards WHERE id = ?").get(boardId);
50
- }
51
-
52
- export function stateByName(db, boardId, name) {
53
- return db.prepare("SELECT * FROM states WHERE board_id = ? AND name = ?").get(boardId, name);
54
- }
55
-
56
- export function stateByRole(db, boardId, role) {
57
- return db
58
- .prepare("SELECT * FROM states WHERE board_id = ? AND role = ? ORDER BY position LIMIT 1")
59
- .get(boardId, role);
60
- }
61
-
62
- export function stateById(db, stateId) {
63
- return db.prepare("SELECT * FROM states WHERE id = ?").get(stateId);
64
- }
65
-
66
- export function defaultStateId(db, boardId) {
67
- const state =
68
- db.prepare("SELECT id FROM states WHERE board_id = ? AND is_default = 1").get(boardId) ||
69
- db.prepare("SELECT id FROM states WHERE board_id = ? ORDER BY position LIMIT 1").get(boardId);
70
- if (!state) throw httpError(400, "board_has_no_states");
71
- return state.id;
72
- }
73
-
74
- export function compactStatePositions(db, boardId) {
75
- const states = db.prepare("SELECT id FROM states WHERE board_id = ? ORDER BY position").all(boardId);
76
- states.forEach((state, position) => {
77
- db.prepare("UPDATE states SET position = ? WHERE id = ?").run(position, state.id);
78
- });
79
- }
80
-
81
- export function labelsForTicket(db, ticketId) {
82
- return db
83
- .prepare(
84
- `SELECT l.*
85
- FROM ticket_labels tl
86
- JOIN labels l ON l.id = tl.label_id
87
- WHERE tl.ticket_id = ?
88
- ORDER BY l.name`
89
- )
90
- .all(ticketId);
91
- }
92
-
93
- export function childTicketsFor(db, ticketId) {
94
- return db
95
- .prepare(
96
- `SELECT t.*, s.name AS state_name, s.role AS state_role,
97
- b.slug AS board_slug, b.name AS board_name
98
- FROM tickets t
99
- JOIN states s ON s.id = t.state_id
100
- JOIN boards b ON b.id = t.board_id
101
- WHERE t.parent_ticket_id = ? AND t.archived_at IS NULL
102
- ORDER BY t.number ASC`
103
- )
104
- .all(ticketId);
105
- }
106
-
107
- export function archivedTicketsForBoard(db, boardId) {
108
- return db
109
- .prepare(
110
- `SELECT t.*, s.name AS state_name, s.role AS state_role,
111
- b.slug AS board_slug, b.name AS board_name
112
- FROM tickets t
113
- JOIN states s ON s.id = t.state_id
114
- JOIN boards b ON b.id = t.board_id
115
- WHERE t.board_id = ? AND t.archived_at IS NOT NULL
116
- ORDER BY t.archived_at DESC`
117
- )
118
- .all(boardId);
119
- }
120
-
121
- export function compactTicket(ticket) {
122
- if (!ticket) return null;
123
- return {
124
- id: ticket.id,
125
- board_id: ticket.board_id,
126
- board_slug: ticket.board_slug,
127
- number: ticket.number,
128
- title: ticket.title,
129
- type: ticket.type,
130
- state_name: ticket.state_name,
131
- state_role: ticket.state_role,
132
- priority: ticket.priority
133
- };
134
- }
135
-
136
- export function uniqueTickets(tickets) {
137
- const seen = new Set();
138
- const result = [];
139
- for (const ticket of tickets) {
140
- if (!ticket || seen.has(ticket.id)) continue;
141
- seen.add(ticket.id);
142
- result.push(ticket);
143
- }
144
- return result;
145
- }
146
-
147
- export function relationRows(db, ticketId) {
148
- const rows = db
149
- .prepare(
150
- `SELECT r.*,
151
- source.title AS source_title,
152
- source.number AS source_number,
153
- source.board_id AS source_board_id,
154
- target.title AS target_title,
155
- target.number AS target_number,
156
- target.board_id AS target_board_id
157
- FROM relations r
158
- JOIN tickets source ON source.id = r.source_ticket_id
159
- JOIN tickets target ON target.id = r.target_ticket_id
160
- WHERE (r.source_ticket_id = ? OR r.target_ticket_id = ?)
161
- AND source.archived_at IS NULL AND target.archived_at IS NULL
162
- ORDER BY r.created_at ASC`
163
- )
164
- .all(ticketId, ticketId);
165
-
166
- return rows.map((row) => {
167
- const isSource = row.source_ticket_id === ticketId;
168
- const otherId = isSource ? row.target_ticket_id : row.source_ticket_id;
169
- const other = ticketById(db, otherId);
170
- return {
171
- id: row.id,
172
- type: row.type,
173
- direction: isSource ? "outgoing" : "incoming",
174
- other_ticket: other
175
- };
176
- });
177
- }
178
-
179
- // Returns the set of tickets currently blocking `ticketId`. The relation row
180
- // IS the source of truth: a `blocked_by` row exists iff the target is still
181
- // blocking. Cleanup happens at state-change time (tickets.js auto-deletes
182
- // blocks/blocked_by rows when a ticket moves to a role='done' lane) or when
183
- // the user removes the relation manually in the UI. So this resolver doesn't
184
- // re-check lane state — it just returns the unarchived rows. Exception: epic
185
- // targets expand to their children, since the epic itself isn't what gets
186
- // "done"; each open child (state role != 'done', not archived) is a blocker
187
- // and carries via_epic_id so callers can explain the dependency.
188
- export function unresolvedBlockers(db, ticketId) {
189
- const direct = db
190
- .prepare(
191
- `SELECT target.*, s.name AS state_name, s.role AS state_role
192
- FROM relations r
193
- JOIN tickets target ON target.id = r.target_ticket_id
194
- JOIN states s ON s.id = target.state_id
195
- WHERE r.source_ticket_id = ? AND r.type = 'blocked_by' AND target.archived_at IS NULL`
196
- )
197
- .all(ticketId);
198
-
199
- const expandEpicChildren = db.prepare(
200
- `SELECT t.*, s.name AS state_name, s.role AS state_role
201
- FROM tickets t
202
- JOIN states s ON s.id = t.state_id
203
- WHERE t.parent_ticket_id = ? AND t.archived_at IS NULL AND (s.role IS NULL OR s.role != 'done')`
204
- );
205
-
206
- const seen = new Set();
207
- const blockers = [];
208
- for (const row of direct) {
209
- if (row.type === "epic") {
210
- const children = expandEpicChildren.all(row.id);
211
- for (const child of children) {
212
- if (seen.has(child.id)) continue;
213
- seen.add(child.id);
214
- blockers.push({ ...child, via_epic_id: row.id });
215
- }
216
- continue;
217
- }
218
- if (seen.has(row.id)) continue;
219
- seen.add(row.id);
220
- blockers.push(row);
221
- }
222
- return blockers;
223
- }
224
-
225
- export function ensureLabel(db, boardId, name, color = "#64748b") {
226
- const cleanName = requiredString(name, "label").trim();
227
- const existing = db.prepare("SELECT * FROM labels WHERE board_id = ? AND name = ?").get(boardId, cleanName);
228
- if (existing) return existing;
229
- const labelId = id();
230
- db.prepare("INSERT INTO labels (id, board_id, name, color, created_at) VALUES (?, ?, ?, ?, ?)").run(
231
- labelId,
232
- boardId,
233
- cleanName,
234
- color,
235
- now()
236
- );
237
- return db.prepare("SELECT * FROM labels WHERE id = ?").get(labelId);
238
- }
239
-
240
- export function addComment(db, ticketId, author, kind, body) {
241
- const commentId = id();
242
- db.prepare(
243
- "INSERT INTO comments (id, ticket_id, author, kind, body, created_at) VALUES (?, ?, ?, ?, ?, ?)"
244
- ).run(commentId, ticketId, author, kind, body, now());
245
- return db.prepare("SELECT * FROM comments WHERE id = ?").get(commentId);
246
- }
@@ -1,211 +0,0 @@
1
- // Registry-level operations. No mutable global "active board" state lives
2
- // here: every function either reads from the registry, opens a known
3
- // per-board DB by path, or scans a bounded set of registry rows to locate
4
- // the board that owns a given ticket / state / entry / relation. Callers
5
- // receive `{ board, db }` and pass it down via `ctx`.
6
-
7
- import { existsSync, mkdirSync } from "node:fs";
8
- import { dirname, join } from "node:path";
9
- import { createBoardSchema, getRegistry, openConnection } from "./db.js";
10
- import { BOARDS_DIR } from "./paths.js";
11
- import { now, slugify } from "./util.js";
12
-
13
- export function listBoards() {
14
- return getRegistry().prepare("SELECT * FROM boards ORDER BY name").all();
15
- }
16
-
17
- export function getBoardByRegistryId(boardId) {
18
- return getRegistry().prepare("SELECT * FROM boards WHERE id = ?").get(boardId);
19
- }
20
-
21
- export function getBoardBySlug(slug) {
22
- return getRegistry().prepare("SELECT * FROM boards WHERE slug = ?").get(slugify(slug));
23
- }
24
-
25
- export function getBoardByRepoPath(repoPathNorm) {
26
- return getRegistry()
27
- .prepare(
28
- `SELECT * FROM boards
29
- WHERE repo_path = ?
30
- ORDER BY (last_active_at IS NULL) ASC, last_active_at DESC, rowid DESC
31
- LIMIT 1`
32
- )
33
- .get(repoPathNorm);
34
- }
35
-
36
- /** Most-recently-active registry row, or null. Used as a tie-breaker for the
37
- * bootstrap-default case when no board is specified by the caller. */
38
- export function pickDefaultBoard() {
39
- return getRegistry()
40
- .prepare(
41
- `SELECT * FROM boards
42
- ORDER BY (last_active_at IS NULL) ASC, last_active_at DESC, rowid DESC
43
- LIMIT 1`
44
- )
45
- .get();
46
- }
47
-
48
- export function deleteBoard(boardId) {
49
- getRegistry().prepare("DELETE FROM boards WHERE id = ?").run(boardId);
50
- }
51
-
52
- export function touchBoardActive(boardId) {
53
- const t = now();
54
- getRegistry()
55
- .prepare("UPDATE boards SET last_active_at = ?, updated_at = ? WHERE id = ?")
56
- .run(t, t, boardId);
57
- }
58
-
59
- export function insertBoard(row) {
60
- const t = row.created_at || now();
61
- getRegistry()
62
- .prepare(
63
- `INSERT INTO boards (id, slug, name, repo_path, db_path, repo_url, default_branch, manage_helper_files, last_active_at, created_at, updated_at)
64
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
65
- )
66
- .run(
67
- row.id,
68
- row.slug,
69
- row.name,
70
- row.repo_path,
71
- row.db_path,
72
- row.repo_url || "",
73
- row.default_branch || "main",
74
- row.manage_helper_files === undefined ? 1 : row.manage_helper_files ? 1 : 0,
75
- row.last_active_at || t,
76
- t,
77
- row.updated_at || t
78
- );
79
- }
80
-
81
- export function updateBoardMeta(boardId, patch) {
82
- const sets = [];
83
- const vals = [];
84
- for (const k of ["slug", "name", "repo_path", "db_path", "repo_url", "default_branch", "manage_helper_files"]) {
85
- if (patch[k] !== undefined) {
86
- sets.push(`${k} = ?`);
87
- vals.push(patch[k]);
88
- }
89
- }
90
- if (!sets.length) return;
91
- vals.push(now(), boardId);
92
- getRegistry()
93
- .prepare(`UPDATE boards SET ${sets.join(", ")}, updated_at = ? WHERE id = ?`)
94
- .run(...vals);
95
- }
96
-
97
- /** Where a new board.db should live: always in the central DATA_DIR/boards/<slug>/ directory.
98
- * Falls back to a numeric suffix if the canonical path already exists on disk (orphaned file). */
99
- export function computeNewBoardDbPath(_repoPathNorm, slug) {
100
- const preferred = join(BOARDS_DIR, slug, "board.db");
101
- if (!existsSync(preferred)) return preferred;
102
- for (let i = 2; i < 1000; i++) {
103
- const alt = join(BOARDS_DIR, `${slug}-${i}`, "board.db");
104
- if (!existsSync(alt)) return alt;
105
- }
106
- return preferred; // callers will handle any existing file
107
- }
108
-
109
- /** Create the directory + file and apply the per-board schema. Returns the
110
- * open connection. */
111
- export function ensureBoardDbFileAndSchema(dbPath) {
112
- mkdirSync(dirname(dbPath), { recursive: true });
113
- const conn = openConnection(dbPath);
114
- createBoardSchema(conn);
115
- return conn;
116
- }
117
-
118
- /** Open the connection for a registry row. */
119
- export function openBoardDb(boardRow) {
120
- return openConnection(boardRow.db_path);
121
- }
122
-
123
- // Whitelisted table names — these come from our own call sites, never user
124
- // input, but interpolating into SQL is only safe because of this guard.
125
- const ROW_OWNER_TABLES = new Set(["tickets", "states", "board_entries", "relations"]);
126
-
127
- /**
128
- * Find which registry board's DB contains a row with `id` in `table`. Pure:
129
- * opens one db per candidate to probe and returns `{ board, db }` (no global
130
- * side effects). If `hintBoardId` is supplied it's checked first to avoid a
131
- * full scan across every registered board.
132
- */
133
- function findBoardForRow(table, rowId, hintBoardId = null) {
134
- if (!ROW_OWNER_TABLES.has(table)) throw new Error(`unsupported owner table: ${table}`);
135
- const probe = `SELECT 1 FROM ${table} WHERE id = ?`;
136
- if (hintBoardId) {
137
- const hint = getBoardByRegistryId(hintBoardId);
138
- if (hint) {
139
- const db = openBoardDb(hint);
140
- if (db.prepare(probe).get(rowId)) return { board: hint, db };
141
- }
142
- }
143
- for (const row of listBoards()) {
144
- if (hintBoardId && row.id === hintBoardId) continue;
145
- const db = openBoardDb(row);
146
- if (db.prepare(probe).get(rowId)) return { board: row, db };
147
- }
148
- return null;
149
- }
150
-
151
- export const findBoardForTicket = (ticketId, hintBoardId = null) =>
152
- findBoardForRow("tickets", ticketId, hintBoardId);
153
- export const findBoardForState = (stateId, hintBoardId = null) =>
154
- findBoardForRow("states", stateId, hintBoardId);
155
- export const findBoardForBoardEntry = (entryId, hintBoardId = null) =>
156
- findBoardForRow("board_entries", entryId, hintBoardId);
157
- export const findBoardForRelation = (relationId, hintBoardId = null) =>
158
- findBoardForRow("relations", relationId, hintBoardId);
159
-
160
- /** Sync a per-board DB's single boards-row into the registry. Idempotent. */
161
- export function syncRegistryFromBoardDb(boardRow, db) {
162
- const b = db.prepare("SELECT * FROM boards LIMIT 1").get();
163
- if (!b) return;
164
-
165
- const reg = getRegistry();
166
- const t = now();
167
- const existing = reg.prepare("SELECT id FROM boards WHERE id = ?").get(b.id);
168
- if (existing) {
169
- reg
170
- .prepare(
171
- `UPDATE boards SET slug = ?, name = ?, db_path = ?, repo_url = ?, default_branch = ?, updated_at = ?
172
- WHERE id = ?`
173
- )
174
- .run(b.slug, b.name, boardRow.db_path, b.repo_url || "", b.default_branch || "main", t, b.id);
175
- return;
176
- }
177
-
178
- const existingPath = reg.prepare("SELECT id FROM boards WHERE db_path = ?").get(boardRow.db_path);
179
- if (existingPath) {
180
- reg
181
- .prepare(
182
- `UPDATE boards SET id = ?, slug = ?, name = ?, repo_path = ?, db_path = ?, repo_url = ?, default_branch = ?, updated_at = ?
183
- WHERE id = ?`
184
- )
185
- .run(
186
- b.id,
187
- b.slug,
188
- b.name,
189
- boardRow.repo_path,
190
- boardRow.db_path,
191
- b.repo_url || "",
192
- b.default_branch || "main",
193
- t,
194
- existingPath.id
195
- );
196
- return;
197
- }
198
-
199
- insertBoard({
200
- id: b.id,
201
- slug: b.slug,
202
- name: b.name,
203
- repo_path: boardRow.repo_path,
204
- db_path: boardRow.db_path,
205
- repo_url: b.repo_url || "",
206
- default_branch: b.default_branch || "main",
207
- last_active_at: t,
208
- created_at: t,
209
- updated_at: t
210
- });
211
- }