@aligndottech/cli 0.49.0 → 0.50.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 (622) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +26 -8
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +4 -1
  7. package/dist/commands/agents.js.map +1 -1
  8. package/dist/commands/check.d.ts.map +1 -1
  9. package/dist/commands/check.js +18 -2
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/connect.d.ts +5 -0
  12. package/dist/commands/connect.d.ts.map +1 -1
  13. package/dist/commands/connect.js +24 -0
  14. package/dist/commands/connect.js.map +1 -1
  15. package/dist/commands/import/confluence.d.ts.map +1 -1
  16. package/dist/commands/import/confluence.js +24 -5
  17. package/dist/commands/import/confluence.js.map +1 -1
  18. package/dist/commands/import/docs.js +2 -2
  19. package/dist/commands/import/git.d.ts.map +1 -1
  20. package/dist/commands/import/git.js +15 -5
  21. package/dist/commands/import/git.js.map +1 -1
  22. package/dist/commands/import/github.d.ts.map +1 -1
  23. package/dist/commands/import/github.js +34 -7
  24. package/dist/commands/import/github.js.map +1 -1
  25. package/dist/commands/import/gitlab.d.ts.map +1 -1
  26. package/dist/commands/import/gitlab.js +10 -5
  27. package/dist/commands/import/gitlab.js.map +1 -1
  28. package/dist/commands/import/jira.d.ts.map +1 -1
  29. package/dist/commands/import/jira.js +10 -5
  30. package/dist/commands/import/jira.js.map +1 -1
  31. package/dist/commands/import/linear.d.ts.map +1 -1
  32. package/dist/commands/import/linear.js +10 -5
  33. package/dist/commands/import/linear.js.map +1 -1
  34. package/dist/commands/import/notion.d.ts.map +1 -1
  35. package/dist/commands/import/notion.js +10 -5
  36. package/dist/commands/import/notion.js.map +1 -1
  37. package/dist/commands/import/sessions.js +2 -2
  38. package/dist/commands/import/slack.d.ts.map +1 -1
  39. package/dist/commands/import/slack.js +12 -7
  40. package/dist/commands/import/slack.js.map +1 -1
  41. package/dist/commands/import/teams.d.ts.map +1 -1
  42. package/dist/commands/import/teams.js +10 -5
  43. package/dist/commands/import/teams.js.map +1 -1
  44. package/dist/commands/import/zoom.d.ts.map +1 -1
  45. package/dist/commands/import/zoom.js +10 -5
  46. package/dist/commands/import/zoom.js.map +1 -1
  47. package/dist/commands/import.d.ts.map +1 -1
  48. package/dist/commands/import.js +43 -1
  49. package/dist/commands/import.js.map +1 -1
  50. package/dist/commands/local.d.ts.map +1 -1
  51. package/dist/commands/local.js +76 -4
  52. package/dist/commands/local.js.map +1 -1
  53. package/dist/commands/mark.d.ts +37 -0
  54. package/dist/commands/mark.d.ts.map +1 -0
  55. package/dist/commands/mark.js +105 -0
  56. package/dist/commands/mark.js.map +1 -0
  57. package/dist/commands/mcp.d.ts +141 -2
  58. package/dist/commands/mcp.d.ts.map +1 -1
  59. package/dist/commands/mcp.js +72 -14
  60. package/dist/commands/mcp.js.map +1 -1
  61. package/dist/commands/registry.d.ts.map +1 -1
  62. package/dist/commands/registry.js +8 -2
  63. package/dist/commands/registry.js.map +1 -1
  64. package/dist/commands/setup.d.ts +39 -0
  65. package/dist/commands/setup.d.ts.map +1 -1
  66. package/dist/commands/setup.js +65 -50
  67. package/dist/commands/setup.js.map +1 -1
  68. package/dist/commands/share.d.ts +7 -0
  69. package/dist/commands/share.d.ts.map +1 -0
  70. package/dist/commands/share.js +60 -0
  71. package/dist/commands/share.js.map +1 -0
  72. package/dist/commands/sync.d.ts +55 -0
  73. package/dist/commands/sync.d.ts.map +1 -0
  74. package/dist/commands/sync.js +278 -0
  75. package/dist/commands/sync.js.map +1 -0
  76. package/dist/commands/telemetry.d.ts +3 -3
  77. package/dist/commands/telemetry.js +6 -6
  78. package/dist/commands/telemetry.js.map +1 -1
  79. package/dist/commands/why.d.ts.map +1 -1
  80. package/dist/commands/why.js +6 -1
  81. package/dist/commands/why.js.map +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/lib/backfill-state.d.ts +116 -0
  87. package/dist/lib/backfill-state.d.ts.map +1 -0
  88. package/dist/lib/backfill-state.js +342 -0
  89. package/dist/lib/backfill-state.js.map +1 -0
  90. package/dist/lib/backup-copy.d.ts +17 -0
  91. package/dist/lib/backup-copy.d.ts.map +1 -0
  92. package/dist/lib/backup-copy.js +13 -0
  93. package/dist/lib/backup-copy.js.map +1 -0
  94. package/dist/lib/capture-report.d.ts +22 -18
  95. package/dist/lib/capture-report.d.ts.map +1 -1
  96. package/dist/lib/capture-report.js +64 -2
  97. package/dist/lib/capture-report.js.map +1 -1
  98. package/dist/lib/config.d.ts +31 -0
  99. package/dist/lib/config.d.ts.map +1 -1
  100. package/dist/lib/config.js +139 -0
  101. package/dist/lib/config.js.map +1 -1
  102. package/dist/lib/confirm-timeout.d.ts +8 -0
  103. package/dist/lib/confirm-timeout.d.ts.map +1 -0
  104. package/dist/lib/confirm-timeout.js +18 -0
  105. package/dist/lib/confirm-timeout.js.map +1 -0
  106. package/dist/lib/curation/check-marks.d.ts +48 -0
  107. package/dist/lib/curation/check-marks.d.ts.map +1 -0
  108. package/dist/lib/curation/check-marks.js +81 -0
  109. package/dist/lib/curation/check-marks.js.map +1 -0
  110. package/dist/lib/curation/guardrail.d.ts +39 -0
  111. package/dist/lib/curation/guardrail.d.ts.map +1 -0
  112. package/dist/lib/curation/guardrail.js +93 -0
  113. package/dist/lib/curation/guardrail.js.map +1 -0
  114. package/dist/lib/curation/judge.d.ts +3 -0
  115. package/dist/lib/curation/judge.d.ts.map +1 -0
  116. package/dist/lib/curation/judge.js +9 -0
  117. package/dist/lib/curation/judge.js.map +1 -0
  118. package/dist/lib/curation/judgements-db.d.ts +96 -0
  119. package/dist/lib/curation/judgements-db.d.ts.map +1 -0
  120. package/dist/lib/curation/judgements-db.js +200 -0
  121. package/dist/lib/curation/judgements-db.js.map +1 -0
  122. package/dist/lib/curation/last-check.d.ts +32 -0
  123. package/dist/lib/curation/last-check.d.ts.map +1 -0
  124. package/dist/lib/curation/last-check.js +81 -0
  125. package/dist/lib/curation/last-check.js.map +1 -0
  126. package/dist/lib/curation/mark.d.ts +76 -0
  127. package/dist/lib/curation/mark.d.ts.map +1 -0
  128. package/dist/lib/curation/mark.js +359 -0
  129. package/dist/lib/curation/mark.js.map +1 -0
  130. package/dist/lib/curation/text.d.ts +6 -0
  131. package/dist/lib/curation/text.d.ts.map +1 -0
  132. package/dist/lib/curation/text.js +35 -0
  133. package/dist/lib/curation/text.js.map +1 -0
  134. package/dist/lib/fetchers/capture.d.ts +49 -2
  135. package/dist/lib/fetchers/capture.d.ts.map +1 -1
  136. package/dist/lib/fetchers/capture.js +7 -1
  137. package/dist/lib/fetchers/capture.js.map +1 -1
  138. package/dist/lib/fetchers/confluence.d.ts +2 -2
  139. package/dist/lib/fetchers/confluence.d.ts.map +1 -1
  140. package/dist/lib/fetchers/confluence.js.map +1 -1
  141. package/dist/lib/fetchers/git.d.ts +1 -0
  142. package/dist/lib/fetchers/git.d.ts.map +1 -1
  143. package/dist/lib/fetchers/git.js +3 -1
  144. package/dist/lib/fetchers/git.js.map +1 -1
  145. package/dist/lib/fetchers/github.d.ts +44 -5
  146. package/dist/lib/fetchers/github.d.ts.map +1 -1
  147. package/dist/lib/fetchers/github.js +122 -5
  148. package/dist/lib/fetchers/github.js.map +1 -1
  149. package/dist/lib/fetchers/gitlab.d.ts +2 -2
  150. package/dist/lib/fetchers/gitlab.d.ts.map +1 -1
  151. package/dist/lib/fetchers/gitlab.js.map +1 -1
  152. package/dist/lib/fetchers/jira.d.ts +2 -2
  153. package/dist/lib/fetchers/jira.d.ts.map +1 -1
  154. package/dist/lib/fetchers/jira.js.map +1 -1
  155. package/dist/lib/fetchers/linear.d.ts +2 -2
  156. package/dist/lib/fetchers/linear.d.ts.map +1 -1
  157. package/dist/lib/fetchers/linear.js.map +1 -1
  158. package/dist/lib/fetchers/notion.d.ts +2 -2
  159. package/dist/lib/fetchers/notion.d.ts.map +1 -1
  160. package/dist/lib/fetchers/notion.js.map +1 -1
  161. package/dist/lib/fetchers/slack.d.ts +2 -3
  162. package/dist/lib/fetchers/slack.d.ts.map +1 -1
  163. package/dist/lib/fetchers/slack.js.map +1 -1
  164. package/dist/lib/fetchers/teams.d.ts +2 -2
  165. package/dist/lib/fetchers/teams.d.ts.map +1 -1
  166. package/dist/lib/fetchers/teams.js.map +1 -1
  167. package/dist/lib/fetchers/zoom.d.ts +2 -2
  168. package/dist/lib/fetchers/zoom.d.ts.map +1 -1
  169. package/dist/lib/fetchers/zoom.js.map +1 -1
  170. package/dist/lib/gateway-client.d.ts +34 -0
  171. package/dist/lib/gateway-client.d.ts.map +1 -1
  172. package/dist/lib/gateway-client.js +19 -1
  173. package/dist/lib/gateway-client.js.map +1 -1
  174. package/dist/lib/github-text.d.ts +3 -0
  175. package/dist/lib/github-text.d.ts.map +1 -0
  176. package/dist/lib/github-text.js +18 -0
  177. package/dist/lib/github-text.js.map +1 -0
  178. package/dist/lib/hook-context.d.ts +10 -0
  179. package/dist/lib/hook-context.d.ts.map +1 -1
  180. package/dist/lib/hook-context.js +14 -0
  181. package/dist/lib/hook-context.js.map +1 -1
  182. package/dist/lib/import-defaults.d.ts +39 -23
  183. package/dist/lib/import-defaults.d.ts.map +1 -1
  184. package/dist/lib/import-defaults.js +28 -26
  185. package/dist/lib/import-defaults.js.map +1 -1
  186. package/dist/lib/launch/adapters/aider.d.ts +17 -0
  187. package/dist/lib/launch/adapters/aider.d.ts.map +1 -0
  188. package/dist/lib/launch/adapters/aider.js +30 -0
  189. package/dist/lib/launch/adapters/aider.js.map +1 -0
  190. package/dist/lib/launch/adapters/auggie.d.ts +18 -0
  191. package/dist/lib/launch/adapters/auggie.d.ts.map +1 -0
  192. package/dist/lib/launch/adapters/auggie.js +34 -0
  193. package/dist/lib/launch/adapters/auggie.js.map +1 -0
  194. package/dist/lib/launch/adapters/cline.d.ts +10 -0
  195. package/dist/lib/launch/adapters/cline.d.ts.map +1 -0
  196. package/dist/lib/launch/adapters/cline.js +41 -0
  197. package/dist/lib/launch/adapters/cline.js.map +1 -0
  198. package/dist/lib/launch/adapters/continue.d.ts +22 -0
  199. package/dist/lib/launch/adapters/continue.d.ts.map +1 -0
  200. package/dist/lib/launch/adapters/continue.js +46 -0
  201. package/dist/lib/launch/adapters/continue.js.map +1 -0
  202. package/dist/lib/launch/adapters/gemini-cli.d.ts +13 -14
  203. package/dist/lib/launch/adapters/gemini-cli.d.ts.map +1 -1
  204. package/dist/lib/launch/adapters/gemini-cli.js +38 -53
  205. package/dist/lib/launch/adapters/gemini-cli.js.map +1 -1
  206. package/dist/lib/launch/adapters/goose.d.ts +20 -0
  207. package/dist/lib/launch/adapters/goose.d.ts.map +1 -0
  208. package/dist/lib/launch/adapters/goose.js +47 -0
  209. package/dist/lib/launch/adapters/goose.js.map +1 -0
  210. package/dist/lib/launch/adapters/notes.d.ts +7 -0
  211. package/dist/lib/launch/adapters/notes.d.ts.map +1 -0
  212. package/dist/lib/launch/adapters/notes.js +5 -0
  213. package/dist/lib/launch/adapters/notes.js.map +1 -0
  214. package/dist/lib/launch/agents.d.ts +2 -0
  215. package/dist/lib/launch/agents.d.ts.map +1 -1
  216. package/dist/lib/launch/agents.js +3 -1
  217. package/dist/lib/launch/agents.js.map +1 -1
  218. package/dist/lib/launch/auggie-state.d.ts +25 -0
  219. package/dist/lib/launch/auggie-state.d.ts.map +1 -0
  220. package/dist/lib/launch/auggie-state.js +74 -0
  221. package/dist/lib/launch/auggie-state.js.map +1 -0
  222. package/dist/lib/launch/cline-state.d.ts +32 -0
  223. package/dist/lib/launch/cline-state.d.ts.map +1 -0
  224. package/dist/lib/launch/cline-state.js +108 -0
  225. package/dist/lib/launch/cline-state.js.map +1 -0
  226. package/dist/lib/launch/config-writes.d.ts +4 -0
  227. package/dist/lib/launch/config-writes.d.ts.map +1 -1
  228. package/dist/lib/launch/config-writes.js +8 -3
  229. package/dist/lib/launch/config-writes.js.map +1 -1
  230. package/dist/lib/launch/continue-state.d.ts +41 -0
  231. package/dist/lib/launch/continue-state.d.ts.map +1 -0
  232. package/dist/lib/launch/continue-state.js +142 -0
  233. package/dist/lib/launch/continue-state.js.map +1 -0
  234. package/dist/lib/launch/detect.d.ts.map +1 -1
  235. package/dist/lib/launch/detect.js +3 -1
  236. package/dist/lib/launch/detect.js.map +1 -1
  237. package/dist/lib/launch/gemini-state.d.ts +26 -20
  238. package/dist/lib/launch/gemini-state.d.ts.map +1 -1
  239. package/dist/lib/launch/gemini-state.js +95 -37
  240. package/dist/lib/launch/gemini-state.js.map +1 -1
  241. package/dist/lib/launch/gemini-system-file.d.ts +17 -0
  242. package/dist/lib/launch/gemini-system-file.d.ts.map +1 -0
  243. package/dist/lib/launch/gemini-system-file.js +55 -0
  244. package/dist/lib/launch/gemini-system-file.js.map +1 -0
  245. package/dist/lib/launch/gemini-trust.d.ts +3 -1
  246. package/dist/lib/launch/gemini-trust.d.ts.map +1 -1
  247. package/dist/lib/launch/gemini-trust.js +10 -3
  248. package/dist/lib/launch/gemini-trust.js.map +1 -1
  249. package/dist/lib/launch/goose-state.d.ts +53 -0
  250. package/dist/lib/launch/goose-state.d.ts.map +1 -0
  251. package/dist/lib/launch/goose-state.js +137 -0
  252. package/dist/lib/launch/goose-state.js.map +1 -0
  253. package/dist/lib/launch/launch-files.d.ts.map +1 -1
  254. package/dist/lib/launch/launch-files.js +3 -2
  255. package/dist/lib/launch/launch-files.js.map +1 -1
  256. package/dist/lib/launch/launch.d.ts +17 -2
  257. package/dist/lib/launch/launch.d.ts.map +1 -1
  258. package/dist/lib/launch/launch.js +36 -3
  259. package/dist/lib/launch/launch.js.map +1 -1
  260. package/dist/lib/launch/mcp-child-env.d.ts +30 -0
  261. package/dist/lib/launch/mcp-child-env.d.ts.map +1 -0
  262. package/dist/lib/launch/mcp-child-env.js +151 -0
  263. package/dist/lib/launch/mcp-child-env.js.map +1 -0
  264. package/dist/lib/launch/pick-agent.d.ts.map +1 -1
  265. package/dist/lib/launch/pick-agent.js +4 -4
  266. package/dist/lib/launch/pick-agent.js.map +1 -1
  267. package/dist/lib/launch/picker-options.d.ts.map +1 -1
  268. package/dist/lib/launch/picker-options.js +7 -2
  269. package/dist/lib/launch/picker-options.js.map +1 -1
  270. package/dist/lib/launch/registry/agents/aider.d.ts +3 -0
  271. package/dist/lib/launch/registry/agents/aider.d.ts.map +1 -0
  272. package/dist/lib/launch/registry/agents/aider.js +13 -0
  273. package/dist/lib/launch/registry/agents/aider.js.map +1 -0
  274. package/dist/lib/launch/registry/agents/auggie.d.ts +3 -0
  275. package/dist/lib/launch/registry/agents/auggie.d.ts.map +1 -0
  276. package/dist/lib/launch/registry/agents/auggie.js +11 -0
  277. package/dist/lib/launch/registry/agents/auggie.js.map +1 -0
  278. package/dist/lib/launch/registry/agents/cline.d.ts +3 -0
  279. package/dist/lib/launch/registry/agents/cline.d.ts.map +1 -0
  280. package/dist/lib/launch/registry/agents/cline.js +19 -0
  281. package/dist/lib/launch/registry/agents/cline.js.map +1 -0
  282. package/dist/lib/launch/registry/agents/continue.d.ts +3 -0
  283. package/dist/lib/launch/registry/agents/continue.d.ts.map +1 -0
  284. package/dist/lib/launch/registry/agents/continue.js +20 -0
  285. package/dist/lib/launch/registry/agents/continue.js.map +1 -0
  286. package/dist/lib/launch/registry/agents/gemini-cli.js +2 -2
  287. package/dist/lib/launch/registry/agents/gemini-cli.js.map +1 -1
  288. package/dist/lib/launch/registry/agents/goose.d.ts +3 -0
  289. package/dist/lib/launch/registry/agents/goose.d.ts.map +1 -0
  290. package/dist/lib/launch/registry/agents/goose.js +20 -0
  291. package/dist/lib/launch/registry/agents/goose.js.map +1 -0
  292. package/dist/lib/launch/registry/index.d.ts +1 -1
  293. package/dist/lib/launch/registry/index.d.ts.map +1 -1
  294. package/dist/lib/launch/registry/index.js +7 -2
  295. package/dist/lib/launch/registry/index.js.map +1 -1
  296. package/dist/lib/launch/registry/types.d.ts +12 -1
  297. package/dist/lib/launch/registry/types.d.ts.map +1 -1
  298. package/dist/lib/launch/strict-entry.d.ts +22 -2
  299. package/dist/lib/launch/strict-entry.d.ts.map +1 -1
  300. package/dist/lib/launch/strict-entry.js +80 -2
  301. package/dist/lib/launch/strict-entry.js.map +1 -1
  302. package/dist/lib/launch/yaml-scan.d.ts +59 -0
  303. package/dist/lib/launch/yaml-scan.d.ts.map +1 -0
  304. package/dist/lib/launch/yaml-scan.js +275 -0
  305. package/dist/lib/launch/yaml-scan.js.map +1 -0
  306. package/dist/lib/local-db-epoch.d.ts +4 -0
  307. package/dist/lib/local-db-epoch.d.ts.map +1 -0
  308. package/dist/lib/local-db-epoch.js +19 -0
  309. package/dist/lib/local-db-epoch.js.map +1 -0
  310. package/dist/lib/local-db-migrate.d.ts +85 -0
  311. package/dist/lib/local-db-migrate.d.ts.map +1 -0
  312. package/dist/lib/local-db-migrate.js +484 -0
  313. package/dist/lib/local-db-migrate.js.map +1 -0
  314. package/dist/lib/local-db-v10.d.ts +28 -0
  315. package/dist/lib/local-db-v10.d.ts.map +1 -0
  316. package/dist/lib/local-db-v10.js +49 -0
  317. package/dist/lib/local-db-v10.js.map +1 -0
  318. package/dist/lib/local-db-v11.d.ts +24 -0
  319. package/dist/lib/local-db-v11.d.ts.map +1 -0
  320. package/dist/lib/local-db-v11.js +25 -0
  321. package/dist/lib/local-db-v11.js.map +1 -0
  322. package/dist/lib/local-db-v7.d.ts +30 -0
  323. package/dist/lib/local-db-v7.d.ts.map +1 -0
  324. package/dist/lib/local-db-v7.js +285 -0
  325. package/dist/lib/local-db-v7.js.map +1 -0
  326. package/dist/lib/local-db-v8.d.ts +22 -0
  327. package/dist/lib/local-db-v8.d.ts.map +1 -0
  328. package/dist/lib/local-db-v8.js +33 -0
  329. package/dist/lib/local-db-v8.js.map +1 -0
  330. package/dist/lib/local-db-v9.d.ts +10 -0
  331. package/dist/lib/local-db-v9.d.ts.map +1 -0
  332. package/dist/lib/local-db-v9.js +8 -0
  333. package/dist/lib/local-db-v9.js.map +1 -0
  334. package/dist/lib/local-db.d.ts +61 -11
  335. package/dist/lib/local-db.d.ts.map +1 -1
  336. package/dist/lib/local-db.js +186 -371
  337. package/dist/lib/local-db.js.map +1 -1
  338. package/dist/lib/local-embeddings.d.ts.map +1 -1
  339. package/dist/lib/local-embeddings.js +2 -2
  340. package/dist/lib/local-embeddings.js.map +1 -1
  341. package/dist/lib/local-gateway-client.d.ts +30 -87
  342. package/dist/lib/local-gateway-client.d.ts.map +1 -1
  343. package/dist/lib/local-gateway-client.js +182 -171
  344. package/dist/lib/local-gateway-client.js.map +1 -1
  345. package/dist/lib/local-ingest.d.ts +129 -0
  346. package/dist/lib/local-ingest.d.ts.map +1 -0
  347. package/dist/lib/local-ingest.js +81 -0
  348. package/dist/lib/local-ingest.js.map +1 -0
  349. package/dist/lib/local-link-pass.d.ts +15 -0
  350. package/dist/lib/local-link-pass.d.ts.map +1 -0
  351. package/dist/lib/local-link-pass.js +61 -0
  352. package/dist/lib/local-link-pass.js.map +1 -0
  353. package/dist/lib/local-mode.d.ts +4 -1
  354. package/dist/lib/local-mode.d.ts.map +1 -1
  355. package/dist/lib/local-mode.js +16 -1
  356. package/dist/lib/local-mode.js.map +1 -1
  357. package/dist/lib/local-provenance.d.ts +14 -0
  358. package/dist/lib/local-provenance.d.ts.map +1 -0
  359. package/dist/lib/local-provenance.js +14 -0
  360. package/dist/lib/local-provenance.js.map +1 -0
  361. package/dist/lib/local-thresholds.d.ts +70 -0
  362. package/dist/lib/local-thresholds.d.ts.map +1 -0
  363. package/dist/lib/local-thresholds.js +79 -0
  364. package/dist/lib/local-thresholds.js.map +1 -0
  365. package/dist/lib/mcp/mark-tool.d.ts +27 -0
  366. package/dist/lib/mcp/mark-tool.d.ts.map +1 -0
  367. package/dist/lib/mcp/mark-tool.js +103 -0
  368. package/dist/lib/mcp/mark-tool.js.map +1 -0
  369. package/dist/lib/mcp/share-tool.d.ts +46 -0
  370. package/dist/lib/mcp/share-tool.d.ts.map +1 -0
  371. package/dist/lib/mcp/share-tool.js +68 -0
  372. package/dist/lib/mcp/share-tool.js.map +1 -0
  373. package/dist/lib/mcp/tool-rules.d.ts +30 -0
  374. package/dist/lib/mcp/tool-rules.d.ts.map +1 -0
  375. package/dist/lib/mcp/tool-rules.js +84 -0
  376. package/dist/lib/mcp/tool-rules.js.map +1 -0
  377. package/dist/lib/mcp-backfill.d.ts +89 -0
  378. package/dist/lib/mcp-backfill.d.ts.map +1 -0
  379. package/dist/lib/mcp-backfill.js +194 -0
  380. package/dist/lib/mcp-backfill.js.map +1 -0
  381. package/dist/lib/mcp-scope.d.ts +90 -0
  382. package/dist/lib/mcp-scope.d.ts.map +1 -0
  383. package/dist/lib/mcp-scope.js +125 -0
  384. package/dist/lib/mcp-scope.js.map +1 -0
  385. package/dist/lib/mcp-sync.d.ts +64 -0
  386. package/dist/lib/mcp-sync.d.ts.map +1 -0
  387. package/dist/lib/mcp-sync.js +183 -0
  388. package/dist/lib/mcp-sync.js.map +1 -0
  389. package/dist/lib/personal-import.d.ts +7 -0
  390. package/dist/lib/personal-import.d.ts.map +1 -1
  391. package/dist/lib/personal-import.js +13 -1
  392. package/dist/lib/personal-import.js.map +1 -1
  393. package/dist/lib/safe-config-write.d.ts.map +1 -1
  394. package/dist/lib/safe-config-write.js +39 -2
  395. package/dist/lib/safe-config-write.js.map +1 -1
  396. package/dist/lib/scope-choices.d.ts +51 -0
  397. package/dist/lib/scope-choices.d.ts.map +1 -0
  398. package/dist/lib/scope-choices.js +193 -0
  399. package/dist/lib/scope-choices.js.map +1 -0
  400. package/dist/lib/scope-connect.d.ts +82 -0
  401. package/dist/lib/scope-connect.d.ts.map +1 -0
  402. package/dist/lib/scope-connect.js +258 -0
  403. package/dist/lib/scope-connect.js.map +1 -0
  404. package/dist/lib/scope-defaults.d.ts +3 -0
  405. package/dist/lib/scope-defaults.d.ts.map +1 -0
  406. package/dist/lib/scope-defaults.js +42 -0
  407. package/dist/lib/scope-defaults.js.map +1 -0
  408. package/dist/lib/scope-real.d.ts +223 -0
  409. package/dist/lib/scope-real.d.ts.map +1 -0
  410. package/dist/lib/scope-real.js +58 -0
  411. package/dist/lib/scope-real.js.map +1 -0
  412. package/dist/lib/scope-values.d.ts +76 -0
  413. package/dist/lib/scope-values.d.ts.map +1 -0
  414. package/dist/lib/scope-values.js +169 -0
  415. package/dist/lib/scope-values.js.map +1 -0
  416. package/dist/lib/scope.d.ts +161 -0
  417. package/dist/lib/scope.d.ts.map +1 -0
  418. package/dist/lib/scope.js +323 -0
  419. package/dist/lib/scope.js.map +1 -0
  420. package/dist/lib/share/command.d.ts +32 -0
  421. package/dist/lib/share/command.d.ts.map +1 -0
  422. package/dist/lib/share/command.js +136 -0
  423. package/dist/lib/share/command.js.map +1 -0
  424. package/dist/lib/share/ledger.d.ts +59 -0
  425. package/dist/lib/share/ledger.d.ts.map +1 -0
  426. package/dist/lib/share/ledger.js +135 -0
  427. package/dist/lib/share/ledger.js.map +1 -0
  428. package/dist/lib/share/payload.d.ts +92 -0
  429. package/dist/lib/share/payload.d.ts.map +1 -0
  430. package/dist/lib/share/payload.js +138 -0
  431. package/dist/lib/share/payload.js.map +1 -0
  432. package/dist/lib/share/pending.d.ts +48 -0
  433. package/dist/lib/share/pending.d.ts.map +1 -0
  434. package/dist/lib/share/pending.js +130 -0
  435. package/dist/lib/share/pending.js.map +1 -0
  436. package/dist/lib/share/preview.d.ts +18 -0
  437. package/dist/lib/share/preview.d.ts.map +1 -0
  438. package/dist/lib/share/preview.js +72 -0
  439. package/dist/lib/share/preview.js.map +1 -0
  440. package/dist/lib/share/run.d.ts +115 -0
  441. package/dist/lib/share/run.d.ts.map +1 -0
  442. package/dist/lib/share/run.js +261 -0
  443. package/dist/lib/share/run.js.map +1 -0
  444. package/dist/lib/share/salt.d.ts +2 -0
  445. package/dist/lib/share/salt.d.ts.map +1 -0
  446. package/dist/lib/share/salt.js +44 -0
  447. package/dist/lib/share/salt.js.map +1 -0
  448. package/dist/lib/share/secret-patterns.json +100 -0
  449. package/dist/lib/share/secret-scan.d.ts +19 -0
  450. package/dist/lib/share/secret-scan.d.ts.map +1 -0
  451. package/dist/lib/share/secret-scan.js +75 -0
  452. package/dist/lib/share/secret-scan.js.map +1 -0
  453. package/dist/lib/share/tty.d.ts +4 -0
  454. package/dist/lib/share/tty.d.ts.map +1 -0
  455. package/dist/lib/share/tty.js +57 -0
  456. package/dist/lib/share/tty.js.map +1 -0
  457. package/dist/lib/share/visible.d.ts +4 -0
  458. package/dist/lib/share/visible.d.ts.map +1 -0
  459. package/dist/lib/share/visible.js +30 -0
  460. package/dist/lib/share/visible.js.map +1 -0
  461. package/dist/lib/share/wire.d.ts +98 -0
  462. package/dist/lib/share/wire.d.ts.map +1 -0
  463. package/dist/lib/share/wire.js +43 -0
  464. package/dist/lib/share/wire.js.map +1 -0
  465. package/dist/lib/similarity/embedding-matrix.d.ts +64 -0
  466. package/dist/lib/similarity/embedding-matrix.d.ts.map +1 -0
  467. package/dist/lib/similarity/embedding-matrix.js +119 -0
  468. package/dist/lib/similarity/embedding-matrix.js.map +1 -0
  469. package/dist/lib/since-flag.d.ts +11 -0
  470. package/dist/lib/since-flag.d.ts.map +1 -0
  471. package/dist/lib/since-flag.js +24 -0
  472. package/dist/lib/since-flag.js.map +1 -0
  473. package/dist/lib/since.d.ts +59 -0
  474. package/dist/lib/since.d.ts.map +1 -0
  475. package/dist/lib/since.js +86 -0
  476. package/dist/lib/since.js.map +1 -0
  477. package/dist/lib/source-key.d.ts +13 -0
  478. package/dist/lib/source-key.d.ts.map +1 -0
  479. package/dist/lib/source-key.js +60 -0
  480. package/dist/lib/source-key.js.map +1 -0
  481. package/dist/lib/source-sync-state.d.ts +12 -0
  482. package/dist/lib/source-sync-state.d.ts.map +1 -0
  483. package/dist/lib/source-sync-state.js +43 -0
  484. package/dist/lib/source-sync-state.js.map +1 -0
  485. package/dist/lib/startup-backfill-track.d.ts +2 -0
  486. package/dist/lib/startup-backfill-track.d.ts.map +1 -0
  487. package/dist/lib/startup-backfill-track.js +6 -0
  488. package/dist/lib/startup-backfill-track.js.map +1 -0
  489. package/dist/lib/startup-xdg.d.ts +2 -0
  490. package/dist/lib/startup-xdg.d.ts.map +1 -0
  491. package/dist/lib/startup-xdg.js +9 -0
  492. package/dist/lib/startup-xdg.js.map +1 -0
  493. package/dist/lib/sync/after-connect.d.ts +2 -0
  494. package/dist/lib/sync/after-connect.d.ts.map +1 -0
  495. package/dist/lib/sync/after-connect.js +18 -0
  496. package/dist/lib/sync/after-connect.js.map +1 -0
  497. package/dist/lib/sync/bg-claim.d.ts +14 -0
  498. package/dist/lib/sync/bg-claim.d.ts.map +1 -0
  499. package/dist/lib/sync/bg-claim.js +97 -0
  500. package/dist/lib/sync/bg-claim.js.map +1 -0
  501. package/dist/lib/sync/blocked.d.ts +8 -0
  502. package/dist/lib/sync/blocked.d.ts.map +1 -0
  503. package/dist/lib/sync/blocked.js +44 -0
  504. package/dist/lib/sync/blocked.js.map +1 -0
  505. package/dist/lib/sync/classify.d.ts +34 -0
  506. package/dist/lib/sync/classify.d.ts.map +1 -0
  507. package/dist/lib/sync/classify.js +91 -0
  508. package/dist/lib/sync/classify.js.map +1 -0
  509. package/dist/lib/sync/drain.d.ts +16 -0
  510. package/dist/lib/sync/drain.d.ts.map +1 -0
  511. package/dist/lib/sync/drain.js +33 -0
  512. package/dist/lib/sync/drain.js.map +1 -0
  513. package/dist/lib/sync/forget.d.ts +12 -0
  514. package/dist/lib/sync/forget.d.ts.map +1 -0
  515. package/dist/lib/sync/forget.js +32 -0
  516. package/dist/lib/sync/forget.js.map +1 -0
  517. package/dist/lib/sync/launch-hook.d.ts +42 -0
  518. package/dist/lib/sync/launch-hook.d.ts.map +1 -0
  519. package/dist/lib/sync/launch-hook.js +117 -0
  520. package/dist/lib/sync/launch-hook.js.map +1 -0
  521. package/dist/lib/sync/lock.d.ts +35 -0
  522. package/dist/lib/sync/lock.d.ts.map +1 -0
  523. package/dist/lib/sync/lock.js +175 -0
  524. package/dist/lib/sync/lock.js.map +1 -0
  525. package/dist/lib/sync/purge.d.ts +38 -0
  526. package/dist/lib/sync/purge.d.ts.map +1 -0
  527. package/dist/lib/sync/purge.js +61 -0
  528. package/dist/lib/sync/purge.js.map +1 -0
  529. package/dist/lib/sync/real-env.d.ts +208 -0
  530. package/dist/lib/sync/real-env.d.ts.map +1 -0
  531. package/dist/lib/sync/real-env.js +47 -0
  532. package/dist/lib/sync/real-env.js.map +1 -0
  533. package/dist/lib/sync/relink.d.ts +14 -0
  534. package/dist/lib/sync/relink.d.ts.map +1 -0
  535. package/dist/lib/sync/relink.js +36 -0
  536. package/dist/lib/sync/relink.js.map +1 -0
  537. package/dist/lib/sync/report.d.ts +3 -0
  538. package/dist/lib/sync/report.d.ts.map +1 -0
  539. package/dist/lib/sync/report.js +54 -0
  540. package/dist/lib/sync/report.js.map +1 -0
  541. package/dist/lib/sync/run-all.d.ts +23 -0
  542. package/dist/lib/sync/run-all.d.ts.map +1 -0
  543. package/dist/lib/sync/run-all.js +30 -0
  544. package/dist/lib/sync/run-all.js.map +1 -0
  545. package/dist/lib/sync/run-source.d.ts +73 -0
  546. package/dist/lib/sync/run-source.d.ts.map +1 -0
  547. package/dist/lib/sync/run-source.js +262 -0
  548. package/dist/lib/sync/run-source.js.map +1 -0
  549. package/dist/lib/sync/safe-read.d.ts +4 -0
  550. package/dist/lib/sync/safe-read.d.ts.map +1 -0
  551. package/dist/lib/sync/safe-read.js +40 -0
  552. package/dist/lib/sync/safe-read.js.map +1 -0
  553. package/dist/lib/sync/should-background-sync.d.ts +31 -0
  554. package/dist/lib/sync/should-background-sync.d.ts.map +1 -0
  555. package/dist/lib/sync/should-background-sync.js +69 -0
  556. package/dist/lib/sync/should-background-sync.js.map +1 -0
  557. package/dist/lib/sync/source-ids.d.ts +10 -0
  558. package/dist/lib/sync/source-ids.d.ts.map +1 -0
  559. package/dist/lib/sync/source-ids.js +9 -0
  560. package/dist/lib/sync/source-ids.js.map +1 -0
  561. package/dist/lib/sync/sources.d.ts +33 -0
  562. package/dist/lib/sync/sources.d.ts.map +1 -0
  563. package/dist/lib/sync/sources.js +37 -0
  564. package/dist/lib/sync/sources.js.map +1 -0
  565. package/dist/lib/sync/spawn-background.d.ts +27 -0
  566. package/dist/lib/sync/spawn-background.d.ts.map +1 -0
  567. package/dist/lib/sync/spawn-background.js +66 -0
  568. package/dist/lib/sync/spawn-background.js.map +1 -0
  569. package/dist/lib/sync/status.d.ts +57 -0
  570. package/dist/lib/sync/status.d.ts.map +1 -0
  571. package/dist/lib/sync/status.js +166 -0
  572. package/dist/lib/sync/status.js.map +1 -0
  573. package/dist/lib/sync/summary-read.d.ts +19 -0
  574. package/dist/lib/sync/summary-read.d.ts.map +1 -0
  575. package/dist/lib/sync/summary-read.js +39 -0
  576. package/dist/lib/sync/summary-read.js.map +1 -0
  577. package/dist/lib/sync/summary.d.ts +10 -0
  578. package/dist/lib/sync/summary.d.ts.map +1 -0
  579. package/dist/lib/sync/summary.js +80 -0
  580. package/dist/lib/sync/summary.js.map +1 -0
  581. package/dist/lib/sync/sync-state.d.ts +128 -0
  582. package/dist/lib/sync/sync-state.d.ts.map +1 -0
  583. package/dist/lib/sync/sync-state.js +253 -0
  584. package/dist/lib/sync/sync-state.js.map +1 -0
  585. package/dist/lib/sync/telemetry.d.ts +20 -0
  586. package/dist/lib/sync/telemetry.d.ts.map +1 -0
  587. package/dist/lib/sync/telemetry.js +31 -0
  588. package/dist/lib/sync/telemetry.js.map +1 -0
  589. package/dist/lib/sync/threads.d.ts +48 -0
  590. package/dist/lib/sync/threads.d.ts.map +1 -0
  591. package/dist/lib/sync/threads.js +82 -0
  592. package/dist/lib/sync/threads.js.map +1 -0
  593. package/dist/lib/sync/window.d.ts +72 -0
  594. package/dist/lib/sync/window.d.ts.map +1 -0
  595. package/dist/lib/sync/window.js +112 -0
  596. package/dist/lib/sync/window.js.map +1 -0
  597. package/dist/lib/team-cta.d.ts +11 -0
  598. package/dist/lib/team-cta.d.ts.map +1 -0
  599. package/dist/lib/team-cta.js +16 -0
  600. package/dist/lib/team-cta.js.map +1 -0
  601. package/dist/lib/telemetry-ci.d.ts +2 -0
  602. package/dist/lib/telemetry-ci.d.ts.map +1 -0
  603. package/dist/lib/telemetry-ci.js +21 -0
  604. package/dist/lib/telemetry-ci.js.map +1 -0
  605. package/dist/lib/telemetry-consent.d.ts +44 -23
  606. package/dist/lib/telemetry-consent.d.ts.map +1 -1
  607. package/dist/lib/telemetry-consent.js +74 -35
  608. package/dist/lib/telemetry-consent.js.map +1 -1
  609. package/dist/lib/telemetry-env.d.ts +1 -1
  610. package/dist/lib/usage-telemetry.d.ts +87 -22
  611. package/dist/lib/usage-telemetry.d.ts.map +1 -1
  612. package/dist/lib/usage-telemetry.js +263 -74
  613. package/dist/lib/usage-telemetry.js.map +1 -1
  614. package/dist/lib/xdg.d.ts +27 -0
  615. package/dist/lib/xdg.d.ts.map +1 -0
  616. package/dist/lib/xdg.js +56 -0
  617. package/dist/lib/xdg.js.map +1 -0
  618. package/package.json +3 -2
  619. package/dist/commands/push.d.ts +0 -25
  620. package/dist/commands/push.d.ts.map +0 -1
  621. package/dist/commands/push.js +0 -98
  622. package/dist/commands/push.js.map +0 -1
@@ -2,65 +2,12 @@ import { DatabaseSync } from 'node:sqlite';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { mkdirSync } from 'node:fs';
4
4
  import { dirname } from 'node:path';
5
- import { repoFromSourceUrl } from './repo-identity.js';
6
- const SCHEMA = `
7
- CREATE TABLE IF NOT EXISTS decisions (
8
- id TEXT PRIMARY KEY,
9
- title TEXT NOT NULL,
10
- summary TEXT NOT NULL,
11
- source_url TEXT,
12
- platform TEXT NOT NULL DEFAULT 'cli',
13
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
14
- repo TEXT,
15
- decided_at TEXT,
16
- decider_kind TEXT,
17
- confirmed_by TEXT,
18
- confirmed_at TEXT,
19
- ratified_by TEXT,
20
- ratified_at TEXT
21
- );
22
-
23
- CREATE TABLE IF NOT EXISTS decision_audit (
24
- id TEXT PRIMARY KEY,
25
- decision_id TEXT NOT NULL,
26
- action TEXT NOT NULL,
27
- actor TEXT,
28
- detail TEXT,
29
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
30
- );
31
-
32
- CREATE TABLE IF NOT EXISTS decision_embeddings (
33
- decision_id TEXT PRIMARY KEY REFERENCES decisions(id) ON DELETE CASCADE,
34
- embedding BLOB NOT NULL,
35
- model TEXT
36
- );
37
-
38
- CREATE TABLE IF NOT EXISTS decision_links (
39
- id TEXT PRIMARY KEY,
40
- source_id TEXT NOT NULL REFERENCES decisions(id) ON DELETE CASCADE,
41
- target_id TEXT NOT NULL REFERENCES decisions(id) ON DELETE CASCADE,
42
- relation TEXT NOT NULL,
43
- confidence REAL NOT NULL DEFAULT 1.0,
44
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
45
- );
46
-
47
- CREATE TABLE IF NOT EXISTS decision_refs (
48
- decision_id TEXT NOT NULL REFERENCES decisions(id) ON DELETE CASCADE,
49
- ref TEXT NOT NULL,
50
- platform TEXT NOT NULL,
51
- PRIMARY KEY (decision_id, ref)
52
- );
53
- `;
54
- /**
55
- * Schema version this build expects. Bump when adding a step to `migrate`.
56
- *
57
- * Exported so a test can assert the migration WROTE the version it claims, without hardcoding
58
- * the number - a test pinning a literal 1 had to be edited by this change rather than passing
59
- * or failing on its own merits. A parity test also derives the highest `version <` branch in
60
- * `migrate` from the source and compares it here, because forgetting the bump leaves the new
61
- * branch running destructively on every open with nothing to stop it.
62
- */
63
- export const SCHEMA_VERSION = 6;
5
+ import { deleteDecisionWithDependents, migrate, SCHEMA, SLACK_TOMBSTONE_TITLE } from './local-db-migrate.js';
6
+ import { bumpRowSetEpoch, rowSetEpoch } from './local-db-epoch.js';
7
+ import { foldKeylessTwin } from './local-db-v7.js';
8
+ import { discussionStart } from './github-text.js';
9
+ import { connectorItemKey, KEYED_PLATFORMS } from './source-key.js';
10
+ export { SCHEMA_VERSION } from './local-db-migrate.js';
64
11
  /**
65
12
  * ALI-829: a source timestamp as this database stores it - ISO-8601 Z - or null.
66
13
  *
@@ -92,10 +39,6 @@ export function normaliseDecidedAt(value) {
92
39
  return null;
93
40
  return new Date(ms).toISOString();
94
41
  }
95
- /** The title connector-core 0.5.0 gave every Slack thread whose root was deleted. The 0.6.0
96
- * fetcher titles such a thread from its first human message, or drops it; either way this
97
- * row is a duplicate-to-be under the (source_url, title) key, or noise. */
98
- const SLACK_TOMBSTONE_TITLE = 'This message was deleted.';
99
42
  /**
100
43
  * A `source_url` is only an identity if it points at ONE thing. Some fetchers substitute a
101
44
  * constant when the per-item link is missing, and connector-core ships one:
@@ -136,308 +79,13 @@ export function identifyingSourceUrl(raw) {
136
79
  }
137
80
  return value;
138
81
  }
139
- /** The five ALI-831 columns, named exactly as the shared contract spells them. One list,
140
- * read by the migration; SCHEMA above spells them a second time because a fresh CREATE
141
- * TABLE cannot read a constant, and the provenance test pins both against each other. */
142
- const PROVENANCE_COLUMNS = ['decider_kind', 'confirmed_by', 'confirmed_at', 'ratified_by', 'ratified_at'];
143
- /**
144
- * ALI-787: the only embedding model this graph has ever written before the `model` column
145
- * existed. Verified, not assumed - `git log --all -p` on local-embeddings.ts and
146
- * local-embeddings-wasm.ts shows exactly one HF model id was ever hardcoded there, across
147
- * every commit. So backfilling every pre-column row with this is a true historical fact,
148
- * unlike `decided_at` (step 4 below), which genuinely cannot be derived from an existing row.
149
- *
150
- * A deliberate LITERAL, not an import of local-embeddings.ts's EMBEDDING_MODEL_ID: this
151
- * constant names what was true when the column was added, and must stay that value even
152
- * after a future model swap changes EMBEDDING_MODEL_ID. Migrations are a historical record.
153
- */
154
- const LEGACY_EMBEDDING_MODEL = 'Xenova/all-MiniLM-L6-v2';
155
- /**
156
- * One-time data migrations, tracked in SQLite's built-in `user_version`.
157
- *
158
- * 1. ALI-503: relabel the cosine-similarity links that were written as `conflicts_with`.
159
- * Every such row at version 0 is provably an artefact, because `insertLink` had exactly
160
- * one caller and it hardcoded that relation for any pair over 0.65 cosine. No classifier
161
- * verdict was ever persisted, so there is no earned row to damage.
162
- *
163
- * 2. A decision's `source_url` identifies it, and nothing enforced that: `insertDecision` minted
164
- * a fresh UUID per call, so re-importing the same commit added a second copy. The documented
165
- * first run does exactly that - `setup --local` seeds from git, then its own outro tells you
166
- * to run `align connect git` - so a graph goes from 2 decisions to 4 by following the tips.
167
- * Collapse the existing duplicates, then make them unrepresentable with a unique index.
168
- *
169
- * 3. ALI-798: add the `repo` column (missing on any graph that predates it) and backfill it
170
- * for every existing row from `source_url`, via the same `repoFromSourceUrl` the importer
171
- * now stamps new rows with - one reader of the URL shape, used for both. A row whose
172
- * source is not code (Jira, Slack, a bare capture) stays NULL, which is "unattributed",
173
- * not "wrong": `--repo` scoping always includes unattributed rows alongside the named
174
- * repo (see local-gateway-client.ts), so nothing existing becomes invisible.
175
- *
176
- * 4. ALI-829: add `decided_at` - when the decision was MADE, from the source's own
177
- * timestamp, as distinct from `created_at`, the minute this CLI imported it. Every one
178
- * of the 684 rows in the 2026-09-02 measurement carried the ingest minute and nothing
179
- * else, so "what changed since March" was unanswerable offline. No backfill, unlike
180
- * `repo`: a repo is derivable from a stored source_url, and a decision date is not
181
- * derivable from anything already in the row. A re-import fills it in; inventing one
182
- * from created_at would write the exact wrong fact this column exists to correct.
183
- *
184
- * Same bump, second step: drop the Slack rows titled by a deleted root. The 0.6.0
185
- * fetcher titles a thread from its first human message, and the dedup key is
186
- * (source_url, title), so each of those rows would otherwise be re-inserted BESIDE its
187
- * tombstone twin on the next import (35 of 39 on the 2026-09-02 graph). The next import
188
- * recreates the real ones with real titles; the ones that were only bot output on a
189
- * deleted root do not come back, which is the point. Foreign keys are off in this
190
- * database (see step 2), so the dependent rows are deleted explicitly - including any
191
- * link a human adjudicated on such a row. That is a real loss, accepted: the row's
192
- * identity is changing under it, and the re-import classifies the retitled thread
193
- * afresh. Scoped to platform = 'slack': a git commit or a captured note that happens to
194
- * carry the same words is not a tombstone and is left alone.
195
- *
196
- * 5. ALI-831: add the decider/ratified columns (`decider_kind`, `confirmed_by`,
197
- * `confirmed_at`, `ratified_by`, `ratified_at`) so an agent-made decision can enter as a
198
- * CLAIM a human later stands behind, and the `decision_audit` table the human act is
199
- * recorded in. No backfill, on purpose: a row captured before the column reads as
200
- * 'unknown', and stamping 'human' on it from its platform would be the retroactive
201
- * guess align-stack's migration 113 refuses. The columns are added here rather than only
202
- * in SCHEMA because CREATE TABLE IF NOT EXISTS never alters an existing table.
203
- *
204
- * The version guard is load-bearing rather than tidiness. The same UPDATE run on every open
205
- * is indistinguishable from this one today, and starts silently eating genuine conflicts the
206
- * moment anything writes one.
207
- */
208
- /**
209
- * Remove a decision and everything hanging off it. Foreign keys are off in this database
210
- * (see migrate, step 2), so the dependents are named explicitly rather than cascaded. One
211
- * writer for the v4 tombstone sweep and the ingest-time twin removal (ALI-829).
212
- */
213
- function deleteDecisionWithDependents(db, id) {
214
- db.prepare('DELETE FROM decision_links WHERE source_id = ? OR target_id = ?').run(id, id);
215
- db.prepare('DELETE FROM decision_refs WHERE decision_id = ?').run(id);
216
- db.prepare('DELETE FROM decision_embeddings WHERE decision_id = ?').run(id);
217
- db.prepare('DELETE FROM decision_audit WHERE decision_id = ?').run(id);
218
- db.prepare('DELETE FROM decisions WHERE id = ?').run(id);
219
- }
220
- function migrate(db) {
221
- const version = db.prepare('PRAGMA user_version').get().user_version;
222
- if (version < 1) {
223
- db.exec(`UPDATE decision_links SET relation = 'relates' WHERE relation = 'conflicts_with'`);
224
- }
225
- if (version < 2) {
226
- // IMMEDIATE, not the default DEFERRED: this transaction reads (the survivor scan) before it
227
- // writes, and under WAL another writer arriving in between makes the first write fail with
228
- // SQLITE_BUSY_SNAPSHOT, which busy_timeout does not retry. Every local command opens the DB
229
- // and the advisory hook fires on every agent edit, so a concurrent open is the normal case.
230
- db.exec('BEGIN IMMEDIATE');
231
- try {
232
- // The survivor is the row inserted FIRST. `rowid` rather than `id` breaks a created_at
233
- // tie: created_at has one-second granularity so rows written in one import tie routinely,
234
- // and ordering by a random UUID would keep an arbitrary one while this comment claimed to
235
- // keep the long-standing id. rowid is insertion order (the table is not WITHOUT ROWID).
236
- //
237
- // Keyed on (source_url, title), not source_url alone. Some fetchers emit a CONSTANT
238
- // source_url when the per-item link is missing - connector-core's Teams fallback is
239
- // literally 'https://teams.microsoft.com' - and collapsing on the URL alone deleted every
240
- // such message but one. Two rows sharing a URL AND a title are duplicates by any reading;
241
- // two sharing only the URL are not.
242
- db.exec(`
243
- CREATE TEMP TABLE dedup_survivor AS
244
- SELECT source_url, title, id FROM (
245
- SELECT source_url, title, id,
246
- ROW_NUMBER() OVER (PARTITION BY source_url, title ORDER BY created_at, rowid) AS rn
247
- FROM decisions WHERE source_url IS NOT NULL
248
- ) WHERE rn = 1;
249
-
250
- CREATE TEMP TABLE dedup_dropped AS
251
- SELECT d.id AS id, s.id AS survivor_id
252
- FROM decisions d JOIN dedup_survivor s
253
- ON d.source_url = s.source_url AND d.title = s.title
254
- WHERE d.id <> s.id;
255
- `);
256
- // Repoint edges rather than dropping them: an edge naming a duplicate is a real edge that
257
- // happened to name the copy. Dropping it would lose a relationship the user earned.
258
- db.exec(`
259
- UPDATE decision_links SET source_id = (
260
- SELECT survivor_id FROM dedup_dropped WHERE id = decision_links.source_id
261
- ) WHERE source_id IN (SELECT id FROM dedup_dropped);
262
- UPDATE decision_links SET target_id = (
263
- SELECT survivor_id FROM dedup_dropped WHERE id = decision_links.target_id
264
- ) WHERE target_id IN (SELECT id FROM dedup_dropped);
265
- `);
266
- // Both cleanups are scoped to the pairs repointing actually touched. An earlier version
267
- // deduplicated decision_links table-wide, which deleted user-earned edges that had nothing
268
- // to do with duplication - and kept the lowest UUID rather than the highest confidence.
269
- db.exec(`
270
- DELETE FROM decision_links
271
- WHERE source_id = target_id
272
- AND (source_id IN (SELECT survivor_id FROM dedup_dropped)
273
- OR target_id IN (SELECT survivor_id FROM dedup_dropped));
274
- `);
275
- // Explicitly, not by CASCADE: SQLite leaves foreign_keys OFF unless asked, so the
276
- // ON DELETE CASCADE in the schema does not fire and these would be orphaned.
277
- db.exec(`DELETE FROM decision_embeddings WHERE decision_id IN (SELECT id FROM dedup_dropped)`);
278
- db.exec(`DELETE FROM decisions WHERE id IN (SELECT id FROM dedup_dropped)`);
279
- db.exec(`DROP TABLE dedup_dropped; DROP TABLE dedup_survivor;`);
280
- // Created here rather than in SCHEMA on purpose: SCHEMA runs before this function, so on
281
- // any already-duplicated graph the index would fail to build before the collapse could
282
- // run. A fresh database passes through the same path with nothing to collapse.
283
- //
284
- // A distinct name from any previous attempt, because IF NOT EXISTS matches on the NAME
285
- // only: an index of the same name with different columns would be silently kept, and
286
- // every later insert would then fail with "ON CONFLICT clause does not match".
287
- db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS decisions_source_title_unique ON decisions(source_url, title)`);
288
- // Same story for edges: insertLink's `OR IGNORE` was decorative without a unique index, so
289
- // a re-import stacked an identical relates row every time. Collapse, then constrain.
290
- //
291
- // This one IS table-wide, unavoidably: the index cannot be created while any duplicate
292
- // triple remains anywhere. Every row it removes duplicates another by definition - same
293
- // source, same target, same relation. It keeps the HIGHEST confidence rather than the
294
- // lowest uuid, so the survivor is the best score rather than an arbitrary one.
295
- db.exec(`
296
- DELETE FROM decision_links WHERE id NOT IN (
297
- SELECT id FROM (
298
- SELECT id, ROW_NUMBER() OVER (
299
- PARTITION BY source_id, target_id, relation ORDER BY confidence DESC, rowid
300
- ) AS rn FROM decision_links
301
- ) WHERE rn = 1
302
- );
303
- `);
304
- db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS decision_links_triple_unique ON decision_links(source_id, target_id, relation)`);
305
- // Stamped INSIDE the transaction. Outside it, a process killed between COMMIT and the
306
- // pragma replays the version-1 relabel on the next open, which turns an adjudicated
307
- // conflicts_with edge into relates - the exact silent loss the docstring above warns of.
308
- //
309
- // Stamps this step's OWN number (2), not SCHEMA_VERSION: a later step (3, below) runs
310
- // after this one commits, and if a crash lands between the two, `user_version` must
311
- // still read 2 so the version-3 step is not skipped on the next open.
312
- db.exec('PRAGMA user_version = 2');
313
- db.exec('COMMIT');
314
- }
315
- catch (err) {
316
- // Guarded: SQLITE_FULL, IOERR, BUSY, NOMEM and INTERRUPT auto-roll-back, and an
317
- // unconditional ROLLBACK then throws "no transaction is active" and buries the real
318
- // cause - so a user whose disk filled mid-migration would be told the wrong thing.
319
- if (db.isTransaction)
320
- db.exec('ROLLBACK');
321
- throw err;
322
- }
323
- }
324
- if (version < 3) {
325
- // The column may already exist: a brand-new database's CREATE TABLE (SCHEMA, above)
326
- // declares `repo` directly, so on a fresh install this ALTER would fail with "duplicate
327
- // column name" if run unconditionally. A legacy database predates the column entirely.
328
- const hasRepoColumn = db.prepare('PRAGMA table_info(decisions)').all()
329
- .some((c) => c.name === 'repo');
330
- if (!hasRepoColumn) {
331
- db.exec('ALTER TABLE decisions ADD COLUMN repo TEXT');
332
- }
333
- // Backfill every row with no repo yet. JS-side because SQLite has no regex function to
334
- // apply `repoFromSourceUrl`'s pattern in SQL - and it must be the SAME function the
335
- // importer stamps new rows with, or a backfilled row and a freshly-imported one for the
336
- // same URL could disagree about which repo they belong to.
337
- const rows = db.prepare('SELECT id, source_url FROM decisions WHERE repo IS NULL')
338
- .all();
339
- if (rows.length) {
340
- db.exec('BEGIN IMMEDIATE');
341
- try {
342
- const update = db.prepare('UPDATE decisions SET repo = ? WHERE id = ?');
343
- for (const row of rows) {
344
- const repo = repoFromSourceUrl(row.source_url);
345
- // Only WRITE when a repo was found: leaving a non-code row NULL is the correct
346
- // outcome (unattributed, not "wrong"), and writing NULL over NULL is a no-op
347
- // this loop can just skip.
348
- if (repo)
349
- update.run(repo, row.id);
350
- }
351
- db.exec('PRAGMA user_version = 3');
352
- db.exec('COMMIT');
353
- }
354
- catch (err) {
355
- if (db.isTransaction)
356
- db.exec('ROLLBACK');
357
- throw err;
358
- }
359
- }
360
- else {
361
- db.exec('PRAGMA user_version = 3');
362
- }
363
- }
364
- if (version < 4) {
365
- db.exec('BEGIN IMMEDIATE');
366
- try {
367
- // Same shape as the `repo` step: a fresh database's CREATE TABLE already declares the
368
- // column, and a legacy one predates it, so the ALTER is conditional. Checked INSIDE
369
- // the write lock, unlike step 3: two processes opening a v3 file at once (the
370
- // advisory hook is the normal concurrent case) could otherwise both read "no column"
371
- // and the second would die on "duplicate column name". SQLite DDL is transactional.
372
- const hasDecidedAt = db.prepare('PRAGMA table_info(decisions)').all()
373
- .some((c) => c.name === 'decided_at');
374
- if (!hasDecidedAt) {
375
- db.exec('ALTER TABLE decisions ADD COLUMN decided_at TEXT');
376
- }
377
- const tombstones = db.prepare(`SELECT id FROM decisions WHERE platform = 'slack' AND title = ?`).all(SLACK_TOMBSTONE_TITLE);
378
- for (const { id } of tombstones)
379
- deleteDecisionWithDependents(db, id);
380
- // Stamped inside the transaction, for the reason step 2 gives.
381
- db.exec('PRAGMA user_version = 4');
382
- db.exec('COMMIT');
383
- }
384
- catch (err) {
385
- if (db.isTransaction)
386
- db.exec('ROLLBACK');
387
- throw err;
388
- }
389
- }
390
- if (version < 5) {
391
- // Inside the write lock, for the reason step 4 gives: two concurrent opens of a v4
392
- // file must not both read "no column" and race the ALTER.
393
- db.exec('BEGIN IMMEDIATE');
394
- try {
395
- const existing = new Set(db.prepare('PRAGMA table_info(decisions)').all().map((c) => c.name));
396
- for (const column of PROVENANCE_COLUMNS) {
397
- if (!existing.has(column))
398
- db.exec(`ALTER TABLE decisions ADD COLUMN ${column} TEXT`);
399
- }
400
- db.exec('PRAGMA user_version = 5');
401
- db.exec('COMMIT');
402
- }
403
- catch (err) {
404
- if (db.isTransaction)
405
- db.exec('ROLLBACK');
406
- throw err;
407
- }
408
- }
409
- if (version < 6) {
410
- // Inside the write lock, for the reason step 4/5 give: two concurrent opens of a v5
411
- // file must not both read "no column" and race the ALTER.
412
- db.exec('BEGIN IMMEDIATE');
413
- try {
414
- const hasModel = db.prepare('PRAGMA table_info(decision_embeddings)').all()
415
- .some((c) => c.name === 'model');
416
- if (!hasModel) {
417
- db.exec('ALTER TABLE decision_embeddings ADD COLUMN model TEXT');
418
- }
419
- // Every row that predates the column was written by LEGACY_EMBEDDING_MODEL - see its
420
- // doc comment for how that is verified rather than assumed. Only rows with no tag yet
421
- // are touched, so re-running this on an already-migrated database is a no-op.
422
- db.prepare('UPDATE decision_embeddings SET model = ? WHERE model IS NULL').run(LEGACY_EMBEDDING_MODEL);
423
- db.exec('PRAGMA user_version = 6');
424
- db.exec('COMMIT');
425
- }
426
- catch (err) {
427
- if (db.isTransaction)
428
- db.exec('ROLLBACK');
429
- throw err;
430
- }
431
- }
432
- if (version < SCHEMA_VERSION) {
433
- db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
434
- }
435
- }
436
82
  /** Every column a DecisionRow carries, aliased to its camelCase name. One writer, read by
437
83
  * both row readers, so a new column cannot be added to one SELECT and missed by the other. */
438
84
  const DECISION_COLUMNS = 'id, title, summary, source_url as sourceUrl, platform, created_at as createdAt, repo, decided_at as decidedAt, ' +
439
85
  'decider_kind as deciderKind, confirmed_by as confirmedBy, confirmed_at as confirmedAt, ' +
440
86
  'ratified_by as ratifiedBy, ratified_at as ratifiedAt';
87
+ /** A person's ratification or confirmation covers the text they read (review finding 4). */
88
+ const ATTESTED = 'decisions.ratified_at IS NOT NULL OR decisions.confirmed_at IS NOT NULL';
441
89
  export function createLocalDb(dbPath) {
442
90
  // SQLite creates the DB file but not its parent directory, so on a clean machine
443
91
  // (~/.config/align-cli absent) `align setup --local` crashed with "unable to open
@@ -448,17 +96,29 @@ export function createLocalDb(dbPath) {
448
96
  mkdirSync(dirname(dbPath), { recursive: true });
449
97
  }
450
98
  const db = new DatabaseSync(dbPath);
451
- db.exec('PRAGMA journal_mode = WAL');
452
- db.exec(SCHEMA);
453
- migrate(db);
99
+ // 30s: the longest measured v7 migration (100k rows, 33k merges) takes about 5s, so a waiting
100
+ // opener outlasts it with margin. First, before anything that needs a lock: every local command and the advisory hook open
101
+ // this file, so a concurrent opener is normal, and without a timeout it fails at once with
102
+ // "database is locked" instead of waiting for the migration in progress.
103
+ db.exec('PRAGMA busy_timeout = 30000');
104
+ try {
105
+ db.exec('PRAGMA journal_mode = WAL');
106
+ db.exec(SCHEMA);
107
+ migrate(db);
108
+ }
109
+ catch (err) {
110
+ db.close();
111
+ throw err;
112
+ }
454
113
  // Shared by insertLink and resolveRefs (ALI-796), so there is one writer of the
455
114
  // decision_links insert rather than two copies of the same ON CONFLICT clause.
456
115
  function insertLinkRow(link) {
457
116
  db.prepare(`INSERT INTO decision_links (id, source_id, target_id, relation, confidence) VALUES (?, ?, ?, ?, ?)
458
117
  ON CONFLICT(source_id, target_id, relation)
459
- DO UPDATE SET confidence = MAX(confidence, excluded.confidence)`).run(randomUUID(), link.sourceId, link.targetId, link.relation, link.confidence);
118
+ DO UPDATE SET confidence = MAX(confidence, excluded.confidence)`).run(link.id ?? randomUUID(), link.sourceId, link.targetId, link.relation, link.confidence);
460
119
  }
461
- return {
120
+ let dataVersionStmt;
121
+ const api = {
462
122
  /**
463
123
  * Insert, or refresh the decision that already carries this `source_url`, returning the id
464
124
  * that now holds it.
@@ -484,15 +144,39 @@ export function createLocalDb(dbPath) {
484
144
  * change which repo it points at between imports.
485
145
  */
486
146
  insertDecision(row) {
487
- const inserted = db.prepare(`INSERT INTO decisions (id, title, summary, source_url, platform, repo, decided_at, decider_kind) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
147
+ // L2: a one-item-per-URL connector item upserts on its source_key, and that branch takes
148
+ // the new title, so an edited PR title updates the row instead of adding a twin. Every
149
+ // other row (sessions, captures, docs) keeps the (source_url, title) identity.
150
+ //
151
+ // Both branches clear enriched_at: the row's text may have changed, so its links are not
152
+ // known to be current until ingestOne's link pass marks it again (Decision 30).
153
+ const sourceUrl = identifyingSourceUrl(row.sourceUrl);
154
+ const key = (row.keyed ? connectorItemKey(row.platform, sourceUrl) : undefined) ?? null;
155
+ api.foldPendingTwin(sourceUrl, row.title, row.platform, row.keyed);
156
+ const inserted = db.prepare(`INSERT INTO decisions (id, title, summary, source_url, platform, repo, decided_at, decider_kind, source_key, detail_pending) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
157
+ ON CONFLICT(source_key) WHERE source_key IS NOT NULL DO UPDATE SET
158
+ title = CASE WHEN ${ATTESTED} THEN decisions.title ELSE excluded.title END,
159
+ summary = CASE WHEN ${ATTESTED} THEN decisions.summary ELSE excluded.summary END,
160
+ platform = excluded.platform,
161
+ repo = COALESCE(excluded.repo, decisions.repo),
162
+ decided_at = COALESCE(excluded.decided_at, decisions.decided_at),
163
+ -- An attested row keeps its text, so it keeps the flag that describes that text (review 8).
164
+ -- A thin arrival whose body IS the stored body (ingestOne passed the stored text on
165
+ -- because the stored one is richer) must not turn a complete row pending again.
166
+ detail_pending = CASE
167
+ WHEN ${ATTESTED} THEN decisions.detail_pending
168
+ WHEN excluded.detail_pending = 1 AND decisions.detail_pending = 0 AND excluded.summary = decisions.summary THEN 0
169
+ ELSE excluded.detail_pending END,
170
+ enriched_at = CASE WHEN ${ATTESTED} THEN decisions.enriched_at ELSE NULL END
488
171
  ON CONFLICT(source_url, title) DO UPDATE SET
489
172
  summary = excluded.summary, platform = excluded.platform,
490
173
  repo = COALESCE(excluded.repo, decisions.repo),
491
- decided_at = COALESCE(excluded.decided_at, decisions.decided_at)
492
- RETURNING id`).get(randomUUID(), row.title, row.summary, identifyingSourceUrl(row.sourceUrl), row.platform, row.repo ?? null,
174
+ decided_at = COALESCE(excluded.decided_at, decisions.decided_at),
175
+ enriched_at = NULL
176
+ RETURNING id`).get(randomUUID(), row.title, row.summary, sourceUrl, row.platform, row.repo ?? null,
493
177
  // `||`, not `??`: COALESCE('', old) is '', so an empty string would blank a stored
494
178
  // date. Callers normalise, but this is the one place the column is written.
495
- row.decidedAt || null, row.deciderKind ?? null);
179
+ row.decidedAt || null, row.deciderKind ?? null, key, row.detailPending ? 1 : 0);
496
180
  return inserted.id;
497
181
  },
498
182
  /**
@@ -519,13 +203,95 @@ export function createLocalDb(dbPath) {
519
203
  * A null `sourceUrl` is always null here: SQLite treats each NULL in a unique index as
520
204
  * distinct, so those rows never conflict and every one of them really is new.
521
205
  */
522
- findIdBySource(sourceUrl, title) {
206
+ findIdBySource(sourceUrl, title, platform, keyed) {
523
207
  const identity = identifyingSourceUrl(sourceUrl);
524
208
  if (identity === null)
525
209
  return null;
210
+ // L2: with the platform, a one-item-per-URL item is found by its source_key, so a retitled
211
+ // PR is recognised as the row insertDecision is about to update, not as a new one.
212
+ const key = platform === undefined || !keyed ? undefined : connectorItemKey(platform, identity);
213
+ if (key !== undefined) {
214
+ const byKey = db.prepare(`SELECT id FROM decisions WHERE source_key = ?`).get(key);
215
+ if (byKey)
216
+ return byKey.id;
217
+ }
526
218
  const row = db.prepare(`SELECT id FROM decisions WHERE source_url = ? AND title = ?`).get(identity, title);
527
219
  return row?.id ?? null;
528
220
  },
221
+ /**
222
+ * Review finding 4: what a keyed re-import may write over row `id`. A ratified or confirmed
223
+ * row keeps the text a person attested; the incoming text is recorded once as a
224
+ * `text_revision_pending` audit note and the stored text is returned. Any other row (or an
225
+ * unknown id) gets the incoming text back unchanged.
226
+ */
227
+ keepProtectedText(id, title, summary, thin = false) {
228
+ const row = db.prepare(`SELECT title, summary, detail_pending, ratified_at, confirmed_at FROM decisions WHERE id = ?`).get(id);
229
+ if (row && row.ratified_at === null && row.confirmed_at === null && thin && row.detail_pending === 0) {
230
+ // L3: `thin` is an items-first arrival (discussion not fetched). The stored row is complete
231
+ // and has a discussion block: keep the BLOCK and take everything else from the arrival
232
+ // (title, body, Status, Repo), so a merge, a rename or an edit still lands. The result
233
+ // equals the stored text when nothing changed, which makes the caller's unchanged-skip fire.
234
+ const keep = discussionStart(row.summary);
235
+ if (keep !== -1 && discussionStart(summary) === -1) {
236
+ return { title, summary: summary + row.summary.slice(keep), keptDiscussion: true };
237
+ }
238
+ }
239
+ if (!row || (row.ratified_at === null && row.confirmed_at === null))
240
+ return { title, summary };
241
+ if (row.title !== title || row.summary !== summary) {
242
+ const detail = JSON.stringify({ title, summary });
243
+ const seen = db.prepare(`SELECT 1 AS hit FROM decision_audit WHERE decision_id = ? AND action = 'text_revision_pending' AND detail = ?`).get(id, detail);
244
+ if (!seen) {
245
+ api.insertAudit({ decisionId: id, action: 'text_revision_pending', actor: null, detail });
246
+ // Bounded: an upstream that changes every sync would otherwise add a row per sync.
247
+ db.prepare(`DELETE FROM decision_audit WHERE decision_id = ? AND action = 'text_revision_pending' AND rowid NOT IN
248
+ (SELECT rowid FROM decision_audit WHERE decision_id = ? AND action = 'text_revision_pending' ORDER BY rowid DESC LIMIT 5)`).run(id, id);
249
+ }
250
+ }
251
+ return { title: row.title, summary: row.summary };
252
+ },
253
+ /** The id of the connector-imported item `url` names, under any platform, or null. A capture
254
+ * of that URL must not rewrite the imported text (L2 review finding 1). */
255
+ findKeyedIdByUrl(url) {
256
+ const identity = identifyingSourceUrl(url);
257
+ if (identity === null)
258
+ return null;
259
+ for (const platform of KEYED_PLATFORMS) {
260
+ const key = connectorItemKey(platform, identity);
261
+ const hit = key === undefined ? undefined : db.prepare(`SELECT id FROM decisions WHERE source_key = ?`).get(key);
262
+ if (hit)
263
+ return hit.id;
264
+ }
265
+ return null;
266
+ },
267
+ /**
268
+ * A keyless row at this (source_url, title) is a twin an older binary wrote beside the keyed
269
+ * one. Adopt it as the keyed row, or fold it with the row that already holds the key (the
270
+ * attested one survives). ingestOne calls this BEFORE it looks the item up, so the text it
271
+ * protects and embeds is the survivor's. No-op for an unkeyed call.
272
+ */
273
+ foldPendingTwin(sourceUrl, title, platform, keyed) {
274
+ const key = keyed ? connectorItemKey(platform, sourceUrl) : undefined;
275
+ if (key === undefined)
276
+ return;
277
+ const twin = db.prepare(`SELECT id FROM decisions WHERE source_url = ? AND title = ? AND source_key IS NULL`).get(sourceUrl, title);
278
+ if (!twin)
279
+ return;
280
+ const holder = db.prepare(`SELECT id FROM decisions WHERE source_key = ?`).get(key);
281
+ if (holder)
282
+ foldKeylessTwin(db, twin.id, holder.id, key);
283
+ else
284
+ db.prepare(`UPDATE decisions SET source_key = ? WHERE id = ?`).run(key, twin.id);
285
+ },
286
+ /** A capture of a URL a connector already imported: audit it, change nothing, return the row. */
287
+ noteCaptureOfHeldItem(url) {
288
+ const id = api.findKeyedIdByUrl(url);
289
+ const row = id === null ? null : api.getDecisionById(id);
290
+ const today = id === null ? undefined : db.prepare(`SELECT 1 AS hit FROM decision_audit WHERE decision_id = ? AND action = 'capture_seen' AND date(created_at) = date('now')`).get(id);
291
+ if (row && !today)
292
+ api.insertAudit({ decisionId: row.id, action: 'capture_seen', actor: null, detail: url });
293
+ return row;
294
+ },
529
295
  /**
530
296
  * `filter.repo` scopes to one repo's rows. `includeUnattributed` (ALI-798's own rule:
531
297
  * "repo = ? OR repo IS NULL") also returns rows with no repo at all - a Jira ticket, a
@@ -585,6 +351,19 @@ export function createLocalDb(dbPath) {
585
351
  RETURNING confirmed_at AS confirmedAt`).get(confirmedBy, confirmedAt, id);
586
352
  return row ?? null;
587
353
  },
354
+ /**
355
+ * L2, Decision 30: the decision's ingest finished, link pass included. ingestOne is the one
356
+ * caller, and calls it as its last write; insertDecision clears it whenever it rewrites the
357
+ * row. NULL therefore means "links not known to be current", and the unchanged-skip will
358
+ * not skip such a row.
359
+ */
360
+ markEnriched(id) {
361
+ db.prepare(`UPDATE decisions SET enriched_at = ? WHERE id = ?`).run(new Date().toISOString(), id);
362
+ },
363
+ getEnrichedAt(id) {
364
+ const row = db.prepare(`SELECT enriched_at AS e FROM decisions WHERE id = ?`).get(id);
365
+ return row?.e ?? null;
366
+ },
588
367
  /** ALI-831: one row per human act on a decision (ratified, pushed), so "who stood behind
589
368
  * this, and when" is answerable after the fact. Append-only by construction. */
590
369
  insertAudit(entry) {
@@ -656,6 +435,10 @@ export function createLocalDb(dbPath) {
656
435
  * (local-gateway-client.ts's ingestOne) always passes EMBEDDING_MODEL_ID.
657
436
  */
658
437
  setEmbedding(decisionId, embedding, model) {
438
+ // Replacing a vector changes what a copy of the embeddings should hold; a first write
439
+ // only adds a row, which the writer appends itself. (rowSetEpoch's contract.)
440
+ if (db.prepare('SELECT 1 FROM decision_embeddings WHERE decision_id = ?').get(decisionId))
441
+ bumpRowSetEpoch(db);
659
442
  db.prepare(`INSERT OR REPLACE INTO decision_embeddings (decision_id, embedding, model) VALUES (?, ?, ?)`).run(decisionId, Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength), model ?? null);
660
443
  },
661
444
  getEmbedding(decisionId) {
@@ -746,6 +529,16 @@ export function createLocalDb(dbPath) {
746
529
  insertLink(link) {
747
530
  insertLinkRow(link);
748
531
  },
532
+ /**
533
+ * LM: the ONE way a link is deleted on behalf of a person's mark. `align mark <a> replaces <b>`
534
+ * writes its supersedes link under the id `mark:<judgement id>` (when the classifier had not
535
+ * already written that edge, in which case the edge keeps its own id and is not ours), and
536
+ * `--undo` removes exactly that row here. The id is built inside this method, so no caller can
537
+ * name another link: a classifier edge, or a link another mark owns, cannot be reached from here.
538
+ */
539
+ deleteMarkLink(judgementId) {
540
+ return Number(db.prepare(`DELETE FROM decision_links WHERE id = ? AND relation = 'supersedes'`).run(`mark:${judgementId}`).changes);
541
+ },
749
542
  /**
750
543
  * ALI-1065: a pair gets ONE edge, never two. `insertLinkRow`'s unique index is on
751
544
  * (source_id, target_id, relation), so writing `supersedes` for a pair that already
@@ -755,7 +548,10 @@ export function createLocalDb(dbPath) {
755
548
  * classification can upgrade a cosine `relates` edge into a typed one atomically.
756
549
  */
757
550
  replaceLink(link) {
758
- db.prepare('DELETE FROM decision_links WHERE (source_id = ? AND target_id = ?) OR (source_id = ? AND target_id = ?)').run(link.sourceId, link.targetId, link.targetId, link.sourceId);
551
+ // LM: an edge a person's `align mark ... replaces` wrote (id `mark:<judgement id>`) is theirs, not
552
+ // the classifier's to upgrade away: deleting it would orphan the judgement, and `--undo` could
553
+ // no longer find its link. The pair may then carry that edge beside the new one.
554
+ db.prepare(`DELETE FROM decision_links WHERE ((source_id = ? AND target_id = ?) OR (source_id = ? AND target_id = ?)) AND id NOT LIKE 'mark:%'`).run(link.sourceId, link.targetId, link.targetId, link.sourceId);
759
555
  insertLinkRow(link);
760
556
  },
761
557
  /**
@@ -862,11 +658,30 @@ export function createLocalDb(dbPath) {
862
658
  // decision_refs listed explicitly: SQLite leaves foreign_keys OFF unless asked,
863
659
  // so the schema's ON DELETE CASCADE never fires (same fact the v2 migration
864
660
  // documents above).
661
+ bumpRowSetEpoch(db);
865
662
  db.exec(`DELETE FROM decision_refs; DELETE FROM decision_links; DELETE FROM decision_embeddings; DELETE FROM decisions;`);
866
663
  },
664
+ /** Goes up whenever a decision is deleted or re-id'd or an embedding is deleted or replaced
665
+ * on this connection (see local-db-epoch.ts). A holder of a copy of the embeddings
666
+ * compares it to the value it built at. */
667
+ rowSetEpoch() {
668
+ return rowSetEpoch(db);
669
+ },
670
+ /** SQLite's data_version: changes when ANOTHER connection commits (another process, an
671
+ * MCP server, a second CLI), never for this connection's own writes. The cross-process
672
+ * half of "has the graph moved under my copy of it". */
673
+ dataVersion() {
674
+ return (dataVersionStmt ??= db.prepare('PRAGMA data_version')).get()['data_version'];
675
+ },
676
+ /** Stored embeddings of every model: an upper bound on what a matrix would hold, read
677
+ * before building one so a huge graph is never loaded just to be refused. */
678
+ countEmbeddings() {
679
+ return db.prepare('SELECT COUNT(*) AS n FROM decision_embeddings').get().n;
680
+ },
867
681
  close() {
868
682
  db.close();
869
683
  },
870
684
  };
685
+ return api;
871
686
  }
872
687
  //# sourceMappingURL=local-db.js.map