@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
package/src/core/db.js DELETED
@@ -1,315 +0,0 @@
1
- // Connection management. There is intentionally no module-level "active board"
2
- // singleton: every caller resolves a per-board DB connection via openConnection
3
- // and threads it through. This is what makes the server safe under concurrent
4
- // HTTP requests for different boards — a global active-pointer would race
5
- // across requests that interleave around `await readJson`.
6
-
7
- import { DatabaseSync } from "node:sqlite";
8
- import { REGISTRY_DB_PATH } from "./paths.js";
9
- import { __enterEventTx, __settleEventTx } from "./events.js";
10
-
11
- const connections = new Map();
12
-
13
- /** Open (or return cached) per-database connection. Safe to share within a process. */
14
- export function openConnection(dbPath) {
15
- if (connections.has(dbPath)) return connections.get(dbPath);
16
- const conn = new DatabaseSync(dbPath);
17
- conn.exec("PRAGMA foreign_keys = ON;");
18
- conn.exec("PRAGMA busy_timeout = 5000;");
19
- conn.exec("PRAGMA journal_mode = WAL;");
20
- // busy_timeout (set above) makes a second writer from another process (MCP
21
- // server, CLI) wait instead of failing immediately with SQLITE_BUSY when an
22
- // agent commit and a UI drag overlap. NORMAL is the recommended
23
- // durability/throughput tradeoff under WAL (a power-loss can lose the last
24
- // commit but never corrupts the DB).
25
- conn.exec("PRAGMA synchronous = NORMAL;");
26
- connections.set(dbPath, conn);
27
- return conn;
28
- }
29
-
30
- export function closeConnection(dbPath) {
31
- const conn = connections.get(dbPath);
32
- if (!conn) return;
33
- try {
34
- conn.exec("PRAGMA wal_checkpoint(TRUNCATE);");
35
- } catch {
36
- // Another process may still be attached; closing this connection is enough
37
- // to release our file handle.
38
- }
39
- try {
40
- conn.close();
41
- } finally {
42
- connections.delete(dbPath);
43
- }
44
- }
45
-
46
- export function hasConnection(dbPath) {
47
- return connections.has(dbPath);
48
- }
49
-
50
- export function closeAllConnections() {
51
- for (const dbPath of [...connections.keys()]) {
52
- try {
53
- closeConnection(dbPath);
54
- } catch {
55
- // Shutdown cleanup should release every handle it can without masking
56
- // the original exit path.
57
- }
58
- }
59
- }
60
-
61
- // Reentrancy depth per connection. Only the outermost tx() issues
62
- // BEGIN/COMMIT/ROLLBACK; inner calls (a tx-wrapped function calling another)
63
- // fold into the enclosing transaction. Execution is synchronous and a request
64
- // never awaits between BEGIN and COMMIT, so per-connection depth is race-free.
65
- const txDepth = new WeakMap();
66
-
67
- /**
68
- * Run `fn` inside an IMMEDIATE transaction on `db` so a multi-statement
69
- * mutation is atomic: it either fully lands or fully rolls back. BEGIN
70
- * IMMEDIATE takes the write lock up front, which also makes read-then-write
71
- * sequences (e.g. `MAX(number)+1` ticket numbering) safe against concurrent
72
- * writer processes. SSE broadcasts for events recorded inside the transaction
73
- * are buffered and only fan out after COMMIT (see events.js).
74
- */
75
- export function tx(db, fn) {
76
- const depth = txDepth.get(db) || 0;
77
- if (depth === 0) db.exec("BEGIN IMMEDIATE;");
78
- txDepth.set(db, depth + 1);
79
- __enterEventTx();
80
- try {
81
- const result = fn();
82
- const next = txDepth.get(db) - 1;
83
- txDepth.set(db, next);
84
- if (next === 0) db.exec("COMMIT;");
85
- __settleEventTx(true);
86
- return result;
87
- } catch (error) {
88
- const next = txDepth.get(db) - 1;
89
- txDepth.set(db, next);
90
- if (next === 0) {
91
- try {
92
- db.exec("ROLLBACK;");
93
- } catch {
94
- // A failed BEGIN leaves no transaction to roll back; ignore.
95
- }
96
- }
97
- __settleEventTx(false);
98
- throw error;
99
- }
100
- }
101
-
102
- export function getRegistry() {
103
- return openConnection(REGISTRY_DB_PATH);
104
- }
105
-
106
- /** Add a column to an existing table only if it is missing. Idempotent: safe to
107
- * call on every startup. SQLite has no `ADD COLUMN IF NOT EXISTS`, so we probe
108
- * `PRAGMA table_info` first. */
109
- function ensureColumn(reg, table, column, definition) {
110
- const cols = reg.prepare(`PRAGMA table_info(${table})`).all();
111
- if (cols.some((c) => c.name === column)) return;
112
- reg.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition};`);
113
- }
114
-
115
- /** Registry metadata table (not the per-board `boards` content row). */
116
- export function createRegistrySchema(reg = getRegistry()) {
117
- reg.exec(`
118
- CREATE TABLE IF NOT EXISTS boards (
119
- id TEXT PRIMARY KEY,
120
- slug TEXT NOT NULL UNIQUE,
121
- name TEXT NOT NULL,
122
- repo_path TEXT NOT NULL DEFAULT '',
123
- db_path TEXT NOT NULL,
124
- repo_url TEXT NOT NULL DEFAULT '',
125
- default_branch TEXT NOT NULL DEFAULT 'main',
126
- last_active_at TEXT,
127
- created_at TEXT NOT NULL,
128
- updated_at TEXT NOT NULL
129
- );
130
- CREATE INDEX IF NOT EXISTS idx_registry_boards_repo_path ON boards(repo_path);
131
- `);
132
- // Whether Orbit keeps SKILL-ORBIT.md / AGENTS.md in sync inside repo_path.
133
- // Defaults to 1 so pre-existing boards (created when files were always
134
- // written) keep their managed copies refreshed on startup.
135
- ensureColumn(reg, "boards", "manage_helper_files", "INTEGER NOT NULL DEFAULT 1");
136
- }
137
-
138
- /** Full Kanban schema on a per-board database connection. */
139
- export function createBoardSchema(db) {
140
- db.exec(`
141
- CREATE TABLE IF NOT EXISTS boards (
142
- id TEXT PRIMARY KEY,
143
- slug TEXT NOT NULL UNIQUE,
144
- name TEXT NOT NULL,
145
- repo_url TEXT NOT NULL DEFAULT '',
146
- system_path TEXT NOT NULL DEFAULT '',
147
- default_branch TEXT NOT NULL DEFAULT 'main',
148
- project_notes TEXT NOT NULL DEFAULT '',
149
- agent_instructions TEXT NOT NULL DEFAULT '',
150
- ai_enabled INTEGER NOT NULL DEFAULT 0,
151
- created_at TEXT NOT NULL,
152
- updated_at TEXT NOT NULL
153
- );
154
-
155
- CREATE TABLE IF NOT EXISTS states (
156
- id TEXT PRIMARY KEY,
157
- board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
158
- name TEXT NOT NULL,
159
- position INTEGER NOT NULL,
160
- is_default INTEGER NOT NULL DEFAULT 0,
161
- role TEXT,
162
- created_at TEXT NOT NULL,
163
- UNIQUE(board_id, name)
164
- );
165
-
166
- CREATE TABLE IF NOT EXISTS tickets (
167
- id TEXT PRIMARY KEY,
168
- board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
169
- number INTEGER NOT NULL,
170
- title TEXT NOT NULL,
171
- description TEXT NOT NULL DEFAULT '',
172
- type TEXT NOT NULL DEFAULT 'task',
173
- parent_ticket_id TEXT,
174
- ai_plan TEXT NOT NULL DEFAULT '',
175
- plan_artifact_path TEXT NOT NULL DEFAULT '',
176
- implementation_summary TEXT NOT NULL DEFAULT '',
177
- implementation_artifact_path TEXT NOT NULL DEFAULT '',
178
- implementation_updates TEXT NOT NULL DEFAULT '',
179
- state_id TEXT NOT NULL REFERENCES states(id),
180
- priority INTEGER NOT NULL DEFAULT 2,
181
- created_by TEXT NOT NULL DEFAULT 'human',
182
- created_at TEXT NOT NULL,
183
- updated_at TEXT NOT NULL,
184
- archived_at TEXT,
185
- UNIQUE(board_id, number)
186
- );
187
-
188
- CREATE TABLE IF NOT EXISTS comments (
189
- id TEXT PRIMARY KEY,
190
- ticket_id TEXT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
191
- author TEXT NOT NULL,
192
- kind TEXT NOT NULL DEFAULT 'comment',
193
- body TEXT NOT NULL,
194
- omitted_for_ai INTEGER NOT NULL DEFAULT 0,
195
- created_at TEXT NOT NULL
196
- );
197
-
198
- CREATE TABLE IF NOT EXISTS review_verdicts (
199
- id TEXT PRIMARY KEY,
200
- ticket_id TEXT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
201
- verdict TEXT NOT NULL CHECK (verdict IN ('PASS', 'BLOCK', 'QUESTION')),
202
- blocking_findings_json TEXT NOT NULL DEFAULT '[]',
203
- optional_findings_json TEXT NOT NULL DEFAULT '[]',
204
- evidence_commands_json TEXT NOT NULL DEFAULT '[]',
205
- reviewer_profile TEXT NOT NULL DEFAULT '',
206
- reviewer_session_id TEXT NOT NULL DEFAULT '',
207
- reviewed_commit_sha TEXT NOT NULL DEFAULT '',
208
- dispatch_run_id TEXT NOT NULL DEFAULT '',
209
- supersedes_prior_review_id TEXT REFERENCES review_verdicts(id) ON DELETE SET NULL,
210
- created_by TEXT NOT NULL DEFAULT 'human',
211
- created_at TEXT NOT NULL
212
- );
213
-
214
- CREATE TABLE IF NOT EXISTS ticket_attachments (
215
- id TEXT PRIMARY KEY,
216
- ticket_id TEXT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
217
- board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
218
- original_name TEXT NOT NULL,
219
- stored_path TEXT NOT NULL,
220
- mime_type TEXT NOT NULL,
221
- size_bytes INTEGER NOT NULL,
222
- sha256 TEXT NOT NULL,
223
- created_by TEXT NOT NULL DEFAULT 'human',
224
- created_at TEXT NOT NULL
225
- );
226
-
227
- CREATE TABLE IF NOT EXISTS board_entries (
228
- id TEXT PRIMARY KEY,
229
- board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
230
- type TEXT NOT NULL,
231
- title TEXT NOT NULL,
232
- body TEXT NOT NULL DEFAULT '',
233
- ticket_id TEXT REFERENCES tickets(id) ON DELETE SET NULL,
234
- created_by TEXT NOT NULL DEFAULT 'human',
235
- created_at TEXT NOT NULL,
236
- updated_at TEXT NOT NULL,
237
- struck_at TEXT
238
- );
239
-
240
- CREATE TABLE IF NOT EXISTS labels (
241
- id TEXT PRIMARY KEY,
242
- board_id TEXT NOT NULL REFERENCES boards(id) ON DELETE CASCADE,
243
- name TEXT NOT NULL,
244
- color TEXT NOT NULL DEFAULT '#64748b',
245
- created_at TEXT NOT NULL,
246
- UNIQUE(board_id, name)
247
- );
248
-
249
- CREATE TABLE IF NOT EXISTS ticket_labels (
250
- ticket_id TEXT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
251
- label_id TEXT NOT NULL REFERENCES labels(id) ON DELETE CASCADE,
252
- PRIMARY KEY(ticket_id, label_id)
253
- );
254
-
255
- CREATE TABLE IF NOT EXISTS relations (
256
- id TEXT PRIMARY KEY,
257
- source_ticket_id TEXT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
258
- target_ticket_id TEXT NOT NULL REFERENCES tickets(id) ON DELETE CASCADE,
259
- type TEXT NOT NULL,
260
- created_at TEXT NOT NULL,
261
- UNIQUE(source_ticket_id, target_ticket_id, type)
262
- );
263
-
264
- CREATE TABLE IF NOT EXISTS events (
265
- id TEXT PRIMARY KEY,
266
- ticket_id TEXT REFERENCES tickets(id) ON DELETE SET NULL,
267
- actor TEXT NOT NULL,
268
- type TEXT NOT NULL,
269
- body_json TEXT NOT NULL DEFAULT '{}',
270
- created_at TEXT NOT NULL
271
- );
272
-
273
- CREATE VIRTUAL TABLE IF NOT EXISTS ticket_fts USING fts5(
274
- ticket_id UNINDEXED,
275
- title,
276
- description,
277
- comments
278
- );
279
-
280
- CREATE INDEX IF NOT EXISTS idx_states_board ON states(board_id, position);
281
- CREATE INDEX IF NOT EXISTS idx_states_role ON states(board_id, role);
282
- CREATE INDEX IF NOT EXISTS idx_tickets_board_state ON tickets(board_id, state_id);
283
- CREATE INDEX IF NOT EXISTS idx_tickets_archived ON tickets(board_id, archived_at);
284
- CREATE INDEX IF NOT EXISTS idx_tickets_parent ON tickets(parent_ticket_id);
285
- CREATE INDEX IF NOT EXISTS idx_comments_ticket ON comments(ticket_id, created_at);
286
- CREATE INDEX IF NOT EXISTS idx_review_verdicts_ticket ON review_verdicts(ticket_id, created_at);
287
- CREATE INDEX IF NOT EXISTS idx_review_verdicts_supersedes ON review_verdicts(supersedes_prior_review_id);
288
- CREATE INDEX IF NOT EXISTS idx_ticket_attachments_ticket ON ticket_attachments(ticket_id, created_at);
289
- CREATE INDEX IF NOT EXISTS idx_ticket_attachments_board ON ticket_attachments(board_id, created_at);
290
- CREATE INDEX IF NOT EXISTS idx_board_entries_board ON board_entries(board_id, type, created_at);
291
- CREATE INDEX IF NOT EXISTS idx_relations_source ON relations(source_ticket_id);
292
- CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_ticket_id);
293
- CREATE INDEX IF NOT EXISTS idx_events_ticket ON events(ticket_id, created_at);
294
- `);
295
- ensureColumn(db, "tickets", "plan_artifact_path", "TEXT NOT NULL DEFAULT ''");
296
- ensureColumn(db, "tickets", "implementation_artifact_path", "TEXT NOT NULL DEFAULT ''");
297
- ensureColumn(db, "comments", "omitted_for_ai", "INTEGER NOT NULL DEFAULT 0");
298
- }
299
-
300
- export function resetBoard(db) {
301
- db.exec(`
302
- DELETE FROM ticket_fts;
303
- DELETE FROM ticket_labels;
304
- DELETE FROM relations;
305
- DELETE FROM comments;
306
- DELETE FROM review_verdicts;
307
- DELETE FROM ticket_attachments;
308
- DELETE FROM board_entries;
309
- DELETE FROM events;
310
- DELETE FROM tickets;
311
- DELETE FROM labels;
312
- DELETE FROM states;
313
- DELETE FROM boards;
314
- `);
315
- }
@@ -1,163 +0,0 @@
1
- import { existsSync, rmSync } from "node:fs";
2
- import { basename, dirname, join, relative } from "node:path";
3
- import { removeOrbitAgentsSection } from "./agents-md.js";
4
- import { requireBoardAccess } from "./auth.js";
5
- import { backupBoardDatabase, backupRegistry, cancelAutomaticBoardBackup } from "./backups.js";
6
- import { closeConnection } from "./db.js";
7
- import { BOARDS_DIR, DATA_DIR, DISPATCH_RUNS_DIR } from "./paths.js";
8
- import { deleteBoard, listBoards, openBoardDb } from "./registry.js";
9
- import { httpError, normalizePath } from "./util.js";
10
-
11
- function isInside(parent, child) {
12
- const rel = relative(parent, child);
13
- return rel && !rel.startsWith("..") && !rel.startsWith("/") && rel !== "..";
14
- }
15
-
16
- function boardDbDeleteTargets(boardRow) {
17
- const dbPath = normalizePath(boardRow.db_path || "");
18
- if (!dbPath || basename(dbPath) !== "board.db") {
19
- throw httpError(409, "unsafe_board_db_path");
20
- }
21
-
22
- // Central storage: db lives inside DATA_DIR/boards/<slug>/board.db
23
- const boardsDirNorm = normalizePath(BOARDS_DIR);
24
- if (dbPath.startsWith(boardsDirNorm + "/")) {
25
- const boardDir = dirname(dbPath);
26
- // Safety: boardDir must be exactly one level below BOARDS_DIR
27
- if (normalizePath(dirname(boardDir)) !== boardsDirNorm) {
28
- throw httpError(409, "unsafe_board_db_path");
29
- }
30
- return [boardDir];
31
- }
32
-
33
- // Legacy in-repo storage: db lives inside <repo>/.orbit/
34
- const repoRoot = normalizePath(boardRow.repo_path || "");
35
- const orbitRoot = normalizePath(join(repoRoot, ".orbit"));
36
- if (!repoRoot || !isInside(orbitRoot, dbPath)) {
37
- throw httpError(409, "unsafe_board_db_path");
38
- }
39
- const boardDir = dirname(dbPath);
40
- const boardsRoot = normalizePath(join(orbitRoot, "boards"));
41
- if (isInside(boardsRoot, boardDir)) return [boardDir];
42
- return [dbPath, `${dbPath}-wal`, `${dbPath}-shm`, `${dbPath}-journal`];
43
- }
44
-
45
- function isLastBoardForRepo(boardRow) {
46
- const repoRoot = normalizePath(boardRow.repo_path || "");
47
- return !listBoards().some((row) => row.id !== boardRow.id && normalizePath(row.repo_path || "") === repoRoot);
48
- }
49
-
50
- function repoArtifactTargets(boardRow) {
51
- const repoRoot = normalizePath(boardRow.repo_path || "");
52
- const dbPath = normalizePath(boardRow.db_path || "");
53
- if (!repoRoot || !dbPath || basename(dbPath) !== "board.db") {
54
- throw httpError(409, "unsafe_board_db_path");
55
- }
56
- return {
57
- orbitRoot: normalizePath(join(repoRoot, ".orbit")),
58
- skillMd: join(repoRoot, "SKILL-ORBIT.md")
59
- };
60
- }
61
-
62
- function removeOptions(recursive = false) {
63
- const options = { force: true };
64
- if (recursive) options.recursive = true;
65
- if (process.platform === "win32") {
66
- options.maxRetries = 10;
67
- options.retryDelay = 100;
68
- }
69
- return options;
70
- }
71
-
72
- function isBusyFilesystemError(error) {
73
- const message = String(error?.message || "").toLowerCase();
74
- return (
75
- ["EBUSY", "EPERM", "ENOTEMPTY"].includes(error?.code) ||
76
- (error?.code === "ERR_SQLITE_ERROR" && (message.includes("locked") || message.includes("busy")))
77
- );
78
- }
79
-
80
- function boardFilesBusyError(error, boardRow) {
81
- if (!isBusyFilesystemError(error)) return error;
82
- const wrapped = new Error(
83
- [
84
- `Orbit could not remove local board files for "${boardRow.slug}".`,
85
- "On Windows this usually means an Orbit web server, MCP helper, AI client, editor, or terminal still has .orbit/board.db open.",
86
- "Close those processes, then retry the delete from Settings or run orbit reset --cwd <repo>.",
87
- `Original error: ${error.message}`
88
- ].join("\n")
89
- );
90
- wrapped.status = 409;
91
- wrapped.code = "board_files_busy";
92
- wrapped.cause = error;
93
- return wrapped;
94
- }
95
-
96
- export function deleteRegisteredBoard(boardRow, body, actor) {
97
- requireBoardAccess(actor, { slug: boardRow.slug });
98
-
99
- const confirmation = String(body?.confirm_slug || "").trim();
100
- if (confirmation !== boardRow.slug) throw httpError(400, "board_slug_confirmation_required");
101
-
102
- const deleteFiles = body?.delete_files !== false;
103
- const removeRepoArtifacts = deleteFiles && isLastBoardForRepo(boardRow);
104
- const repoArtifacts = removeRepoArtifacts ? repoArtifactTargets(boardRow) : null;
105
- // Always delete the board's own db files/directory; when it is the last board
106
- // for a repo also sweep in-repo artifacts (.orbit/, SKILL-ORBIT.md, AGENTS.md).
107
- // Also remove the board's central dispatch-runs directory.
108
- const dispatchRunsDir = join(DISPATCH_RUNS_DIR, boardRow.slug);
109
- const targets = deleteFiles
110
- ? [
111
- ...boardDbDeleteTargets(boardRow),
112
- ...(repoArtifacts ? [repoArtifacts.orbitRoot] : []),
113
- ...(existsSync(dispatchRunsDir) ? [dispatchRunsDir] : [])
114
- ]
115
- : [];
116
- const artifactResults = [];
117
- cancelAutomaticBoardBackup(boardRow.id);
118
- if (deleteFiles) {
119
- try {
120
- backupBoardDatabase(boardRow, openBoardDb(boardRow), "pre-board-delete");
121
- } catch (error) {
122
- throw boardFilesBusyError(error, boardRow);
123
- } finally {
124
- closeConnection(boardRow.db_path);
125
- }
126
- }
127
- backupRegistry("pre-board-delete");
128
- closeConnection(boardRow.db_path);
129
-
130
- try {
131
- for (const target of targets) {
132
- if (existsSync(target)) {
133
- rmSync(target, removeOptions(true));
134
- artifactResults.push({ path: target, removed: true });
135
- }
136
- }
137
- if (repoArtifacts?.skillMd && existsSync(repoArtifacts.skillMd)) {
138
- rmSync(repoArtifacts.skillMd, removeOptions(false));
139
- artifactResults.push({ path: repoArtifacts.skillMd, removed: true });
140
- }
141
- } catch (error) {
142
- throw boardFilesBusyError(error, boardRow);
143
- }
144
- if (removeRepoArtifacts) {
145
- const agentsCleanup = removeOrbitAgentsSection(boardRow.repo_path);
146
- artifactResults.push({
147
- path: agentsCleanup.path,
148
- removed: agentsCleanup.removed,
149
- ok: agentsCleanup.ok,
150
- reason: agentsCleanup.reason
151
- });
152
- }
153
-
154
- deleteBoard(boardRow.id);
155
- return {
156
- ok: true,
157
- deleted_board_id: boardRow.id,
158
- deleted_board_slug: boardRow.slug,
159
- deleted_files: deleteFiles,
160
- deleted_repo_artifacts: removeRepoArtifacts,
161
- artifacts: artifactResults
162
- };
163
- }