spine-framework 0.3.145 → 0.4.1
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.
- package/.env.example +76 -0
- package/.framework/.spine-manifest.json +19 -0
- package/.framework/cli/commands/agents.ts +61 -33
- package/.framework/cli/commands/auth.ts +60 -28
- package/.framework/cli/commands/create-app.ts +212 -80
- package/.framework/cli/commands/dev.ts +184 -68
- package/.framework/cli/commands/doctor.ts +324 -152
- package/.framework/cli/commands/generate.ts +119 -68
- package/.framework/cli/commands/init.ts +141 -242
- package/.framework/cli/commands/install-app.ts +1336 -314
- package/.framework/cli/commands/items.ts +31 -29
- package/.framework/cli/commands/migrate.ts +299 -156
- package/.framework/cli/commands/migrations.ts +62 -30
- package/.framework/cli/commands/pipelines.ts +31 -29
- package/.framework/cli/commands/status.ts +105 -45
- package/.framework/cli/commands/system.ts +47 -42
- package/.framework/cli/commands/test.ts +161 -102
- package/.framework/cli/commands/upgrade.ts +260 -0
- package/.framework/cli/commands/validate-app.ts +517 -161
- package/.framework/cli/context.ts +117 -85
- package/.framework/cli/env-loader.ts +74 -43
- package/.framework/cli/index.ts +70 -46
- package/.framework/cli/lib/project-files.ts +404 -0
- package/.framework/docs/guides/testing-guide.md +1 -1
- package/.framework/docs/runtime-types.md +72 -72
- package/.framework/functions/_shared/access.ts +84 -0
- package/.framework/functions/_shared/agent-runner.ts +5067 -1026
- package/.framework/functions/_shared/app-manifest.ts +214 -94
- package/.framework/functions/_shared/ast.ts +214 -0
- package/.framework/functions/_shared/audit.ts +91 -55
- package/.framework/functions/_shared/cil.ts +368 -127
- package/.framework/functions/_shared/db.ts +337 -68
- package/.framework/functions/_shared/funnel.ts +228 -0
- package/.framework/functions/_shared/geo.ts +105 -0
- package/.framework/functions/_shared/hash-api-key.ts +94 -17
- package/.framework/functions/_shared/index.ts +22 -224
- package/.framework/functions/_shared/kb.ts +832 -58
- package/.framework/functions/_shared/llm.ts +118 -18
- package/.framework/functions/_shared/logging.ts +317 -19
- package/.framework/functions/_shared/middleware.ts +701 -131
- package/.framework/functions/_shared/notify.ts +70 -0
- package/.framework/functions/_shared/outcomes.ts +34 -19
- package/.framework/functions/_shared/permissions.ts +501 -315
- package/.framework/functions/_shared/pipeline-runner.ts +704 -155
- package/.framework/functions/_shared/principal.ts +436 -263
- package/.framework/functions/_shared/rate-limit.ts +233 -0
- package/.framework/functions/_shared/resolve-credentials.ts +86 -19
- package/.framework/functions/_shared/resolve-ids.ts +203 -139
- package/.framework/functions/_shared/retry-deliveries.ts +51 -16
- package/.framework/functions/_shared/schema-utils.ts +602 -521
- package/.framework/functions/_shared/security.ts +345 -0
- package/.framework/functions/_shared/testing.ts +197 -172
- package/.framework/functions/_shared/tracing.ts +351 -21
- package/.framework/functions/_shared/trigger-engine.ts +425 -136
- package/.framework/functions/_shared/webhook-registration.ts +71 -70
- package/.framework/functions/_shared/webhook-registry.ts +126 -83
- package/.framework/functions/account-nodes.ts +30 -30
- package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
- package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
- package/.framework/functions/adapters/openai-outbound.ts +15 -16
- package/.framework/functions/admin-data.ts +596 -173
- package/.framework/functions/admin-funnel.ts +64 -60
- package/.framework/functions/admin-install-funnel.ts +84 -66
- package/.framework/functions/admin-signals.ts +69 -51
- package/.framework/functions/ai-agents.ts +431 -106
- package/.framework/functions/ai-interactions.ts +186 -112
- package/.framework/functions/api-keys.ts +107 -107
- package/.framework/functions/apps.ts +154 -136
- package/.framework/functions/auth.ts +83 -65
- package/.framework/functions/cil-agent-ingest.ts +189 -232
- package/.framework/functions/cil-agent.ts +433 -177
- package/.framework/functions/cil-callmap.ts +642 -430
- package/.framework/functions/cil-config.ts +139 -135
- package/.framework/functions/cil-context.ts +214 -127
- package/.framework/functions/cil-doc.ts +587 -458
- package/.framework/functions/cil-embeddings.ts +453 -364
- package/.framework/functions/cil-graph.ts +427 -142
- package/.framework/functions/cil-ingest.ts +1418 -1154
- package/.framework/functions/cil-pipeline.ts +276 -21
- package/.framework/functions/cil-project.ts +659 -132
- package/.framework/functions/cil-validate.ts +198 -164
- package/.framework/functions/debug-auth.ts +57 -55
- package/.framework/functions/deliveries.ts +80 -21
- package/.framework/functions/embeddings.ts +1023 -318
- package/.framework/functions/eval-harness.ts +146 -108
- package/.framework/functions/integration-outbound.ts +205 -75
- package/.framework/functions/integration-routes.ts +128 -23
- package/.framework/functions/integrations.ts +125 -100
- package/.framework/functions/invites.ts +1095 -460
- package/.framework/functions/item-progress.ts +158 -102
- package/.framework/functions/kb.ts +34 -10
- package/.framework/functions/logs.ts +133 -133
- package/.framework/functions/observability.ts +152 -122
- package/.framework/functions/pipeline-executions.ts +147 -132
- package/.framework/functions/pipelines.ts +134 -119
- package/.framework/functions/prompt-configs.ts +96 -80
- package/.framework/functions/roles.ts +112 -82
- package/.framework/functions/security.ts +450 -0
- package/.framework/functions/system-cron.ts +117 -80
- package/.framework/functions/system.ts +158 -98
- package/.framework/functions/tests.ts +29 -29
- package/.framework/functions/timers.ts +94 -95
- package/.framework/functions/trigger-scheduler.ts +10 -0
- package/.framework/functions/triggers.ts +179 -149
- package/.framework/functions/types.ts +137 -125
- package/.framework/mcp/index.ts +133 -0
- package/.framework/mcp/load-env.ts +60 -0
- package/.framework/mcp/shared/app-manifest.ts +275 -0
- package/.framework/mcp/shared/db.ts +477 -0
- package/.framework/mcp/shared/kb.ts +118 -0
- package/.framework/mcp/tools/architect.ts +431 -0
- package/.framework/mcp/tools/cil/context.ts +345 -0
- package/.framework/mcp/tools/cil/graph.ts +242 -0
- package/.framework/mcp/tools/cil/ingest.ts +1217 -0
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
- package/.framework/mcp/tools/cil/pipeline.ts +767 -0
- package/.framework/mcp/tools/cil/project.ts +791 -0
- package/.framework/mcp/tools/cil/retrieval.ts +336 -0
- package/.framework/mcp/tools/cil/search-core.ts +454 -0
- package/.framework/mcp/tools/cil/search.ts +97 -0
- package/.framework/mcp/tools/cil/suggest.ts +186 -0
- package/.framework/mcp/tools/cil/validate.ts +1369 -0
- package/.framework/mcp/tools/kb/delete.ts +139 -0
- package/.framework/mcp/tools/kb/index.ts +52 -0
- package/.framework/mcp/tools/kb/ingest.ts +84 -0
- package/.framework/mcp/tools/kb/list-types.ts +102 -0
- package/.framework/mcp/tools/kb/publish.ts +100 -0
- package/.framework/mcp/tools/kb/reindex.ts +86 -0
- package/.framework/mcp/tools/kb/revoke.ts +85 -0
- package/.framework/mcp/tools/kb/search.ts +101 -0
- package/.framework/mcp/tools/kb/upsert.ts +125 -0
- package/.framework/mcp/tools/observability.ts +510 -0
- package/.framework/migrations/001_core_schema.sql +15 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
- package/.framework/migrations/003_core_system_data.sql +16 -1
- package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
- package/.framework/migrations/005_core_observability.sql +17 -1
- package/.framework/migrations/006_security_hardening_rls.sql +15 -0
- package/.framework/migrations/007_kb_article_list.sql +17 -1
- package/.framework/migrations/008_items_target.sql +15 -0
- package/.framework/migrations/009_roles_slug_unique.sql +15 -0
- package/.framework/migrations/010_auth_user_trigger.sql +15 -0
- package/.framework/migrations/011_spine_system_account.sql +16 -0
- package/.framework/migrations/012_service_role_grants.sql +16 -0
- package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
- package/.framework/migrations/014_apps_required_roles.sql +16 -0
- package/.framework/migrations/015_types_kind_slug_unique.sql +15 -0
- package/.framework/migrations/016_cil_types.sql +281 -0
- package/.framework/migrations/017_cil_stable_ids.sql +37 -0
- package/.framework/migrations/018_runtime_tables.sql +129 -0
- package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
- package/.framework/migrations/020_schema_reconciliation.sql +67 -0
- package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
- package/.framework/migrations/032_cil_status_migration.sql +73 -0
- package/.framework/migrations/033_cil_versioning.sql +73 -0
- package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
- package/.framework/migrations/035_rate_limits.sql +55 -0
- package/.framework/migrations/036_demo_record_flags.sql +72 -0
- package/.framework/migrations/037_kb_types_canonical.sql +244 -0
- package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
- package/.framework/migrations/042_registration_hierarchy.sql +309 -0
- package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
- package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
- package/.framework/migrations/045_security_critical_rls.sql +139 -0
- package/.framework/migrations/046_security_missing_rls.sql +79 -0
- package/.framework/migrations/047_people_updated_by.sql +27 -0
- package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
- package/.framework/migrations/049_security_blocklist.sql +148 -0
- package/.framework/migrations/050_notifications.sql +49 -0
- package/.framework/migrations/051_message_agent_fields.sql +74 -0
- package/.framework/migrations/052_registration_security.sql +421 -0
- package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
- package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
- package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
- package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
- package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
- package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
- package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
- package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
- package/.framework/migrations/061_fk_dedup.sql +32 -0
- package/.framework/public/spine-logo.svg +10 -3
- package/.framework/scripts/app-install-cli.ts +190 -93
- package/.framework/scripts/assemble-frontend.sh +127 -44
- package/.framework/scripts/assemble-functions.sh +128 -30
- package/.framework/scripts/boundary-check.sh +6 -2
- package/.framework/scripts/build-manifest.sh +24 -68
- package/.framework/scripts/check-core-integrity.sh +15 -76
- package/.framework/scripts/checksum-manifest.mts +343 -0
- package/.framework/scripts/cil-migrate-markers.ts +403 -0
- package/.framework/scripts/fix-cil-markers.mjs +176 -0
- package/.framework/scripts/verify-integrity.sh +21 -57
- package/.framework/scripts/verify-manifest.mts +305 -0
- package/.framework/seeds/types/funnel_signal_use.json +74 -21
- package/.framework/seeds/types/spine_instance.json +73 -21
- package/.framework/src/App.tsx +118 -72
- package/.framework/src/apps/admin/index.tsx +855 -58
- package/.framework/src/components/AppWrapper.tsx +43 -45
- package/.framework/src/components/CustomAppLoader.tsx +160 -116
- package/.framework/src/components/admin/AdminListPage.tsx +46 -44
- package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
- package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
- package/.framework/src/components/admin/PageInstructions.tsx +98 -2
- package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
- package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
- package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
- package/.framework/src/components/app-sidebar.tsx +42 -45
- package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
- package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
- package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
- package/.framework/src/components/kb/KBArticleList.tsx +251 -10
- package/.framework/src/components/layout/AppShell.tsx +51 -45
- package/.framework/src/components/layout/Header.tsx +42 -43
- package/.framework/src/components/layout/Layout.tsx +46 -46
- package/.framework/src/components/layout/Sidebar.tsx +196 -96
- package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
- package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
- package/.framework/src/components/runtime/DataFilters.tsx +49 -74
- package/.framework/src/components/runtime/DataHeader.tsx +53 -32
- package/.framework/src/components/runtime/DataListPage.tsx +51 -155
- package/.framework/src/components/runtime/DataStats.tsx +66 -44
- package/.framework/src/components/runtime/DataTable.tsx +49 -149
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
- package/.framework/src/components/runtime/index.ts +119 -16
- package/.framework/src/components/search-form.tsx +46 -29
- package/.framework/src/components/shared/AgentView.tsx +65 -48
- package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
- package/.framework/src/components/shared/InviteManager.tsx +207 -97
- package/.framework/src/components/shared/MemberList.tsx +97 -0
- package/.framework/src/components/shared/SchemaFields.tsx +107 -86
- package/.framework/src/components/ui/DataTable.tsx +80 -68
- package/.framework/src/components/ui/Form.tsx +157 -146
- package/.framework/src/components/ui/ItemCard.tsx +74 -67
- package/.framework/src/components/ui/ItemListView.tsx +73 -68
- package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
- package/.framework/src/components/ui/Modal.tsx +78 -68
- package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
- package/.framework/src/components/ui/accordion.tsx +85 -86
- package/.framework/src/components/ui/alert-dialog.tsx +197 -184
- package/.framework/src/components/ui/alert.tsx +105 -94
- package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
- package/.framework/src/components/ui/avatar.tsx +122 -107
- package/.framework/src/components/ui/badge.tsx +60 -55
- package/.framework/src/components/ui/breadcrumb.tsx +127 -128
- package/.framework/src/components/ui/button-group.tsx +86 -72
- package/.framework/src/components/ui/button.tsx +71 -50
- package/.framework/src/components/ui/calendar.tsx +72 -57
- package/.framework/src/components/ui/card.tsx +136 -129
- package/.framework/src/components/ui/carousel.tsx +226 -107
- package/.framework/src/components/ui/chart.tsx +252 -147
- package/.framework/src/components/ui/checkbox.tsx +43 -44
- package/.framework/src/components/ui/collapsible.tsx +86 -71
- package/.framework/src/components/ui/combobox.tsx +282 -249
- package/.framework/src/components/ui/command.tsx +158 -146
- package/.framework/src/components/ui/context-menu.tsx +239 -240
- package/.framework/src/components/ui/dialog.tsx +174 -157
- package/.framework/src/components/ui/direction.tsx +72 -56
- package/.framework/src/components/ui/drawer.tsx +168 -169
- package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
- package/.framework/src/components/ui/empty.tsx +131 -120
- package/.framework/src/components/ui/field.tsx +205 -173
- package/.framework/src/components/ui/hover-card.tsx +71 -72
- package/.framework/src/components/ui/input-group.tsx +148 -133
- package/.framework/src/components/ui/input-otp.tsx +99 -86
- package/.framework/src/components/ui/input.tsx +43 -41
- package/.framework/src/components/ui/item.tsx +207 -185
- package/.framework/src/components/ui/kbd.tsx +57 -56
- package/.framework/src/components/ui/label.tsx +45 -41
- package/.framework/src/components/ui/menubar.tsx +268 -236
- package/.framework/src/components/ui/native-select.tsx +85 -86
- package/.framework/src/components/ui/navigation-menu.tsx +166 -156
- package/.framework/src/components/ui/pagination.tsx +131 -141
- package/.framework/src/components/ui/popover.tsx +125 -128
- package/.framework/src/components/ui/progress.tsx +47 -44
- package/.framework/src/components/ui/radio-group.tsx +60 -55
- package/.framework/src/components/ui/resizable.tsx +85 -72
- package/.framework/src/components/ui/scroll-area.tsx +57 -58
- package/.framework/src/components/ui/select.tsx +182 -169
- package/.framework/src/components/ui/separator.tsx +42 -43
- package/.framework/src/components/ui/sheet.tsx +170 -159
- package/.framework/src/components/ui/sidebar.tsx +395 -398
- package/.framework/src/components/ui/skeleton.tsx +42 -43
- package/.framework/src/components/ui/slider.tsx +61 -44
- package/.framework/src/components/ui/sonner.tsx +44 -44
- package/.framework/src/components/ui/spinner.tsx +47 -42
- package/.framework/src/components/ui/switch.tsx +56 -42
- package/.framework/src/components/ui/table-primitives.tsx +272 -132
- package/.framework/src/components/ui/table.tsx +140 -142
- package/.framework/src/components/ui/tabs.tsx +110 -93
- package/.framework/src/components/ui/textarea.tsx +43 -44
- package/.framework/src/components/ui/toggle-group.tsx +87 -71
- package/.framework/src/components/ui/toggle.tsx +56 -56
- package/.framework/src/components/ui/tooltip.tsx +99 -85
- package/.framework/src/contexts/AppContext.tsx +166 -114
- package/.framework/src/contexts/AuthContext.tsx +232 -137
- package/.framework/src/hooks/use-mobile.ts +43 -44
- package/.framework/src/hooks/useApi.ts +185 -172
- package/.framework/src/hooks/useAppPath.ts +31 -29
- package/.framework/src/hooks/useApps.ts +77 -42
- package/.framework/src/hooks/useEntityList.ts +66 -29
- package/.framework/src/hooks/useEntityRecord.ts +76 -32
- package/.framework/src/hooks/useForm.ts +62 -30
- package/.framework/src/hooks/useListSchema.ts +60 -43
- package/.framework/src/hooks/useSchemaRecord.ts +115 -95
- package/.framework/src/lib/api.ts +258 -134
- package/.framework/src/lib/registration.ts +181 -0
- package/.framework/src/lib/supabase.ts +118 -81
- package/.framework/src/lib/utils.ts +266 -266
- package/.framework/src/main.tsx +43 -44
- package/.framework/src/pages/DashboardPage.tsx +44 -43
- package/.framework/src/pages/NoAppPage.tsx +50 -0
- package/.framework/src/pages/NotFoundPage.tsx +32 -29
- package/.framework/src/pages/SetupPage.tsx +34 -33
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
- package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
- package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
- package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
- package/.framework/src/pages/admin/AppsPage.tsx +45 -42
- package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
- package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
- package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
- package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
- package/.framework/src/pages/admin/LogsPage.tsx +87 -137
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
- package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
- package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
- package/.framework/src/pages/admin/RolesPage.tsx +47 -164
- package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
- package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
- package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
- package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
- package/.framework/src/pages/admin/TimersPage.tsx +44 -42
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
- package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
- package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
- package/.framework/src/pages/admin/TypesPage.tsx +45 -42
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
- package/.framework/src/pages/auth/LoginPage.tsx +38 -32
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
- package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
- package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
- package/.framework/src/types/auth.ts +101 -93
- package/.framework/src/types/types.ts +283 -262
- package/.framework/templates/project.json +87 -0
- package/.framework/templates/spine.config.neutral.json +7 -0
- package/.framework/tests/api/api-surface.test.ts +381 -0
- package/.framework/tests/fixtures/seed.ts +427 -0
- package/.framework/tests/fixtures/teardown.ts +228 -0
- package/.framework/tests/reporter.ts +351 -0
- package/.framework/tests/setup.ts +135 -0
- package/.framework/tests/ui/ui-sweep.ts +278 -0
- package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
- package/.framework/tests/unit/agent-runner.test.ts +2389 -0
- package/.framework/tests/unit/ai-interactions.test.ts +81 -0
- package/.framework/tests/unit/app-manifest.test.ts +359 -0
- package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
- package/.framework/tests/unit/cil-determinism.test.ts +85 -0
- package/.framework/tests/unit/core-isolation.test.ts +153 -0
- package/.framework/tests/unit/funnel-signals.test.ts +135 -0
- package/.framework/tests/unit/install-app.test.ts +1759 -0
- package/.framework/tests/unit/invites-security.test.ts +572 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
- package/.framework/tests/unit/kb-search.test.ts +119 -0
- package/.framework/tests/unit/migrate.test.ts +509 -0
- package/.framework/tests/unit/notify.test.ts +136 -0
- package/.framework/tests/unit/permissions.test.ts +269 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
- package/.framework/tests/unit/principal-cache.test.ts +138 -0
- package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
- package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
- package/.framework/tests/unit/schema-utils.test.ts +225 -0
- package/.framework/tests/unit/trigger-engine.test.ts +513 -0
- package/.framework/tests/unit/validate-app.test.ts +527 -0
- package/README.md +34 -3
- package/bin/spine-framework.cjs +1 -0
- package/config/tailwind.config.cjs +7 -0
- package/config/tsconfig.functions.json +42 -0
- package/config/tsconfig.json +37 -9
- package/config/vite.config.ts +31 -2
- package/netlify.toml +31 -3
- package/package.json +46 -29
- package/scripts/assemble-frontend.sh +127 -44
- package/scripts/assemble-functions.sh +127 -29
- package/scripts/boundary-check.sh +6 -2
- package/scripts/build-manifest.sh +24 -68
- package/scripts/checksum-manifest.mts +345 -0
- package/scripts/dev-clean.sh +86 -0
- package/scripts/dev-netlify.sh +49 -0
- package/scripts/netlify-dev-wrapper.sh +2 -0
- package/scripts/verify-install.ts +358 -0
- package/scripts/verify-integrity.sh +21 -57
- package/scripts/verify-manifest.mts +305 -0
- package/templates/project/gitignore +45 -0
- package/{config → templates/project}/package-lock.json +22335 -9366
- package/vitest.config.ts +46 -0
- package/.framework/public/spine.config.json +0 -91
- package/config/deno.lock +0 -108
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/vite.config.d.ts +0 -2
- package/custom/.devin/AGENT.md +0 -357
- package/custom/.devin/skills/install-app/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
- package/custom/.devin/workflows/app-install.yaml +0 -83
- package/custom/README.md +0 -79
- package/docs/README.md +0 -15
- package/scripts/app-install-cli.ts +0 -286
- package/scripts/check-core-integrity.sh +0 -82
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/kb-ingest-docs.ts +0 -203
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/test-login.js +0 -33
- package/spine.config.json +0 -91
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:module_header:module_header" type="module_header" identity="module_header" parent="~/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="~/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="~/scripts/checksum-manifest.mts:PROTECTED_DIRS:constant" type="constant" identity="PROTECTED_DIRS" parent="~/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="~/scripts/checksum-manifest.mts:PROTECTED_DIRS:constant"]
|
|
31
|
+
|
|
32
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:EXCLUDED_DIR_NAMES:constant" type="constant" identity="EXCLUDED_DIR_NAMES" parent="~/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="~/scripts/checksum-manifest.mts:EXCLUDED_DIR_NAMES:constant"]
|
|
54
|
+
|
|
55
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:LOCKFILE_NAMES:constant" type="constant" identity="LOCKFILE_NAMES" parent="~/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="~/scripts/checksum-manifest.mts:LOCKFILE_NAMES:constant"]
|
|
73
|
+
|
|
74
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:isExcludedName:function" type="function" identity="isExcludedName" parent="~/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
|
+
// Generated by `tsc -b` (tsconfig.node.json composite emit) — output, not source
|
|
88
|
+
if (name === 'vite.config.d.ts') return true
|
|
89
|
+
if (LOCKFILE_NAMES.has(name)) return true
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:isExcludedName:function"]
|
|
93
|
+
|
|
94
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:ManifestEntry:interface" type="interface" identity="ManifestEntry" parent="~/scripts/checksum-manifest.mts"]
|
|
95
|
+
// CIL[DOC-MACRO purpose="One enumerated protected file/symlink awaiting hashing"]
|
|
96
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Carry the POSIX-relative path, absolute path and symlink flag through enumeration to hashing"]
|
|
97
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
|
|
98
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
99
|
+
// 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"]
|
|
100
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
101
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
102
|
+
export interface ManifestEntry {
|
|
103
|
+
/** POSIX-style path relative to root, e.g. ".framework/functions/kb.ts" */
|
|
104
|
+
rel: string
|
|
105
|
+
/** Absolute filesystem path */
|
|
106
|
+
abs: string
|
|
107
|
+
/** True when the entry itself is a symlink (hashed by link target, not content) */
|
|
108
|
+
symlink: boolean
|
|
109
|
+
}
|
|
110
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:ManifestEntry:interface"]
|
|
111
|
+
|
|
112
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:collectProtectedFiles:function" type="function" identity="collectProtectedFiles" parent="~/scripts/checksum-manifest.mts"]
|
|
113
|
+
// CIL[DOC-MACRO purpose="Enumerate all hashable entries under the protected trees"]
|
|
114
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Recursively walk each protected dir, apply exclusion rules, and return entries sorted by UTF-8 byte order"]
|
|
115
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
|
|
116
|
+
// 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"]
|
|
117
|
+
// 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"]
|
|
118
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
119
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
120
|
+
export function collectProtectedFiles(
|
|
121
|
+
root: string,
|
|
122
|
+
includeDirs: readonly string[] = [],
|
|
123
|
+
excludeRels: ReadonlySet<string> = new Set(),
|
|
124
|
+
): ManifestEntry[] {
|
|
125
|
+
const rootAbs = path.resolve(root)
|
|
126
|
+
const dirs = [...PROTECTED_DIRS, ...includeDirs]
|
|
127
|
+
const out: ManifestEntry[] = []
|
|
128
|
+
|
|
129
|
+
for (const dir of dirs) {
|
|
130
|
+
const dirAbs = path.join(rootAbs, dir)
|
|
131
|
+
if (!fs.existsSync(dirAbs)) continue
|
|
132
|
+
const stack: string[] = [dirAbs]
|
|
133
|
+
|
|
134
|
+
while (stack.length > 0) {
|
|
135
|
+
const current = stack.pop() as string
|
|
136
|
+
for (const dent of fs.readdirSync(current, { withFileTypes: true })) {
|
|
137
|
+
const abs = path.join(current, dent.name)
|
|
138
|
+
const rel = path.relative(rootAbs, abs).split(path.sep).join('/')
|
|
139
|
+
if (excludeRels.has(rel)) continue
|
|
140
|
+
|
|
141
|
+
if (dent.isDirectory()) {
|
|
142
|
+
if (EXCLUDED_DIR_NAMES.has(dent.name)) continue
|
|
143
|
+
stack.push(abs)
|
|
144
|
+
} else if (dent.isSymbolicLink()) {
|
|
145
|
+
if (!isExcludedName(dent.name)) out.push({ rel, abs, symlink: true })
|
|
146
|
+
} else if (dent.isFile()) {
|
|
147
|
+
if (!isExcludedName(dent.name)) out.push({ rel, abs, symlink: false })
|
|
148
|
+
}
|
|
149
|
+
// sockets/fifos/etc are not hashable — skipped
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
out.sort((a, b) => Buffer.compare(Buffer.from(a.rel, 'utf8'), Buffer.from(b.rel, 'utf8')))
|
|
155
|
+
return out
|
|
156
|
+
}
|
|
157
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:collectProtectedFiles:function"]
|
|
158
|
+
|
|
159
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:hashEntry:function" type="function" identity="hashEntry" parent="~/scripts/checksum-manifest.mts"]
|
|
160
|
+
// CIL[DOC-MACRO purpose="Compute the sha256 integrity hash for one manifest entry"]
|
|
161
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Hash regular file bytes, or the symlink target text for links, returning a 'sha256:<hex>' value"]
|
|
162
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
|
|
163
|
+
// CIL[DOC-CONTRACT inputs="entry: ManifestEntry" outputs="'sha256:<hex>' string" errors="throws on unreadable file" side_effects="reads file content"]
|
|
164
|
+
// 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"]
|
|
165
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
166
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
167
|
+
export function hashEntry(entry: ManifestEntry): string {
|
|
168
|
+
const hash = createHash('sha256')
|
|
169
|
+
if (entry.symlink) {
|
|
170
|
+
hash.update('symlink:')
|
|
171
|
+
hash.update(fs.readlinkSync(entry.abs))
|
|
172
|
+
} else {
|
|
173
|
+
hash.update(fs.readFileSync(entry.abs))
|
|
174
|
+
}
|
|
175
|
+
return `sha256:${hash.digest('hex')}`
|
|
176
|
+
}
|
|
177
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:hashEntry:function"]
|
|
178
|
+
|
|
179
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:topLevelDir:function" type="function" identity="topLevelDir" parent="~/scripts/checksum-manifest.mts"]
|
|
180
|
+
// CIL[DOC-MACRO purpose="Extract the first path segment of a manifest-relative path"]
|
|
181
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Let the verifier union manifest-listed trees with the default protected dirs"]
|
|
182
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
|
|
183
|
+
// CIL[DOC-CONTRACT inputs="rel: POSIX-relative path" outputs="first segment, or null for root-level files" errors="none" side_effects="none"]
|
|
184
|
+
// CIL[DOC-MICRO logic_summary="Split on '/' and return the head" notable_decisions="root-level listed files contribute no extra tree"]
|
|
185
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
186
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
187
|
+
export function topLevelDir(rel: string): string | null {
|
|
188
|
+
const idx = rel.indexOf('/')
|
|
189
|
+
return idx === -1 ? null : rel.slice(0, idx)
|
|
190
|
+
}
|
|
191
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:topLevelDir:function"]
|
|
192
|
+
|
|
193
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:getSourceCommit:function" type="function" identity="getSourceCommit" parent="~/scripts/checksum-manifest.mts"]
|
|
194
|
+
// CIL[DOC-MACRO purpose="Resolve source_commit: HEAD only when the worktree is clean"]
|
|
195
|
+
// 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"]
|
|
196
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/checksum-manifest.mts"]
|
|
197
|
+
// 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"]
|
|
198
|
+
// 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"]
|
|
199
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
200
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
201
|
+
function getSourceCommit(root: string, manifestRel: string): string | null {
|
|
202
|
+
try {
|
|
203
|
+
const head = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
204
|
+
cwd: root,
|
|
205
|
+
encoding: 'utf8',
|
|
206
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
207
|
+
}).trim()
|
|
208
|
+
const porcelain = execFileSync('git', ['status', '--porcelain'], {
|
|
209
|
+
cwd: root,
|
|
210
|
+
encoding: 'utf8',
|
|
211
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
212
|
+
})
|
|
213
|
+
const dirty = porcelain
|
|
214
|
+
.split('\n')
|
|
215
|
+
.map((line) => line.slice(3).trim().replace(/^"|"$/g, ''))
|
|
216
|
+
.filter((p) => p.length > 0 && p !== manifestRel)
|
|
217
|
+
return dirty.length === 0 ? head : null
|
|
218
|
+
} catch {
|
|
219
|
+
return null
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:getSourceCommit:function"]
|
|
223
|
+
|
|
224
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:getFrameworkVersion:function" type="function" identity="getFrameworkVersion" parent="~/scripts/checksum-manifest.mts"]
|
|
225
|
+
// CIL[DOC-MACRO purpose="Resolve framework_version from package.json"]
|
|
226
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Read <root>/package.json and return its version field; null when unreadable"]
|
|
227
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="package.json"]
|
|
228
|
+
// 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"]
|
|
229
|
+
// 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"]
|
|
230
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
231
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
232
|
+
function getFrameworkVersion(root: string): string | null {
|
|
233
|
+
try {
|
|
234
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
|
|
235
|
+
return typeof pkg.version === 'string' ? pkg.version : null
|
|
236
|
+
} catch {
|
|
237
|
+
return null
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:getFrameworkVersion:function"]
|
|
241
|
+
|
|
242
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:computeTreeDigest:function" type="function" identity="computeTreeDigest" parent="~/scripts/checksum-manifest.mts"]
|
|
243
|
+
// CIL[DOC-MACRO purpose="Compute the tree_digest over the sorted path:hash list"]
|
|
244
|
+
// CIL[DOC-RESPONSIBILITY responsibility="sha256 over 'rel:hash' lines joined with LF and a trailing LF — order matches the sorted files map"]
|
|
245
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/verify-manifest.mts"]
|
|
246
|
+
// CIL[DOC-CONTRACT inputs="files: sorted [rel, hash] pairs" outputs="'sha256:<hex>'" errors="none" side_effects="none"]
|
|
247
|
+
// 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"]
|
|
248
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
249
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
250
|
+
export function computeTreeDigest(files: ReadonlyArray<readonly [string, string]>): string {
|
|
251
|
+
const hash = createHash('sha256')
|
|
252
|
+
for (const [rel, fileHash] of files) {
|
|
253
|
+
hash.update(`${rel}:${fileHash}\n`)
|
|
254
|
+
}
|
|
255
|
+
return `sha256:${hash.digest('hex')}`
|
|
256
|
+
}
|
|
257
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:computeTreeDigest:function"]
|
|
258
|
+
|
|
259
|
+
// CIL[BEGIN stable_id="~/scripts/checksum-manifest.mts:main:function" type="function" identity="main" parent="~/scripts/checksum-manifest.mts"]
|
|
260
|
+
// CIL[DOC-MACRO purpose="CLI entry — parse args, hash protected trees, write .spine-manifest.json"]
|
|
261
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Validate argv, build the manifest object, serialize with sorted keys and LF endings, print a summary"]
|
|
262
|
+
// CIL[DOC-CONTEXT module="spine-integrity" layer="build" related_files="scripts/build-manifest.sh,package.json"]
|
|
263
|
+
// 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"]
|
|
264
|
+
// 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"]
|
|
265
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
266
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
267
|
+
function main(): void {
|
|
268
|
+
const args = process.argv.slice(2)
|
|
269
|
+
const positional: string[] = []
|
|
270
|
+
const includeDirs: string[] = []
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < args.length; i++) {
|
|
273
|
+
const arg = args[i]
|
|
274
|
+
if (arg === '--include-dir') {
|
|
275
|
+
const dir = args[++i]
|
|
276
|
+
if (!dir) {
|
|
277
|
+
console.error('checksum-manifest: --include-dir requires a value')
|
|
278
|
+
process.exit(2)
|
|
279
|
+
}
|
|
280
|
+
includeDirs.push(dir)
|
|
281
|
+
} else if (arg.startsWith('--')) {
|
|
282
|
+
console.error(`checksum-manifest: unknown flag ${arg}`)
|
|
283
|
+
process.exit(2)
|
|
284
|
+
} else {
|
|
285
|
+
positional.push(arg)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (positional.length !== 2) {
|
|
290
|
+
console.error('usage: checksum-manifest.mts <root> <output> [--include-dir <d>...]')
|
|
291
|
+
process.exit(2)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const root = path.resolve(positional[0])
|
|
295
|
+
const output = path.resolve(positional[1])
|
|
296
|
+
|
|
297
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
298
|
+
console.error(`checksum-manifest: root is not a directory: ${root}`)
|
|
299
|
+
process.exit(1)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const extraDirs: string[] = []
|
|
303
|
+
for (const dir of includeDirs) {
|
|
304
|
+
const rel = path.relative(root, path.resolve(root, dir)).split(path.sep).join('/')
|
|
305
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
306
|
+
console.error(`checksum-manifest: --include-dir escapes root: ${dir}`)
|
|
307
|
+
process.exit(2)
|
|
308
|
+
}
|
|
309
|
+
extraDirs.push(rel)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const manifestRel = path.relative(root, output).split(path.sep).join('/')
|
|
313
|
+
const excludeRels = new Set<string>([manifestRel])
|
|
314
|
+
|
|
315
|
+
const entries = collectProtectedFiles(root, extraDirs, excludeRels)
|
|
316
|
+
const pairs: Array<readonly [string, string]> = entries.map(
|
|
317
|
+
(entry) => [entry.rel, hashEntry(entry)] as const,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
const files: Record<string, string> = {}
|
|
321
|
+
for (const [rel, hash] of pairs) files[rel] = hash
|
|
322
|
+
|
|
323
|
+
const manifest = {
|
|
324
|
+
format_version: 1,
|
|
325
|
+
frozen: true,
|
|
326
|
+
framework_version: getFrameworkVersion(root),
|
|
327
|
+
source_commit: getSourceCommit(root, manifestRel),
|
|
328
|
+
tree_digest: computeTreeDigest(pairs),
|
|
329
|
+
node: '>=22.13.0',
|
|
330
|
+
npm: '>=10.0.0',
|
|
331
|
+
files,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fs.writeFileSync(output, JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
335
|
+
|
|
336
|
+
console.log(` manifest: ${manifestRel}`)
|
|
337
|
+
console.log(` files hashed: ${pairs.length}`)
|
|
338
|
+
console.log(` tree_digest: ${manifest.tree_digest}`)
|
|
339
|
+
console.log(` source_commit: ${manifest.source_commit ?? 'null (dirty worktree or no git)'}`)
|
|
340
|
+
}
|
|
341
|
+
// CIL[END stable_id="~/scripts/checksum-manifest.mts:main:function"]
|
|
342
|
+
|
|
343
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
344
|
+
main()
|
|
345
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# CIL[BEGIN stable_id="~/scripts/dev-clean.sh:module:script" type="script" identity="dev-clean" parent="~/scripts/dev-clean.sh"]
|
|
4
|
+
# CIL[DOC-MACRO purpose="Kill leftover dev-server processes for THIS repo only — never cross-project port holders."]
|
|
5
|
+
# CIL[DOC-RESPONSIBILITY responsibility="Free dev ports 8888/3001 when the listener's cwd is under PROJECT_ROOT; kill repo-scoped vite/netlify/esbuild/lambda-local leftovers; warn (not kill) foreign listeners."]
|
|
6
|
+
# CIL[DOC-CONTEXT module="dev-tooling" layer="dx" related_files="scripts/dev-netlify.sh,scripts/netlify-dev-wrapper.sh,package.json"]
|
|
7
|
+
# CIL[DOC-CONTRACT inputs="running processes + port listeners" outputs="freed repo dev ports; warnings for foreign listeners" errors="nonzero-free report only" side_effects="SIGTERM to repo-scoped dev processes"]
|
|
8
|
+
# CIL[DOC-MICRO logic_summary="lsof port listeners → cwd check via lsof -p awk $4==cwd → kill only under PROJECT_ROOT; pgrep -f repo path for leftovers; final remaining-listener report." notable_decisions="ports are global across projects, so cwd-scoping is mandatory"]
|
|
9
|
+
# CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
10
|
+
# CIL[DOC-STABILITY stability="stable"]
|
|
11
|
+
# CIL[END stable_id="~/scripts/dev-clean.sh:module:script"]
|
|
12
|
+
|
|
13
|
+
# Dev Process Cleanup - kills leftover dev-server processes for THIS repo.
|
|
14
|
+
#
|
|
15
|
+
# Targets, in order:
|
|
16
|
+
# 1. Whatever is LISTENING on the dev ports (8888 netlify, 3001 vite) — but
|
|
17
|
+
# only when the listener's cwd is inside this repo; foreign listeners get
|
|
18
|
+
# a warning, not a kill (ports are shared across projects)
|
|
19
|
+
# 2. Orphaned processes spawned from this repo (vite, netlify, lambda-local,
|
|
20
|
+
# esbuild service workers) — matched by project path in their cmdline
|
|
21
|
+
# 3. Processes named like netlify dev children whose cwd is this repo
|
|
22
|
+
#
|
|
23
|
+
# Deliberately does NOT kill the Devin MCP `tsx` server — it is matched by
|
|
24
|
+
# cmdline pattern only when it contains vite/netlify/esbuild/lambda-local.
|
|
25
|
+
#
|
|
26
|
+
# Usage: npm run dev:stop
|
|
27
|
+
|
|
28
|
+
set -uo pipefail
|
|
29
|
+
|
|
30
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
31
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
32
|
+
|
|
33
|
+
killed=0
|
|
34
|
+
note() { echo " $1"; }
|
|
35
|
+
|
|
36
|
+
# 1. Kill listeners on the dev ports — ONLY when the listener's cwd is under
|
|
37
|
+
# this repo. Ports are global: another project's dev server on 8888/3001 must
|
|
38
|
+
# be warned about, never killed.
|
|
39
|
+
for port in 8888 3001; do
|
|
40
|
+
pids=$(lsof -ti :"$port" -sTCP:LISTEN 2>/dev/null || true)
|
|
41
|
+
if [ -n "$pids" ]; then
|
|
42
|
+
for pid in $pids; do
|
|
43
|
+
cmd=$(ps -p "$pid" -o command= 2>/dev/null | cut -c1-80)
|
|
44
|
+
cwd=$(lsof -p "$pid" 2>/dev/null | awk '$4=="cwd" {print $NF}' | head -1)
|
|
45
|
+
case "$cwd" in
|
|
46
|
+
"$PROJECT_ROOT"|"$PROJECT_ROOT"/*)
|
|
47
|
+
note "port $port: killing $cmd (pid $pid)"
|
|
48
|
+
kill "$pid" 2>/dev/null || true
|
|
49
|
+
killed=$((killed + 1))
|
|
50
|
+
;;
|
|
51
|
+
*)
|
|
52
|
+
note "port $port held by ${cmd:-unknown} (pid $pid) outside this repo — skipping"
|
|
53
|
+
;;
|
|
54
|
+
esac
|
|
55
|
+
done
|
|
56
|
+
fi
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
# 2. Kill repo-scoped node leftovers (vite/netlify/lambda-local/esbuild under this tree)
|
|
60
|
+
pgrep -f "$PROJECT_ROOT" 2>/dev/null | while read -r pid; do
|
|
61
|
+
cmd=$(ps -p "$pid" -o command= 2>/dev/null || true)
|
|
62
|
+
case "$cmd" in
|
|
63
|
+
*vite*|*netlify*|*esbuild*|*lambda-local*)
|
|
64
|
+
note "repo proc: killing $(echo "$cmd" | cut -c1-80) (pid $pid)"
|
|
65
|
+
kill "$pid" 2>/dev/null || true
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
# 3. Kill netlify-dev children whose cwd is this repo (cmdline has no path)
|
|
71
|
+
pgrep -f "netlify dev|netlify-cli|lambda-local" 2>/dev/null | while read -r pid; do
|
|
72
|
+
cwd=$(lsof -p "$pid" 2>/dev/null | awk '$4=="cwd" {print $NF}' | head -1)
|
|
73
|
+
if [ "$cwd" = "$PROJECT_ROOT" ]; then
|
|
74
|
+
note "netlify child: killing $(ps -p "$pid" -o command= 2>/dev/null | cut -c1-80) (pid $pid)"
|
|
75
|
+
kill "$pid" 2>/dev/null || true
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
sleep 1
|
|
80
|
+
# Report anything still holding the ports
|
|
81
|
+
remaining=$(lsof -ti :8888 -i :3001 -sTCP:LISTEN 2>/dev/null | wc -l | tr -d ' ')
|
|
82
|
+
if [ "$remaining" = "0" ]; then
|
|
83
|
+
echo "✅ Ports 8888/3001 free"
|
|
84
|
+
else
|
|
85
|
+
echo "⚠ $remaining process(es) still listening — re-run with sudo or kill -9 manually"
|
|
86
|
+
fi
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# Netlify Dev Launcher - starts `netlify dev` with raised resource limits.
|
|
4
|
+
# Uses the pinned local binary (devDependency netlify-cli) — never npx,
|
|
5
|
+
# which would auto-download whatever version the registry happens to serve.
|
|
6
|
+
#
|
|
7
|
+
# Why: this project has ~100 bundled functions. macOS's default soft file
|
|
8
|
+
# descriptor limit (256) and default Node heap are too small when Netlify
|
|
9
|
+
# reloads many function bundles at once, which previously crashed the dev
|
|
10
|
+
# server mid-reload with no error output.
|
|
11
|
+
#
|
|
12
|
+
# Also cleans up stale listeners/orphans before starting and on exit —
|
|
13
|
+
# `netlify dev` has a habit of leaving the Vite child behind when it dies.
|
|
14
|
+
#
|
|
15
|
+
# Launch is fragile: do NOT hit http://localhost:8888 (UI or functions) until
|
|
16
|
+
# the "Local dev server ready" banner prints — early requests during function
|
|
17
|
+
# bundling reliably crash the whole dev server.
|
|
18
|
+
#
|
|
19
|
+
# Usage: npm run dev:netlify (or: bash scripts/dev-netlify.sh)
|
|
20
|
+
|
|
21
|
+
set -uo pipefail
|
|
22
|
+
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
24
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
25
|
+
cd "$PROJECT_ROOT"
|
|
26
|
+
|
|
27
|
+
echo "🧹 Cleaning stale dev processes..."
|
|
28
|
+
bash "$SCRIPT_DIR/dev-clean.sh" || true
|
|
29
|
+
|
|
30
|
+
ulimit -n 10240 2>/dev/null || true
|
|
31
|
+
export NODE_OPTIONS="${NODE_OPTIONS:-} --max-old-space-size=8192"
|
|
32
|
+
|
|
33
|
+
# When netlify dev exits (crash, Ctrl-C, reload storm), reap the Vite child
|
|
34
|
+
# and anything else it left behind so ports are free for the next start.
|
|
35
|
+
cleanup() {
|
|
36
|
+
echo ""
|
|
37
|
+
echo "🧹 netlify dev exited — reaping leftovers..."
|
|
38
|
+
bash "$SCRIPT_DIR/dev-clean.sh" || true
|
|
39
|
+
}
|
|
40
|
+
trap cleanup EXIT INT TERM
|
|
41
|
+
|
|
42
|
+
NETLIFY="$PROJECT_ROOT/node_modules/.bin/netlify"
|
|
43
|
+
if [ ! -x "$NETLIFY" ]; then
|
|
44
|
+
echo "❌ netlify-cli not found at $NETLIFY" >&2
|
|
45
|
+
echo " Run 'npm install' first — netlify-cli is a pinned devDependency." >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
"$NETLIFY" dev "$@"
|