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.
- 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 +5051 -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 +20 -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,1369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module validate
|
|
3
|
+
* @audience internal
|
|
4
|
+
* @layer app-specific
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* This module provides validation utilities for the MCP (Model Context Protocol) server, ensuring that incoming data adheres to defined schemas. It plays a crucial role in maintaining data integrity and security within the application by validating requests before they are processed. INVARIANT: All incoming data must conform to the specified Zod schemas to prevent runtime errors.
|
|
8
|
+
*
|
|
9
|
+
* @seeAlso db.js (provides database access for validation checks).
|
|
10
|
+
* @seeAlso server.js (defines the MCP server context and its operations).
|
|
11
|
+
* @seeAlso zod (used for schema validation).
|
|
12
|
+
*
|
|
13
|
+
* @cil-generated
|
|
14
|
+
*/
|
|
15
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
16
|
+
// CIL[DOC-MACRO purpose="MCP validation utilities"]
|
|
17
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provides validation utilities for the MCP CIL server and registers the related MCP tools."]
|
|
18
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts"]
|
|
19
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="unknown" errors="none" side_effects="none"]
|
|
20
|
+
// CIL[DOC-MICRO logic_summary="Provides validation utilities for the MCP CIL server and registers the related MCP tools." notable_decisions="[]"]
|
|
21
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
22
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
23
|
+
// CIL[DOC-AUDIENCE audience="internal"]
|
|
24
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
25
|
+
import { z } from 'zod'
|
|
26
|
+
import { adminDb } from '../../shared/db.js'
|
|
27
|
+
import fs from 'node:fs'
|
|
28
|
+
import path from 'node:path'
|
|
29
|
+
import crypto from 'node:crypto'
|
|
30
|
+
import * as ts from 'typescript'
|
|
31
|
+
import { computeContentHash } from '../../../functions/_shared/cil.js'
|
|
32
|
+
// CIL[DOC-VECTOR type="context" content="purpose='MCP validation utilities'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
33
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
34
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
35
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
36
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
37
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
38
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:module_header:module_header"]
|
|
39
|
+
|
|
40
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:normaliseAgentChunkContent:function" type="function" identity="normaliseAgentChunkContent" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
41
|
+
// CIL[DOC-MACRO purpose="normaliseAgentChunkContent function"]
|
|
42
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the normaliseAgentChunkContent function."]
|
|
43
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
44
|
+
// CIL[DOC-CONTRACT inputs="raw: string" outputs="unknown" errors="none" side_effects="none"]
|
|
45
|
+
// CIL[DOC-MICRO logic_summary="Implements the normaliseAgentChunkContent function." notable_decisions="[]"]
|
|
46
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
47
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
48
|
+
function normaliseAgentChunkContent(raw: string): string {
|
|
49
|
+
const lines = raw.replace(/\r\n/g, '\n').split('\n')
|
|
50
|
+
const filtered = lines.filter(line => {
|
|
51
|
+
const trimmed = line.trim()
|
|
52
|
+
return !trimmed.match(/^(?:(?:\/\/|--|#)\s*|<!--\s*)CIL\[(BEGIN|END)/) && !trimmed.match(/^(?:(?:\/\/|--|#)\s*|<!--\s*)CIL\[DOC-/)
|
|
53
|
+
})
|
|
54
|
+
const collapsed: string[] = []
|
|
55
|
+
let lastBlank = false
|
|
56
|
+
for (const line of filtered) {
|
|
57
|
+
const isBlank = line.trim() === ''
|
|
58
|
+
if (isBlank && lastBlank) continue
|
|
59
|
+
collapsed.push(line)
|
|
60
|
+
lastBlank = isBlank
|
|
61
|
+
}
|
|
62
|
+
return collapsed.join('\n').trim()
|
|
63
|
+
}
|
|
64
|
+
// CIL[DOC-VECTOR type="context" content="purpose='normaliseAgentChunkContent function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
65
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
66
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
67
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
68
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
69
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
70
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:normaliseAgentChunkContent:function"]
|
|
71
|
+
|
|
72
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:computeChunkHash:function" type="function" identity="computeChunkHash" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
73
|
+
// CIL[DOC-MACRO purpose="computeChunkHash function"]
|
|
74
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the computeChunkHash function."]
|
|
75
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
76
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
77
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
78
|
+
// CIL[DOC-CONTRACT inputs="content: string" outputs="unknown" errors="none" side_effects="none"]
|
|
79
|
+
// CIL[DOC-MICRO logic_summary="Implements the computeChunkHash function." notable_decisions="[]"]
|
|
80
|
+
function computeChunkHash(content: string): string {
|
|
81
|
+
return crypto.createHash('sha256').update(normaliseAgentChunkContent(content), 'utf8').digest('hex').slice(0, 16)
|
|
82
|
+
}
|
|
83
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:computeChunkHash:function"]
|
|
84
|
+
|
|
85
|
+
// CIL[DOC-VECTOR type="context" content="purpose='computeChunkHash function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
86
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
87
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
88
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
89
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
90
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
91
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:getChunkByStableId:function" type="function" identity="getChunkByStableId" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
92
|
+
// CIL[DOC-MACRO purpose="getChunkByStableId function"]
|
|
93
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the getChunkByStableId function."]
|
|
94
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
95
|
+
// CIL[DOC-CONTRACT inputs="projectId: string, stableId: string, versionId?: string" outputs="unknown" errors="none" side_effects="none"]
|
|
96
|
+
// CIL[DOC-MICRO logic_summary="Implements the getChunkByStableId function." notable_decisions="[]"]
|
|
97
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
98
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
99
|
+
async function getChunkByStableId(projectId: string, stableId: string, versionId?: string) {
|
|
100
|
+
let q = adminDb.from('items').select('id, title, data')
|
|
101
|
+
.filter('data->>kb_type', 'eq', 'cil_chunk')
|
|
102
|
+
.filter('data->>project_id', 'eq', projectId)
|
|
103
|
+
.filter('data->>stable_id', 'eq', stableId)
|
|
104
|
+
.eq('is_active', true)
|
|
105
|
+
if (versionId) q = (q as any).filter('data->>version_id', 'eq', versionId)
|
|
106
|
+
const { data } = await (q as any).maybeSingle()
|
|
107
|
+
return data
|
|
108
|
+
}
|
|
109
|
+
// CIL[DOC-VECTOR type="context" content="purpose='getChunkByStableId function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
110
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
111
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
112
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
113
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
114
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
115
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:getChunkByStableId:function"]
|
|
116
|
+
|
|
117
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:parseAgentAttributes:function" type="function" identity="parseAgentAttributes" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
118
|
+
// CIL[DOC-MACRO purpose="parseAgentAttributes function"]
|
|
119
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the parseAgentAttributes function."]
|
|
120
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
121
|
+
// CIL[DOC-CONTRACT inputs="attrString: string" outputs="unknown" errors="none" side_effects="none"]
|
|
122
|
+
// CIL[DOC-MICRO logic_summary="Implements the parseAgentAttributes function." notable_decisions="[]"]
|
|
123
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
124
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
125
|
+
/**
|
|
126
|
+
* Parses a string of agent attributes formatted as key-value pairs into a record. This function is useful for extracting structured data from unstructured attribute strings.
|
|
127
|
+
*
|
|
128
|
+
* @inputSpec
|
|
129
|
+
* - attrString: string (required) — a string containing agent attributes in the format key="value".
|
|
130
|
+
* @outputSpec Record<string, string> — an object mapping attribute names to their corresponding values.
|
|
131
|
+
* @sideEffects none
|
|
132
|
+
* @stability stable
|
|
133
|
+
* @cil-generated
|
|
134
|
+
*/
|
|
135
|
+
function parseAgentAttributes(attrString: string): Record<string, string> {
|
|
136
|
+
const attrs: Record<string, string> = {}
|
|
137
|
+
const regex = /(\w+)="([^"]*)"/g
|
|
138
|
+
let match: RegExpExecArray | null
|
|
139
|
+
while ((match = regex.exec(attrString)) !== null) {
|
|
140
|
+
attrs[match[1]] = match[2]
|
|
141
|
+
}
|
|
142
|
+
return attrs
|
|
143
|
+
}
|
|
144
|
+
// CIL[DOC-VECTOR type="context" content="purpose='parseAgentAttributes function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
145
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
146
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
147
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
148
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
149
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
150
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:parseAgentAttributes:function"]
|
|
151
|
+
|
|
152
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:detectLanguage:function" type="function" identity="detectLanguage" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
153
|
+
// CIL[DOC-MACRO purpose="detectLanguage function"]
|
|
154
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the detectLanguage function."]
|
|
155
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
156
|
+
// CIL[DOC-CONTRACT inputs="filePath: string" outputs="unknown" errors="none" side_effects="none"]
|
|
157
|
+
// CIL[DOC-MICRO logic_summary="Implements the detectLanguage function." notable_decisions="[]"]
|
|
158
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
159
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
160
|
+
/**
|
|
161
|
+
* The detectLanguage function determines the programming language of a file based on its file extension. It maps known extensions to their corresponding languages for identification purposes.
|
|
162
|
+
*
|
|
163
|
+
* @macro Language detection utility for file types.
|
|
164
|
+
* @micro Utilizes a mapping object to return the language associated with the file extension.
|
|
165
|
+
* @inputSpec
|
|
166
|
+
* - filePath: string (required) — the path of the file whose language is to be detected.
|
|
167
|
+
* @outputSpec string — the detected language or 'unknown' if the extension is not recognized.
|
|
168
|
+
* @sideEffects none
|
|
169
|
+
* @stability stable
|
|
170
|
+
* @cil-generated
|
|
171
|
+
*/
|
|
172
|
+
function detectLanguage(filePath: string): string {
|
|
173
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
174
|
+
const map: Record<string, string> = {
|
|
175
|
+
'.ts': 'typescript',
|
|
176
|
+
'.tsx': 'typescriptreact',
|
|
177
|
+
'.js': 'javascript',
|
|
178
|
+
'.jsx': 'javascriptreact',
|
|
179
|
+
'.sql': 'sql',
|
|
180
|
+
'.yaml': 'yaml',
|
|
181
|
+
'.yml': 'yaml',
|
|
182
|
+
'.toml': 'toml',
|
|
183
|
+
'.sh': 'shell',
|
|
184
|
+
'.md': 'markdown',
|
|
185
|
+
}
|
|
186
|
+
return map[ext] || 'unknown'
|
|
187
|
+
}
|
|
188
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:detectLanguage:function"]
|
|
189
|
+
|
|
190
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:ParsedMarker:interface" type="interface" identity="ParsedMarker" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
191
|
+
// CIL[DOC-VECTOR type="context" content="purpose='detectLanguage function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
192
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
193
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
194
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
195
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
196
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
197
|
+
// CIL[DOC-MACRO purpose="ParsedMarker interface"]
|
|
198
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the ParsedMarker interface."]
|
|
199
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
200
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
201
|
+
// CIL[DOC-MICRO logic_summary="Implements the ParsedMarker interface." notable_decisions="[]"]
|
|
202
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
203
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
204
|
+
/**
|
|
205
|
+
* ParsedMarker defines the structure for a marker that has been parsed from a source file, capturing essential metadata about its location and type. This interface is used to facilitate the validation and processing of markers within the module.
|
|
206
|
+
*
|
|
207
|
+
* @sideEffects none
|
|
208
|
+
* @stability stable
|
|
209
|
+
* @cil-generated
|
|
210
|
+
*/
|
|
211
|
+
interface ParsedMarker {
|
|
212
|
+
stable_id: string
|
|
213
|
+
type: string
|
|
214
|
+
identity: string
|
|
215
|
+
parent: string
|
|
216
|
+
start_line: number
|
|
217
|
+
end_line: number
|
|
218
|
+
}
|
|
219
|
+
// CIL[DOC-VECTOR type="context" content="purpose='ParsedMarker interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
220
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
221
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
222
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
223
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
224
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
225
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:ParsedMarker:interface"]
|
|
226
|
+
|
|
227
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:ParseResult:interface" type="interface" identity="ParseResult" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
228
|
+
// CIL[DOC-MACRO purpose="ParseResult interface"]
|
|
229
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the ParseResult interface."]
|
|
230
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
231
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
232
|
+
// CIL[DOC-MICRO logic_summary="Implements the ParseResult interface." notable_decisions="[]"]
|
|
233
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
234
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
235
|
+
interface ParseResult {
|
|
236
|
+
markers: ParsedMarker[]
|
|
237
|
+
errors: Array<{ line: number; type: string; message: string }>
|
|
238
|
+
unclosed: Array<{ line: number; stable_id: string }>
|
|
239
|
+
orphan_ends: Array<{ line: number; stable_id: string }>
|
|
240
|
+
}
|
|
241
|
+
// CIL[DOC-VECTOR type="context" content="purpose='ParseResult interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
242
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
243
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
244
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
245
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
246
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
247
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:ParseResult:interface"]
|
|
248
|
+
|
|
249
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:parseCilMarkers:function" type="function" identity="parseCilMarkers" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
250
|
+
// CIL[DOC-MACRO purpose="parseCilMarkers function"]
|
|
251
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the parseCilMarkers function."]
|
|
252
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
253
|
+
// CIL[DOC-CONTRACT inputs="lines: string[]" outputs="unknown" errors="none" side_effects="none"]
|
|
254
|
+
// CIL[DOC-MICRO logic_summary="Implements the parseCilMarkers function." notable_decisions="[]"]
|
|
255
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
256
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
257
|
+
function parseCilMarkers(lines: string[]): ParseResult {
|
|
258
|
+
const result: ParseResult = { markers: [], errors: [], unclosed: [], orphan_ends: [] }
|
|
259
|
+
const stack: Array<{ stable_id: string; type: string; identity: string; parent: string; start_line: number; attrs: string }> = []
|
|
260
|
+
const beginRe = /^(?:\/\/|--|#)\s*CIL\[BEGIN\s+(.+)\]\s*$/
|
|
261
|
+
const endRe = /^(?:\/\/|--|#)\s*CIL\[END\s+stable_id="([^"]+)"[^\]]*\]\s*$/
|
|
262
|
+
const mdBeginRe = /^<!--\s*CIL\[BEGIN\s+(.+)\]\s*-->$/
|
|
263
|
+
const mdEndRe = /^<!--\s*CIL\[END\s+stable_id="([^"]+)"[^\]]*\]\s*-->$/
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < lines.length; i++) {
|
|
266
|
+
const line = lines[i]
|
|
267
|
+
const trimmed = line.trim()
|
|
268
|
+
let beginMatch = trimmed.match(beginRe)
|
|
269
|
+
let endMatch = trimmed.match(endRe)
|
|
270
|
+
if (!beginMatch) beginMatch = trimmed.match(mdBeginRe)
|
|
271
|
+
if (!endMatch) endMatch = trimmed.match(mdEndRe)
|
|
272
|
+
|
|
273
|
+
if (beginMatch) {
|
|
274
|
+
const attrs = parseAgentAttributes(beginMatch[1])
|
|
275
|
+
if (!attrs.stable_id) {
|
|
276
|
+
result.errors.push({ line: i + 1, type: 'missing_stable_id', message: 'BEGIN marker has no stable_id attribute' })
|
|
277
|
+
continue
|
|
278
|
+
}
|
|
279
|
+
stack.push({
|
|
280
|
+
stable_id: attrs.stable_id,
|
|
281
|
+
type: attrs.type || 'unknown',
|
|
282
|
+
identity: attrs.identity || '',
|
|
283
|
+
parent: attrs.parent || 'none',
|
|
284
|
+
start_line: i + 1,
|
|
285
|
+
attrs: beginMatch[1],
|
|
286
|
+
})
|
|
287
|
+
} else if (endMatch) {
|
|
288
|
+
const endStableId = endMatch[1]
|
|
289
|
+
if (stack.length === 0) {
|
|
290
|
+
result.orphan_ends.push({ line: i + 1, stable_id: endStableId })
|
|
291
|
+
continue
|
|
292
|
+
}
|
|
293
|
+
const opened = stack.pop()!
|
|
294
|
+
if (opened.stable_id !== endStableId) {
|
|
295
|
+
result.errors.push({
|
|
296
|
+
line: i + 1,
|
|
297
|
+
type: 'mismatched_end',
|
|
298
|
+
message: `END marker stable_id "${endStableId}" does not match BEGIN "${opened.stable_id}" at line ${opened.start_line}`,
|
|
299
|
+
})
|
|
300
|
+
// Continue as if it closed the opened marker so downstream checks can still run
|
|
301
|
+
}
|
|
302
|
+
result.markers.push({
|
|
303
|
+
stable_id: opened.stable_id,
|
|
304
|
+
type: opened.type,
|
|
305
|
+
identity: opened.identity,
|
|
306
|
+
parent: opened.parent,
|
|
307
|
+
start_line: opened.start_line,
|
|
308
|
+
end_line: i + 1,
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
while (stack.length > 0) {
|
|
314
|
+
const opened = stack.pop()!
|
|
315
|
+
result.unclosed.push({ line: opened.start_line, stable_id: opened.stable_id })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return result
|
|
319
|
+
}
|
|
320
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:parseCilMarkers:function"]
|
|
321
|
+
|
|
322
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:StableIdIssue:interface" type="interface" identity="StableIdIssue" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
323
|
+
// CIL[DOC-VECTOR type="context" content="purpose='parseCilMarkers function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
324
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
325
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
326
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
327
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
328
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
329
|
+
// CIL[DOC-MACRO purpose="StableIdIssue interface"]
|
|
330
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the StableIdIssue interface."]
|
|
331
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
332
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
333
|
+
// CIL[DOC-MICRO logic_summary="Implements the StableIdIssue interface." notable_decisions="[]"]
|
|
334
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
335
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
336
|
+
interface StableIdIssue {
|
|
337
|
+
stable_id: string
|
|
338
|
+
line: number
|
|
339
|
+
issue: string
|
|
340
|
+
}
|
|
341
|
+
// CIL[DOC-VECTOR type="context" content="purpose='StableIdIssue interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
342
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
343
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
344
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
345
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
346
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
347
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:StableIdIssue:interface"]
|
|
348
|
+
|
|
349
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:validateStableIds:function" type="function" identity="validateStableIds" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
350
|
+
// CIL[DOC-MACRO purpose="validateStableIds function"]
|
|
351
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the validateStableIds function."]
|
|
352
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
353
|
+
// CIL[DOC-CONTRACT inputs="markers: ParsedMarker[], canonicalPath: string" outputs="unknown" errors="none" side_effects="none"]
|
|
354
|
+
// CIL[DOC-MICRO logic_summary="Implements the validateStableIds function." notable_decisions="[]"]
|
|
355
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
356
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
357
|
+
/**
|
|
358
|
+
* Validates a list of stable IDs against a specified canonical path and checks for various structural issues. This function ensures that stable IDs conform to expected formats and values.
|
|
359
|
+
*
|
|
360
|
+
* @macro Validator for stable IDs in a given context.
|
|
361
|
+
* @micro Iterates through markers to check the format of stable IDs and collects issues based on specific validation rules.
|
|
362
|
+
* @inputSpec
|
|
363
|
+
* - markers: ParsedMarker[] (required) — an array of markers containing stable IDs to validate.
|
|
364
|
+
* - canonicalPath: string (required) — the expected canonical path that stable IDs should match.
|
|
365
|
+
* @outputSpec StableIdIssue[] — an array of issues found during validation, each containing details about the stable ID and the nature of the issue.
|
|
366
|
+
* @sideEffects none
|
|
367
|
+
* @stability stable
|
|
368
|
+
* @cil-generated
|
|
369
|
+
*/
|
|
370
|
+
function validateStableIds(markers: ParsedMarker[], canonicalPath: string): StableIdIssue[] {
|
|
371
|
+
const issues: StableIdIssue[] = []
|
|
372
|
+
for (const m of markers) {
|
|
373
|
+
const parts = m.stable_id.split(':')
|
|
374
|
+
if (parts.length < 3) {
|
|
375
|
+
issues.push({ stable_id: m.stable_id, line: m.start_line, issue: 'stable_id must be canonical_path:identity:type' })
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
378
|
+
const type = parts.pop()!
|
|
379
|
+
const identity = parts.pop()!
|
|
380
|
+
const pathPart = parts.join(':')
|
|
381
|
+
if (!pathPart.startsWith('~')) {
|
|
382
|
+
issues.push({ stable_id: m.stable_id, line: m.start_line, issue: 'canonical_path must start with ~' })
|
|
383
|
+
}
|
|
384
|
+
if (pathPart !== canonicalPath) {
|
|
385
|
+
issues.push({ stable_id: m.stable_id, line: m.start_line, issue: `canonical_path mismatch: expected ${canonicalPath}` })
|
|
386
|
+
}
|
|
387
|
+
if (!identity) {
|
|
388
|
+
issues.push({ stable_id: m.stable_id, line: m.start_line, issue: 'identity must not be empty' })
|
|
389
|
+
}
|
|
390
|
+
if (!type) {
|
|
391
|
+
issues.push({ stable_id: m.stable_id, line: m.start_line, issue: 'type must not be empty' })
|
|
392
|
+
}
|
|
393
|
+
if (m.type === 'module_header' && (identity !== 'module_header' || type !== 'module_header')) {
|
|
394
|
+
issues.push({ stable_id: m.stable_id, line: m.start_line, issue: 'module_header chunk identity/type must both be module_header' })
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return issues
|
|
398
|
+
}
|
|
399
|
+
// CIL[DOC-VECTOR type="context" content="purpose='validateStableIds function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
400
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
401
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
402
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
403
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
404
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
405
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:validateStableIds:function"]
|
|
406
|
+
|
|
407
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:CoverageIssue:interface" type="interface" identity="CoverageIssue" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
408
|
+
// CIL[DOC-MACRO purpose="CoverageIssue interface"]
|
|
409
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the CoverageIssue interface."]
|
|
410
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
411
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
412
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
413
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
414
|
+
// CIL[DOC-MICRO logic_summary="Implements the CoverageIssue interface." notable_decisions="[]"]
|
|
415
|
+
/**
|
|
416
|
+
* The CoverageIssue interface defines the structure for representing issues related to code coverage, specifically identifying lines of code that are either uncovered or overlapping. It exists to facilitate the reporting and handling of coverage-related problems in the codebase.
|
|
417
|
+
*
|
|
418
|
+
* @sideEffects none
|
|
419
|
+
* @stability stable
|
|
420
|
+
* @cil-generated
|
|
421
|
+
*/
|
|
422
|
+
interface CoverageIssue {
|
|
423
|
+
line: number
|
|
424
|
+
type: 'uncovered' | 'overlapping'
|
|
425
|
+
message: string
|
|
426
|
+
}
|
|
427
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:CoverageIssue:interface"]
|
|
428
|
+
|
|
429
|
+
// CIL[DOC-VECTOR type="context" content="purpose='CoverageIssue interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
430
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
431
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
432
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
433
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
434
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
435
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:computeCoverage:function" type="function" identity="computeCoverage" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
436
|
+
// CIL[DOC-MACRO purpose="computeCoverage function"]
|
|
437
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the computeCoverage function."]
|
|
438
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
439
|
+
// CIL[DOC-CONTRACT inputs="lines: string[], markers: ParsedMarker[]" outputs="unknown" errors="none" side_effects="none"]
|
|
440
|
+
// CIL[DOC-MICRO logic_summary="Implements the computeCoverage function." notable_decisions="[]"]
|
|
441
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
442
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
443
|
+
function computeCoverage(lines: string[], markers: ParsedMarker[]): CoverageIssue[] {
|
|
444
|
+
const issues: CoverageIssue[] = []
|
|
445
|
+
const coverage: number[] = new Array(lines.length).fill(0)
|
|
446
|
+
|
|
447
|
+
for (const m of markers) {
|
|
448
|
+
for (let i = m.start_line - 1; i <= m.end_line - 1; i++) {
|
|
449
|
+
if (i >= 0 && i < lines.length) coverage[i]++
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
let inBlockComment = false
|
|
454
|
+
for (let i = 0; i < lines.length; i++) {
|
|
455
|
+
const line = lines[i]
|
|
456
|
+
const trimmed = line.trim()
|
|
457
|
+
const isMarker = /^\s*(?:\/\/|--|#)\s*CIL\[(BEGIN|END|DOC-)/.test(line) || /^\s*<!--\s*CIL\[(BEGIN|END|DOC-)/.test(line)
|
|
458
|
+
const isLineComment = /^\s*(?:\/\/|--|#)/.test(line)
|
|
459
|
+
const isBlank = trimmed === ''
|
|
460
|
+
|
|
461
|
+
// Track multi-line /* ... */ blocks so JSDoc/module comments don't need their own chunk
|
|
462
|
+
if (trimmed.startsWith('/*')) inBlockComment = true
|
|
463
|
+
if (trimmed.endsWith('*/')) {
|
|
464
|
+
inBlockComment = false
|
|
465
|
+
continue
|
|
466
|
+
}
|
|
467
|
+
if (inBlockComment || isLineComment || isBlank || isMarker) continue
|
|
468
|
+
|
|
469
|
+
if (coverage[i] === 0) {
|
|
470
|
+
issues.push({ line: i + 1, type: 'uncovered', message: `Line ${i + 1} is not inside any CIL chunk` })
|
|
471
|
+
} else if (coverage[i] > 1) {
|
|
472
|
+
issues.push({ line: i + 1, type: 'overlapping', message: `Line ${i + 1} is inside ${coverage[i]} overlapping chunks` })
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return issues
|
|
477
|
+
}
|
|
478
|
+
// CIL[DOC-VECTOR type="context" content="purpose='computeCoverage function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
479
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
480
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
481
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
482
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
483
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
484
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:computeCoverage:function"]
|
|
485
|
+
|
|
486
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:JsxLeak:interface" type="interface" identity="JsxLeak" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
487
|
+
// CIL[DOC-MACRO purpose="JsxLeak interface"]
|
|
488
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the JsxLeak interface."]
|
|
489
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
490
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
491
|
+
// CIL[DOC-MICRO logic_summary="Implements the JsxLeak interface." notable_decisions="[]"]
|
|
492
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
493
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
494
|
+
/**
|
|
495
|
+
* The JsxLeak interface defines the structure for reporting issues related to JSX leaks during validation. It provides essential information such as the line number and a descriptive message for each leak detected.
|
|
496
|
+
*
|
|
497
|
+
* @sideEffects none
|
|
498
|
+
* @stability stable
|
|
499
|
+
* @cil-generated
|
|
500
|
+
*/
|
|
501
|
+
interface JsxLeak {
|
|
502
|
+
line: number
|
|
503
|
+
message: string
|
|
504
|
+
}
|
|
505
|
+
// CIL[DOC-VECTOR type="context" content="purpose='JsxLeak interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
506
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
507
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
508
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
509
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
510
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
511
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:JsxLeak:interface"]
|
|
512
|
+
|
|
513
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:checkJsxLeakage:function" type="function" identity="checkJsxLeakage" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
514
|
+
// CIL[DOC-MACRO purpose="checkJsxLeakage function"]
|
|
515
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the checkJsxLeakage function."]
|
|
516
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
517
|
+
// CIL[DOC-CONTRACT inputs="content: string, lines: string[], language: string" outputs="unknown" errors="none" side_effects="none"]
|
|
518
|
+
// CIL[DOC-MICRO logic_summary="Implements the checkJsxLeakage function." notable_decisions="[]"]
|
|
519
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
520
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
521
|
+
/**
|
|
522
|
+
* Checks for leakage of CIL markers into JSX content by analyzing the provided code. This function ensures that specific markers do not appear within JSX elements, which could lead to unintended behavior.
|
|
523
|
+
*
|
|
524
|
+
* @inputSpec
|
|
525
|
+
* - content: string (required) — the JSX content to be analyzed.
|
|
526
|
+
* - lines: string[] (required) — an array of lines from the content for leakage checking.
|
|
527
|
+
* - language: string (required) — specifies the language type, either 'typescriptreact' or 'javascriptreact'.
|
|
528
|
+
* @outputSpec JsxLeak[] — an array of leakage objects containing line numbers and messages.
|
|
529
|
+
* @sideEffects none
|
|
530
|
+
* @stability stable
|
|
531
|
+
* @calls ts.createSourceFile, ts.isJsxText, ts.isJsxExpression, ts.forEachChild
|
|
532
|
+
* @cil-generated
|
|
533
|
+
*/
|
|
534
|
+
function checkJsxLeakage(content: string, lines: string[], language: string): JsxLeak[] {
|
|
535
|
+
const leaks: JsxLeak[] = []
|
|
536
|
+
if (!['typescriptreact', 'javascriptreact'].includes(language)) return leaks
|
|
537
|
+
|
|
538
|
+
const scriptKind = language === 'typescriptreact' ? ts.ScriptKind.TSX : ts.ScriptKind.JSX
|
|
539
|
+
const sourceFile = ts.createSourceFile('temp.tsx', content, ts.ScriptTarget.Latest, true, scriptKind)
|
|
540
|
+
|
|
541
|
+
function findJsxText(node: ts.Node) {
|
|
542
|
+
if (ts.isJsxText(node) || ts.isJsxExpression(node)) {
|
|
543
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line
|
|
544
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line
|
|
545
|
+
for (let i = start; i <= end; i++) {
|
|
546
|
+
if (lines[i] && lines[i].includes('CIL[')) {
|
|
547
|
+
leaks.push({ line: i + 1, message: `CIL marker text leaked into JSX at line ${i + 1}` })
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
ts.forEachChild(node, findJsxText)
|
|
552
|
+
}
|
|
553
|
+
findJsxText(sourceFile)
|
|
554
|
+
return leaks
|
|
555
|
+
}
|
|
556
|
+
// CIL[DOC-VECTOR type="context" content="purpose='checkJsxLeakage function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
557
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
558
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
559
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
560
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
561
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
562
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:checkJsxLeakage:function"]
|
|
563
|
+
|
|
564
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:AstProposal:interface" type="interface" identity="AstProposal" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
565
|
+
// CIL[DOC-MACRO purpose="AstProposal interface"]
|
|
566
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the AstProposal interface."]
|
|
567
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
568
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
569
|
+
// CIL[DOC-MICRO logic_summary="Implements the AstProposal interface." notable_decisions="[]"]
|
|
570
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
571
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
572
|
+
/**
|
|
573
|
+
* The AstProposal interface defines the structure for proposals in the AST validation process, encapsulating essential metadata about each proposal. It serves to standardize the representation of proposals within the module.
|
|
574
|
+
*
|
|
575
|
+
* @sideEffects none
|
|
576
|
+
* @stability stable
|
|
577
|
+
* @cil-generated
|
|
578
|
+
*/
|
|
579
|
+
interface AstProposal {
|
|
580
|
+
stable_id: string
|
|
581
|
+
identity: string
|
|
582
|
+
type: string
|
|
583
|
+
line_start: number
|
|
584
|
+
line_end: number
|
|
585
|
+
}
|
|
586
|
+
// CIL[DOC-VECTOR type="context" content="purpose='AstProposal interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
587
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
588
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
589
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
590
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
591
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
592
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:AstProposal:interface"]
|
|
593
|
+
|
|
594
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:proposeAstChunks:function" type="function" identity="proposeAstChunks" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
595
|
+
// CIL[DOC-MACRO purpose="proposeAstChunks function"]
|
|
596
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the proposeAstChunks function."]
|
|
597
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
598
|
+
// CIL[DOC-CONTRACT inputs="content: string, canonicalPath: string, language: string" outputs="unknown" errors="none" side_effects="none"]
|
|
599
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
600
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
601
|
+
// CIL[DOC-MICRO logic_summary="Implements the proposeAstChunks function." notable_decisions="[]"]
|
|
602
|
+
/**
|
|
603
|
+
* The proposeAstChunks function analyzes a given source code content and generates a list of AST proposals based on the structure of the code. It identifies module headers, functions, classes, interfaces, type aliases, and constants for TypeScript and JavaScript files.
|
|
604
|
+
*
|
|
605
|
+
* @macro AST proposal generator for TypeScript and JavaScript source files.
|
|
606
|
+
* @micro Utilizes TypeScript's compiler API to parse source files and extract relevant declarations.
|
|
607
|
+
* @inputSpec
|
|
608
|
+
* - content: string (required) — the source code to analyze.
|
|
609
|
+
* - canonicalPath: string (required) — the path to the source file.
|
|
610
|
+
* - language: string (required) — the programming language, must be one of 'typescript', 'typescriptreact', 'javascript', 'javascriptreact'.
|
|
611
|
+
* @outputSpec AstProposal[] — an array of proposals representing different code structures.
|
|
612
|
+
* @sideEffects none
|
|
613
|
+
* @stability stable
|
|
614
|
+
* @calls ts.createSourceFile, ts.forEachChild, ts.isFunctionDeclaration, ts.isClassDeclaration, ts.isInterfaceDeclaration, ts.isTypeAliasDeclaration, ts.isVariableStatement, ts.isIdentifier
|
|
615
|
+
* @cil-generated
|
|
616
|
+
*/
|
|
617
|
+
function proposeAstChunks(content: string, canonicalPath: string, language: string): AstProposal[] {
|
|
618
|
+
const proposals: AstProposal[] = []
|
|
619
|
+
if (!['typescript', 'typescriptreact', 'javascript', 'javascriptreact'].includes(language)) return proposals
|
|
620
|
+
|
|
621
|
+
const scriptKind =
|
|
622
|
+
language === 'typescript' ? ts.ScriptKind.TS :
|
|
623
|
+
language === 'typescriptreact' ? ts.ScriptKind.TSX :
|
|
624
|
+
language === 'javascript' ? ts.ScriptKind.JS :
|
|
625
|
+
ts.ScriptKind.JSX
|
|
626
|
+
|
|
627
|
+
const sourceFile = ts.createSourceFile(canonicalPath, content, ts.ScriptTarget.Latest, true, scriptKind)
|
|
628
|
+
const imports = sourceFile.statements.filter(s => ts.isImportDeclaration(s))
|
|
629
|
+
let headerStart = 1
|
|
630
|
+
let headerEnd = 1
|
|
631
|
+
if (imports.length > 0) {
|
|
632
|
+
const firstImport = imports[0]
|
|
633
|
+
const lastImport = imports[imports.length - 1]
|
|
634
|
+
headerStart = sourceFile.getLineAndCharacterOfPosition(firstImport.getStart(sourceFile)).line + 1
|
|
635
|
+
headerEnd = sourceFile.getLineAndCharacterOfPosition(lastImport.getEnd()).line + 1
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
proposals.push({ stable_id: `${canonicalPath}:module_header:module_header`, identity: 'module_header', type: 'module_header', line_start: headerStart, line_end: headerEnd })
|
|
639
|
+
|
|
640
|
+
function addSymbol(name: string, type: string, node: ts.Node) {
|
|
641
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1
|
|
642
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
643
|
+
proposals.push({ stable_id: `${canonicalPath}:${name.toLowerCase()}:${type}`, identity: name, type, line_start: start, line_end: end })
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
647
|
+
if (ts.isFunctionDeclaration(node) && node.name) addSymbol(node.name.text, 'function', node)
|
|
648
|
+
else if (ts.isClassDeclaration(node) && node.name) addSymbol(node.name.text, 'class', node)
|
|
649
|
+
else if (ts.isInterfaceDeclaration(node) && node.name) addSymbol(node.name.text, 'interface', node)
|
|
650
|
+
else if (ts.isTypeAliasDeclaration(node) && node.name) addSymbol(node.name.text, 'type', node)
|
|
651
|
+
else if (ts.isVariableStatement(node)) {
|
|
652
|
+
for (const decl of node.declarationList.declarations) {
|
|
653
|
+
if (ts.isIdentifier(decl.name)) {
|
|
654
|
+
addSymbol(decl.name.text, 'constant', node)
|
|
655
|
+
break
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
return proposals
|
|
662
|
+
}
|
|
663
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:proposeAstChunks:function"]
|
|
664
|
+
|
|
665
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:BoundaryMismatch:interface" type="interface" identity="BoundaryMismatch" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
666
|
+
// CIL[DOC-VECTOR type="context" content="purpose='proposeAstChunks function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
667
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
668
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
669
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
670
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
671
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
672
|
+
// CIL[DOC-MACRO purpose="BoundaryMismatch interface"]
|
|
673
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the BoundaryMismatch interface."]
|
|
674
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
675
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
676
|
+
// CIL[DOC-STABILITY stability="internal"]
|
|
677
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
678
|
+
// CIL[DOC-MICRO logic_summary="Implements the BoundaryMismatch interface." notable_decisions="[]"]
|
|
679
|
+
interface BoundaryMismatch {
|
|
680
|
+
stable_id: string
|
|
681
|
+
existing_start: number
|
|
682
|
+
existing_end: number
|
|
683
|
+
proposed_start: number
|
|
684
|
+
proposed_end: number
|
|
685
|
+
message: string
|
|
686
|
+
}
|
|
687
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:BoundaryMismatch:interface"]
|
|
688
|
+
|
|
689
|
+
// CIL[DOC-VECTOR type="context" content="purpose='BoundaryMismatch interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
690
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
691
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
692
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
693
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
694
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
695
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:compareWithProposals:function" type="function" identity="compareWithProposals" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
696
|
+
// CIL[DOC-MACRO purpose="compareWithProposals function"]
|
|
697
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the compareWithProposals function."]
|
|
698
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
699
|
+
// CIL[DOC-CONTRACT inputs="markers: ParsedMarker[], proposals: AstProposal[], lines: string[]" outputs="unknown" errors="none" side_effects="none"]
|
|
700
|
+
// CIL[DOC-MICRO logic_summary="Implements the compareWithProposals function." notable_decisions="[]"]
|
|
701
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
702
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
703
|
+
/**
|
|
704
|
+
* Compares parsed markers with AST proposals to identify boundary mismatches in code chunks. This function ensures that the code boundaries defined by markers align with the proposed boundaries in the AST.
|
|
705
|
+
*
|
|
706
|
+
* @inputSpec
|
|
707
|
+
* - markers: ParsedMarker[] (required) — an array of parsed markers representing code boundaries.
|
|
708
|
+
* - proposals: AstProposal[] (required) — an array of AST proposals containing expected boundaries.
|
|
709
|
+
* - lines: string[] (required) — an array of lines of code to analyze.
|
|
710
|
+
* @outputSpec BoundaryMismatch[] — an array of boundary mismatch objects detailing discrepancies between markers and proposals.
|
|
711
|
+
* @sideEffects none
|
|
712
|
+
* @stability stable
|
|
713
|
+
* @cil-generated
|
|
714
|
+
*/
|
|
715
|
+
function compareWithProposals(markers: ParsedMarker[], proposals: AstProposal[], lines: string[]): BoundaryMismatch[] {
|
|
716
|
+
const mismatches: BoundaryMismatch[] = []
|
|
717
|
+
const proposalMap = new Map(proposals.map(p => [p.stable_id, p]))
|
|
718
|
+
const markerRe = /^\s*(?:\/\/|--|#)\s*CIL\[(BEGIN|END)\s+stable_id=/
|
|
719
|
+
const docRe = /^\s*(?:\/\/|--|#)\s*CIL\[DOC-/
|
|
720
|
+
function innerCodeBoundary(start: number, end: number): { start: number; end: number } {
|
|
721
|
+
let s = start
|
|
722
|
+
let e = end
|
|
723
|
+
while (s <= e && (markerRe.test(lines[s - 1]) || docRe.test(lines[s - 1]) || lines[s - 1].trim() === '')) s++
|
|
724
|
+
while (e >= s && (markerRe.test(lines[e - 1]) || docRe.test(lines[e - 1]) || lines[e - 1].trim() === '')) e--
|
|
725
|
+
return { start: s, end: e }
|
|
726
|
+
}
|
|
727
|
+
for (const m of markers) {
|
|
728
|
+
const p = proposalMap.get(m.stable_id)
|
|
729
|
+
if (!p) continue // extra markers are handled elsewhere; unproposed chunks may be intentional
|
|
730
|
+
const existing = innerCodeBoundary(m.start_line, m.end_line)
|
|
731
|
+
if (existing.start !== p.line_start || existing.end !== p.line_end) {
|
|
732
|
+
mismatches.push({
|
|
733
|
+
stable_id: m.stable_id,
|
|
734
|
+
existing_start: existing.start,
|
|
735
|
+
existing_end: existing.end,
|
|
736
|
+
proposed_start: p.line_start,
|
|
737
|
+
proposed_end: p.line_end,
|
|
738
|
+
message: `Chunk ${m.stable_id} inner code boundaries (${existing.start}-${existing.end}) do not match AST proposal (${p.line_start}-${p.line_end})`,
|
|
739
|
+
})
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return mismatches
|
|
743
|
+
}
|
|
744
|
+
// CIL[DOC-VECTOR type="context" content="purpose='compareWithProposals function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
745
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
746
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
747
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
748
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
749
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
750
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:compareWithProposals:function"]
|
|
751
|
+
|
|
752
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:ValidateChunkResult:interface" type="interface" identity="ValidateChunkResult" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
753
|
+
// CIL[DOC-MACRO purpose="ValidateChunkResult interface"]
|
|
754
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the ValidateChunkResult interface."]
|
|
755
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
756
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
|
|
757
|
+
// CIL[DOC-MICRO logic_summary="Implements the ValidateChunkResult interface." notable_decisions="[]"]
|
|
758
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
759
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
760
|
+
/**
|
|
761
|
+
* The ValidateChunkResult interface defines the structure of the result returned from validating a chunk of code. It encapsulates the validation status, file paths, language details, and any associated errors or issues.
|
|
762
|
+
*
|
|
763
|
+
* @sideEffects none
|
|
764
|
+
* @stability stable
|
|
765
|
+
* @cil-generated
|
|
766
|
+
*/
|
|
767
|
+
interface ValidateChunkResult {
|
|
768
|
+
ok: boolean
|
|
769
|
+
status: 'valid' | 'invalid' | 'not_chunked'
|
|
770
|
+
file_path: string
|
|
771
|
+
canonical_path: string
|
|
772
|
+
language: string
|
|
773
|
+
errors: Array<{ line?: number; type: string; message: string }>
|
|
774
|
+
marker_count: number
|
|
775
|
+
coverage_issues: CoverageIssue[]
|
|
776
|
+
stable_id_issues: StableIdIssue[]
|
|
777
|
+
boundary_mismatches: BoundaryMismatch[]
|
|
778
|
+
jsx_leaks: JsxLeak[]
|
|
779
|
+
proposals?: AstProposal[]
|
|
780
|
+
}
|
|
781
|
+
// CIL[DOC-VECTOR type="context" content="purpose='ValidateChunkResult interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
782
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
783
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
784
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
785
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
786
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
787
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:ValidateChunkResult:interface"]
|
|
788
|
+
|
|
789
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:validateChunkFile:function" type="function" identity="validateChunkFile" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
790
|
+
// CIL[DOC-MACRO purpose="validateChunkFile function"]
|
|
791
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the validateChunkFile function."]
|
|
792
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
793
|
+
// CIL[DOC-CONTRACT inputs="projectId: string, filePath: string" outputs="unknown" errors="none" side_effects="none"]
|
|
794
|
+
// CIL[DOC-MICRO logic_summary="Implements the validateChunkFile function." notable_decisions="[]"]
|
|
795
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
796
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
797
|
+
/**
|
|
798
|
+
* Validates the existence and structure of a chunk file associated with a project, returning detailed information about its validity and any issues found. This function is essential for ensuring that chunk files conform to expected formats and contain valid markers.
|
|
799
|
+
*
|
|
800
|
+
* @macro Async function that validates chunk files for a specified project.
|
|
801
|
+
* @micro Reads file content and analyzes it for markers, errors, and coverage issues.
|
|
802
|
+
* @inputSpec
|
|
803
|
+
* projectId: string (required) — The ID of the project to which the file belongs.
|
|
804
|
+
* filePath: string (required) — The path of the file to validate.
|
|
805
|
+
* @outputSpec Promise<ValidateChunkResult> — An object containing validation results including status, errors, and issue counts.
|
|
806
|
+
* @sideEffects Reads from the filesystem to check file existence and content.
|
|
807
|
+
* @stability stable
|
|
808
|
+
* @calls adminDb.from('items'), fs.existsSync, fs.readFileSync, detectLanguage, parseCilMarkers, validateStableIds, computeCoverage, checkJsxLeakage, proposeAstChunks, compareWithProposals
|
|
809
|
+
* @cil-generated
|
|
810
|
+
*/
|
|
811
|
+
async function validateChunkFile(projectId: string, filePath: string): Promise<ValidateChunkResult> {
|
|
812
|
+
const { data: projectRows } = await adminDb.from('items').select('data').eq('id', projectId).maybeSingle()
|
|
813
|
+
const repoPath = projectRows?.data?.repo_path || process.env.REPO_PATH || ''
|
|
814
|
+
const absolutePath = repoPath ? path.join(repoPath, filePath.replace(/^\//, '')) : filePath
|
|
815
|
+
|
|
816
|
+
if (!fs.existsSync(absolutePath)) {
|
|
817
|
+
return { ok: false, status: 'invalid', file_path: filePath, canonical_path: '', language: '', errors: [{ type: 'file_not_found', message: `File not found: ${absolutePath}` }], marker_count: 0, coverage_issues: [], stable_id_issues: [], boundary_mismatches: [], jsx_leaks: [] }
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const content = fs.readFileSync(absolutePath, 'utf-8')
|
|
821
|
+
const lines = content.split('\n')
|
|
822
|
+
const language = detectLanguage(filePath)
|
|
823
|
+
const canonicalPath = `~/${filePath.replace(/^\//, '')}`
|
|
824
|
+
|
|
825
|
+
const parseResult = parseCilMarkers(lines)
|
|
826
|
+
const markers = parseResult.markers
|
|
827
|
+
|
|
828
|
+
const errors: Array<{ line?: number; type: string; message: string }> = [...parseResult.errors]
|
|
829
|
+
for (const o of parseResult.orphan_ends) {
|
|
830
|
+
errors.push({ line: o.line, type: 'orphan_end', message: `Orphan END marker at line ${o.line} for ${o.stable_id}` })
|
|
831
|
+
}
|
|
832
|
+
for (const u of parseResult.unclosed) {
|
|
833
|
+
errors.push({ line: u.line, type: 'unclosed_begin', message: `Unclosed BEGIN marker at line ${u.line} for ${u.stable_id}` })
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
if (markers.length === 0) {
|
|
837
|
+
return { ok: false, status: 'not_chunked', file_path: filePath, canonical_path: canonicalPath, language, errors: [], marker_count: 0, coverage_issues: [], stable_id_issues: [], boundary_mismatches: [], jsx_leaks: [] }
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const stableIdIssues = validateStableIds(markers, canonicalPath)
|
|
841
|
+
const coverageIssues = computeCoverage(lines, markers)
|
|
842
|
+
const jsxLeaks = checkJsxLeakage(content, lines, language)
|
|
843
|
+
|
|
844
|
+
for (const issue of stableIdIssues) {
|
|
845
|
+
errors.push({ line: issue.line, type: 'invalid_stable_id', message: issue.issue })
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const proposals = ['typescript', 'typescriptreact', 'javascript', 'javascriptreact'].includes(language)
|
|
849
|
+
? proposeAstChunks(content, canonicalPath, language)
|
|
850
|
+
: []
|
|
851
|
+
const boundaryMismatches = compareWithProposals(markers, proposals, lines)
|
|
852
|
+
for (const m of boundaryMismatches) {
|
|
853
|
+
errors.push({ type: 'boundary_mismatch', message: m.message })
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const allIssues = errors.length + coverageIssues.length + jsxLeaks.length
|
|
857
|
+
const ok = allIssues === 0
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
ok,
|
|
861
|
+
status: ok ? 'valid' : 'invalid',
|
|
862
|
+
file_path: filePath,
|
|
863
|
+
canonical_path: canonicalPath,
|
|
864
|
+
language,
|
|
865
|
+
errors,
|
|
866
|
+
marker_count: markers.length,
|
|
867
|
+
coverage_issues: coverageIssues,
|
|
868
|
+
stable_id_issues: stableIdIssues,
|
|
869
|
+
boundary_mismatches: boundaryMismatches,
|
|
870
|
+
jsx_leaks: jsxLeaks,
|
|
871
|
+
proposals,
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:validateChunkFile:function"]
|
|
875
|
+
|
|
876
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:ProjectValidationResult:interface" type="interface" identity="ProjectValidationResult" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
877
|
+
// CIL[DOC-VECTOR type="context" content="purpose='validateChunkFile function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
878
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
879
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
880
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
881
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
882
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
883
|
+
// CIL[DOC-MACRO purpose="ProjectValidationResult interface"]
|
|
884
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the ProjectValidationResult interface."]
|
|
885
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
886
|
+
// CIL[DOC-CONTRACT inputs="ValidateChunkResult & { file_path: string }" outputs="none" errors="none" side_effects="none"]
|
|
887
|
+
// CIL[DOC-MICRO logic_summary="Implements the ProjectValidationResult interface." notable_decisions="[]"]
|
|
888
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
889
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
890
|
+
/**
|
|
891
|
+
* The ProjectValidationResult interface encapsulates the results of validating a project, providing details about the validation outcome and statistics. It serves to structure the response data for project validation operations.
|
|
892
|
+
*
|
|
893
|
+
* @sideEffects none
|
|
894
|
+
* @stability stable
|
|
895
|
+
* @cil-generated
|
|
896
|
+
*/
|
|
897
|
+
interface ProjectValidationResult {
|
|
898
|
+
ok: boolean
|
|
899
|
+
project_id: string
|
|
900
|
+
file_path: string | null
|
|
901
|
+
limit: number
|
|
902
|
+
offset: number
|
|
903
|
+
total_files: number
|
|
904
|
+
valid: number
|
|
905
|
+
invalid: number
|
|
906
|
+
not_chunked: number
|
|
907
|
+
results: (ValidateChunkResult & { file_path: string })[]
|
|
908
|
+
}
|
|
909
|
+
// CIL[DOC-VECTOR type="context" content="purpose='ProjectValidationResult interface'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
910
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
911
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
912
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
913
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
914
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
915
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:ProjectValidationResult:interface"]
|
|
916
|
+
|
|
917
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:validateProjectFiles:function" type="function" identity="validateProjectFiles" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
918
|
+
// CIL[DOC-MACRO purpose="validateProjectFiles function"]
|
|
919
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the validateProjectFiles function."]
|
|
920
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
921
|
+
// CIL[DOC-CONTRACT inputs="projectId: string, filePath?: string, limit = 50, offset = 0" outputs="unknown" errors="none" side_effects="none"]
|
|
922
|
+
// CIL[DOC-MICRO logic_summary="Implements the validateProjectFiles function." notable_decisions="[]"]
|
|
923
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
924
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
925
|
+
async function validateProjectFiles(
|
|
926
|
+
projectId: string,
|
|
927
|
+
filePath?: string,
|
|
928
|
+
limit = 50,
|
|
929
|
+
offset = 0
|
|
930
|
+
): Promise<ProjectValidationResult> {
|
|
931
|
+
let q = adminDb
|
|
932
|
+
.from('items')
|
|
933
|
+
.select('id, title, data')
|
|
934
|
+
.filter('data->>kb_type', 'eq', 'cil_file')
|
|
935
|
+
.filter('data->>project_id', 'eq', projectId)
|
|
936
|
+
.eq('is_active', true)
|
|
937
|
+
.order('id')
|
|
938
|
+
|
|
939
|
+
if (filePath) {
|
|
940
|
+
q = q.filter('data->>file_path', 'eq', filePath)
|
|
941
|
+
} else {
|
|
942
|
+
q = q.range(offset, offset + limit - 1)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const { data: files, error } = await q
|
|
946
|
+
if (error) {
|
|
947
|
+
return {
|
|
948
|
+
ok: false,
|
|
949
|
+
project_id: projectId,
|
|
950
|
+
file_path: filePath || null,
|
|
951
|
+
limit,
|
|
952
|
+
offset,
|
|
953
|
+
total_files: 0,
|
|
954
|
+
valid: 0,
|
|
955
|
+
invalid: 0,
|
|
956
|
+
not_chunked: 0,
|
|
957
|
+
results: [{ file_path: '', ok: false, status: 'invalid' as const, canonical_path: '', language: '', errors: [{ type: 'db_error', message: error.message }], marker_count: 0, coverage_issues: [], stable_id_issues: [], boundary_mismatches: [], jsx_leaks: [] }],
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
const results: (ValidateChunkResult & { file_path: string })[] = []
|
|
962
|
+
let valid = 0, invalid = 0, notChunked = 0
|
|
963
|
+
|
|
964
|
+
for (const f of files || []) {
|
|
965
|
+
const fp = f.data?.file_path || f.title || ''
|
|
966
|
+
const r = await validateChunkFile(projectId, fp)
|
|
967
|
+
results.push({ file_path: fp, ...r })
|
|
968
|
+
if (r.status === 'valid') valid++
|
|
969
|
+
else if (r.status === 'invalid') invalid++
|
|
970
|
+
else notChunked++
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
return {
|
|
974
|
+
ok: invalid === 0,
|
|
975
|
+
project_id: projectId,
|
|
976
|
+
file_path: filePath || null,
|
|
977
|
+
limit,
|
|
978
|
+
offset,
|
|
979
|
+
total_files: files?.length || 0,
|
|
980
|
+
valid,
|
|
981
|
+
invalid,
|
|
982
|
+
not_chunked: notChunked,
|
|
983
|
+
results,
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
// CIL[DOC-VECTOR type="context" content="purpose='validateProjectFiles function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
987
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
988
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
989
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
990
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
991
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
992
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:validateProjectFiles:function"]
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Registers a tool for validating chunk hashes in a specified source file. It checks the integrity of file chunks against stored hashes after modifications.
|
|
996
|
+
*
|
|
997
|
+
* @macro Tool registration function for validating file integrity.
|
|
998
|
+
* @micro Reads a file from disk, computes hashes for its chunks, and compares them to stored values.
|
|
999
|
+
* @inputSpec
|
|
1000
|
+
* - server: McpServer (required) — The server instance to register the tool with.
|
|
1001
|
+
* @outputSpec Promise<{ content: Array<{ type: string, text: string }> }> — Returns validation results or error messages.
|
|
1002
|
+
* @sideEffects Reads from the file system, queries the database.
|
|
1003
|
+
* @stability stable
|
|
1004
|
+
* @calls adminDb.from, fs.existsSync, fs.readFileSync, computeChunkHash
|
|
1005
|
+
* @cil-generated
|
|
1006
|
+
*/
|
|
1007
|
+
// CIL[BEGIN stable_id="~/.framework/mcp/tools/cil/validate.ts:register:function" type="function" identity="register" parent="~/.framework/mcp/tools/cil/validate.ts"]
|
|
1008
|
+
// CIL[DOC-MACRO purpose="register function"]
|
|
1009
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Implements the register function."]
|
|
1010
|
+
// CIL[DOC-CONTEXT module="validate" layer="app-specific" related_files=".framework/mcp/shared/db.ts,.framework/mcp/tools/cil/validate.ts"]
|
|
1011
|
+
// CIL[DOC-CONTRACT inputs="server: McpServer" outputs="unknown" errors="none" side_effects="none"]
|
|
1012
|
+
// CIL[DOC-MICRO logic_summary="Implements the register function." notable_decisions="[]"]
|
|
1013
|
+
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
1014
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
1015
|
+
// CIL[DOC-LEARNING topic="caution" content="spine_cil_reindex_chunk hashes lines.slice(lineStart+1, lineEnd) (inner lines only) while spine_cil_validate_file hashes the inclusive range — a reindex can never clear drift_detection on its own, and neither tool updates stale line ranges. The correct post-edit sync is spine_cil_store_markers, which re-derives ranges from CIL[BEGIN/END] markers on disk"]
|
|
1016
|
+
// CIL[DOC-LEARNING topic="convention" content="cil_validate_docs param check: input_spec is split on commas/newlines at depth 0 and only each entry's FIRST identifier counts as a documented param; only the FIRST '(function|const|let|var) name(' signature inside a chunk (including comment text) is checked — phrases like 'function sections (with comments)' in DOC lines parse as signatures. List every param as its own 'name: type' entry"]
|
|
1017
|
+
export function register(server: McpServer) {
|
|
1018
|
+
const splitParameterList = (text: string): string[] => {
|
|
1019
|
+
const parts: string[] = []
|
|
1020
|
+
let depth = 0
|
|
1021
|
+
let inString = false
|
|
1022
|
+
let quote = ''
|
|
1023
|
+
let current = ''
|
|
1024
|
+
for (const ch of text) {
|
|
1025
|
+
if (inString) {
|
|
1026
|
+
if (ch === quote) inString = false
|
|
1027
|
+
current += ch
|
|
1028
|
+
continue
|
|
1029
|
+
}
|
|
1030
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
1031
|
+
inString = true
|
|
1032
|
+
quote = ch
|
|
1033
|
+
current += ch
|
|
1034
|
+
continue
|
|
1035
|
+
}
|
|
1036
|
+
if (ch === '(' || ch === '[' || ch === '<' || ch === '{') depth++
|
|
1037
|
+
else if (ch === ')' || ch === ']' || ch === '>' || ch === '}') depth = Math.max(0, depth - 1)
|
|
1038
|
+
else if ((ch === ',' || ch === '\n') && depth === 0) {
|
|
1039
|
+
parts.push(current)
|
|
1040
|
+
current = ''
|
|
1041
|
+
continue
|
|
1042
|
+
}
|
|
1043
|
+
current += ch
|
|
1044
|
+
}
|
|
1045
|
+
parts.push(current)
|
|
1046
|
+
return parts.map(s => s.trim()).filter(Boolean)
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const extractIdentifierNames = (s: string): string[] => {
|
|
1050
|
+
const raw = s.trim()
|
|
1051
|
+
if (!raw) return []
|
|
1052
|
+
|
|
1053
|
+
// Strip trailing type/default, then optional ?
|
|
1054
|
+
let left = raw.split(/[:=]/)[0].trim().replace(/\?$/, '')
|
|
1055
|
+
|
|
1056
|
+
// Object / array destructuring
|
|
1057
|
+
if ((left.startsWith('{') && left.endsWith('}')) || (left.startsWith('[') && left.endsWith(']'))) {
|
|
1058
|
+
const inner = left.slice(1, -1)
|
|
1059
|
+
const names = splitParameterList(inner)
|
|
1060
|
+
.map(p => p.trim().split(/[:=]/)[0].replace(/\?$/, '').replace(/^\.\.\./, ''))
|
|
1061
|
+
.map(p => p.match(/([a-zA-Z0-9_]+)/)?.[0] || '')
|
|
1062
|
+
.filter(Boolean)
|
|
1063
|
+
return names
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Rest parameter or regular parameter (tolerate leading "- " / "* " list bullets)
|
|
1067
|
+
left = left.replace(/^\.\.\./, '')
|
|
1068
|
+
const m = left.match(/^[-*\s]*([a-zA-Z0-9_]+)/)
|
|
1069
|
+
return m ? [m[1]] : []
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
server.registerTool(
|
|
1073
|
+
'spine_cil_validate_file',
|
|
1074
|
+
{
|
|
1075
|
+
description: 'Re-read a source file from disk and validate all chunk hashes. Returns drift status for each chunk: current | drift_detected | not_found. Use this after editing to confirm hashes are up to date.',
|
|
1076
|
+
inputSchema: z.object({
|
|
1077
|
+
project_id: z.string().uuid().describe('The cil_project UUID'),
|
|
1078
|
+
file_path: z.string().describe('Relative file path from repo root, e.g. ".framework/cli/commands/auth.ts"'),
|
|
1079
|
+
}),
|
|
1080
|
+
},
|
|
1081
|
+
async ({ project_id, file_path }) => {
|
|
1082
|
+
const { data: proj } = await adminDb.from('items').select('data').eq('id', project_id).maybeSingle()
|
|
1083
|
+
if (!proj) return { content: [{ type: 'text', text: JSON.stringify({ error: `Project not found: ${project_id}` }) }] }
|
|
1084
|
+
|
|
1085
|
+
const resolvedRepo = proj.data?.repo_path || process.env.REPO_PATH || ''
|
|
1086
|
+
if (!resolvedRepo) return { content: [{ type: 'text', text: JSON.stringify({ error: 'No repo_path on project and REPO_PATH env not set' }) }] }
|
|
1087
|
+
|
|
1088
|
+
const absolutePath = `${resolvedRepo.replace(/\/$/, '')}/${file_path.replace(/^\//, '')}`
|
|
1089
|
+
|
|
1090
|
+
if (!fs.existsSync(absolutePath)) {
|
|
1091
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: `File not found on disk: ${absolutePath}`, file_path }) }] }
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const fileContent = fs.readFileSync(absolutePath, 'utf-8')
|
|
1095
|
+
const lines = fileContent.split('\n')
|
|
1096
|
+
|
|
1097
|
+
const activeVersionId = proj.data?.active_version_id
|
|
1098
|
+
let chunkQuery = adminDb.from('items').select('id, data')
|
|
1099
|
+
.filter('data->>kb_type', 'eq', 'cil_chunk')
|
|
1100
|
+
.filter('data->>project_id', 'eq', project_id)
|
|
1101
|
+
.filter('data->>file_path', 'eq', file_path)
|
|
1102
|
+
.eq('is_active', true)
|
|
1103
|
+
if (activeVersionId) {
|
|
1104
|
+
chunkQuery = chunkQuery.filter('data->>version_id', 'eq', activeVersionId)
|
|
1105
|
+
}
|
|
1106
|
+
const { data: chunks } = await chunkQuery
|
|
1107
|
+
|
|
1108
|
+
const results: any[] = []
|
|
1109
|
+
let current = 0, drifted = 0, notFound = 0
|
|
1110
|
+
|
|
1111
|
+
for (const chunk of chunks || []) {
|
|
1112
|
+
const cd = chunk.data || {}
|
|
1113
|
+
const stableId = cd.stable_id || cd.legacy_chunk_id || cd.chunk_id || chunk.id
|
|
1114
|
+
const lineStart = (cd.line_start || 1) - 1
|
|
1115
|
+
const lineEnd = (cd.line_end || 1) - 1
|
|
1116
|
+
|
|
1117
|
+
if (lineEnd >= lines.length) {
|
|
1118
|
+
results.push({ chunk_id: chunk.id, stable_id: stableId, chunk_name: cd.chunk_name || '', status: 'not_found', reason: 'line range out of bounds' })
|
|
1119
|
+
notFound++
|
|
1120
|
+
continue
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const extractedContent = lines.slice(lineStart, lineEnd + 1).join('\n')
|
|
1124
|
+
const shortHash = computeChunkHash(extractedContent)
|
|
1125
|
+
const fullHash = computeContentHash(extractedContent)
|
|
1126
|
+
const storedHash = cd.stored_hash || cd.content_hash
|
|
1127
|
+
|
|
1128
|
+
if (!storedHash) {
|
|
1129
|
+
results.push({ chunk_id: chunk.id, stable_id: stableId, chunk_name: cd.chunk_name || '', status: 'not_found', reason: 'no stored hash' })
|
|
1130
|
+
notFound++
|
|
1131
|
+
} else if (storedHash === shortHash || storedHash === fullHash) {
|
|
1132
|
+
results.push({ chunk_id: chunk.id, stable_id: stableId, chunk_name: cd.chunk_name || '', status: 'current', line_start: cd.line_start, line_end: cd.line_end })
|
|
1133
|
+
current++
|
|
1134
|
+
} else {
|
|
1135
|
+
results.push({ chunk_id: chunk.id, stable_id: stableId, chunk_name: cd.chunk_name || '', status: 'drift_detected', stored_hash: storedHash, computed_hash: shortHash, line_start: cd.line_start, line_end: cd.line_end })
|
|
1136
|
+
drifted++
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
const total = chunks?.length || 0
|
|
1141
|
+
const trustRating = total > 0 ? Math.round((current / total) * 100) : 100
|
|
1142
|
+
|
|
1143
|
+
return {
|
|
1144
|
+
content: [{
|
|
1145
|
+
type: 'text',
|
|
1146
|
+
text: JSON.stringify({
|
|
1147
|
+
project_id,
|
|
1148
|
+
file_path,
|
|
1149
|
+
summary: { total, current, drift_detected: drifted, not_found: notFound, trust_rating: trustRating },
|
|
1150
|
+
chunks: results,
|
|
1151
|
+
}, null, 2),
|
|
1152
|
+
}],
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
server.registerTool(
|
|
1158
|
+
'spine_cil_validate_chunk',
|
|
1159
|
+
{
|
|
1160
|
+
description: 'Strictly validate CIL chunk markers in a source file before storage. Checks marker pairing, full line coverage, valid stable IDs, boundary agreement with the AST, and JSX leakage. Returns a structured error list so the caller can fix markers before calling spine_cil_store_markers.',
|
|
1161
|
+
inputSchema: z.object({
|
|
1162
|
+
project_id: z.string().uuid().describe('The cil_project UUID'),
|
|
1163
|
+
file_path: z.string().describe('Relative file path from repo root, e.g. ".framework/mcp/tools/cil/validate.ts"'),
|
|
1164
|
+
}),
|
|
1165
|
+
},
|
|
1166
|
+
async ({ project_id, file_path }) => {
|
|
1167
|
+
const result = await validateChunkFile(project_id, file_path)
|
|
1168
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
|
|
1169
|
+
}
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
server.registerTool(
|
|
1173
|
+
'spine_cil_validate_project',
|
|
1174
|
+
{
|
|
1175
|
+
description: 'Validate all selected/chunked files in a CIL project. Checks marker pairing, full line coverage, valid stable IDs, boundary agreement with the AST, and JSX leakage. Supports optional file_path scope and batching.',
|
|
1176
|
+
inputSchema: z.object({
|
|
1177
|
+
project_id: z.string().uuid().describe('The cil_project UUID'),
|
|
1178
|
+
file_path: z.string().optional().describe('Limit validation to a single file path'),
|
|
1179
|
+
limit: z.number().int().min(1).max(100).optional().describe('Max files to validate in this call (default 50)'),
|
|
1180
|
+
offset: z.number().int().min(0).optional().describe('File offset for batching'),
|
|
1181
|
+
}),
|
|
1182
|
+
},
|
|
1183
|
+
async ({ project_id, file_path, limit, offset }) => {
|
|
1184
|
+
const result = await validateProjectFiles(project_id, file_path, limit ?? 50, offset ?? 0)
|
|
1185
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
|
|
1186
|
+
}
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
server.registerTool(
|
|
1190
|
+
'spine_cil_reindex_chunk',
|
|
1191
|
+
{
|
|
1192
|
+
description: 'Re-read a chunk from disk by its stable_id, recompute its hash and update code_content in the database. Call this after editing a chunk to keep the CIL in sync. Set also_embed: true to also trigger an embedding update.',
|
|
1193
|
+
inputSchema: z.object({
|
|
1194
|
+
project_id: z.string().uuid().describe('The cil_project UUID'),
|
|
1195
|
+
stable_id: z.string().describe('The stable_id of the chunk to reindex'),
|
|
1196
|
+
version_id: z.string().optional().describe('Optional version UUID to scope the lookup'),
|
|
1197
|
+
also_embed: z.boolean().default(false).optional().describe('If true, trigger embed_chunk after updating the hash'),
|
|
1198
|
+
repo_path: z.string().optional().describe('Absolute path to repo root (overrides project data.repo_path)'),
|
|
1199
|
+
}),
|
|
1200
|
+
},
|
|
1201
|
+
async ({ project_id, stable_id, version_id, also_embed, repo_path }) => {
|
|
1202
|
+
const chunk = await getChunkByStableId(project_id, stable_id, version_id)
|
|
1203
|
+
if (!chunk) return { content: [{ type: 'text', text: JSON.stringify({ error: `Chunk not found: ${stable_id}` }) }] }
|
|
1204
|
+
|
|
1205
|
+
const cd = chunk.data || {}
|
|
1206
|
+
|
|
1207
|
+
let resolvedRepo = repo_path || process.env.REPO_PATH || ''
|
|
1208
|
+
if (!resolvedRepo) {
|
|
1209
|
+
const { data: proj } = await adminDb.from('items').select('data').eq('id', project_id).maybeSingle()
|
|
1210
|
+
resolvedRepo = proj?.data?.repo_path || ''
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const filePath = cd.file_path || ''
|
|
1214
|
+
const absolutePath = resolvedRepo ? `${resolvedRepo.replace(/\/$/, '')}/${filePath.replace(/^\//, '')}` : filePath
|
|
1215
|
+
|
|
1216
|
+
if (!fs.existsSync(absolutePath)) {
|
|
1217
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: `File not found on disk: ${absolutePath}` }) }] }
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const fileContent = fs.readFileSync(absolutePath, 'utf-8')
|
|
1221
|
+
const lines = fileContent.split('\n')
|
|
1222
|
+
const lineStart = (cd.line_start || 1) - 1
|
|
1223
|
+
const lineEnd = (cd.line_end || 1) - 1
|
|
1224
|
+
const extractedContent = lines.slice(lineStart + 1, lineEnd).join('\n')
|
|
1225
|
+
|
|
1226
|
+
const newHash = computeContentHash(extractedContent)
|
|
1227
|
+
const now = new Date().toISOString()
|
|
1228
|
+
|
|
1229
|
+
const newData = {
|
|
1230
|
+
...cd,
|
|
1231
|
+
code_content: extractedContent,
|
|
1232
|
+
stored_hash: newHash,
|
|
1233
|
+
content_hash: newHash,
|
|
1234
|
+
hash_updated_at: now,
|
|
1235
|
+
token_count: Math.round(extractedContent.length / 4),
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const { error } = await adminDb.from('items')
|
|
1239
|
+
.update({ data: newData, updated_at: now })
|
|
1240
|
+
.eq('id', chunk.id)
|
|
1241
|
+
|
|
1242
|
+
if (error) return { content: [{ type: 'text', text: JSON.stringify({ error: `Failed to update chunk: ${error.message}` }) }] }
|
|
1243
|
+
|
|
1244
|
+
const result: any = {
|
|
1245
|
+
ok: true,
|
|
1246
|
+
chunk_id: chunk.id,
|
|
1247
|
+
stable_id,
|
|
1248
|
+
version_id: version_id || cd.version_id || null,
|
|
1249
|
+
chunk_name: cd.chunk_name || '',
|
|
1250
|
+
file_path: filePath,
|
|
1251
|
+
new_hash: newHash,
|
|
1252
|
+
hash_updated_at: now,
|
|
1253
|
+
lines_extracted: lineEnd - lineStart + 1,
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (also_embed) {
|
|
1257
|
+
result.embed_note = `Re-embedding requires the Netlify function endpoint. Use POST /api/custom_cil-embeddings?action=embed_chunk with { project_id, stable_id, version_id }`
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
|
|
1261
|
+
}
|
|
1262
|
+
)
|
|
1263
|
+
|
|
1264
|
+
server.registerTool(
|
|
1265
|
+
'spine_cil_validate_docs',
|
|
1266
|
+
{
|
|
1267
|
+
description: 'Validate that chunks meet the canonical inline documentation contract. Returns chunks that are missing required fields (module, audience, layer, macro, micro, input_spec, output_spec, side_effects, stability) or have undocumented parameters. For enriched chunks, also checks required DOC-VECTOR types and related_files.',
|
|
1268
|
+
inputSchema: z.object({
|
|
1269
|
+
project_id: z.string().uuid().describe('The cil_project UUID'),
|
|
1270
|
+
file_path: z.string().optional().describe('Limit validation to a single file path'),
|
|
1271
|
+
}),
|
|
1272
|
+
},
|
|
1273
|
+
async ({ project_id, file_path }) => {
|
|
1274
|
+
const PAGE_SIZE = 500
|
|
1275
|
+
const allChunks: any[] = []
|
|
1276
|
+
let from = 0
|
|
1277
|
+
while (true) {
|
|
1278
|
+
let q = adminDb.from('items').select('id, title, data')
|
|
1279
|
+
.filter('data->>kb_type', 'eq', 'cil_chunk')
|
|
1280
|
+
.filter('data->>project_id', 'eq', project_id)
|
|
1281
|
+
.eq('is_active', true)
|
|
1282
|
+
.order('id')
|
|
1283
|
+
.range(from, from + PAGE_SIZE - 1)
|
|
1284
|
+
if (file_path) q = (q as any).filter('data->>file_path', 'eq', file_path)
|
|
1285
|
+
|
|
1286
|
+
const { data: chunks, error } = await q
|
|
1287
|
+
if (error) return { content: [{ type: 'text', text: JSON.stringify({ error: `Failed to load chunks: ${error.message}` }) }] }
|
|
1288
|
+
if (!chunks || chunks.length === 0) break
|
|
1289
|
+
allChunks.push(...chunks)
|
|
1290
|
+
if (chunks.length < PAGE_SIZE) break
|
|
1291
|
+
from += PAGE_SIZE
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const requiredModule = ['module', 'audience', 'layer', 'stability', 'description']
|
|
1295
|
+
const requiredChunk = ['description', 'macro', 'micro', 'input_spec', 'output_spec', 'side_effects', 'stability']
|
|
1296
|
+
|
|
1297
|
+
const invalid: any[] = []
|
|
1298
|
+
for (const chunk of allChunks) {
|
|
1299
|
+
const d = chunk.data || {}
|
|
1300
|
+
const isModule = d.chunk_type === 'module_header'
|
|
1301
|
+
const required = isModule ? requiredModule : requiredChunk
|
|
1302
|
+
const errors: string[] = []
|
|
1303
|
+
for (const field of required) {
|
|
1304
|
+
if (!d[field] || String(d[field]).trim() === '') errors.push(`missing ${field}`)
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
const hasVectors = d.vector_content && Object.keys(d.vector_content).length > 0
|
|
1308
|
+
if (hasVectors) {
|
|
1309
|
+
const requiredVectors = ['context', 'blast_radius', 'configuration', 'constraint']
|
|
1310
|
+
for (const v of requiredVectors) {
|
|
1311
|
+
const vec = d.vector_content[v]
|
|
1312
|
+
if (!vec || vec.status !== 'ready' || !String(vec.content).trim()) {
|
|
1313
|
+
errors.push(`missing or non-ready DOC-VECTOR: ${v}`)
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
const relatedFilesRaw = d.related_files || d.see_also || ''
|
|
1317
|
+
const relatedFilesTrimmed = String(relatedFilesRaw).trim()
|
|
1318
|
+
const hasRelatedFiles = relatedFilesTrimmed !== '' && relatedFilesTrimmed !== '[]'
|
|
1319
|
+
if (!hasRelatedFiles) {
|
|
1320
|
+
errors.push('missing related_files')
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
if (!isModule && d.input_spec && d.input_spec !== 'none') {
|
|
1325
|
+
const paramNames = splitParameterList(d.input_spec).flatMap(extractIdentifierNames)
|
|
1326
|
+
const code = d.code_content || ''
|
|
1327
|
+
const sig = code.match(/(?:function|const|let|var)\s+\w+\s*\(([^)]*)\)/)
|
|
1328
|
+
if (sig) {
|
|
1329
|
+
const declared = splitParameterList(sig[1]).flatMap(extractIdentifierNames)
|
|
1330
|
+
for (const name of declared) {
|
|
1331
|
+
if (!paramNames.includes(name)) errors.push(`undocumented parameter: ${name}`)
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (errors.length > 0) {
|
|
1337
|
+
invalid.push({
|
|
1338
|
+
chunk_id: chunk.id,
|
|
1339
|
+
chunk_name: d.chunk_name || chunk.title,
|
|
1340
|
+
file_path: d.file_path || '',
|
|
1341
|
+
chunk_type: d.chunk_type || '',
|
|
1342
|
+
errors,
|
|
1343
|
+
})
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
return {
|
|
1348
|
+
content: [{
|
|
1349
|
+
type: 'text',
|
|
1350
|
+
text: JSON.stringify({
|
|
1351
|
+
project_id,
|
|
1352
|
+
file_path: file_path || null,
|
|
1353
|
+
total_chunks: allChunks.length,
|
|
1354
|
+
valid_chunks: allChunks.length - invalid.length,
|
|
1355
|
+
invalid_chunks: invalid.length,
|
|
1356
|
+
invalid,
|
|
1357
|
+
}, null, 2),
|
|
1358
|
+
}],
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
)
|
|
1362
|
+
}
|
|
1363
|
+
// CIL[DOC-VECTOR type="context" content="purpose='register function'; No verified external callers; imports: .framework/mcp/shared/db.ts"]
|
|
1364
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
1365
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
1366
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
1367
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
1368
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
1369
|
+
// CIL[END stable_id="~/.framework/mcp/tools/cil/validate.ts:register:function"]
|