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
@@ -2,60 +2,72 @@
2
2
 
3
3
 
4
4
 
5
- // CIL[BEGIN chunk_id="index_moduleheader_1784068794" type="module_header" identity="imports" parent="none"]
5
+ // CIL[BEGIN stable_id="~/.framework/cli/index.ts:module_header:module_header" type="module_header" identity="module_header" parent="none"]
6
6
  // CIL[DOC-MACRO purpose="Import CLI dependencies and environment loader"]
7
7
  // CIL[DOC-RESPONSIBILITY responsibility="Load required modules for CLI setup"]
8
- // CIL[DOC-CONTEXT module="cli" layer="entry" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
8
+ // CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts"]
9
9
  // CIL[DOC-CONTRACT inputs="none" outputs="loaded_modules" errors="none" side_effects="none"]
10
10
  // CIL[DOC-MICRO logic_summary="Imports environment loader and Commander.js" notable_decisions="[]"]
11
11
  // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
12
12
  // CIL[DOC-STABILITY stability="stable"]
13
13
  // CIL[DOC-AUDIENCE audience="developer"]
14
- // CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
15
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
16
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
17
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
18
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
19
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
20
14
  import './env-loader.ts'
21
15
  import { Command } from 'commander'
22
- // CIL[END chunk_id="index_moduleheader_1784068794"]
23
- // CIL[BEGIN chunk_id="index_program_1784068794" type="variable" identity="program" parent="none"]
16
+ import { readFileSync } from 'fs'
17
+ import { resolve, dirname, join } from 'path'
18
+ import { fileURLToPath } from 'url'
19
+ // CIL[DOC-VECTOR type="context" content="purpose='Import CLI dependencies and environment loader'; No verified external callers; imports: .framework/cli/env-loader.ts"]
20
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
21
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
22
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
23
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
24
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
25
+ // CIL[END stable_id="~/.framework/cli/index.ts:module_header:module_header"]
26
+ // CIL[BEGIN stable_id="~/.framework/cli/index.ts:program:variable" type="variable" identity="program" parent="none"]
24
27
  // CIL[DOC-MACRO purpose="Initialize CLI program with basic configuration"]
25
28
  // CIL[DOC-RESPONSIBILITY responsibility="Configure main CLI program instance"]
26
- // CIL[DOC-CONTEXT module="cli" layer="entry" related_files="[\".framework/functions/_shared/schema-utils.ts\",\".framework/src/components/ui/dialog.tsx\",\".framework/functions/_shared/principal.ts\",\".framework/src/components/ui/table-primitives.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
29
+ // CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
27
30
  // CIL[DOC-CONTRACT inputs="none" outputs="configured_program" errors="none" side_effects="none"]
28
31
  // CIL[DOC-MICRO logic_summary="Creates Commander program with name, description, version, and account option" notable_decisions="[]"]
29
32
  // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
30
33
  // CIL[DOC-STABILITY stability="stable"]
31
- // CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, .framework/src/components/ui/table-primitives.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
32
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
33
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
34
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
35
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
36
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
37
34
  const program = new Command()
38
35
 
36
+ // Version: the spine-framework package.json when running from the package,
37
+ // otherwise the project's frozen manifest (vendored CLI copy).
38
+ function cliVersion(): string {
39
+ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../')
40
+ try {
41
+ const pkg = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'))
42
+ if (pkg.name === 'spine-framework' && typeof pkg.version === 'string') return pkg.version
43
+ } catch { /* fall through */ }
44
+ try {
45
+ const manifest = JSON.parse(readFileSync(join(process.cwd(), '.spine-manifest.json'), 'utf8'))
46
+ if (typeof manifest.framework_version === 'string') return manifest.framework_version
47
+ } catch { /* fall through */ }
48
+ return '0.0.0'
49
+ }
50
+
39
51
  program
40
52
  .name('spine-framework')
41
53
  .description('Spine Framework CLI — install, migrate, and manage your Spine instance')
42
- .version('2.0.0')
54
+ .version(cliVersion())
43
55
  .option('--account <id>', 'Override the account ID for this command')
44
- // CIL[END chunk_id="index_program_1784068794"]
45
- // CIL[BEGIN chunk_id="index_imports_1784068794" type="block" identity="command_imports" parent="none"]
56
+ // CIL[DOC-VECTOR type="context" content="purpose='Initialize CLI program with basic configuration'; No verified external callers; imports: .framework/cli/env-loader.ts"]
57
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
58
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
59
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
60
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
61
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
62
+ // CIL[END stable_id="~/.framework/cli/index.ts:program:variable"]
63
+ // CIL[BEGIN stable_id="~/.framework/cli/index.ts:command_imports:block" type="block" identity="command_imports" parent="none"]
46
64
  // CIL[DOC-MACRO purpose="Dynamically import all CLI command modules"]
47
65
  // CIL[DOC-RESPONSIBILITY responsibility="Load command registration functions"]
48
- // CIL[DOC-CONTEXT module="cli" layer="entry" related_files="[\".framework/migrations/015_simplify_apps_table.sql\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/components/ui/table-primitives.tsx\",\"\\\"]"]
66
+ // CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
49
67
  // CIL[DOC-CONTRACT inputs="none" outputs="command_modules" errors="import_failures" side_effects="none"]
50
68
  // CIL[DOC-MICRO logic_summary="Uses Promise.all to parallel import all command modules" notable_decisions="[]"]
51
69
  // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
52
70
  // CIL[DOC-STABILITY stability="stable"]
53
- // CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/ui/table-primitives.tsx, \."]
54
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
55
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
56
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
57
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
58
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
59
71
  const [
60
72
  { registerInitCommands },
61
73
  { registerMigrateCommands },
@@ -72,6 +84,8 @@ const [
72
84
  { registerSystemCommands },
73
85
  { registerGenerateCommands },
74
86
  { registerCreateAppCommands },
87
+ { registerValidateAppCommands },
88
+ { registerUpgradeCommands },
75
89
  ] = await Promise.all([
76
90
  import('./commands/init.ts'),
77
91
  import('./commands/migrate.ts'),
@@ -88,22 +102,24 @@ const [
88
102
  import('./commands/system.ts'),
89
103
  import('./commands/generate.ts'),
90
104
  import('./commands/create-app.ts'),
105
+ import('./commands/validate-app.ts'),
106
+ import('./commands/upgrade.ts'),
91
107
  ])
92
- // CIL[END chunk_id="index_imports_1784068794"]
93
- // CIL[BEGIN chunk_id="index_registrations_1784068794" type="block" identity="command_registrations" parent="none"]
108
+ // CIL[DOC-VECTOR type="context" content="purpose='Dynamically import all CLI command modules'; No verified external callers; imports: .framework/cli/env-loader.ts"]
109
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
110
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
111
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
112
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
113
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
114
+ // CIL[END stable_id="~/.framework/cli/index.ts:command_imports:block"]
115
+ // CIL[BEGIN stable_id="~/.framework/cli/index.ts:command_registrations:block" type="block" identity="command_registrations" parent="none"]
94
116
  // CIL[DOC-MACRO purpose="Register all CLI commands with the program"]
95
117
  // CIL[DOC-RESPONSIBILITY responsibility="Configure CLI with all available commands"]
96
- // CIL[DOC-CONTEXT module="cli" layer="entry" related_files="[\".framework/migrations/015_simplify_apps_table.sql\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/components/ui/table-primitives.tsx\",\"\\\"]"]
118
+ // CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
97
119
  // CIL[DOC-CONTRACT inputs="none" outputs="registered_commands" errors="none" side_effects="none"]
98
120
  // CIL[DOC-MICRO logic_summary="Calls each command registration function with the program instance" notable_decisions="[]"]
99
121
  // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
100
122
  // CIL[DOC-STABILITY stability="stable"]
101
- // CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/ui/table-primitives.tsx, \."]
102
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
103
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
104
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
105
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
106
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
107
123
  registerInitCommands(program)
108
124
  registerMigrateCommands(program)
109
125
  registerInstallAppCommands(program)
@@ -119,23 +135,31 @@ registerTestCommands(program)
119
135
  registerSystemCommands(program)
120
136
  registerGenerateCommands(program)
121
137
  registerCreateAppCommands(program)
122
- // CIL[END chunk_id="index_registrations_1784068794"]
123
- // CIL[BEGIN chunk_id="index_parseasync_1784068794" type="block" identity="error_handling" parent="none"]
138
+ registerValidateAppCommands(program)
139
+ registerUpgradeCommands(program)
140
+ // CIL[DOC-VECTOR type="context" content="purpose='Register all CLI commands with the program'; No verified external callers; imports: .framework/cli/env-loader.ts"]
141
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
142
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
143
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
144
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
145
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
146
+ // CIL[END stable_id="~/.framework/cli/index.ts:command_registrations:block"]
147
+ // CIL[BEGIN stable_id="~/.framework/cli/index.ts:error_handling:block" type="block" identity="error_handling" parent="none"]
124
148
  // CIL[DOC-MACRO purpose="Parse CLI arguments and handle errors"]
125
149
  // CIL[DOC-RESPONSIBILITY responsibility="Execute CLI and handle uncaught errors"]
126
- // CIL[DOC-CONTEXT module="cli" layer="entry" related_files="[\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/pages/admin/RolesPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/components/ui/dialog.tsx\",\"\\\"]"]
150
+ // CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
127
151
  // CIL[DOC-CONTRACT inputs="none" outputs="command_execution" errors="cli_errors" side_effects="process_exit"]
128
152
  // CIL[DOC-MICRO logic_summary="Parses async command line arguments and exits on error" notable_decisions="[]"]
129
153
  // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
130
154
  // CIL[DOC-STABILITY stability="stable"]
131
- // CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/RolesPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/ui/dialog.tsx, \."]
132
- // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
133
- // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
134
- // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
135
- // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
136
- // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
137
155
  program.parseAsync(process.argv).catch((error) => {
138
156
  console.error('CLI Error:', error.message)
139
157
  process.exit(1)
140
158
  })
141
- // CIL[END chunk_id="index_parseasync_1784068794"]
159
+ // CIL[DOC-VECTOR type="context" content="purpose='Parse CLI arguments and handle errors'; No verified external callers; imports: .framework/cli/env-loader.ts"]
160
+ // CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
161
+ // CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
162
+ // CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
163
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
164
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
165
+ // CIL[END stable_id="~/.framework/cli/index.ts:error_handling:block"]
@@ -0,0 +1,404 @@
1
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/cli/lib/project-files.ts"]
2
+ // CIL[DOC-MACRO purpose="Framework-owned project fileset — collect files to vend, render netlify.toml/.gitignore, merge package.json, and write the frozen .spine-manifest.json"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide the shared primitives for `init` (scaffold a new project) and `upgrade` (sync an installed project to a newer spine-framework) — always app-agnostic, driven by .framework/templates/project.json"]
4
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json,.framework/cli/commands/init.ts,.framework/cli/commands/upgrade.ts,scripts/checksum-manifest.mts"]
5
+ // CIL[DOC-CONTRACT inputs="pkgRoot (spine-framework package/monorepo root), projectRoot" outputs="file lists, rendered config text, frozen manifest" errors="throws on missing declared framework files" side_effects="writeFrozenManifest writes .spine-manifest.json; all other exports are pure"]
6
+ // CIL[DOC-MICRO logic_summary="collectFrameworkFiles mirrors export-standalone's collectCopyList semantics: excludes apply to walked dirs only, explicitly listed files bypass them. renderNetlifyToml treats [functions.\"custom_*\"] sections as app-owned: strip them from the framework source, re-append the project's existing custom_* sections verbatim" notable_decisions="framework never writes custom/, spine.config.json, .env, or netlify.toml custom_* sections — those belong to apps and the customer"]
7
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
8
+ // CIL[DOC-STABILITY stability="experimental"]
9
+ // CIL[DOC-NOTE type="design" content="Kept dependency-free of export-standalone.ts on purpose: the packager profile lives in .framework/templates/project.json so the framework never learns app specifics"]
10
+ // CIL[DOC-AUDIENCE audience="developer"]
11
+
12
+ import fs from 'node:fs'
13
+ import path from 'node:path'
14
+ import { createHash } from 'node:crypto'
15
+ import {
16
+ collectProtectedFiles,
17
+ hashEntry,
18
+ computeTreeDigest,
19
+ } from '../../../scripts/checksum-manifest.mts'
20
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:module_header:module_header"]
21
+
22
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:types:block" type="block" identity="types" parent="~/.framework/cli/lib/project-files.ts"]
23
+ // CIL[DOC-MACRO purpose="Project profile + collected-file shapes shared by init and upgrade"]
24
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json"]
25
+ // CIL[DOC-CONTRACT inputs="none" outputs="ProjectProfile, CollectedFile interfaces" errors="none" side_effects="none"]
26
+ // CIL[DOC-MICRO logic_summary="ProjectProfile mirrors templates/project.json keys; CollectedFile is the {rel, abs} copy unit" notable_decisions="one profile shape drives both scaffold and upgrade"]
27
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
28
+ // CIL[DOC-STABILITY stability="stable"]
29
+ // CIL[DOC-AUDIENCE audience="developer"]
30
+ export interface ProjectProfile {
31
+ framework_dirs: string[]
32
+ framework_files: string[]
33
+ config_files: string[]
34
+ root_scripts: string[]
35
+ bin: string[]
36
+ exclude_globs: string[]
37
+ package_scripts: Record<string, string>
38
+ }
39
+
40
+ export interface CollectedFile {
41
+ rel: string
42
+ abs: string
43
+ symlink: boolean
44
+ }
45
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:types:block"]
46
+
47
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:globs_and_hashes:block" type="block" identity="globs_and_hashes" parent="~/.framework/cli/lib/project-files.ts"]
48
+ // CIL[DOC-MACRO purpose="Minimal `**`/`*`/`?` glob→regex matcher (export-standalone semantics) and sha256 file/text helpers"]
49
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json,scripts/checksum-manifest.mts"]
50
+ // CIL[DOC-CONTRACT inputs="glob: string; abs: string; text: string" outputs="RegExp; sha256 hex" errors="none" side_effects="none"]
51
+ // CIL[DOC-MICRO logic_summary="globToRegex escapes literals then expands `**`→.* `*`→[^/]* `?`→[^/]; sha256 helpers feed both the frozen manifest and upgrade integrity checks" notable_decisions="no external glob dep — the exclude vocabulary is deliberately small"]
52
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
53
+ // CIL[DOC-STABILITY stability="stable"]
54
+ // CIL[DOC-AUDIENCE audience="developer"]
55
+
56
+ /** Minimal glob matcher supporting `**`, `*`, and `?` — same semantics as export-standalone. */
57
+ export function globToRegex(glob: string): RegExp {
58
+ const parts = glob.split('**').map(seg =>
59
+ seg
60
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
61
+ .replace(/\*/g, '[^/]*')
62
+ .replace(/\?/g, '[^/]')
63
+ )
64
+ return new RegExp(`^${parts.join('.*')}$`)
65
+ }
66
+
67
+ export function sha256File(abs: string): string {
68
+ return `sha256:${createHash('sha256').update(fs.readFileSync(abs)).digest('hex')}`
69
+ }
70
+
71
+ export function sha256Text(text: string): string {
72
+ return `sha256:${createHash('sha256').update(text, 'utf8').digest('hex')}`
73
+ }
74
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:globs_and_hashes:block"]
75
+
76
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:collect:block" type="block" identity="collect" parent="~/.framework/cli/lib/project-files.ts"]
77
+ // CIL[DOC-MACRO purpose="Profile load + framework file collection — walked dirs honor excludes, listed files bypass them"]
78
+ // CIL[DOC-CONTRACT inputs="dirAbs: string, rootAbs: string, excludes: RegExp[], out: CollectedFile[], pkgRoot: string, profile: ProjectProfile" outputs="sorted CollectedFile[]" errors="throws listing missing required framework_dirs/framework_files/root_scripts" side_effects="none (read-only)"]
79
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json"]
80
+ // CIL[DOC-MICRO logic_summary="loadProjectProfile reads templates/project.json; collectFrameworkFiles walks declared dirs (honoring excludes) and appends declared files/locks verbatim, deduping by rel" notable_decisions="walked entries honor excludes; explicitly listed files bypass them — export-standalone parity"]
81
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
82
+ // CIL[DOC-STABILITY stability="stable"]
83
+ // CIL[DOC-AUDIENCE audience="developer"]
84
+ function walkDir(
85
+ dirAbs: string,
86
+ rootAbs: string,
87
+ excludes: RegExp[],
88
+ out: CollectedFile[] = [],
89
+ ): CollectedFile[] {
90
+ for (const dent of fs.readdirSync(dirAbs, { withFileTypes: true })) {
91
+ const abs = path.join(dirAbs, dent.name)
92
+ const rel = path.relative(rootAbs, abs).split(path.sep).join('/')
93
+ if (excludes.some((re) => re.test(rel))) continue
94
+ if (dent.isDirectory()) walkDir(abs, rootAbs, excludes, out)
95
+ else if (dent.isSymbolicLink()) out.push({ rel, abs, symlink: true })
96
+ else if (dent.isFile()) out.push({ rel, abs, symlink: false })
97
+ }
98
+ return out
99
+ }
100
+
101
+ /** Load the framework's own project profile shipped at .framework/templates/project.json. */
102
+ export function loadProjectProfile(pkgRoot: string): ProjectProfile {
103
+ const file = path.join(pkgRoot, '.framework/templates/project.json')
104
+ return JSON.parse(fs.readFileSync(file, 'utf8')) as ProjectProfile
105
+ }
106
+
107
+ /**
108
+ * Collect the framework-owned file set declared by the project profile.
109
+ * Directories are walked with excludes; individually listed files bypass
110
+ * excludes (so `.env.example` survives env-glob exclusions).
111
+ * Missing `framework_dirs`/`framework_files`/`root_scripts` entries are fatal;
112
+ * `config_files` and `bin` are optional (warn + skip).
113
+ */
114
+ export function collectFrameworkFiles(pkgRoot: string, profile: ProjectProfile): CollectedFile[] {
115
+ const excludes = profile.exclude_globs.map(globToRegex)
116
+ const seen = new Map<string, CollectedFile>()
117
+ const missing: string[] = []
118
+
119
+ const addFile = (rel: string, required: boolean) => {
120
+ const abs = path.join(pkgRoot, rel)
121
+ if (!fs.existsSync(abs)) {
122
+ if (required) missing.push(rel)
123
+ else console.log(` ○ optional ${rel} absent — skipped`)
124
+ return
125
+ }
126
+ const lst = fs.lstatSync(abs)
127
+ seen.set(rel, { rel, abs, symlink: lst.isSymbolicLink() })
128
+ }
129
+ const addDir = (rel: string) => {
130
+ const abs = path.join(pkgRoot, rel)
131
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
132
+ missing.push(rel)
133
+ return
134
+ }
135
+ for (const e of walkDir(abs, pkgRoot, excludes)) seen.set(e.rel, e)
136
+ }
137
+
138
+ for (const d of profile.framework_dirs) addDir(d)
139
+ for (const f of profile.framework_files) addFile(f, true)
140
+ for (const f of profile.config_files) addFile(f, false)
141
+ for (const f of profile.root_scripts) addFile(f, true)
142
+ for (const f of profile.bin) addFile(f, false)
143
+
144
+ if (missing.length > 0) {
145
+ throw new Error(`framework package is missing declared files:\n ${missing.join('\n ')}`)
146
+ }
147
+ return [...seen.values()].sort((a, b) => Buffer.compare(Buffer.from(a.rel), Buffer.from(b.rel)))
148
+ }
149
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:collect:block"]
150
+
151
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:toml:block" type="block" identity="toml" parent="~/.framework/cli/lib/project-files.ts"]
152
+ // CIL[DOC-MACRO purpose="netlify.toml split/render — framework blocks owned upstream, [functions.\"custom_*\"] sections app-owned and preserved verbatim; .gitignore render"]
153
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files="netlify.toml,.gitignore"]
154
+ // CIL[DOC-CONTRACT inputs="toml: string; frameworkToml: string; existingToml?: string; template: string" outputs="{ base, customSections } | rendered TOML | rendered gitignore" errors="none" side_effects="none (pure)"]
155
+ // CIL[DOC-MICRO logic_summary="splitNetlifyToml removes app-owned custom_* scheduled sections, comments attached, from framework source and returns them verbatim; renderNetlifyToml re-appends the project's own sections; renderGitignore ensures .env ignore lines" notable_decisions="custom_* ownership boundary is the core of app-independence — identical rendered output must not create a .bak upstream"]
156
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
157
+ // CIL[DOC-STABILITY stability="stable"]
158
+ // CIL[DOC-NOTE type="invariant" content="splitNetlifyToml must stay idempotent — extracted sections attach preceding comments but never leading blank lines, or every upgrade writes a phantom diff"]
159
+ // CIL[DOC-AUDIENCE audience="developer"]
160
+ const CUSTOM_SECTION_RE = /^\[functions\."custom_/
161
+
162
+ /**
163
+ * Split a netlify.toml into base content and app-owned [functions."custom_*"]
164
+ * sections (each including its contiguous preceding comment block).
165
+ */
166
+ export function splitNetlifyToml(toml: string): { base: string; customSections: string[] } {
167
+ const lines = toml.split('\n')
168
+ const base: string[] = []
169
+ const customSections: string[] = []
170
+ let skipping = false
171
+ let pendingComments: string[] = []
172
+ let current: string[] | null = null
173
+
174
+ const flushSection = () => {
175
+ if (current) customSections.push(current.join('\n').replace(/\s+$/, ''))
176
+ current = null
177
+ }
178
+
179
+ for (const line of lines) {
180
+ const trimmed = line.trim()
181
+ if (trimmed === '' ) {
182
+ if (!skipping && current === null) pendingComments.push(line)
183
+ else current?.push(line)
184
+ continue
185
+ }
186
+ if (trimmed.startsWith('#')) {
187
+ if (!skipping && current === null) pendingComments.push(line)
188
+ else current?.push(line)
189
+ continue
190
+ }
191
+ if (trimmed.startsWith('[')) {
192
+ if (current !== null) flushSection()
193
+ skipping = CUSTOM_SECTION_RE.test(trimmed)
194
+ if (skipping) {
195
+ // Attach the immediately preceding comment block, but not blank lines —
196
+ // rendering already separates sections, and blanks here would drift
197
+ // on every round-trip.
198
+ const comments = [...pendingComments]
199
+ while (comments.length > 0 && comments[0].trim() === '') comments.shift()
200
+ current = [...comments, line]
201
+ } else {
202
+ base.push(...pendingComments, line)
203
+ }
204
+ pendingComments = []
205
+ continue
206
+ }
207
+ if (skipping) { current?.push(line); continue }
208
+ base.push(...pendingComments, line)
209
+ pendingComments = []
210
+ }
211
+ flushSection()
212
+ return {
213
+ base: base.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, ''),
214
+ customSections,
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Render a project netlify.toml: framework-owned blocks from the package,
220
+ * app-owned [functions."custom_*"] sections preserved from the existing file.
221
+ */
222
+ export function renderNetlifyToml(frameworkToml: string, existingToml?: string): string {
223
+ const { base } = splitNetlifyToml(frameworkToml)
224
+ const { customSections } = existingToml ? splitNetlifyToml(existingToml) : { customSections: [] }
225
+ return customSections.length > 0
226
+ ? `${base}\n\n${customSections.join('\n\n')}\n`
227
+ : `${base}\n`
228
+ }
229
+
230
+ /** Render the project .gitignore from the packed template (templates/project/gitignore). */
231
+ export function renderGitignore(template: string): string {
232
+ return template
233
+ .split('\n')
234
+ .filter((l) => l.trim() !== '.env.example')
235
+ .join('\n')
236
+ }
237
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:toml:block"]
238
+
239
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:package_merge:block" type="block" identity="package_merge" parent="~/.framework/cli/lib/project-files.ts"]
240
+ // CIL[DOC-MACRO purpose="Merge framework-owned scripts/deps/pin into a project package.json — removes only deps the previous framework version owned"]
241
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files="package.json,.spine-manifest.json"]
242
+ // CIL[DOC-CONTRACT inputs="opts: { existing, name, description, scripts, newDependencies, newDevDependencies, spineVersion, oldFrameworkDeps }" outputs="{ text, frameworkDeps }" errors="none" side_effects="none"]
243
+ // CIL[DOC-MICRO logic_summary="Removes deps recorded in the old manifest's framework_dependencies, installs the new set, applies profile scripts, pins spine-framework exactly" notable_decisions="dep removal is scoped to previously framework-owned deps — customer-added deps/scripts always survive"]
244
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
245
+ // CIL[DOC-STABILITY stability="stable"]
246
+ // CIL[DOC-AUDIENCE audience="developer"]
247
+ export interface MergedPackageJson {
248
+ text: string
249
+ frameworkDeps: string[]
250
+ }
251
+
252
+ /**
253
+ * Merge framework-owned content into a project package.json.
254
+ * - scripts listed in the profile are written (framework-owned keys)
255
+ * - deps listed in newFrameworkDeps are written verbatim
256
+ * - a dep is removed only when the previous version owned it (present in
257
+ * prevFrameworkDeps) and the new version dropped it
258
+ * - customer-added keys everywhere else are preserved
259
+ */
260
+ export function mergePackageJson(opts: {
261
+ existing?: Record<string, any>
262
+ name: string
263
+ version?: string
264
+ description?: string
265
+ scripts: Record<string, string>
266
+ newDependencies: Record<string, string>
267
+ newDevDependencies: Record<string, string>
268
+ prevFrameworkDeps?: string[]
269
+ spineVersion: string
270
+ }): MergedPackageJson {
271
+ const prevOwned = new Set(opts.prevFrameworkDeps ?? [])
272
+ const dependencies: Record<string, string> = { ...(opts.existing?.dependencies ?? {}) }
273
+ const devDependencies: Record<string, string> = { ...(opts.existing?.devDependencies ?? {}) }
274
+
275
+ for (const dep of prevOwned) {
276
+ if (!opts.newDependencies[dep] && !opts.newDevDependencies[dep]) {
277
+ delete dependencies[dep]
278
+ delete devDependencies[dep]
279
+ }
280
+ }
281
+ for (const [k, v] of Object.entries(opts.newDependencies)) dependencies[k] = v
282
+ for (const [k, v] of Object.entries(opts.newDevDependencies)) devDependencies[k] = v
283
+ dependencies['spine-framework'] = opts.spineVersion
284
+
285
+ const frameworkDeps = [
286
+ ...Object.keys(opts.newDependencies),
287
+ ...Object.keys(opts.newDevDependencies),
288
+ 'spine-framework',
289
+ ].sort()
290
+
291
+ const pkg = {
292
+ name: opts.existing?.name ?? opts.name,
293
+ version: opts.existing?.version ?? opts.version ?? '0.1.0',
294
+ private: true,
295
+ type: 'module',
296
+ ...(opts.existing?.description ?? opts.description
297
+ ? { description: opts.existing?.description ?? opts.description }
298
+ : {}),
299
+ ...(opts.existing ?? {}),
300
+ scripts: { ...(opts.existing?.scripts ?? {}), ...opts.scripts },
301
+ dependencies,
302
+ devDependencies,
303
+ }
304
+ // Re-assert generated fields over any copied `existing` keys above.
305
+ pkg.private = true
306
+ pkg.type = 'module'
307
+
308
+ return { text: JSON.stringify(pkg, null, 2) + '\n', frameworkDeps }
309
+ }
310
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:package_merge:block"]
311
+
312
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:manifest:block" type="block" identity="manifest" parent="~/.framework/cli/lib/project-files.ts"]
313
+ // CIL[DOC-MACRO purpose="Write the frozen .spine-manifest.json — per-file hashes over the protected trees plus framework_version + framework_dependencies"]
314
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".spine-manifest.json,scripts/checksum-manifest.mts"]
315
+ // CIL[DOC-CONTRACT inputs="opts: { projectRoot, frameworkVersion, frameworkDeps }" outputs=".spine-manifest.json written; {fileCount, treeDigest}" errors="none" side_effects="writes projectRoot/.spine-manifest.json"]
316
+ // CIL[DOC-MICRO logic_summary="Hashes every protected file and records framework_version + framework_dependencies so upgrade can detect local mods and owned deps" notable_decisions="manifest is the integrity contract — same hash inputs as scripts/checksum-manifest.mts verify"]
317
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
318
+ // CIL[DOC-STABILITY stability="stable"]
319
+ // CIL[DOC-AUDIENCE audience="developer"]
320
+ /** Write the frozen .spine-manifest.json for a project at the given framework version. */
321
+ export function writeFrozenManifest(opts: {
322
+ projectRoot: string
323
+ frameworkVersion: string
324
+ frameworkDeps: string[]
325
+ }): { fileCount: number; treeDigest: string } {
326
+ const { projectRoot } = opts
327
+ const entries = collectProtectedFiles(projectRoot, ['bin'], new Set(['.spine-manifest.json']))
328
+ const pairs = entries.map((e) => [e.rel, hashEntry(e)] as const)
329
+ const files: Record<string, string> = {}
330
+ for (const [rel, hash] of pairs) files[rel] = hash
331
+ const manifest = {
332
+ format_version: 1,
333
+ frozen: true,
334
+ framework_version: opts.frameworkVersion,
335
+ framework_dependencies: opts.frameworkDeps,
336
+ source_commit: null,
337
+ tree_digest: computeTreeDigest(pairs),
338
+ node: '>=22.13.0',
339
+ npm: '>=10.0.0',
340
+ files,
341
+ }
342
+ fs.writeFileSync(
343
+ path.join(projectRoot, '.spine-manifest.json'),
344
+ JSON.stringify(manifest, null, 2) + '\n',
345
+ )
346
+ return { fileCount: pairs.length, treeDigest: manifest.tree_digest }
347
+ }
348
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:manifest:block"]
349
+
350
+ // CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:file_io:block" type="block" identity="file_io" parent="~/.framework/cli/lib/project-files.ts"]
351
+ // CIL[DOC-MACRO purpose="Copy collected files (mode-preserving, symlink safety-checked), sanitize dir names to npm package names, compare semvers"]
352
+ // CIL[DOC-CONTEXT module="cli" layer="lib" related_files=""]
353
+ // CIL[DOC-CONTRACT inputs="files: CollectedFile[], projectRoot: string, name: string, a: string, b: string" outputs="copied tree; sanitized name; -1|0|1" errors="throws when a source symlink resolves outside pkgRoot" side_effects="copyCollectedFiles writes + chmods files under projectRoot"]
354
+ // CIL[DOC-MICRO logic_summary="copyCollectedFiles resolves symlinks (rejecting escapes), preserves executable bits, creates parent dirs; sanitizePackageName lowercases and strips invalid chars; cmpSemver compares numeric triples" notable_decisions="symlink escape guard keeps packaged payload inside the project"]
355
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
356
+ // CIL[DOC-STABILITY stability="stable"]
357
+ // CIL[DOC-AUDIENCE audience="developer"]
358
+ /** Copy a collected file list into the project, preserving modes and safe symlinks. */
359
+ export function copyCollectedFiles(files: CollectedFile[], projectRoot: string): void {
360
+ for (const f of files) {
361
+ const dest = path.join(projectRoot, f.rel)
362
+ fs.mkdirSync(path.dirname(dest), { recursive: true })
363
+ if (f.symlink) {
364
+ const target = fs.readlinkSync(f.abs)
365
+ const outResolved = path.resolve(path.dirname(dest), target)
366
+ if (!outResolved.startsWith(projectRoot + path.sep) && outResolved !== projectRoot) {
367
+ throw new Error(`unsafe symlink: ${f.rel} → ${target} resolves outside the project tree`)
368
+ }
369
+ try { fs.rmSync(dest, { force: true }) } catch { /* absent */ }
370
+ fs.symlinkSync(target, dest)
371
+ } else {
372
+ fs.copyFileSync(f.abs, dest)
373
+ fs.chmodSync(dest, fs.statSync(f.abs).mode)
374
+ }
375
+ }
376
+ }
377
+
378
+ /** Sanitize a directory basename into a valid npm package name. */
379
+ export function sanitizePackageName(name: string): string {
380
+ const cleaned = name
381
+ .toLowerCase()
382
+ .replace(/[^a-z0-9._~+-]+/g, '-')
383
+ .replace(/^[._-]+/, '')
384
+ .replace(/[._-]+$/, '')
385
+ return cleaned || 'spine-project'
386
+ }
387
+
388
+ /** Compare two semver strings (numeric core + prerelease); returns -1/0/1. */
389
+ export function cmpSemver(a: string, b: string): number {
390
+ const parse = (v: string) => {
391
+ const [core, pre] = v.split('-', 2)
392
+ const nums = core.split('.').map((n) => parseInt(n, 10) || 0)
393
+ return { nums, pre: pre ?? null }
394
+ }
395
+ const pa = parse(a); const pb = parse(b)
396
+ for (let i = 0; i < 3; i++) {
397
+ if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] < pb.nums[i] ? -1 : 1
398
+ }
399
+ if (pa.pre === pb.pre) return 0
400
+ if (pa.pre === null) return 1
401
+ if (pb.pre === null) return -1
402
+ return pa.pre < pb.pre ? -1 : 1
403
+ }
404
+ // CIL[END stable_id="~/.framework/cli/lib/project-files.ts:file_io:block"]
@@ -37,7 +37,7 @@ See `.windsurf/workflows/run-tests.md` for the full step-by-step workflow.
37
37
  Integration, API, and UI tests need credentials in `v2-core/.xenv` or `v2-core/.xenv.test`:
38
38
 
39
39
  ```env
40
- SUPABASE_URL=https://uyokuiibztwfasdprsov.supabase.co
40
+ SUPABASE_URL=https://<your-project-ref>.supabase.co
41
41
  SUPABASE_SERVICE_ROLE_KEY=<service-role-key>
42
42
  SPINE_TEST_ACCOUNT_ID=<uuid>
43
43
  DB_SCHEMA=public