spine-framework 0.3.144 → 0.4.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 (451) hide show
  1. package/.env.example +76 -0
  2. package/.framework/.spine-manifest.json +19 -0
  3. package/.framework/cli/commands/agents.ts +61 -33
  4. package/.framework/cli/commands/auth.ts +60 -28
  5. package/.framework/cli/commands/create-app.ts +212 -80
  6. package/.framework/cli/commands/dev.ts +184 -68
  7. package/.framework/cli/commands/doctor.ts +324 -152
  8. package/.framework/cli/commands/generate.ts +119 -68
  9. package/.framework/cli/commands/init.ts +141 -242
  10. package/.framework/cli/commands/install-app.ts +1336 -314
  11. package/.framework/cli/commands/items.ts +31 -29
  12. package/.framework/cli/commands/migrate.ts +299 -156
  13. package/.framework/cli/commands/migrations.ts +62 -30
  14. package/.framework/cli/commands/pipelines.ts +31 -29
  15. package/.framework/cli/commands/status.ts +105 -45
  16. package/.framework/cli/commands/system.ts +47 -42
  17. package/.framework/cli/commands/test.ts +161 -102
  18. package/.framework/cli/commands/upgrade.ts +260 -0
  19. package/.framework/cli/commands/validate-app.ts +517 -161
  20. package/.framework/cli/context.ts +117 -85
  21. package/.framework/cli/env-loader.ts +74 -43
  22. package/.framework/cli/index.ts +70 -46
  23. package/.framework/cli/lib/project-files.ts +404 -0
  24. package/.framework/docs/guides/testing-guide.md +1 -1
  25. package/.framework/docs/runtime-types.md +72 -72
  26. package/.framework/functions/_shared/access.ts +84 -0
  27. package/.framework/functions/_shared/agent-runner.ts +5051 -1026
  28. package/.framework/functions/_shared/app-manifest.ts +214 -94
  29. package/.framework/functions/_shared/ast.ts +214 -0
  30. package/.framework/functions/_shared/audit.ts +91 -55
  31. package/.framework/functions/_shared/cil.ts +368 -127
  32. package/.framework/functions/_shared/db.ts +337 -68
  33. package/.framework/functions/_shared/funnel.ts +228 -0
  34. package/.framework/functions/_shared/geo.ts +105 -0
  35. package/.framework/functions/_shared/hash-api-key.ts +94 -17
  36. package/.framework/functions/_shared/index.ts +22 -224
  37. package/.framework/functions/_shared/kb.ts +832 -58
  38. package/.framework/functions/_shared/llm.ts +118 -18
  39. package/.framework/functions/_shared/logging.ts +317 -19
  40. package/.framework/functions/_shared/middleware.ts +701 -131
  41. package/.framework/functions/_shared/notify.ts +70 -0
  42. package/.framework/functions/_shared/outcomes.ts +34 -19
  43. package/.framework/functions/_shared/permissions.ts +501 -315
  44. package/.framework/functions/_shared/pipeline-runner.ts +704 -155
  45. package/.framework/functions/_shared/principal.ts +436 -263
  46. package/.framework/functions/_shared/rate-limit.ts +233 -0
  47. package/.framework/functions/_shared/resolve-credentials.ts +86 -19
  48. package/.framework/functions/_shared/resolve-ids.ts +203 -139
  49. package/.framework/functions/_shared/retry-deliveries.ts +51 -16
  50. package/.framework/functions/_shared/schema-utils.ts +602 -521
  51. package/.framework/functions/_shared/security.ts +345 -0
  52. package/.framework/functions/_shared/testing.ts +197 -172
  53. package/.framework/functions/_shared/tracing.ts +351 -21
  54. package/.framework/functions/_shared/trigger-engine.ts +425 -136
  55. package/.framework/functions/_shared/webhook-registration.ts +71 -70
  56. package/.framework/functions/_shared/webhook-registry.ts +126 -83
  57. package/.framework/functions/account-nodes.ts +30 -30
  58. package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
  59. package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
  60. package/.framework/functions/adapters/openai-outbound.ts +15 -16
  61. package/.framework/functions/admin-data.ts +596 -173
  62. package/.framework/functions/admin-funnel.ts +64 -60
  63. package/.framework/functions/admin-install-funnel.ts +84 -66
  64. package/.framework/functions/admin-signals.ts +69 -51
  65. package/.framework/functions/ai-agents.ts +431 -106
  66. package/.framework/functions/ai-interactions.ts +186 -112
  67. package/.framework/functions/api-keys.ts +107 -107
  68. package/.framework/functions/apps.ts +154 -136
  69. package/.framework/functions/auth.ts +83 -65
  70. package/.framework/functions/cil-agent-ingest.ts +189 -232
  71. package/.framework/functions/cil-agent.ts +433 -177
  72. package/.framework/functions/cil-callmap.ts +642 -430
  73. package/.framework/functions/cil-config.ts +139 -135
  74. package/.framework/functions/cil-context.ts +214 -127
  75. package/.framework/functions/cil-doc.ts +587 -458
  76. package/.framework/functions/cil-embeddings.ts +453 -364
  77. package/.framework/functions/cil-graph.ts +427 -142
  78. package/.framework/functions/cil-ingest.ts +1418 -1154
  79. package/.framework/functions/cil-pipeline.ts +276 -21
  80. package/.framework/functions/cil-project.ts +659 -132
  81. package/.framework/functions/cil-validate.ts +198 -164
  82. package/.framework/functions/debug-auth.ts +57 -55
  83. package/.framework/functions/deliveries.ts +80 -21
  84. package/.framework/functions/embeddings.ts +1023 -318
  85. package/.framework/functions/eval-harness.ts +146 -108
  86. package/.framework/functions/integration-outbound.ts +205 -75
  87. package/.framework/functions/integration-routes.ts +128 -23
  88. package/.framework/functions/integrations.ts +125 -100
  89. package/.framework/functions/invites.ts +1095 -460
  90. package/.framework/functions/item-progress.ts +158 -102
  91. package/.framework/functions/kb.ts +34 -10
  92. package/.framework/functions/logs.ts +133 -133
  93. package/.framework/functions/observability.ts +152 -122
  94. package/.framework/functions/pipeline-executions.ts +147 -132
  95. package/.framework/functions/pipelines.ts +134 -119
  96. package/.framework/functions/prompt-configs.ts +96 -80
  97. package/.framework/functions/roles.ts +112 -82
  98. package/.framework/functions/security.ts +450 -0
  99. package/.framework/functions/system-cron.ts +117 -80
  100. package/.framework/functions/system.ts +158 -98
  101. package/.framework/functions/tests.ts +29 -29
  102. package/.framework/functions/timers.ts +94 -95
  103. package/.framework/functions/trigger-scheduler.ts +10 -0
  104. package/.framework/functions/triggers.ts +179 -149
  105. package/.framework/functions/types.ts +137 -125
  106. package/.framework/mcp/index.ts +133 -0
  107. package/.framework/mcp/load-env.ts +60 -0
  108. package/.framework/mcp/shared/app-manifest.ts +275 -0
  109. package/.framework/mcp/shared/db.ts +477 -0
  110. package/.framework/mcp/shared/kb.ts +118 -0
  111. package/.framework/mcp/tools/architect.ts +431 -0
  112. package/.framework/mcp/tools/cil/context.ts +345 -0
  113. package/.framework/mcp/tools/cil/graph.ts +242 -0
  114. package/.framework/mcp/tools/cil/ingest.ts +1217 -0
  115. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
  116. package/.framework/mcp/tools/cil/pipeline.ts +767 -0
  117. package/.framework/mcp/tools/cil/project.ts +791 -0
  118. package/.framework/mcp/tools/cil/retrieval.ts +336 -0
  119. package/.framework/mcp/tools/cil/search-core.ts +454 -0
  120. package/.framework/mcp/tools/cil/search.ts +97 -0
  121. package/.framework/mcp/tools/cil/suggest.ts +186 -0
  122. package/.framework/mcp/tools/cil/validate.ts +1369 -0
  123. package/.framework/mcp/tools/kb/delete.ts +139 -0
  124. package/.framework/mcp/tools/kb/index.ts +52 -0
  125. package/.framework/mcp/tools/kb/ingest.ts +84 -0
  126. package/.framework/mcp/tools/kb/list-types.ts +102 -0
  127. package/.framework/mcp/tools/kb/publish.ts +100 -0
  128. package/.framework/mcp/tools/kb/reindex.ts +86 -0
  129. package/.framework/mcp/tools/kb/revoke.ts +85 -0
  130. package/.framework/mcp/tools/kb/search.ts +101 -0
  131. package/.framework/mcp/tools/kb/upsert.ts +125 -0
  132. package/.framework/mcp/tools/observability.ts +510 -0
  133. package/.framework/migrations/001_core_schema.sql +15 -0
  134. package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
  135. package/.framework/migrations/003_core_system_data.sql +16 -1
  136. package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
  137. package/.framework/migrations/005_core_observability.sql +17 -1
  138. package/.framework/migrations/006_security_hardening_rls.sql +15 -0
  139. package/.framework/migrations/007_kb_article_list.sql +17 -1
  140. package/.framework/migrations/008_items_target.sql +15 -0
  141. package/.framework/migrations/009_roles_slug_unique.sql +15 -0
  142. package/.framework/migrations/010_auth_user_trigger.sql +15 -0
  143. package/.framework/migrations/011_spine_system_account.sql +16 -0
  144. package/.framework/migrations/012_service_role_grants.sql +16 -0
  145. package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
  146. package/.framework/migrations/014_apps_required_roles.sql +16 -0
  147. package/.framework/migrations/015_types_kind_slug_unique.sql +20 -0
  148. package/.framework/migrations/016_cil_types.sql +281 -0
  149. package/.framework/migrations/017_cil_stable_ids.sql +37 -0
  150. package/.framework/migrations/018_runtime_tables.sql +129 -0
  151. package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
  152. package/.framework/migrations/020_schema_reconciliation.sql +67 -0
  153. package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
  154. package/.framework/migrations/032_cil_status_migration.sql +73 -0
  155. package/.framework/migrations/033_cil_versioning.sql +73 -0
  156. package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
  157. package/.framework/migrations/035_rate_limits.sql +55 -0
  158. package/.framework/migrations/036_demo_record_flags.sql +72 -0
  159. package/.framework/migrations/037_kb_types_canonical.sql +244 -0
  160. package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
  161. package/.framework/migrations/042_registration_hierarchy.sql +309 -0
  162. package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
  163. package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
  164. package/.framework/migrations/045_security_critical_rls.sql +139 -0
  165. package/.framework/migrations/046_security_missing_rls.sql +79 -0
  166. package/.framework/migrations/047_people_updated_by.sql +27 -0
  167. package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
  168. package/.framework/migrations/049_security_blocklist.sql +148 -0
  169. package/.framework/migrations/050_notifications.sql +49 -0
  170. package/.framework/migrations/051_message_agent_fields.sql +74 -0
  171. package/.framework/migrations/052_registration_security.sql +421 -0
  172. package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
  173. package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
  174. package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
  175. package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
  176. package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
  177. package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
  178. package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
  179. package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
  180. package/.framework/migrations/061_fk_dedup.sql +32 -0
  181. package/.framework/public/spine-logo.svg +10 -3
  182. package/.framework/scripts/app-install-cli.ts +190 -93
  183. package/.framework/scripts/assemble-frontend.sh +127 -44
  184. package/.framework/scripts/assemble-functions.sh +128 -30
  185. package/.framework/scripts/boundary-check.sh +6 -2
  186. package/.framework/scripts/build-manifest.sh +24 -68
  187. package/.framework/scripts/check-core-integrity.sh +15 -76
  188. package/.framework/scripts/checksum-manifest.mts +343 -0
  189. package/.framework/scripts/cil-migrate-markers.ts +403 -0
  190. package/.framework/scripts/fix-cil-markers.mjs +176 -0
  191. package/.framework/scripts/verify-integrity.sh +21 -57
  192. package/.framework/scripts/verify-manifest.mts +305 -0
  193. package/.framework/seeds/types/funnel_signal_use.json +74 -21
  194. package/.framework/seeds/types/spine_instance.json +73 -21
  195. package/.framework/src/App.tsx +118 -72
  196. package/.framework/src/apps/admin/index.tsx +855 -58
  197. package/.framework/src/components/AppWrapper.tsx +43 -45
  198. package/.framework/src/components/CustomAppLoader.tsx +160 -116
  199. package/.framework/src/components/admin/AdminListPage.tsx +46 -44
  200. package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
  201. package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
  202. package/.framework/src/components/admin/PageInstructions.tsx +98 -2
  203. package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
  204. package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
  205. package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
  206. package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
  207. package/.framework/src/components/app-sidebar.tsx +42 -45
  208. package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
  209. package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
  210. package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
  211. package/.framework/src/components/kb/KBArticleList.tsx +251 -10
  212. package/.framework/src/components/layout/AppShell.tsx +51 -45
  213. package/.framework/src/components/layout/Header.tsx +42 -43
  214. package/.framework/src/components/layout/Layout.tsx +46 -46
  215. package/.framework/src/components/layout/Sidebar.tsx +196 -96
  216. package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
  217. package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
  218. package/.framework/src/components/runtime/DataFilters.tsx +49 -74
  219. package/.framework/src/components/runtime/DataHeader.tsx +53 -32
  220. package/.framework/src/components/runtime/DataListPage.tsx +51 -155
  221. package/.framework/src/components/runtime/DataStats.tsx +66 -44
  222. package/.framework/src/components/runtime/DataTable.tsx +49 -149
  223. package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
  224. package/.framework/src/components/runtime/index.ts +119 -16
  225. package/.framework/src/components/search-form.tsx +46 -29
  226. package/.framework/src/components/shared/AgentView.tsx +65 -48
  227. package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
  228. package/.framework/src/components/shared/InviteManager.tsx +207 -97
  229. package/.framework/src/components/shared/MemberList.tsx +97 -0
  230. package/.framework/src/components/shared/SchemaFields.tsx +107 -86
  231. package/.framework/src/components/ui/DataTable.tsx +80 -68
  232. package/.framework/src/components/ui/Form.tsx +157 -146
  233. package/.framework/src/components/ui/ItemCard.tsx +74 -67
  234. package/.framework/src/components/ui/ItemListView.tsx +73 -68
  235. package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
  236. package/.framework/src/components/ui/Modal.tsx +78 -68
  237. package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
  238. package/.framework/src/components/ui/accordion.tsx +85 -86
  239. package/.framework/src/components/ui/alert-dialog.tsx +197 -184
  240. package/.framework/src/components/ui/alert.tsx +105 -94
  241. package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
  242. package/.framework/src/components/ui/avatar.tsx +122 -107
  243. package/.framework/src/components/ui/badge.tsx +60 -55
  244. package/.framework/src/components/ui/breadcrumb.tsx +127 -128
  245. package/.framework/src/components/ui/button-group.tsx +86 -72
  246. package/.framework/src/components/ui/button.tsx +71 -50
  247. package/.framework/src/components/ui/calendar.tsx +72 -57
  248. package/.framework/src/components/ui/card.tsx +136 -129
  249. package/.framework/src/components/ui/carousel.tsx +226 -107
  250. package/.framework/src/components/ui/chart.tsx +252 -147
  251. package/.framework/src/components/ui/checkbox.tsx +43 -44
  252. package/.framework/src/components/ui/collapsible.tsx +86 -71
  253. package/.framework/src/components/ui/combobox.tsx +282 -249
  254. package/.framework/src/components/ui/command.tsx +158 -146
  255. package/.framework/src/components/ui/context-menu.tsx +239 -240
  256. package/.framework/src/components/ui/dialog.tsx +174 -157
  257. package/.framework/src/components/ui/direction.tsx +72 -56
  258. package/.framework/src/components/ui/drawer.tsx +168 -169
  259. package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
  260. package/.framework/src/components/ui/empty.tsx +131 -120
  261. package/.framework/src/components/ui/field.tsx +205 -173
  262. package/.framework/src/components/ui/hover-card.tsx +71 -72
  263. package/.framework/src/components/ui/input-group.tsx +148 -133
  264. package/.framework/src/components/ui/input-otp.tsx +99 -86
  265. package/.framework/src/components/ui/input.tsx +43 -41
  266. package/.framework/src/components/ui/item.tsx +207 -185
  267. package/.framework/src/components/ui/kbd.tsx +57 -56
  268. package/.framework/src/components/ui/label.tsx +45 -41
  269. package/.framework/src/components/ui/menubar.tsx +268 -236
  270. package/.framework/src/components/ui/native-select.tsx +85 -86
  271. package/.framework/src/components/ui/navigation-menu.tsx +166 -156
  272. package/.framework/src/components/ui/pagination.tsx +131 -141
  273. package/.framework/src/components/ui/popover.tsx +125 -128
  274. package/.framework/src/components/ui/progress.tsx +47 -44
  275. package/.framework/src/components/ui/radio-group.tsx +60 -55
  276. package/.framework/src/components/ui/resizable.tsx +85 -72
  277. package/.framework/src/components/ui/scroll-area.tsx +57 -58
  278. package/.framework/src/components/ui/select.tsx +182 -169
  279. package/.framework/src/components/ui/separator.tsx +42 -43
  280. package/.framework/src/components/ui/sheet.tsx +170 -159
  281. package/.framework/src/components/ui/sidebar.tsx +395 -398
  282. package/.framework/src/components/ui/skeleton.tsx +42 -43
  283. package/.framework/src/components/ui/slider.tsx +61 -44
  284. package/.framework/src/components/ui/sonner.tsx +44 -44
  285. package/.framework/src/components/ui/spinner.tsx +47 -42
  286. package/.framework/src/components/ui/switch.tsx +56 -42
  287. package/.framework/src/components/ui/table-primitives.tsx +272 -132
  288. package/.framework/src/components/ui/table.tsx +140 -142
  289. package/.framework/src/components/ui/tabs.tsx +110 -93
  290. package/.framework/src/components/ui/textarea.tsx +43 -44
  291. package/.framework/src/components/ui/toggle-group.tsx +87 -71
  292. package/.framework/src/components/ui/toggle.tsx +56 -56
  293. package/.framework/src/components/ui/tooltip.tsx +99 -85
  294. package/.framework/src/contexts/AppContext.tsx +166 -114
  295. package/.framework/src/contexts/AuthContext.tsx +232 -137
  296. package/.framework/src/hooks/use-mobile.ts +43 -44
  297. package/.framework/src/hooks/useApi.ts +185 -172
  298. package/.framework/src/hooks/useAppPath.ts +31 -29
  299. package/.framework/src/hooks/useApps.ts +77 -42
  300. package/.framework/src/hooks/useEntityList.ts +66 -29
  301. package/.framework/src/hooks/useEntityRecord.ts +76 -32
  302. package/.framework/src/hooks/useForm.ts +62 -30
  303. package/.framework/src/hooks/useListSchema.ts +60 -43
  304. package/.framework/src/hooks/useSchemaRecord.ts +115 -95
  305. package/.framework/src/lib/api.ts +258 -134
  306. package/.framework/src/lib/registration.ts +181 -0
  307. package/.framework/src/lib/supabase.ts +118 -81
  308. package/.framework/src/lib/utils.ts +266 -266
  309. package/.framework/src/main.tsx +43 -44
  310. package/.framework/src/pages/DashboardPage.tsx +44 -43
  311. package/.framework/src/pages/NoAppPage.tsx +50 -0
  312. package/.framework/src/pages/NotFoundPage.tsx +32 -29
  313. package/.framework/src/pages/SetupPage.tsx +34 -33
  314. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
  315. package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
  316. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
  317. package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
  318. package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
  319. package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
  320. package/.framework/src/pages/admin/AppsPage.tsx +45 -42
  321. package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
  322. package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
  323. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
  324. package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
  325. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
  326. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
  327. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
  328. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
  329. package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
  330. package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
  331. package/.framework/src/pages/admin/LogsPage.tsx +87 -137
  332. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
  333. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
  334. package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
  335. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
  336. package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
  337. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
  338. package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
  339. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
  340. package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
  341. package/.framework/src/pages/admin/RolesPage.tsx +47 -164
  342. package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
  343. package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
  344. package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
  345. package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
  346. package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
  347. package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
  348. package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
  349. package/.framework/src/pages/admin/TimersPage.tsx +44 -42
  350. package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
  351. package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
  352. package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
  353. package/.framework/src/pages/admin/TypesPage.tsx +45 -42
  354. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
  355. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
  356. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
  357. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
  358. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
  359. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
  360. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
  361. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
  362. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
  363. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
  364. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
  365. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
  366. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  367. package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
  368. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
  369. package/.framework/src/pages/auth/LoginPage.tsx +38 -32
  370. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
  371. package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
  372. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
  373. package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
  374. package/.framework/src/types/auth.ts +101 -93
  375. package/.framework/src/types/types.ts +283 -262
  376. package/.framework/templates/project.json +87 -0
  377. package/.framework/templates/spine.config.neutral.json +7 -0
  378. package/.framework/tests/api/api-surface.test.ts +381 -0
  379. package/.framework/tests/fixtures/seed.ts +427 -0
  380. package/.framework/tests/fixtures/teardown.ts +228 -0
  381. package/.framework/tests/reporter.ts +351 -0
  382. package/.framework/tests/setup.ts +135 -0
  383. package/.framework/tests/ui/ui-sweep.ts +278 -0
  384. package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
  385. package/.framework/tests/unit/agent-runner.test.ts +2389 -0
  386. package/.framework/tests/unit/ai-interactions.test.ts +81 -0
  387. package/.framework/tests/unit/app-manifest.test.ts +359 -0
  388. package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
  389. package/.framework/tests/unit/cil-determinism.test.ts +85 -0
  390. package/.framework/tests/unit/core-isolation.test.ts +153 -0
  391. package/.framework/tests/unit/funnel-signals.test.ts +135 -0
  392. package/.framework/tests/unit/install-app.test.ts +1759 -0
  393. package/.framework/tests/unit/invites-security.test.ts +572 -0
  394. package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
  395. package/.framework/tests/unit/kb-search.test.ts +119 -0
  396. package/.framework/tests/unit/migrate.test.ts +509 -0
  397. package/.framework/tests/unit/notify.test.ts +136 -0
  398. package/.framework/tests/unit/permissions.test.ts +269 -0
  399. package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
  400. package/.framework/tests/unit/principal-cache.test.ts +138 -0
  401. package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
  402. package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
  403. package/.framework/tests/unit/schema-utils.test.ts +225 -0
  404. package/.framework/tests/unit/trigger-engine.test.ts +513 -0
  405. package/.framework/tests/unit/validate-app.test.ts +527 -0
  406. package/README.md +34 -3
  407. package/bin/spine-framework.cjs +1 -0
  408. package/config/tailwind.config.cjs +7 -0
  409. package/config/tsconfig.functions.json +42 -0
  410. package/config/tsconfig.json +37 -9
  411. package/config/vite.config.ts +31 -2
  412. package/netlify.toml +31 -3
  413. package/package.json +46 -29
  414. package/scripts/assemble-frontend.sh +127 -44
  415. package/scripts/assemble-functions.sh +127 -29
  416. package/scripts/boundary-check.sh +6 -2
  417. package/scripts/build-manifest.sh +24 -68
  418. package/scripts/checksum-manifest.mts +345 -0
  419. package/scripts/dev-clean.sh +86 -0
  420. package/scripts/dev-netlify.sh +49 -0
  421. package/scripts/netlify-dev-wrapper.sh +2 -0
  422. package/scripts/verify-install.ts +358 -0
  423. package/scripts/verify-integrity.sh +21 -57
  424. package/scripts/verify-manifest.mts +305 -0
  425. package/templates/project/gitignore +45 -0
  426. package/{config → templates/project}/package-lock.json +22335 -9366
  427. package/vitest.config.ts +46 -0
  428. package/.framework/public/spine.config.json +0 -91
  429. package/config/deno.lock +0 -108
  430. package/config/tsconfig.node.tsbuildinfo +0 -1
  431. package/config/tsconfig.tsbuildinfo +0 -1
  432. package/config/vite.config.d.ts +0 -2
  433. package/custom/.devin/AGENT.md +0 -357
  434. package/custom/.devin/skills/install-app/SKILL.md +0 -39
  435. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
  436. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
  437. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
  438. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
  439. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
  440. package/custom/.devin/workflows/app-install.yaml +0 -83
  441. package/custom/README.md +0 -79
  442. package/docs/README.md +0 -15
  443. package/scripts/app-install-cli.ts +0 -286
  444. package/scripts/check-core-integrity.sh +0 -82
  445. package/scripts/ingest-chunks.cjs +0 -202
  446. package/scripts/kb-chunk-parser.cjs +0 -312
  447. package/scripts/kb-chunk-parser.ts +0 -330
  448. package/scripts/kb-ingest-docs.ts +0 -203
  449. package/scripts/load-test-app-install.ts +0 -484
  450. package/scripts/test-login.js +0 -33
  451. package/spine.config.json +0 -91
@@ -0,0 +1,343 @@
1
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/scripts/checksum-manifest.mts"]
2
+ // CIL[DOC-MACRO purpose="Deterministic generator for .spine-manifest.json — sha256 manifest over protected source trees"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Enumerate protected trees, hash every listed file, emit a frozen format_version:1 manifest with sorted paths and a tree digest"]
4
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts,scripts/build-manifest.sh,scripts/verify-integrity.sh,scripts/check-core-integrity.sh"]
5
+ // CIL[DOC-CONTRACT inputs="argv: <root> <output> [--include-dir <d>...]" outputs="manifest JSON file at <output>" errors="exit 2 usage, exit 1 unreadable root" side_effects="writes <output>, prints summary"]
6
+ // CIL[DOC-MICRO logic_summary="Walk .framework/, config/, scripts/ (plus --include-dir extras) with exclusion rules; sha256 file bytes (symlinks hash their link target); sort paths by UTF-8 bytes; write JSON with LF endings" notable_decisions="no timestamp field so repeated runs are byte-identical; no git dependency for enumeration so exported standalones work"]
7
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
8
+ // CIL[DOC-STABILITY stability="stable"]
9
+ // CIL[DOC-AUDIENCE audience="system-admin"]
10
+ // CIL[DOC-NOTE type="design" content="Protected-tree enumeration is deliberately git-independent: standalone exports may not be a git worktree. source_commit is null whenever the worktree is dirty or git is unavailable."]
11
+ // CIL[DOC-VECTOR type="context" content="Generated .spine-manifest.json is consumed by scripts/verify-manifest.mts via npm run verify and CI integrity gates"]
12
+ // CIL[DOC-VECTOR type="implementation_pattern" content="Sibling of scripts/verify-manifest.mts — both share PROTECTED_DIRS, isExcludedName and collectProtectedFiles from this module" status="ready"]
13
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:module_header:module_header"]
14
+
15
+ import { createHash } from 'node:crypto'
16
+ import { execFileSync } from 'node:child_process'
17
+ import * as fs from 'node:fs'
18
+ import * as path from 'node:path'
19
+ import { pathToFileURL } from 'node:url'
20
+
21
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:PROTECTED_DIRS:constant" type="constant" identity="PROTECTED_DIRS" parent="~/.framework/scripts/checksum-manifest.mts"]
22
+ // CIL[DOC-MACRO purpose="Default protected source trees hashed into the manifest"]
23
+ // CIL[DOC-RESPONSIBILITY responsibility="Declare which root-level directories are covered by integrity hashing"]
24
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
25
+ // CIL[DOC-CONTRACT inputs="none" outputs="readonly string[]" errors="none" side_effects="none"]
26
+ // CIL[DOC-MICRO logic_summary="Covers .framework (all extensions), config/, and scripts/; extra trees can be appended with --include-dir" notable_decisions="custom/ is intentionally excluded — app code is not framework-frozen"]
27
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
28
+ // CIL[DOC-STABILITY stability="stable"]
29
+ export const PROTECTED_DIRS: readonly string[] = ['.framework', 'config', 'scripts']
30
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:PROTECTED_DIRS:constant"]
31
+
32
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:EXCLUDED_DIR_NAMES:constant" type="constant" identity="EXCLUDED_DIR_NAMES" parent="~/.framework/scripts/checksum-manifest.mts"]
33
+ // CIL[DOC-MACRO purpose="Directory basenames skipped during protected-tree enumeration"]
34
+ // CIL[DOC-RESPONSIBILITY responsibility="Keep caches, dependency dirs and build output out of the manifest"]
35
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
36
+ // CIL[DOC-CONTRACT inputs="none" outputs="ReadonlySet<string>" errors="none" side_effects="none"]
37
+ // CIL[DOC-MICRO logic_summary="Exact-match directory names; matched at every level below a protected root" notable_decisions="the protected roots themselves are never excluded even though .framework is dot-prefixed"]
38
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
39
+ // CIL[DOC-STABILITY stability="stable"]
40
+ const EXCLUDED_DIR_NAMES: ReadonlySet<string> = new Set([
41
+ 'node_modules',
42
+ 'dist',
43
+ '.assembled',
44
+ '.netlify',
45
+ '.functions-assemble-tmp',
46
+ '.frontend-assemble-tmp',
47
+ '.vite',
48
+ '.turbo',
49
+ '.cache',
50
+ 'coverage',
51
+ '__pycache__',
52
+ ])
53
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:EXCLUDED_DIR_NAMES:constant"]
54
+
55
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:LOCKFILE_NAMES:constant" type="constant" identity="LOCKFILE_NAMES" parent="~/.framework/scripts/checksum-manifest.mts"]
56
+ // CIL[DOC-MACRO purpose="Lockfile basenames excluded as generated output artifacts"]
57
+ // CIL[DOC-RESPONSIBILITY responsibility="Prevent package-manager lockfiles inside protected trees (e.g. config/package-lock.json) from being treated as frozen source"]
58
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="config/package-lock.json,config/deno.lock"]
59
+ // CIL[DOC-CONTRACT inputs="none" outputs="ReadonlySet<string>" errors="none" side_effects="none"]
60
+ // CIL[DOC-MICRO logic_summary="Exact basenames for npm/yarn/pnpm/bun/deno lockfiles" notable_decisions="lockfiles are installer output, not authored source; hashing them would break manifests whenever deps re-resolve"]
61
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
62
+ // CIL[DOC-STABILITY stability="stable"]
63
+ const LOCKFILE_NAMES: ReadonlySet<string> = new Set([
64
+ 'package-lock.json',
65
+ 'npm-shrinkwrap.json',
66
+ 'yarn.lock',
67
+ 'pnpm-lock.yaml',
68
+ 'bun.lock',
69
+ 'bun.lockb',
70
+ 'deno.lock',
71
+ ])
72
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:LOCKFILE_NAMES:constant"]
73
+
74
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:isExcludedName:function" type="function" identity="isExcludedName" parent="~/.framework/scripts/checksum-manifest.mts"]
75
+ // CIL[DOC-MACRO purpose="Decide whether a file basename is excluded from the manifest"]
76
+ // CIL[DOC-RESPONSIBILITY responsibility="Filter secret env files, editor/cache droppings, and lockfiles out of protected enumeration"]
77
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
78
+ // CIL[DOC-CONTRACT inputs="name: file basename" outputs="boolean — true when excluded" errors="none" side_effects="none"]
79
+ // CIL[DOC-MICRO logic_summary=".env*/.xenv secret files are never hashed so local credentials cannot leak or differ across machines; .DS_Store and *.tsbuildinfo are churn-prone caches" notable_decisions=".xenv.example stays included — it is a tracked template, not a secret"]
80
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
81
+ // CIL[DOC-STABILITY stability="stable"]
82
+ export function isExcludedName(name: string): boolean {
83
+ if (name === '.env' || name.startsWith('.env.')) return true
84
+ if (name === '.xenv' || name === '.xenv.test') return true
85
+ if (name === '.DS_Store' || name === 'Thumbs.db') return true
86
+ if (name.endsWith('.tsbuildinfo')) return true
87
+ if (LOCKFILE_NAMES.has(name)) return true
88
+ return false
89
+ }
90
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:isExcludedName:function"]
91
+
92
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:ManifestEntry:interface" type="interface" identity="ManifestEntry" parent="~/.framework/scripts/checksum-manifest.mts"]
93
+ // CIL[DOC-MACRO purpose="One enumerated protected file/symlink awaiting hashing"]
94
+ // CIL[DOC-RESPONSIBILITY responsibility="Carry the POSIX-relative path, absolute path and symlink flag through enumeration to hashing"]
95
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
96
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
97
+ // CIL[DOC-MICRO logic_summary="rel is always POSIX-style so manifests are platform-stable" notable_decisions="symlinked directories are leaves, never traversed — prevents escaping the protected root"]
98
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
99
+ // CIL[DOC-STABILITY stability="stable"]
100
+ export interface ManifestEntry {
101
+ /** POSIX-style path relative to root, e.g. ".framework/functions/kb.ts" */
102
+ rel: string
103
+ /** Absolute filesystem path */
104
+ abs: string
105
+ /** True when the entry itself is a symlink (hashed by link target, not content) */
106
+ symlink: boolean
107
+ }
108
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:ManifestEntry:interface"]
109
+
110
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:collectProtectedFiles:function" type="function" identity="collectProtectedFiles" parent="~/.framework/scripts/checksum-manifest.mts"]
111
+ // CIL[DOC-MACRO purpose="Enumerate all hashable entries under the protected trees"]
112
+ // CIL[DOC-RESPONSIBILITY responsibility="Recursively walk each protected dir, apply exclusion rules, and return entries sorted by UTF-8 byte order"]
113
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
114
+ // CIL[DOC-CONTRACT inputs="root: absolute project root; includeDirs: extra protected dirs (root-relative POSIX); excludeRels: rel paths to skip (e.g. the manifest itself)" outputs="ManifestEntry[] sorted by rel" errors="throws on unreadable directory" side_effects="reads filesystem"]
115
+ // CIL[DOC-MICRO logic_summary="Iterative DFS over lstat'd entries; symlinks recorded as leaves; sort uses Buffer.compare on UTF-8 so ordering is locale-independent" notable_decisions="missing protected dirs are skipped silently so the same code runs against partial exports"]
116
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
117
+ // CIL[DOC-STABILITY stability="stable"]
118
+ export function collectProtectedFiles(
119
+ root: string,
120
+ includeDirs: readonly string[] = [],
121
+ excludeRels: ReadonlySet<string> = new Set(),
122
+ ): ManifestEntry[] {
123
+ const rootAbs = path.resolve(root)
124
+ const dirs = [...PROTECTED_DIRS, ...includeDirs]
125
+ const out: ManifestEntry[] = []
126
+
127
+ for (const dir of dirs) {
128
+ const dirAbs = path.join(rootAbs, dir)
129
+ if (!fs.existsSync(dirAbs)) continue
130
+ const stack: string[] = [dirAbs]
131
+
132
+ while (stack.length > 0) {
133
+ const current = stack.pop() as string
134
+ for (const dent of fs.readdirSync(current, { withFileTypes: true })) {
135
+ const abs = path.join(current, dent.name)
136
+ const rel = path.relative(rootAbs, abs).split(path.sep).join('/')
137
+ if (excludeRels.has(rel)) continue
138
+
139
+ if (dent.isDirectory()) {
140
+ if (EXCLUDED_DIR_NAMES.has(dent.name)) continue
141
+ stack.push(abs)
142
+ } else if (dent.isSymbolicLink()) {
143
+ if (!isExcludedName(dent.name)) out.push({ rel, abs, symlink: true })
144
+ } else if (dent.isFile()) {
145
+ if (!isExcludedName(dent.name)) out.push({ rel, abs, symlink: false })
146
+ }
147
+ // sockets/fifos/etc are not hashable — skipped
148
+ }
149
+ }
150
+ }
151
+
152
+ out.sort((a, b) => Buffer.compare(Buffer.from(a.rel, 'utf8'), Buffer.from(b.rel, 'utf8')))
153
+ return out
154
+ }
155
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:collectProtectedFiles:function"]
156
+
157
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:hashEntry:function" type="function" identity="hashEntry" parent="~/.framework/scripts/checksum-manifest.mts"]
158
+ // CIL[DOC-MACRO purpose="Compute the sha256 integrity hash for one manifest entry"]
159
+ // CIL[DOC-RESPONSIBILITY responsibility="Hash regular file bytes, or the symlink target text for links, returning a 'sha256:<hex>' value"]
160
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
161
+ // CIL[DOC-CONTRACT inputs="entry: ManifestEntry" outputs="'sha256:<hex>' string" errors="throws on unreadable file" side_effects="reads file content"]
162
+ // CIL[DOC-MICRO logic_summary="Symlinks hash 'symlink:<raw readlink>' — the integrity of a link is its target, and this never reads outside the root" notable_decisions="the 'symlink:' domain prefix prevents a file whose bytes equal a link string from colliding"]
163
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
164
+ // CIL[DOC-STABILITY stability="stable"]
165
+ export function hashEntry(entry: ManifestEntry): string {
166
+ const hash = createHash('sha256')
167
+ if (entry.symlink) {
168
+ hash.update('symlink:')
169
+ hash.update(fs.readlinkSync(entry.abs))
170
+ } else {
171
+ hash.update(fs.readFileSync(entry.abs))
172
+ }
173
+ return `sha256:${hash.digest('hex')}`
174
+ }
175
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:hashEntry:function"]
176
+
177
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:topLevelDir:function" type="function" identity="topLevelDir" parent="~/.framework/scripts/checksum-manifest.mts"]
178
+ // CIL[DOC-MACRO purpose="Extract the first path segment of a manifest-relative path"]
179
+ // CIL[DOC-RESPONSIBILITY responsibility="Let the verifier union manifest-listed trees with the default protected dirs"]
180
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
181
+ // CIL[DOC-CONTRACT inputs="rel: POSIX-relative path" outputs="first segment, or null for root-level files" errors="none" side_effects="none"]
182
+ // CIL[DOC-MICRO logic_summary="Split on '/' and return the head" notable_decisions="root-level listed files contribute no extra tree"]
183
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
184
+ // CIL[DOC-STABILITY stability="stable"]
185
+ export function topLevelDir(rel: string): string | null {
186
+ const idx = rel.indexOf('/')
187
+ return idx === -1 ? null : rel.slice(0, idx)
188
+ }
189
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:topLevelDir:function"]
190
+
191
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:getSourceCommit:function" type="function" identity="getSourceCommit" parent="~/.framework/scripts/checksum-manifest.mts"]
192
+ // CIL[DOC-MACRO purpose="Resolve source_commit: HEAD only when the worktree is clean"]
193
+ // CIL[DOC-RESPONSIBILITY responsibility="Run git rev-parse HEAD plus git status --porcelain; return the commit hash only if nothing (tracked or untracked) deviates, else null"]
194
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/checksum-manifest.mts"]
195
+ // CIL[DOC-CONTRACT inputs="root: project root; manifestRel: POSIX rel path of the manifest being written (excluded from the dirty check)" outputs="commit sha or null" errors="returns null when git is unavailable or not a repo" side_effects="spawns git subprocesses"]
196
+ // CIL[DOC-MICRO logic_summary="The manifest output path is filtered out of porcelain lines so consecutive manifest regenerations stay deterministic" notable_decisions="without the filter, a clean tree would emit HEAD on run 1 and null on run 2 once the manifest exists untracked"]
197
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
198
+ // CIL[DOC-STABILITY stability="stable"]
199
+ function getSourceCommit(root: string, manifestRel: string): string | null {
200
+ try {
201
+ const head = execFileSync('git', ['rev-parse', 'HEAD'], {
202
+ cwd: root,
203
+ encoding: 'utf8',
204
+ stdio: ['ignore', 'pipe', 'ignore'],
205
+ }).trim()
206
+ const porcelain = execFileSync('git', ['status', '--porcelain'], {
207
+ cwd: root,
208
+ encoding: 'utf8',
209
+ stdio: ['ignore', 'pipe', 'ignore'],
210
+ })
211
+ const dirty = porcelain
212
+ .split('\n')
213
+ .map((line) => line.slice(3).trim().replace(/^"|"$/g, ''))
214
+ .filter((p) => p.length > 0 && p !== manifestRel)
215
+ return dirty.length === 0 ? head : null
216
+ } catch {
217
+ return null
218
+ }
219
+ }
220
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:getSourceCommit:function"]
221
+
222
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:getFrameworkVersion:function" type="function" identity="getFrameworkVersion" parent="~/.framework/scripts/checksum-manifest.mts"]
223
+ // CIL[DOC-MACRO purpose="Resolve framework_version from package.json"]
224
+ // CIL[DOC-RESPONSIBILITY responsibility="Read <root>/package.json and return its version field; null when unreadable"]
225
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="package.json"]
226
+ // CIL[DOC-CONTRACT inputs="root: project root" outputs="version string or null" errors="returns null on missing/malformed package.json" side_effects="reads package.json"]
227
+ // CIL[DOC-MICRO logic_summary="Single JSON read; no .framework version file exists in this repo" notable_decisions="no version file check — package.json (name: spine-framework) is authoritative"]
228
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
229
+ // CIL[DOC-STABILITY stability="stable"]
230
+ function getFrameworkVersion(root: string): string | null {
231
+ try {
232
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
233
+ return typeof pkg.version === 'string' ? pkg.version : null
234
+ } catch {
235
+ return null
236
+ }
237
+ }
238
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:getFrameworkVersion:function"]
239
+
240
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:computeTreeDigest:function" type="function" identity="computeTreeDigest" parent="~/.framework/scripts/checksum-manifest.mts"]
241
+ // CIL[DOC-MACRO purpose="Compute the tree_digest over the sorted path:hash list"]
242
+ // CIL[DOC-RESPONSIBILITY responsibility="sha256 over 'rel:hash' lines joined with LF and a trailing LF — order matches the sorted files map"]
243
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
244
+ // CIL[DOC-CONTRACT inputs="files: sorted [rel, hash] pairs" outputs="'sha256:<hex>'" errors="none" side_effects="none"]
245
+ // CIL[DOC-MICRO logic_summary="Pure function — the verifier recomputes the same digest to detect manifest tampering" notable_decisions="trailing newline keeps the digest unambiguous for empty lists"]
246
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
247
+ // CIL[DOC-STABILITY stability="stable"]
248
+ export function computeTreeDigest(files: ReadonlyArray<readonly [string, string]>): string {
249
+ const hash = createHash('sha256')
250
+ for (const [rel, fileHash] of files) {
251
+ hash.update(`${rel}:${fileHash}\n`)
252
+ }
253
+ return `sha256:${hash.digest('hex')}`
254
+ }
255
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:computeTreeDigest:function"]
256
+
257
+ // CIL[BEGIN stable_id="~/.framework/scripts/checksum-manifest.mts:main:function" type="function" identity="main" parent="~/.framework/scripts/checksum-manifest.mts"]
258
+ // CIL[DOC-MACRO purpose="CLI entry — parse args, hash protected trees, write .spine-manifest.json"]
259
+ // CIL[DOC-RESPONSIBILITY responsibility="Validate argv, build the manifest object, serialize with sorted keys and LF endings, print a summary"]
260
+ // CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/build-manifest.sh,package.json"]
261
+ // CIL[DOC-CONTRACT inputs="argv: <root> <output> [--include-dir <d>...]" outputs="exit 0 with manifest written" errors="exit 2 usage error; exit 1 on fs/git failures" side_effects="writes <output>, stdout summary"]
262
+ // CIL[DOC-MICRO logic_summary="frozen is always true on generated manifests; node/npm baselines are stamped from the plan G baseline (node >=22.13.0, npm >=10.0.0)" notable_decisions="no timestamp — determinism is verified by running the generator twice and diffing"]
263
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
264
+ // CIL[DOC-STABILITY stability="stable"]
265
+ function main(): void {
266
+ const args = process.argv.slice(2)
267
+ const positional: string[] = []
268
+ const includeDirs: string[] = []
269
+
270
+ for (let i = 0; i < args.length; i++) {
271
+ const arg = args[i]
272
+ if (arg === '--include-dir') {
273
+ const dir = args[++i]
274
+ if (!dir) {
275
+ console.error('checksum-manifest: --include-dir requires a value')
276
+ process.exit(2)
277
+ }
278
+ includeDirs.push(dir)
279
+ } else if (arg.startsWith('--')) {
280
+ console.error(`checksum-manifest: unknown flag ${arg}`)
281
+ process.exit(2)
282
+ } else {
283
+ positional.push(arg)
284
+ }
285
+ }
286
+
287
+ if (positional.length !== 2) {
288
+ console.error('usage: checksum-manifest.mts <root> <output> [--include-dir <d>...]')
289
+ process.exit(2)
290
+ }
291
+
292
+ const root = path.resolve(positional[0])
293
+ const output = path.resolve(positional[1])
294
+
295
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
296
+ console.error(`checksum-manifest: root is not a directory: ${root}`)
297
+ process.exit(1)
298
+ }
299
+
300
+ const extraDirs: string[] = []
301
+ for (const dir of includeDirs) {
302
+ const rel = path.relative(root, path.resolve(root, dir)).split(path.sep).join('/')
303
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
304
+ console.error(`checksum-manifest: --include-dir escapes root: ${dir}`)
305
+ process.exit(2)
306
+ }
307
+ extraDirs.push(rel)
308
+ }
309
+
310
+ const manifestRel = path.relative(root, output).split(path.sep).join('/')
311
+ const excludeRels = new Set<string>([manifestRel])
312
+
313
+ const entries = collectProtectedFiles(root, extraDirs, excludeRels)
314
+ const pairs: Array<readonly [string, string]> = entries.map(
315
+ (entry) => [entry.rel, hashEntry(entry)] as const,
316
+ )
317
+
318
+ const files: Record<string, string> = {}
319
+ for (const [rel, hash] of pairs) files[rel] = hash
320
+
321
+ const manifest = {
322
+ format_version: 1,
323
+ frozen: true,
324
+ framework_version: getFrameworkVersion(root),
325
+ source_commit: getSourceCommit(root, manifestRel),
326
+ tree_digest: computeTreeDigest(pairs),
327
+ node: '>=22.13.0',
328
+ npm: '>=10.0.0',
329
+ files,
330
+ }
331
+
332
+ fs.writeFileSync(output, JSON.stringify(manifest, null, 2) + '\n', 'utf8')
333
+
334
+ console.log(` manifest: ${manifestRel}`)
335
+ console.log(` files hashed: ${pairs.length}`)
336
+ console.log(` tree_digest: ${manifest.tree_digest}`)
337
+ console.log(` source_commit: ${manifest.source_commit ?? 'null (dirty worktree or no git)'}`)
338
+ }
339
+ // CIL[END stable_id="~/.framework/scripts/checksum-manifest.mts:main:function"]
340
+
341
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
342
+ main()
343
+ }