@anchrd/intel 0.59.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 (364) hide show
  1. package/README.md +496 -0
  2. package/bin/intel.mjs +134 -0
  3. package/dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts +42 -0
  4. package/dist/adapters/cloudflare/cloudflare-flow-workflow.js +89 -0
  5. package/dist/adapters/cloudflare/cloudflare.d.ts +29 -0
  6. package/dist/adapters/cloudflare/cloudflare.js +416 -0
  7. package/dist/adapters/cloudflare/cloudflare.types.d.ts +31 -0
  8. package/dist/adapters/cloudflare/cloudflare.types.js +1 -0
  9. package/dist/adapters/content/content.d.ts +3 -0
  10. package/dist/adapters/content/content.js +128 -0
  11. package/dist/adapters/content/content.types.d.ts +38 -0
  12. package/dist/adapters/content/content.types.js +1 -0
  13. package/dist/adapters/db/db-audit.d.ts +62 -0
  14. package/dist/adapters/db/db-audit.js +150 -0
  15. package/dist/adapters/db/db-boards.d.ts +6 -0
  16. package/dist/adapters/db/db-boards.js +324 -0
  17. package/dist/adapters/db/db-feed.d.ts +30 -0
  18. package/dist/adapters/db/db-feed.js +276 -0
  19. package/dist/adapters/db/db-flows.d.ts +42 -0
  20. package/dist/adapters/db/db-flows.js +1311 -0
  21. package/dist/adapters/db/db-grants.d.ts +133 -0
  22. package/dist/adapters/db/db-grants.js +269 -0
  23. package/dist/adapters/db/db-indexing.d.ts +3 -0
  24. package/dist/adapters/db/db-indexing.js +200 -0
  25. package/dist/adapters/db/db-oauth.d.ts +3 -0
  26. package/dist/adapters/db/db-oauth.js +26 -0
  27. package/dist/adapters/db/db-prompts.d.ts +46 -0
  28. package/dist/adapters/db/db-prompts.js +144 -0
  29. package/dist/adapters/db/db.d.ts +6 -0
  30. package/dist/adapters/db/db.js +1687 -0
  31. package/dist/adapters/db/db.types.d.ts +16 -0
  32. package/dist/adapters/db/db.types.js +1 -0
  33. package/dist/adapters/document-converter/document-converter.d.ts +2 -0
  34. package/dist/adapters/document-converter/document-converter.js +26 -0
  35. package/dist/adapters/document-converter/document-converter.types.d.ts +17 -0
  36. package/dist/adapters/document-converter/document-converter.types.js +1 -0
  37. package/dist/adapters/flow-runtime/flow-runtime.d.ts +3 -0
  38. package/dist/adapters/flow-runtime/flow-runtime.js +22 -0
  39. package/dist/adapters/flow-runtime/flow-runtime.types.d.ts +19 -0
  40. package/dist/adapters/flow-runtime/flow-runtime.types.js +1 -0
  41. package/dist/adapters/index-queue/index-queue.d.ts +10 -0
  42. package/dist/adapters/index-queue/index-queue.js +12 -0
  43. package/dist/adapters/index-queue/index-queue.types.d.ts +11 -0
  44. package/dist/adapters/index-queue/index-queue.types.js +1 -0
  45. package/dist/adapters/json-schema/json-schema.d.ts +2 -0
  46. package/dist/adapters/json-schema/json-schema.js +77 -0
  47. package/dist/adapters/openid/openid.d.ts +5 -0
  48. package/dist/adapters/openid/openid.js +301 -0
  49. package/dist/adapters/portal-tokens/portal-tokens.d.ts +8 -0
  50. package/dist/adapters/portal-tokens/portal-tokens.js +79 -0
  51. package/dist/adapters/remote-tools/remote-tools.d.ts +5 -0
  52. package/dist/adapters/remote-tools/remote-tools.js +129 -0
  53. package/dist/adapters/semantic-index/semantic-index.d.ts +2 -0
  54. package/dist/adapters/semantic-index/semantic-index.js +139 -0
  55. package/dist/adapters/semantic-index/semantic-index.types.d.ts +56 -0
  56. package/dist/adapters/semantic-index/semantic-index.types.js +1 -0
  57. package/dist/adapters/session-cookie/session-cookie.d.ts +5 -0
  58. package/dist/adapters/session-cookie/session-cookie.js +87 -0
  59. package/dist/adapters/tool-source-policy/tool-source-policy.d.ts +1 -0
  60. package/dist/adapters/tool-source-policy/tool-source-policy.js +22 -0
  61. package/dist/audit/audit.d.ts +2 -0
  62. package/dist/audit/audit.js +87 -0
  63. package/dist/audit/audit.types.d.ts +23 -0
  64. package/dist/audit/audit.types.js +1 -0
  65. package/dist/auth/auth.d.ts +2 -0
  66. package/dist/auth/auth.js +284 -0
  67. package/dist/auth/auth.types.d.ts +108 -0
  68. package/dist/auth/auth.types.js +1 -0
  69. package/dist/boards/boards.d.ts +44 -0
  70. package/dist/boards/boards.js +447 -0
  71. package/dist/boards/boards.types.d.ts +117 -0
  72. package/dist/boards/boards.types.js +1 -0
  73. package/dist/build/build.d.ts +2 -0
  74. package/dist/build/build.js +192 -0
  75. package/dist/build/build.types.d.ts +17 -0
  76. package/dist/build/build.types.js +1 -0
  77. package/dist/bundle/bundle.d.ts +4 -0
  78. package/dist/bundle/bundle.js +1194 -0
  79. package/dist/bundle/bundle.types.d.ts +33 -0
  80. package/dist/bundle/bundle.types.js +1 -0
  81. package/dist/cli/cli.d.ts +4 -0
  82. package/dist/cli/cli.js +262 -0
  83. package/dist/cli/cli.types.d.ts +14 -0
  84. package/dist/cli/cli.types.js +1 -0
  85. package/dist/contract/audit.d.ts +50 -0
  86. package/dist/contract/audit.js +72 -0
  87. package/dist/contract/board.d.ts +150 -0
  88. package/dist/contract/board.js +262 -0
  89. package/dist/contract/bundle.d.ts +147 -0
  90. package/dist/contract/bundle.js +139 -0
  91. package/dist/contract/contract.d.ts +112 -0
  92. package/dist/contract/contract.js +143 -0
  93. package/dist/contract/feed.d.ts +126 -0
  94. package/dist/contract/feed.js +149 -0
  95. package/dist/contract/flow-run.d.ts +346 -0
  96. package/dist/contract/flow-run.js +194 -0
  97. package/dist/contract/flow.d.ts +1057 -0
  98. package/dist/contract/flow.js +552 -0
  99. package/dist/contract/node.d.ts +551 -0
  100. package/dist/contract/node.js +456 -0
  101. package/dist/contract/share.d.ts +216 -0
  102. package/dist/contract/share.js +134 -0
  103. package/dist/contract/table.d.ts +171 -0
  104. package/dist/contract/table.js +117 -0
  105. package/dist/contract/tool.d.ts +122 -0
  106. package/dist/contract/tool.js +172 -0
  107. package/dist/feed/feed.d.ts +2 -0
  108. package/dist/feed/feed.js +77 -0
  109. package/dist/feed/feed.types.d.ts +23 -0
  110. package/dist/feed/feed.types.js +1 -0
  111. package/dist/flows/flows.d.ts +41 -0
  112. package/dist/flows/flows.js +2216 -0
  113. package/dist/flows/flows.types.d.ts +339 -0
  114. package/dist/flows/flows.types.js +1 -0
  115. package/dist/http/http.d.ts +9 -0
  116. package/dist/http/http.js +874 -0
  117. package/dist/http/http.types.d.ts +22 -0
  118. package/dist/http/http.types.js +1 -0
  119. package/dist/indexing/indexing.d.ts +6 -0
  120. package/dist/indexing/indexing.js +239 -0
  121. package/dist/indexing/indexing.types.d.ts +11 -0
  122. package/dist/indexing/indexing.types.js +1 -0
  123. package/dist/intel/intel.d.ts +3 -0
  124. package/dist/intel/intel.js +194 -0
  125. package/dist/intel/intel.types.d.ts +24 -0
  126. package/dist/intel/intel.types.js +1 -0
  127. package/dist/mcp/mcp.d.ts +2 -0
  128. package/dist/mcp/mcp.js +1292 -0
  129. package/dist/mcp/mcp.types.d.ts +25 -0
  130. package/dist/mcp/mcp.types.js +1 -0
  131. package/dist/nodes/document-links/document-links.d.ts +20 -0
  132. package/dist/nodes/document-links/document-links.js +57 -0
  133. package/dist/nodes/nodes.d.ts +2 -0
  134. package/dist/nodes/nodes.js +1667 -0
  135. package/dist/nodes/nodes.types.d.ts +511 -0
  136. package/dist/nodes/nodes.types.js +1 -0
  137. package/dist/prepare/prepare.d.ts +4 -0
  138. package/dist/prepare/prepare.js +16 -0
  139. package/dist/prepare/prepare.types.d.ts +9 -0
  140. package/dist/prepare/prepare.types.js +1 -0
  141. package/dist/prompts/prompts.d.ts +2 -0
  142. package/dist/prompts/prompts.js +65 -0
  143. package/dist/prompts/prompts.types.d.ts +71 -0
  144. package/dist/prompts/prompts.types.js +1 -0
  145. package/dist/shared/callers/callers.d.ts +28 -0
  146. package/dist/shared/callers/callers.js +30 -0
  147. package/dist/shared/csv/csv.d.ts +13 -0
  148. package/dist/shared/csv/csv.js +85 -0
  149. package/dist/shared/document-text/document-text.d.ts +21 -0
  150. package/dist/shared/document-text/document-text.js +31 -0
  151. package/dist/shared/gate-authorization/gate-authorization.d.ts +5 -0
  152. package/dist/shared/gate-authorization/gate-authorization.js +26 -0
  153. package/dist/shared/grant-expiry/grant-expiry.d.ts +19 -0
  154. package/dist/shared/grant-expiry/grant-expiry.js +26 -0
  155. package/dist/shared/intel-error/intel-error.d.ts +22 -0
  156. package/dist/shared/intel-error/intel-error.js +27 -0
  157. package/dist/shared/plain-title/plain-title.d.ts +16 -0
  158. package/dist/shared/plain-title/plain-title.js +45 -0
  159. package/dist/shared/problem-details/problem-details.d.ts +30 -0
  160. package/dist/shared/problem-details/problem-details.js +18 -0
  161. package/dist/shared/report-unexpected-error/report-unexpected-error.d.ts +1 -0
  162. package/dist/shared/report-unexpected-error/report-unexpected-error.js +37 -0
  163. package/dist/shared/safe-return-path/safe-return-path.d.ts +2 -0
  164. package/dist/shared/safe-return-path/safe-return-path.js +11 -0
  165. package/dist/shared/sha256/sha256.d.ts +2 -0
  166. package/dist/shared/sha256/sha256.js +14 -0
  167. package/dist/tools/tool-servers/tool-servers.d.ts +46 -0
  168. package/dist/tools/tool-servers/tool-servers.js +177 -0
  169. package/dist/tools/tools.d.ts +2 -0
  170. package/dist/tools/tools.js +207 -0
  171. package/dist/tools/tools.types.d.ts +56 -0
  172. package/dist/tools/tools.types.js +1 -0
  173. package/examples/branding/favicon.svg +5 -0
  174. package/examples/branding/logo.svg +5 -0
  175. package/examples/branding/theme.css +16 -0
  176. package/examples/dev.vars.example +7 -0
  177. package/examples/intel.json +9 -0
  178. package/migrations/0000_intel_foundation.sql +203 -0
  179. package/migrations/0001_portal_native_tools.sql +21 -0
  180. package/migrations/0002_flows_in_the_knowledge_tree.sql +34 -0
  181. package/migrations/0003_folder_permissions.sql +211 -0
  182. package/migrations/0004_subflow_runs.sql +14 -0
  183. package/migrations/0005_flow_node_cleanup.sql +28 -0
  184. package/migrations/0005_tables_in_the_knowledge_tree.sql +68 -0
  185. package/migrations/0006_links_are_written_in_the_text.sql +20 -0
  186. package/migrations/0007_no_node_waits.sql +69 -0
  187. package/migrations/0008_three_layers.sql +130 -0
  188. package/migrations/0009_no_context_policy.sql +48 -0
  189. package/migrations/0010_no_run_waits_a_year.sql +27 -0
  190. package/migrations/0011_one_name_for_the_tree.sql +53 -0
  191. package/migrations/0012_table_snapshots.sql +29 -0
  192. package/migrations/0017_a_vector_per_card.sql +38 -0
  193. package/migrations/0018_no_context_policy_at_last.sql +97 -0
  194. package/migrations/0019_one_name_for_the_grants.sql +52 -0
  195. package/migrations/0020_cascade_purge_replay.sql +11 -0
  196. package/migrations/0021_a_flow_carries_its_own_grant.sql +44 -0
  197. package/migrations/0022_a_cursor_over_the_journal.sql +14 -0
  198. package/migrations/0023_a_board_and_its_tasks.sql +116 -0
  199. package/migrations/0024_the_archive_is_a_column.sql +33 -0
  200. package/migrations/0025_archiving_takes_the_subtree.sql +29 -0
  201. package/migrations/0026_one_feed_over_two_kinds.sql +23 -0
  202. package/migrations/0027_the_runs_of_every_flow.sql +27 -0
  203. package/migrations/0028_a_prompt_name_over_two_kinds.sql +70 -0
  204. package/package.json +118 -0
  205. package/src/contract/CLAUDE.md +152 -0
  206. package/src/contract/README.md +102 -0
  207. package/src/contract/audit.ts +85 -0
  208. package/src/contract/board.ts +306 -0
  209. package/src/contract/bundle.ts +157 -0
  210. package/src/contract/contract.ts +161 -0
  211. package/src/contract/feed.ts +165 -0
  212. package/src/contract/flow-run.ts +241 -0
  213. package/src/contract/flow.ts +675 -0
  214. package/src/contract/node.ts +583 -0
  215. package/src/contract/share.ts +196 -0
  216. package/src/contract/table.ts +178 -0
  217. package/src/contract/tool.ts +202 -0
  218. package/ui/README.md +110 -0
  219. package/ui/components.json +21 -0
  220. package/ui/index.html +13 -0
  221. package/ui/package.json +11 -0
  222. package/ui/scripts/lint-tokens.mjs +187 -0
  223. package/ui/src/access-summary/access-summary.tsx +385 -0
  224. package/ui/src/app/action-slot/action-slot.tsx +27 -0
  225. package/ui/src/app/app-sidebar/app-sidebar.tsx +71 -0
  226. package/ui/src/app/app-tree/app-tree.tsx +1175 -0
  227. package/ui/src/app/app.tsx +87 -0
  228. package/ui/src/app/header-search/header-search.tsx +294 -0
  229. package/ui/src/app/reindex-dialog/reindex-dialog.tsx +79 -0
  230. package/ui/src/app/settings-dialog/settings-dialog.tsx +135 -0
  231. package/ui/src/app/sidebar-preferences/sidebar-preferences.ts +68 -0
  232. package/ui/src/app/sidebar-preferences/sidebar-preferences.types.ts +15 -0
  233. package/ui/src/app/sidebar-resize-handle/sidebar-resize-handle.tsx +86 -0
  234. package/ui/src/app/tree-expansion/tree-expansion.ts +107 -0
  235. package/ui/src/app/tree-expansion/tree-expansion.types.ts +31 -0
  236. package/ui/src/app/tree-move/tree-move.tsx +427 -0
  237. package/ui/src/app/tree-reveal/tree-reveal.ts +64 -0
  238. package/ui/src/app/tree-reveal/tree-reveal.types.ts +16 -0
  239. package/ui/src/app/user-footer/user-footer.tsx +188 -0
  240. package/ui/src/app/view-menu/view-menu.tsx +115 -0
  241. package/ui/src/app-root/app-root.tsx +109 -0
  242. package/ui/src/app-root/app-root.types.ts +27 -0
  243. package/ui/src/archive/archive.tsx +417 -0
  244. package/ui/src/attachment-detail/attachment-detail.ts +52 -0
  245. package/ui/src/attachment-viewer/attachment-viewer.tsx +166 -0
  246. package/ui/src/blocknote-view/blocknote-view.tsx +30 -0
  247. package/ui/src/board/board-assignee/board-assignee-picker.tsx +132 -0
  248. package/ui/src/board/board-assignee/board-assignee.ts +48 -0
  249. package/ui/src/board/board-assignee/board-assignee.tsx +75 -0
  250. package/ui/src/board/board-chip/board-chip.tsx +46 -0
  251. package/ui/src/board/board-crumbs/board-crumbs.ts +56 -0
  252. package/ui/src/board/board-crumbs/board-crumbs.tsx +111 -0
  253. package/ui/src/board/board-data/board-data.ts +195 -0
  254. package/ui/src/board/board-data/board-data.types.ts +38 -0
  255. package/ui/src/board/board-dates/board-dates.ts +40 -0
  256. package/ui/src/board/board-dates/board-dates.tsx +105 -0
  257. package/ui/src/board/board-kanban/board-kanban.ts +162 -0
  258. package/ui/src/board/board-kanban/board-kanban.tsx +680 -0
  259. package/ui/src/board/board-open-task/board-open-task.ts +32 -0
  260. package/ui/src/board/board-panel/board-panel.tsx +259 -0
  261. package/ui/src/board/board-settings/board-settings.tsx +226 -0
  262. package/ui/src/board/board-status/board-status.tsx +60 -0
  263. package/ui/src/board/board-stripes/board-stripes.ts +128 -0
  264. package/ui/src/board/board-table/board-table.ts +53 -0
  265. package/ui/src/board/board-table/board-table.tsx +610 -0
  266. package/ui/src/board/board-task/board-task.ts +108 -0
  267. package/ui/src/board/board-task/board-task.tsx +335 -0
  268. package/ui/src/board/board-title-row/board-title-row.tsx +66 -0
  269. package/ui/src/branding/branding.tsx +17 -0
  270. package/ui/src/branding/custom-logo.ts +1 -0
  271. package/ui/src/branding/favicon.default.svg +5 -0
  272. package/ui/src/branding/favicon.svg +5 -0
  273. package/ui/src/components/ui/avatar.tsx +39 -0
  274. package/ui/src/components/ui/button.tsx +64 -0
  275. package/ui/src/components/ui/collapsible.tsx +20 -0
  276. package/ui/src/components/ui/command.tsx +160 -0
  277. package/ui/src/components/ui/dialog.tsx +143 -0
  278. package/ui/src/components/ui/dropdown-menu.tsx +187 -0
  279. package/ui/src/components/ui/input.tsx +21 -0
  280. package/ui/src/components/ui/popover.tsx +41 -0
  281. package/ui/src/components/ui/select.tsx +163 -0
  282. package/ui/src/components/ui/separator.tsx +26 -0
  283. package/ui/src/components/ui/sheet.tsx +136 -0
  284. package/ui/src/components/ui/sidebar.tsx +693 -0
  285. package/ui/src/components/ui/skeleton.tsx +13 -0
  286. package/ui/src/components/ui/switch.tsx +25 -0
  287. package/ui/src/components/ui/table.tsx +82 -0
  288. package/ui/src/components/ui/tabs.tsx +52 -0
  289. package/ui/src/components/ui/tooltip.tsx +51 -0
  290. package/ui/src/data/intel-data-provider/intel-data-provider.ts +685 -0
  291. package/ui/src/data/intel-data-provider/intel-data-provider.types.ts +266 -0
  292. package/ui/src/data/query-client.ts +18 -0
  293. package/ui/src/data/request-refusal/refusal-notice.tsx +36 -0
  294. package/ui/src/data/request-refusal/request-refusal.ts +51 -0
  295. package/ui/src/data/sign-in/sign-in.ts +97 -0
  296. package/ui/src/data/sign-in/sign-in.types.ts +15 -0
  297. package/ui/src/document-link/document-link.tsx +114 -0
  298. package/ui/src/document-markdown/document-markdown.ts +195 -0
  299. package/ui/src/editor-schema/editor-schema.ts +23 -0
  300. package/ui/src/entry-picker/entry-picker.tsx +238 -0
  301. package/ui/src/feed/feed.tsx +344 -0
  302. package/ui/src/file-preview/file-preview-view.tsx +496 -0
  303. package/ui/src/file-preview/file-preview.tsx +28 -0
  304. package/ui/src/file-preview/pdf-file-preview.tsx +5 -0
  305. package/ui/src/file-preview/presentation-file-preview.tsx +21 -0
  306. package/ui/src/file-preview/spreadsheet-file-preview.tsx +5 -0
  307. package/ui/src/file-preview/word-file-preview.tsx +5 -0
  308. package/ui/src/flow-runs/flow-runs.tsx +238 -0
  309. package/ui/src/flows/flows.tsx +1646 -0
  310. package/ui/src/flows/node-icon/node-icon.ts +34 -0
  311. package/ui/src/flows/node-palette/node-palette.tsx +214 -0
  312. package/ui/src/flows/node-palette/node-palette.types.ts +15 -0
  313. package/ui/src/folder-contents/folder-contents.tsx +321 -0
  314. package/ui/src/folder-index/folder-index.ts +55 -0
  315. package/ui/src/frontmatter/frontmatter.tsx +193 -0
  316. package/ui/src/graph-pane/graph-pane.tsx +53 -0
  317. package/ui/src/hooks/use-mobile.ts +19 -0
  318. package/ui/src/i18n/custom.json +4 -0
  319. package/ui/src/i18n/de.json +609 -0
  320. package/ui/src/i18n/en.json +609 -0
  321. package/ui/src/i18n/es.json +609 -0
  322. package/ui/src/i18n/i18n-context.tsx +65 -0
  323. package/ui/src/i18n/i18n-languages/i18n-languages.ts +83 -0
  324. package/ui/src/i18n/i18n.ts +32 -0
  325. package/ui/src/i18n/i18n.types.ts +49 -0
  326. package/ui/src/kind-icon.ts +97 -0
  327. package/ui/src/lib/utils.ts +6 -0
  328. package/ui/src/main.tsx +68 -0
  329. package/ui/src/modal/modal.tsx +53 -0
  330. package/ui/src/node-details/node-details.tsx +545 -0
  331. package/ui/src/node-editor/node-editor.tsx +304 -0
  332. package/ui/src/node-graph/graph-notice.tsx +21 -0
  333. package/ui/src/node-graph/node-graph.ts +214 -0
  334. package/ui/src/node-graph/node-graph.tsx +295 -0
  335. package/ui/src/node-import/node-import.tsx +418 -0
  336. package/ui/src/node-save-error.ts +46 -0
  337. package/ui/src/node-source/node-source.tsx +128 -0
  338. package/ui/src/node-table/node-table.tsx +107 -0
  339. package/ui/src/nodes/nodes.tsx +240 -0
  340. package/ui/src/rename-resource/rename-resource.ts +80 -0
  341. package/ui/src/resolved-names/resolved-names.tsx +67 -0
  342. package/ui/src/resource-error.ts +33 -0
  343. package/ui/src/resource-menu/resource-menu.tsx +1156 -0
  344. package/ui/src/router/router-context.ts +6 -0
  345. package/ui/src/router/router.tsx +67 -0
  346. package/ui/src/router/router.types.ts +8 -0
  347. package/ui/src/router/selection-search.ts +88 -0
  348. package/ui/src/save-button/save-button.tsx +94 -0
  349. package/ui/src/section-hint/section-hint.tsx +40 -0
  350. package/ui/src/sign-in-refused/sign-in-refused.tsx +35 -0
  351. package/ui/src/styles.css +189 -0
  352. package/ui/src/table-columns/table-columns.tsx +379 -0
  353. package/ui/src/theme/custom.css +1 -0
  354. package/ui/src/theme/theme-context.tsx +89 -0
  355. package/ui/src/theme/theme.ts +70 -0
  356. package/ui/src/time/relative-time.tsx +41 -0
  357. package/ui/src/time/time-context.tsx +78 -0
  358. package/ui/src/time/time.ts +165 -0
  359. package/ui/src/title-row/title-row.tsx +185 -0
  360. package/ui/src/tools/tools.tsx +546 -0
  361. package/ui/src/user-name/user-name.ts +61 -0
  362. package/ui/src/verbatim/verbatim.tsx +68 -0
  363. package/ui/tsconfig.json +17 -0
  364. package/ui/vite.config.ts +129 -0
@@ -0,0 +1,116 @@
1
+ -- Two more node kinds, `board` and `task`, and the two side tables that carry what a node row must
2
+ -- not (D66, anchrd/intel#376).
3
+ --
4
+ -- ⚠️ BOTH words in ONE rebuild. Adding them in two migrations would be the same risky operation
5
+ -- twice, and this one has gone wrong before: `0005` committed with every row of `node_links` gone.
6
+ --
7
+ -- The split this migration encodes: IDENTITY through `kind`, FIELDS through a side table.
8
+ -- * `kind` because the tree has to hide `task` on EVERY level query. As a kind that is a condition
9
+ -- on a column the query already carries; as a `document` plus a side-table row it would be a
10
+ -- join on the hottest path in the interface.
11
+ -- * the side table because `status`, `assignee` and the dates are FILTER columns. In the `nodes`
12
+ -- row they would stand empty for every other kind.
13
+ --
14
+ -- The recipe below is the one in `packages/intel/CLAUDE.md`, *Rebuilding `nodes` is a recipe*. All
15
+ -- four points, and each is one a previous migration got wrong first:
16
+ -- 1. the new table is created under its FINAL name — never `ALTER TABLE … RENAME`, which rewrites
17
+ -- every `REFERENCES` clause and drags the children onto the table about to be dropped;
18
+ -- 2. `node_links` is carried out and back with `INSERT OR IGNORE`, because it is the one child
19
+ -- declared `ON DELETE CASCADE` and `DROP TABLE nodes` fires an implicit `DELETE FROM`;
20
+ -- 3. `PRAGMA defer_foreign_keys`, never `foreign_keys = OFF` — D1 ignores the latter over its HTTP
21
+ -- API while miniflare honours it, so a green test here would prove nothing about production;
22
+ -- 4. the columns are named on BOTH sides of `INSERT … SELECT`, so a future reordering cannot turn
23
+ -- every title into an owner id and still commit.
24
+ PRAGMA defer_foreign_keys = TRUE;
25
+
26
+ CREATE TABLE nodes_carry AS SELECT * FROM nodes;
27
+ CREATE TABLE node_links_carry AS SELECT * FROM node_links;
28
+
29
+ DROP TABLE nodes;
30
+
31
+ CREATE TABLE nodes (
32
+ id TEXT PRIMARY KEY NOT NULL,
33
+ parent_id TEXT REFERENCES nodes(id),
34
+ kind TEXT NOT NULL CHECK (kind IN ('folder', 'document', 'attachment', 'table', 'board', 'task')),
35
+ title TEXT NOT NULL CHECK (length(title) BETWEEN 1 AND 240),
36
+ description TEXT CHECK (description IS NULL OR length(description) <= 2000),
37
+ owner_id TEXT NOT NULL,
38
+ current_version_id TEXT,
39
+ created_at TEXT NOT NULL,
40
+ updated_at TEXT NOT NULL,
41
+ archived_at TEXT
42
+ );
43
+
44
+ INSERT INTO nodes (
45
+ id, parent_id, kind, title, description, owner_id,
46
+ current_version_id, created_at, updated_at, archived_at
47
+ )
48
+ SELECT
49
+ id, parent_id, kind, title, description, owner_id,
50
+ current_version_id, created_at, updated_at, archived_at
51
+ FROM nodes_carry;
52
+
53
+ INSERT OR IGNORE INTO node_links SELECT * FROM node_links_carry;
54
+
55
+ DROP TABLE nodes_carry;
56
+ DROP TABLE node_links_carry;
57
+
58
+ CREATE INDEX nodes_parent_idx ON nodes(parent_id, archived_at, title);
59
+ CREATE INDEX nodes_owner_idx ON nodes(owner_id, archived_at);
60
+
61
+ -- The board's own configuration: which columns it has and in what order. It is one row per board
62
+ -- node, and it exists because a list of statuses is not a node property — every other kind would
63
+ -- carry it empty.
64
+ --
65
+ -- ⚠️ `ON DELETE CASCADE` here and on `board_tasks` below, and that is deliberate in a repository
66
+ -- whose rule is *refuse rather than cascade*. The rule protects a CONTAINER from taking its contents
67
+ -- down with it; these two rows are not contents but the node's own fields, split off only because
68
+ -- SQLite has no per-kind columns. A board whose row here outlived it would be a configuration for a
69
+ -- board that does not exist.
70
+ CREATE TABLE boards (
71
+ node_id TEXT PRIMARY KEY NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
72
+ statuses_json TEXT NOT NULL DEFAULT '[]',
73
+ created_at TEXT NOT NULL,
74
+ updated_at TEXT NOT NULL
75
+ );
76
+
77
+ -- What a task carries beyond a node. Every column here is one somebody filters or sorts by — that
78
+ -- is the whole reason they are not in the body.
79
+ --
80
+ -- ⚠️ `position` is REAL, not INTEGER. Dragging a card between two neighbours then needs no rewrite
81
+ -- of the whole column: the new value is the midpoint of the two. With integers every drop would
82
+ -- renumber the rows below it, and two people dropping at once would fight over rows neither touched.
83
+ CREATE TABLE board_tasks (
84
+ node_id TEXT PRIMARY KEY NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
85
+ board_id TEXT NOT NULL REFERENCES nodes(id),
86
+ status TEXT NOT NULL,
87
+ assignee_id TEXT,
88
+ labels_json TEXT NOT NULL DEFAULT '[]',
89
+ start_date TEXT,
90
+ due_date TEXT,
91
+ -- "waits for", as a COLUMN and not as a sentence in the body. The DoD of #376 says it in one
92
+ -- line: relations are columns the text points at, never the other way round. A dependency living
93
+ -- in prose cannot be asked "what is blocked right now", and `node_links` cannot answer it either
94
+ -- — that table says two nodes are related, not that one waits for the other.
95
+ --
96
+ -- ⚠️ It points at `nodes`, not at `board_tasks`. A task may wait for something that is not a
97
+ -- task: a document that has to be approved first, a board that has to be finished. Narrowing it
98
+ -- to the side table would decide today that only tasks can block, and nothing asked for that.
99
+ depends_on TEXT REFERENCES nodes(id),
100
+ position REAL NOT NULL DEFAULT 0,
101
+ created_at TEXT NOT NULL,
102
+ updated_at TEXT NOT NULL
103
+ );
104
+
105
+ -- The board view reads one column at a time, ordered. Both columns of the sort are in the index, so
106
+ -- the read needs no sorting pass — the same reason `audit_events_feed_idx` carries its tie-break
107
+ -- (anchrd/intel#620).
108
+ CREATE INDEX board_tasks_column_idx ON board_tasks(board_id, status, position);
109
+
110
+ -- "What is assigned to me, soonest first" — across every board, which is why `assignee_id` leads
111
+ -- and `board_id` does not appear.
112
+ CREATE INDEX board_tasks_assignee_idx ON board_tasks(assignee_id, due_date);
113
+
114
+ -- "What is blocked by this node" — the direction the interface asks in. Without it the question is
115
+ -- a scan of every task on every board.
116
+ CREATE INDEX board_tasks_depends_idx ON board_tasks(depends_on) WHERE depends_on IS NOT NULL;
@@ -0,0 +1,33 @@
1
+ -- The archive as a column every board has (D68, #674).
2
+ --
3
+ -- ⚠️ Only the VISIBILITY is stored. Which cards are in the archive is `nodes.archived_at` and
4
+ -- nothing else — a second column holding "is archived" would be the two truths D68 exists against.
5
+ -- A card is in the archive column BECAUSE it is archived; dropping it there archives it, and
6
+ -- pulling it out restores it into the working column `board_tasks.status` still names.
7
+ --
8
+ -- ⚠️ Default 1: the column is there on every board unless somebody hides it. A board that predates
9
+ -- this migration therefore shows it too, which is the decided behaviour rather than an accident —
10
+ -- an archive nobody can see is an archive nobody empties.
11
+ ALTER TABLE boards ADD COLUMN archive_visible INTEGER NOT NULL DEFAULT 1;
12
+
13
+ -- ⚠️ **Existing data.** Until this migration `BoardColumn.id` was a free string: a board could
14
+ -- configure a column called `archived` itself. From here the name is reserved, and without this
15
+ -- step such a board would show TWO columns with one id — the stored one and the derived one — and
16
+ -- the cards of both would be indistinguishable.
17
+ --
18
+ -- ⚠️ **Every row with that status, not only the ones whose board still configures it.** Columns are
19
+ -- REPLACED rather than merged, so a board may have used the column and dropped it again; those
20
+ -- cards still carry `status = 'archived'` while `statuses_json` has long forgotten it. Left alone
21
+ -- they would surface in the archive column with `archived_at` still `null` — a card that looks
22
+ -- archived, is not, and cannot be restored, because there is nothing to restore.
23
+ --
24
+ -- The new name is deliberately ugly, because it is meant to be seen: whoever reads it knows
25
+ -- something was renamed here and can put it right in the settings.
26
+ UPDATE board_tasks SET status = 'archived_column_renamed' WHERE status = 'archived';
27
+
28
+ UPDATE boards
29
+ SET statuses_json = replace(statuses_json, '"id":"archived"', '"id":"archived_column_renamed"')
30
+ WHERE node_id IN (
31
+ SELECT b.node_id FROM boards b, json_each(b.statuses_json)
32
+ WHERE json_extract(json_each.value, '$.id') = 'archived'
33
+ );
@@ -0,0 +1,29 @@
1
+ -- Archiving takes the whole subtree, and this column is what lets it come back (D71, #733).
2
+ --
3
+ -- ⚠️ **The two steps were the wrong way round.** `purge` cascaded over the subtree since #492 while
4
+ -- `archive` touched a single row, so a folder archived over live children handed those children to
5
+ -- the one step that cannot be undone. Cascading the REVERSIBLE step instead closes the whole chain:
6
+ -- nothing live sits under an archived node any more, so the purge can keep cascading without ever
7
+ -- reaching something alive.
8
+ --
9
+ -- What this column holds is the OPERATION a node fell with, not a flag. A restore brings back
10
+ -- exactly the nodes that fell together, and a child that was already archived before keeps its own
11
+ -- older operation id — and stays archived.
12
+ --
13
+ -- ⚠️ **Existing data keeps `NULL` here**, and that is not the same as "fell alone": it means
14
+ -- "archived before this migration, and nobody recorded what it fell with". A restore therefore
15
+ -- treats `NULL` as itself only, never as a group — otherwise restoring one old node would revive
16
+ -- every node ever archived before today.
17
+ ALTER TABLE nodes ADD COLUMN archived_with TEXT;
18
+
19
+ -- The restore reads this column for one node and then finds its siblings by it. Without the index
20
+ -- that is a scan of the whole table on every restore of a large folder.
21
+ CREATE INDEX IF NOT EXISTS nodes_archived_with ON nodes (archived_with) WHERE archived_with IS NOT NULL;
22
+
23
+ -- ⚠️ **Flows fall with the tree, so they need the same column.** They hang in the folder tree by
24
+ -- `parent_id` and carry their own `archived_at`, and `inspectPurgeTree` collects them by that same
25
+ -- parent. A cascade that took only `nodes` would leave exactly one kind of live thing under an
26
+ -- archived folder — the kind this repository's own flows are filed as.
27
+ ALTER TABLE flows ADD COLUMN archived_with TEXT;
28
+
29
+ CREATE INDEX IF NOT EXISTS flows_archived_with ON flows (archived_with) WHERE archived_with IS NOT NULL;
@@ -0,0 +1,23 @@
1
+ -- The feed reads both kinds at once — nodes and flows — and `audit_events_feed_idx` cannot serve
2
+ -- that. It leads with `resource_type` because every reader until now named exactly one kind, so for
3
+ -- a query saying `resource_type IN ('node', 'flow')` that column is no longer an equality prefix but
4
+ -- an ordinary filter, and the ordering falls off the index.
5
+ --
6
+ -- ⚠️ Measured against the real `feedPageQuery` before this file existed, not assumed
7
+ -- (anchrd/intel#763):
8
+ --
9
+ -- SEARCH e USING INDEX audit_events_feed_idx (resource_type=?)
10
+ -- … | USE TEMP B-TREE FOR ORDER BY
11
+ --
12
+ -- That last line is a sorting pass over the whole table. It costs nothing on the first page and
13
+ -- everything far down — which is exactly where an endless scroll goes.
14
+ --
15
+ -- ⚠️ Both columns, in the order the cursor compares them, for the reason `0022` gives: two events
16
+ -- written in the same millisecond are the normal case inside one batch, and without the tie-break
17
+ -- on `id` a reader skips the second one with no error and no log.
18
+ --
19
+ -- ⚠️ This does NOT replace `audit_events_feed_idx`. That one still serves `audit_list`, which names
20
+ -- one kind and is the contract `anchrd/signals` builds on; dropping it would put a sorting pass into
21
+ -- the reader that has none today. Two readers, two shapes, two indexes.
22
+ CREATE INDEX audit_events_time_idx
23
+ ON audit_events(occurred_at, id);
@@ -0,0 +1,27 @@
1
+ -- `flow_run_list` may now be asked without a flow (#774): "did anything run at all", over every
2
+ -- flow the caller may read. `flow_runs_flow_idx (flow_id, created_at DESC)` cannot serve that — it
3
+ -- leads with `flow_id`, and without an equality on that column the ordering falls off the index and
4
+ -- SQLite sorts the whole table instead.
5
+ --
6
+ -- ⚠️ Measured against the real `flowRunsPageQuery({ scoped: false })` before this file existed, on
7
+ -- the same engine and the same schema:
8
+ --
9
+ -- SCAN run
10
+ -- … | USE TEMP B-TREE FOR ORDER BY
11
+ --
12
+ -- That last line is a sorting pass over every run in the installation. It costs nothing while a
13
+ -- customer has fifty runs and everything once an agent has been running for a month.
14
+ --
15
+ -- ⚠️ Both columns, in the order the cursor compares them, for the reason `0022` and `0026` give:
16
+ -- two runs created in the same millisecond are not exotic, and without the tie-break on `id` a
17
+ -- reader continuing on `created_at` alone skips the second one with no error and no log.
18
+ --
19
+ -- ⚠️ `DESC` on both, because this page is newest-first. An ASC index can be walked backwards by
20
+ -- SQLite, but only as long as EVERY term agrees on the direction — matching the `ORDER BY` exactly
21
+ -- is the one shape that keeps that true when a third term is added later.
22
+ --
23
+ -- ⚠️ This does NOT replace `flow_runs_flow_idx`. That one still serves the scoped page — the runs
24
+ -- of ONE flow — which is the older and by far the more frequent question; dropping it would put a
25
+ -- full scan into the call that has none today. Two questions, two shapes, two indexes.
26
+ CREATE INDEX flow_runs_time_idx
27
+ ON flow_runs(created_at DESC, id DESC);
@@ -0,0 +1,70 @@
1
+ -- A document and a published flow can both be offered as a slash command (#775). What they need is
2
+ -- one short name, and the name has to be unique across BOTH of them: they land in the same
3
+ -- `prompts/list` catalogue, so a document and a flow can take each other's name.
4
+ --
5
+ -- ⚠️ **One field, not two.** `prompt_name` set means offered, `NULL` means not. A boolean beside it
6
+ -- would allow the state "on, without a name", and that is not a thing `prompts/list` could answer.
7
+ --
8
+ -- ⚠️ **No third table, and the choice is deliberate.** A `prompt_names(name PRIMARY KEY, …)`
9
+ -- catalogue would carry the cross-kind uniqueness as a real constraint, and it would cost two
10
+ -- things this schema is not willing to pay: a second truth about the name of one node — every
11
+ -- `SELECT` over `nodes` would have to remember a join, and the one that forgets it answers `NULL`
12
+ -- in silence — and a fourth child table for the `nodes` rebuild recipe to carry (see the package
13
+ -- CLAUDE.md; `node_links` is the one that already has to be carried out and back). The uniqueness
14
+ -- WITHIN one kind is an index here; the uniqueness ACROSS the two is a condition on the write, on
15
+ -- every statement that sets a name, in the same batch that writes it.
16
+ --
17
+ -- ⚠️ The condition on the write is not a nicety. A `SELECT` before an `UPDATE` in a separate call
18
+ -- decides on a state that may be gone by the time the write lands; the check has to be part of the
19
+ -- statement so that the two cannot come apart. What the caller is told — WHICH node or flow holds
20
+ -- the name — is a second, cheap read that only runs once the write has already refused.
21
+
22
+ -- ⚠️ **This form is MEASURED against real D1, not only against miniflare.** No other file in this
23
+ -- folder adds a `CHECK` through `ALTER TABLE … ADD COLUMN` that reads a DIFFERENT column, and the
24
+ -- package CLAUDE.md is explicit that a green run under the emulator proves nothing about the
25
+ -- deployment — `foreign_keys = OFF` is honoured there and ignored by D1 over its HTTP API. A
26
+ -- migration file is immutable after release, so the failure would surface at the deploy.
27
+ --
28
+ -- Run against a throwaway D1 (`intel-check-0028`, `--remote`) on 2026-08-25 and then deleted. Both
29
+ -- statements applied, `sqlite_master` shows the CHECK on the column, and all six refusals fire with
30
+ -- `SQLITE_CONSTRAINT_CHECK` / `SQLITE_CONSTRAINT_UNIQUE`: a folder and a table with a name, an
31
+ -- uppercase name, a name with a space, a 41-character name, and a second row taking a name that is
32
+ -- already held. A document with a valid name and two rows with none go through.
33
+ --
34
+ -- ⚠️ `kind = 'document'` is in the CHECK, not only in the service. A prompt is an instruction, not
35
+ -- material: a folder, a table, an attachment, a board or a task never carries one (#775). The
36
+ -- service refuses it with a sentence, and this line is what makes that refusal true even for a
37
+ -- write that never passes the service.
38
+ --
39
+ -- ⚠️ `NOT GLOB '*[^a-z0-9-]*'` is the grammar `^[a-z0-9-]{1,40}$` says on the contract, spelled the
40
+ -- one way SQLite can spell it: "no character outside the class anywhere". The obvious
41
+ -- `GLOB '[a-z0-9-]*'` is NOT that check — it constrains the first character and lets every other
42
+ -- one through, so `a_B C` passes it. The hyphen sits last inside the class, where it is a literal
43
+ -- rather than a range.
44
+ ALTER TABLE nodes ADD COLUMN prompt_name TEXT
45
+ CHECK (
46
+ prompt_name IS NULL
47
+ OR (
48
+ kind = 'document'
49
+ AND length(prompt_name) BETWEEN 1 AND 40
50
+ AND prompt_name NOT GLOB '*[^a-z0-9-]*'
51
+ )
52
+ );
53
+
54
+ ALTER TABLE flows ADD COLUMN prompt_name TEXT
55
+ CHECK (
56
+ prompt_name IS NULL
57
+ OR (length(prompt_name) BETWEEN 1 AND 40 AND prompt_name NOT GLOB '*[^a-z0-9-]*')
58
+ );
59
+
60
+ -- ⚠️ Partial, and that is the whole reason they work. A plain `UNIQUE` index would let several
61
+ -- rows carry `NULL` too — SQLite treats NULLs as distinct — so the `WHERE` clause buys no
62
+ -- correctness here; it buys the SIZE. Almost every node has no prompt name, and an index over
63
+ -- them all would be one entry per node in the tree to serve a handful of rows.
64
+ --
65
+ -- ⚠️ These two carry the uniqueness WITHIN a kind and nothing more. Two documents cannot share a
66
+ -- name and two flows cannot; a document and a flow can, as far as these indexes are concerned, and
67
+ -- the condition on the write is the only thing that stops them. Reading one of these index names in
68
+ -- a query plan is therefore not evidence that the cross-kind rule held.
69
+ CREATE UNIQUE INDEX nodes_prompt_name_idx ON nodes(prompt_name) WHERE prompt_name IS NOT NULL;
70
+ CREATE UNIQUE INDEX flows_prompt_name_idx ON flows(prompt_name) WHERE prompt_name IS NOT NULL;
package/package.json ADDED
@@ -0,0 +1,118 @@
1
+ {
2
+ "name": "@anchrd/intel",
3
+ "version": "0.59.0",
4
+ "type": "module",
5
+ "license": "UNLICENSED",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/anchrd/intel.git",
9
+ "directory": "packages/intel"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "bin": {
15
+ "intel": "bin/intel.mjs"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/intel/intel.d.ts",
20
+ "default": "./dist/intel/intel.js"
21
+ },
22
+ "./cloudflare": {
23
+ "types": "./dist/adapters/cloudflare/cloudflare.d.ts",
24
+ "default": "./dist/adapters/cloudflare/cloudflare.js"
25
+ },
26
+ "./cloudflare-workflow": {
27
+ "types": "./dist/adapters/cloudflare/cloudflare-flow-workflow.d.ts",
28
+ "default": "./dist/adapters/cloudflare/cloudflare-flow-workflow.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "bin",
33
+ "dist",
34
+ "examples",
35
+ "migrations",
36
+ "src/contract",
37
+ "!src/contract/**/*.unit.ts",
38
+ "ui/package.json",
39
+ "ui/README.md",
40
+ "ui/index.html",
41
+ "ui/vite.config.ts",
42
+ "ui/tsconfig.json",
43
+ "ui/components.json",
44
+ "ui/scripts",
45
+ "ui/src",
46
+ "!ui/src/**/*.unit.ts",
47
+ "!ui/src/**/*.unit.tsx",
48
+ "!ui/src/**/*.int.ts",
49
+ "!ui/src/**/*.int.tsx",
50
+ "!ui/src/**/*.e2e.ts",
51
+ "!ui/src/**/*.e2e.tsx"
52
+ ],
53
+ "scripts": {
54
+ "build": "tsc -p tsconfig.build.json && node ../../scripts/pack/fix-dts.mjs",
55
+ "dev": "vite ui",
56
+ "lint:tokens": "node ui/scripts/lint-tokens.mjs ui/src",
57
+ "prepack": "bun run build && node ../../scripts/pack/to-dist-manifest.mjs",
58
+ "postpack": "node ../../scripts/pack/restore-manifest.mjs",
59
+ "test": "vitest run",
60
+ "typecheck": "tsc --noEmit && tsc --noEmit -p ui/tsconfig.json",
61
+ "typecheck:core": "tsc --noEmit"
62
+ },
63
+ "dependencies": {
64
+ "@anchrd/gate-sdk": "^0.28.0",
65
+ "@blocknote/core": "^0.52.1",
66
+ "@blocknote/react": "^0.52.1",
67
+ "@blocknote/shadcn": "^0.52.1",
68
+ "@cfworker/json-schema": "^4.1.1",
69
+ "@dnd-kit/core": "^6.3.1",
70
+ "@dnd-kit/sortable": "^10.0.0",
71
+ "@dnd-kit/utilities": "^3.2.2",
72
+ "@file-viewer/react": "2.3.0",
73
+ "@file-viewer/renderer-pdf": "2.3.0",
74
+ "@file-viewer/renderer-presentation": "2.3.0",
75
+ "@file-viewer/renderer-spreadsheet": "2.3.2",
76
+ "@file-viewer/renderer-word": "2.3.1",
77
+ "@file-viewer/vite-plugin": "2.3.1",
78
+ "@modelcontextprotocol/sdk": "^1.30.0",
79
+ "@tailwindcss/vite": "^4.3.3",
80
+ "@tanstack/react-query": "^5.101.4",
81
+ "@tanstack/react-router": "^1.170.18",
82
+ "@tanstack/react-table": "^9.1.2",
83
+ "@vitejs/plugin-react": "^6.0.4",
84
+ "@xyflow/react": "^12.11.2",
85
+ "class-variance-authority": "^0.7.1",
86
+ "clsx": "^2.1.1",
87
+ "cmdk": "^1.1.1",
88
+ "fflate": "^0.8.3",
89
+ "fractional-indexing": "^4.0.0",
90
+ "hono": "^4.12.32",
91
+ "lucide-react": "^1.25.0",
92
+ "openid-client": "^6.8.4",
93
+ "radix-ui": "^1.6.7",
94
+ "react": "^19.2.0",
95
+ "react-aria-components": "^1.19.0",
96
+ "react-dom": "^19.2.0",
97
+ "readable-stream": "2.3.8",
98
+ "tailwind-merge": "^3.6.0",
99
+ "tailwindcss": "^4.3.3",
100
+ "ulid": "^3.0.2",
101
+ "vite": "^8.1.5",
102
+ "zod": "^4.4.3"
103
+ },
104
+ "devDependencies": {
105
+ "@cloudflare/vitest-pool-workers": "^0.19.0",
106
+ "@testing-library/dom": "^10.4.1",
107
+ "@testing-library/react": "^16.3.2",
108
+ "@types/node": "^24.13.3",
109
+ "@types/react": "^19.2.17",
110
+ "@types/react-dom": "^19.2.3",
111
+ "jsdom": "^29.1.1",
112
+ "vitest": "^4.1.10",
113
+ "wrangler": "^4.115.0"
114
+ },
115
+ "main": "./dist/intel/intel.js",
116
+ "module": "./dist/intel/intel.js",
117
+ "types": "./dist/intel/intel.d.ts"
118
+ }
@@ -0,0 +1,152 @@
1
+ # The wire contract
2
+
3
+ Public, runtime-validated wire formats shared by the Intel server and the browser application.
4
+
5
+ ⚠️ **This was `@anchrd/intel-contract` until #827** (**D84**). It is no longer a package of its own:
6
+ it lives here, inside `@anchrd/intel`, and it has **two** readers with two ways in.
7
+
8
+ | reader | how it imports |
9
+ |---|---|
10
+ | the server, `packages/intel/src` | **relative** — `../contract/node.ts`; it is the same TypeScript program |
11
+ | the surface, `packages/intel/ui/src` | the **`@contract` alias** — `@contract/node.ts` |
12
+
13
+ The alias exists because the surface is a separate Vite program that merely shares the directory. It
14
+ resolves to `../src/contract` in `ui/vite.config.ts`, `ui/vitest.config.ts` and `ui/tsconfig.json`,
15
+ and that one relative path is correct **both** in this tree and inside the published tarball — the
16
+ surface sits at `<package>/ui` in both. ⚠️ That is also why `src/contract` is in the `files`
17
+ whitelist as source and not only as built `dist`: a customer's `intel build` compiles these files.
18
+
19
+ ⚠️ **The alias is the surface's ONLY door out of `ui/`**, and `bun run check:boundaries` enforces it
20
+ by name: a relative path climbing out of `ui/src`, or an import of `@anchrd/intel`, is a violation.
21
+ Before #827 the package boundary did that on its own.
22
+
23
+ - Export schemas and inferred types from the file of their domain; do not add barrels.
24
+ - Inputs are `z.strictObject` schemas. A misspelled field must fail at the boundary.
25
+ - Keep business rules and authorization out of these files.
26
+ - Add a schema only when at least two surfaces consume it — the server, the browser application, or
27
+ an external caller.
28
+ - Every schema change needs parsing tests for valid, malformed, and unknown-field cases.
29
+ - **A field in a tool input schema without `.describe()` is unfinished**, and `mcp.e2e.ts` says so.
30
+
31
+ ## ⚠️ The description belongs to the field, and the field lives here
32
+
33
+ A good tool name gets a model to the right **tool**; a described parameter gets it to the right
34
+ **call**. The distance between those two is where the failures live, and they arrive as empty
35
+ results rather than as exceptions — which is why nobody reports them and why this went unnoticed
36
+ until #398: **one** of 119 fields carried a description, and the one that did had been written eight
37
+ months earlier for #126 with the reasoning spelled out beside it.
38
+
39
+ The description goes on the **Zod schema in this package**, never on the tool registration in
40
+ `mcp.ts`. Then HTTP validation, the MCP schema and the generated types all carry the same sentence
41
+ and there is no second place to drift. `mcp.e2e.ts` asserts it over the whole surface at once, so a
42
+ new field is red on the day it lands rather than when somebody remembers to extend a list.
43
+
44
+ **What a description does:**
45
+
46
+ - says what the field **means**, not what type it is — the type is already there;
47
+ - says **where the value comes from** when it is another call's answer ("the `versionId` from
48
+ `node_version_list`");
49
+ - says **what happens if it is left out**, for an optional field;
50
+ - says **what goes wrong** where that is not obvious — the exact place a model would otherwise guess.
51
+
52
+ **What it must not do** is restate the field name in prose. `nodeId: "The ID of the node"` costs
53
+ bytes and teaches nothing. ⚠️ **An honest gap beats a filler sentence**: `tools/list` is sent on
54
+ every connection, and the descriptions added in #398 grew it from 47 222 to 65 026 bytes. That is
55
+ worth paying for 118 sentences that answer a question; it is not worth paying for paraphrase.
56
+
57
+ ⚠️ **A word that means the same thing everywhere gets ONE described primitive**, not the same
58
+ sentence twenty times — `IdempotencyKey` in `contract.ts` is the example. Twenty copies are twenty
59
+ places to drift, and every one of them ships in the listing.
60
+
61
+ ## ⚠️ One domain, one file — and why the split has to stay
62
+
63
+ There is **no barrel**, and `biome.json` enforces it (`noBarrelFile: error`, `noReExportAll: error`).
64
+ Every domain owns a file, and every reader names that file:
65
+
66
+ ```text
67
+ contract.ts the shared primitives, and nothing else
68
+ node.ts table.ts share.ts tool.ts flow.ts flow-run.ts bundle.ts audit.ts board.ts feed.ts
69
+ ```
70
+
71
+ Until anchrd/intel#394 there was one file with one door, and a package with one door and no barrel
72
+ **must** put everything in it. `contract.ts` was 2219 lines, and it was not carelessness — it was the
73
+ bill.
74
+
75
+ ⚠️ **Until #827 those files were also `exports` subpaths of a published package** — `@anchrd/intel-
76
+ contract/node` and its siblings — and a comment about "a subpath is not a barrel" belonged here. They
77
+ are not exported any more, because nothing outside `@anchrd/intel` reads them: `agents` and `signals`
78
+ talk to Intel over HTTP and MCP (ADR-0007), and the installation only ever imported
79
+ `@anchrd/intel/cloudflare`. Adding a public `./contract*` export back is a YAGNI violation until a
80
+ consumer exists; adding one then is one line.
81
+
82
+ Three things follow, and none is optional:
83
+
84
+ - **A new schema goes in the file of its domain**, and a domain with no file gets one. Adding it to
85
+ `contract.ts` because that is where the imports already point re-grows the file this split paid
86
+ for.
87
+ - **`contract.ts` holds what every domain needs and nothing else** — `IntelId`, `IsoDateTime`, the
88
+ handful of primitives. It re-exports nothing. If a schema in it is used by exactly one domain, it
89
+ is in the wrong file.
90
+ - **An import names the narrowest file that has the symbol.** `flow-run.ts` over `flow.ts`,
91
+ `flow.ts` over `contract.ts`. That is what makes a change to the flow schemas cost a rebuild of the
92
+ files that read flows, and nothing else.
93
+
94
+ ⚠️ **The dependency graph between these files has to stay acyclic**, and it is easy to break by
95
+ accident: a name mentioned in a COMMENT looks like a dependency to anything that scans for one. The
96
+ graph today is `table → node`, `flow → node`, `flow-run → flow`, and everything → `contract.ts`. If
97
+ two domains genuinely need each other, the shared part belongs in `contract.ts`.
98
+
99
+ ⚠️ `flow → node` was added in #376 and is worth the edge it costs: `RelationNodeKind` used to write
100
+ the node kinds out by hand, and when D66 added two of them the copy did not follow — the relation
101
+ graph would have gone on answering about four kinds while the tree held six, silently. It is now
102
+ `z.enum([...NodeKind.options, "flow"])`. **A derived union beats an edge**, and the extra member is
103
+ exactly why `check:boundaries` never caught the copy: the rule there refuses a union that only
104
+ restates `NodeKind`, and this one had one word more.
105
+
106
+ ⚠️ **These files ship twice, and both copies are load-bearing.** `tsc -p tsconfig.build.json` emits
107
+ them into `dist/contract/` for the server, and the `files` whitelist ships `src/contract/` verbatim
108
+ for the surface's own Vite build. A new file here needs neither entry changed — both are directory
109
+ whitelists — but `bun run test:pack` installs the real tarball and names it if one of them stops
110
+ arriving.
111
+
112
+ ## ⚠️ `nullable()` is not `optional()`, and the caller who confuses them reads the wrong sentence
113
+
114
+ A field declared `nullable()` and not `optional()` is **required**: `null` is a value it takes, and
115
+ leaving it out is refused. Both halves are deliberate wherever a write names the version it was made
116
+ against — `SaveNodeVersionInput`, `SaveAttachmentInput`, `SaveFlowVersionInput`. `null` is the only
117
+ way to write the first content of a node that has none, and the field stays required so that a
118
+ caller who simply forgot it is refused instead of overwriting whatever somebody else wrote in
119
+ between.
120
+
121
+ ⚠️ **The trap is not the rule, it is what the refusal says.** Zod answers a missing required field
122
+ with `Invalid input: expected string, received undefined`, and to somebody holding a node created one
123
+ call earlier that reads as "this field needs an id" — about a field that cannot have one yet. #437
124
+ was opened as "the first version of a document cannot be set through the API" for exactly that
125
+ reason, and it was never true: the caller had tried `undefined` and `""`, never `null`.
126
+
127
+ **A required-nullable field therefore carries its own refusal**, one sentence naming both values it
128
+ takes, on the schema in this package so that HTTP, MCP and the generated types all say it:
129
+
130
+ ```ts
131
+ z.union([IntelId.min(1, { error: rule }), z.null()], { error: rule })
132
+ ```
133
+
134
+ ⚠️ **It is `baseVersion(rule)` in `contract.ts`, and all three writers take it** (#459). It lived
135
+ beside the two node writers until the flow graph became the third — the point at which this
136
+ repository stops copying and names the thing. `flow.ts` could not have imported it from `node.ts`
137
+ anyway: the graph is `table → node`, `flow-run → flow`, and everything → `contract.ts`, so the move
138
+ was the only shape that did not either break the graph or leave one sentence in two places to drift.
139
+
140
+ ⚠️ **The sentence says what the field TAKES, not what the caller did wrong.** It answers a wrong
141
+ type as well as an absent field, and "is required" would be false about the first — the same kind of
142
+ misdirection one corner further on. `baseVersionId takes the currentVersionId from node_get, or null
143
+ when the node has no version yet — and it has to be sent.`
144
+
145
+ The repeated `min(1)` adds no bound — it puts the sentence beside Zod's own `Too small` for the empty
146
+ string, which is the second thing a caller tries after `undefined`. The JSON Schema it produces is
147
+ the one `IntelId.nullable()` produced (`anyOf` of the string with its bounds and `null`, still
148
+ `required`), so nothing on the MCP surface moves.
149
+
150
+ ⚠️ **Making the field `optional()` instead is the repair that breaks the guard.** An absent field
151
+ would then have to mean "there is no version", and the forgetful caller silently replaces the newest
152
+ one — the very overwrite the field exists to refuse.