spine-framework 0.4.4 → 0.5.0-rc.5
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 +13 -3
- package/.framework/cli/commands/bootstrap.ts +392 -0
- package/.framework/cli/commands/dev.ts +91 -218
- package/.framework/cli/commands/doctor.ts +132 -26
- package/.framework/cli/commands/generate.ts +38 -298
- package/.framework/cli/commands/init.ts +20 -1
- package/.framework/cli/commands/install-app.ts +183 -13
- package/.framework/cli/commands/migrate.ts +36 -30
- package/.framework/cli/commands/migrations.ts +16 -56
- package/.framework/cli/commands/upgrade.ts +35 -0
- package/.framework/cli/commands/validate-app.ts +127 -1
- package/.framework/cli/commands/validate-schema.ts +147 -0
- package/.framework/cli/context.ts +223 -110
- package/.framework/cli/index.ts +6 -0
- package/.framework/cli/lib/project-files.ts +241 -1
- package/.framework/functions/_shared/app-manifest.ts +2 -0
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/permissions.ts +4 -0
- package/.framework/functions/admin-data.ts +51 -28
- package/.framework/functions/ai-agents.ts +10 -6
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +63 -29
- package/.framework/functions/cil-ingest.ts +48 -15
- package/.framework/functions/debug-auth.ts +14 -8
- package/.framework/functions/deliveries.ts +39 -12
- package/.framework/functions/embeddings.ts +149 -78
- package/.framework/functions/integration-outbound.ts +67 -27
- package/.framework/functions/integrations.ts +254 -127
- package/.framework/functions/logs.ts +273 -133
- package/.framework/functions/observability.ts +278 -148
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +240 -127
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
- package/.framework/migrations/075_security_definer_grants.sql +157 -0
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/layout/Sidebar.tsx +300 -177
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/contexts/AppContext.tsx +5 -12
- package/.framework/src/contexts/AuthContext.tsx +17 -9
- package/.framework/src/hooks/useApps.ts +18 -22
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +74 -0
- package/.framework/src/instructions/accounts.md +97 -0
- package/.framework/src/instructions/ai-agents.md +69 -0
- package/.framework/src/instructions/alerts.md +80 -0
- package/.framework/src/instructions/api-keys.md +87 -0
- package/.framework/src/instructions/apps.md +77 -0
- package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
- package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
- package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
- package/.framework/src/instructions/concepts/automation.md +186 -0
- package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
- package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
- package/.framework/src/instructions/concepts/design-schema.md +152 -0
- package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
- package/.framework/src/instructions/concepts/observability.md +116 -0
- package/.framework/src/instructions/concepts/permissions.md +150 -0
- package/.framework/src/instructions/concepts/runtime-records.md +111 -0
- package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
- package/.framework/src/instructions/concepts/spine-way.md +172 -0
- package/.framework/src/instructions/deliveries.md +59 -0
- package/.framework/src/instructions/guide/00-orientation.md +106 -0
- package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
- package/.framework/src/instructions/guide/02-project-map.md +147 -0
- package/.framework/src/instructions/guide/03-domain-model.md +223 -0
- package/.framework/src/instructions/guide/04-first-app.md +227 -0
- package/.framework/src/instructions/guide/05-glossary.md +183 -0
- package/.framework/src/instructions/integrations.md +96 -0
- package/.framework/src/instructions/internal/cil-markers.md +83 -0
- package/.framework/src/instructions/internal/docs-contract.md +158 -0
- package/.framework/src/instructions/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.md +77 -0
- package/.framework/src/instructions/notifications.md +77 -0
- package/.framework/src/instructions/observability.md +61 -0
- package/.framework/src/instructions/operations/deployment.md +115 -0
- package/.framework/src/instructions/operations/migrations.md +125 -0
- package/.framework/src/instructions/operations/security-checklist.md +170 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/people.md +91 -0
- package/.framework/src/instructions/pipeline-executions.md +62 -0
- package/.framework/src/instructions/pipelines.md +78 -0
- package/.framework/src/instructions/prompts.md +95 -0
- package/.framework/src/instructions/reference/api/admin-data.md +162 -0
- package/.framework/src/instructions/reference/api/auth.md +113 -0
- package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
- package/.framework/src/instructions/reference/app-manifest.md +184 -0
- package/.framework/src/instructions/reference/cli.md +124 -0
- package/.framework/src/instructions/reference/database.md +132 -0
- package/.framework/src/instructions/reference/design-schema.md +280 -0
- package/.framework/src/instructions/reference/env-vars.md +133 -0
- package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
- package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
- package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
- package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
- package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
- package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
- package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
- package/.framework/src/instructions/reference/functions/apps.md +140 -0
- package/.framework/src/instructions/reference/functions/auth.md +99 -0
- package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
- package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
- package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
- package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
- package/.framework/src/instructions/reference/functions/index.md +103 -0
- package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
- package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
- package/.framework/src/instructions/reference/functions/integrations.md +130 -0
- package/.framework/src/instructions/reference/functions/invites.md +182 -0
- package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
- package/.framework/src/instructions/reference/functions/kb.md +166 -0
- package/.framework/src/instructions/reference/functions/logs.md +141 -0
- package/.framework/src/instructions/reference/functions/observability.md +105 -0
- package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
- package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
- package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
- package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
- package/.framework/src/instructions/reference/functions/roles.md +121 -0
- package/.framework/src/instructions/reference/functions/security.md +140 -0
- package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
- package/.framework/src/instructions/reference/functions/system.md +106 -0
- package/.framework/src/instructions/reference/functions/tests.md +97 -0
- package/.framework/src/instructions/reference/functions/timers.md +117 -0
- package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
- package/.framework/src/instructions/reference/functions/triggers.md +140 -0
- package/.framework/src/instructions/reference/functions/types.md +146 -0
- package/.framework/src/instructions/reference/seeds.md +126 -0
- package/.framework/src/instructions/reference/shared-modules.md +127 -0
- package/.framework/src/instructions/reference/spine-config.md +114 -0
- package/.framework/src/instructions/roles.md +80 -0
- package/.framework/src/instructions/runtime-collaboration.md +106 -0
- package/.framework/src/instructions/runtime-detail.md +70 -0
- package/.framework/src/instructions/runtime-list.md +77 -0
- package/.framework/src/instructions/security.md +67 -0
- package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
- package/.framework/src/instructions/tasks/add-a-role.md +96 -0
- package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
- package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
- package/.framework/src/instructions/tasks/add-a-type.md +151 -0
- package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
- package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
- package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
- package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
- package/.framework/src/instructions/tasks/document-your-app.md +117 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
- package/.framework/src/instructions/tasks/register-an-app.md +137 -0
- package/.framework/src/instructions/tasks/test-your-code.md +108 -0
- package/.framework/src/instructions/tasks/write-a-function.md +132 -0
- package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
- package/.framework/src/instructions/testing.md +59 -0
- package/.framework/src/instructions/timers.md +84 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/app-mount.ts +88 -0
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/LoginPage.tsx +3 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
- package/.framework/templates/agent/AGENTS.md +167 -0
- package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
- package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
- package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
- package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
- package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +4 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +479 -44
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/app-mount.test.ts +80 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -12,11 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
import type { Command } from 'commander'
|
|
15
|
-
import { readdirSync, existsSync } from 'fs'
|
|
16
|
-
import { resolve, dirname } from 'path'
|
|
17
|
-
import { fileURLToPath } from 'url'
|
|
18
15
|
import { buildCliContext, printResult, handleError } from '../context.ts'
|
|
19
16
|
import { adminDb } from '../../functions/_shared/index.ts'
|
|
17
|
+
import { collectMigrations } from './migrate.ts'
|
|
20
18
|
// CIL[DOC-VECTOR type="context" content="purpose='CLI migration inspection commands for listing applied migrations and comparing local SQL files'; No verified external callers; imports: .framework/cli/context.ts,.framework/functions/_shared/index.ts"]
|
|
21
19
|
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
22
20
|
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
@@ -25,42 +23,10 @@ import { adminDb } from '../../functions/_shared/index.ts'
|
|
|
25
23
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
26
24
|
// CIL[END stable_id="~/.framework/cli/commands/migrations.ts:module_header:module_header"]
|
|
27
25
|
|
|
28
|
-
// CIL[BEGIN stable_id="~/.framework/cli/commands/migrations.ts:__filename:constant" type="constant" identity="__filename" parent="~/.framework/cli/commands/migrations.ts"]
|
|
29
|
-
// CIL[DOC-MACRO purpose="Resolve the current module filename from import.meta.url"]
|
|
30
|
-
// CIL[DOC-RESPONSIBILITY responsibility="Provide a CommonJS-style __filename value in ESM"]
|
|
31
|
-
// CIL[DOC-CONTEXT module="cli/commands/migrations" layer="utility" related_files=".framework/cli/commands/migrations.ts,.framework/cli/context.ts,.framework/functions/_shared/index.ts"]
|
|
32
|
-
// CIL[DOC-CONTRACT inputs="import.meta.url" outputs="absolute __filename" errors="none" side_effects="none"]
|
|
33
|
-
// CIL[DOC-MICRO logic_summary="call fileURLToPath on import.meta.url" notable_decisions="[]"]
|
|
34
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
35
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
36
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
37
|
-
// CIL[DOC-VECTOR type="context" content="purpose='Resolve the current module filename from import.meta.url'; No verified external callers; imports: .framework/cli/context.ts,.framework/functions/_shared/index.ts"]
|
|
38
|
-
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
39
|
-
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
40
|
-
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
41
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
42
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
43
|
-
// CIL[END stable_id="~/.framework/cli/commands/migrations.ts:__filename:constant"]
|
|
44
|
-
// CIL[BEGIN stable_id="~/.framework/cli/commands/migrations.ts:__dirname:constant" type="constant" identity="__dirname" parent="~/.framework/cli/commands/migrations.ts"]
|
|
45
|
-
// CIL[DOC-MACRO purpose="Resolve the current module directory name"]
|
|
46
|
-
// CIL[DOC-RESPONSIBILITY responsibility="Provide a CommonJS-style __dirname value in ESM"]
|
|
47
|
-
// CIL[DOC-CONTEXT module="cli/commands/migrations" layer="utility" related_files=".framework/cli/commands/migrations.ts,.framework/cli/context.ts,.framework/functions/_shared/index.ts"]
|
|
48
|
-
// CIL[DOC-CONTRACT inputs="__filename" outputs="absolute __dirname" errors="none" side_effects="none"]
|
|
49
|
-
// CIL[DOC-MICRO logic_summary="call dirname on the resolved __filename" notable_decisions="[]"]
|
|
50
|
-
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
51
|
-
// CIL[DOC-STABILITY stability="stable"]
|
|
52
|
-
const __dirname = dirname(__filename)
|
|
53
|
-
// CIL[DOC-VECTOR type="context" content="purpose='Resolve the current module directory name'; No verified external callers; imports: .framework/cli/context.ts,.framework/functions/_shared/index.ts"]
|
|
54
|
-
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
55
|
-
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
56
|
-
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
57
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
58
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
59
|
-
// CIL[END stable_id="~/.framework/cli/commands/migrations.ts:__dirname:constant"]
|
|
60
26
|
|
|
61
27
|
// CIL[BEGIN stable_id="~/.framework/cli/commands/migrations.ts:registerMigrationCommands:function" type="function" identity="registerMigrationCommands" parent="~/.framework/cli/commands/migrations.ts"]
|
|
62
28
|
// CIL[DOC-MACRO purpose="Register migration list and status CLI subcommands"]
|
|
63
|
-
// CIL[DOC-RESPONSIBILITY responsibility="Expose commands that query
|
|
29
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Expose commands that query _spine_migrations and compare local SQL files"]
|
|
64
30
|
// CIL[DOC-CONTEXT module="cli/commands/migrations" layer="cli" related_files=".framework/cli/commands/migrations.ts,.framework/cli/context.ts,.framework/functions/_shared/index.ts"]
|
|
65
31
|
// CIL[DOC-CONTRACT inputs="program: Command" outputs="void" errors="Throws on missing migrations directory or failed DB queries" side_effects="Adds commands and options to the Commander program"]
|
|
66
32
|
// CIL[DOC-MICRO logic_summary="Define migrations command, add list and status subcommands with options, perform DB calls and console output" notable_decisions="[]"]
|
|
@@ -73,17 +39,16 @@ export function registerMigrationCommands(program: Command) {
|
|
|
73
39
|
|
|
74
40
|
migrations
|
|
75
41
|
.command('list')
|
|
76
|
-
.description('List applied migrations from
|
|
42
|
+
.description('List applied migrations from _spine_migrations')
|
|
77
43
|
.option('--json', 'Output as JSON')
|
|
78
44
|
.action(async (opts) => {
|
|
79
45
|
try {
|
|
80
46
|
await buildCliContext()
|
|
81
47
|
|
|
82
48
|
const { data, error } = await adminDb
|
|
83
|
-
.
|
|
84
|
-
.
|
|
85
|
-
.
|
|
86
|
-
.order('version', { ascending: true })
|
|
49
|
+
.from('_spine_migrations' as any)
|
|
50
|
+
.select('filename, applied_at')
|
|
51
|
+
.order('filename', { ascending: true })
|
|
87
52
|
|
|
88
53
|
if (error) {
|
|
89
54
|
throw new Error(
|
|
@@ -106,34 +71,29 @@ export function registerMigrationCommands(program: Command) {
|
|
|
106
71
|
try {
|
|
107
72
|
await buildCliContext()
|
|
108
73
|
|
|
109
|
-
const
|
|
74
|
+
const migrationFiles = collectMigrations(false)
|
|
110
75
|
|
|
111
|
-
if (
|
|
112
|
-
throw new Error(
|
|
76
|
+
if (migrationFiles.length === 0) {
|
|
77
|
+
throw new Error('Migrations directory not found (.framework/migrations)')
|
|
113
78
|
}
|
|
114
79
|
|
|
115
|
-
const localFiles = readdirSync(migrationsDir)
|
|
116
|
-
.filter((f: string) => f.endsWith('.sql'))
|
|
117
|
-
.sort()
|
|
118
|
-
|
|
119
80
|
const { data: applied, error } = await adminDb
|
|
120
|
-
.
|
|
121
|
-
.
|
|
122
|
-
.select('version')
|
|
81
|
+
.from('_spine_migrations' as any)
|
|
82
|
+
.select('filename')
|
|
123
83
|
|
|
124
84
|
if (error) {
|
|
125
85
|
throw new Error(`Could not query migrations: ${error.message}`)
|
|
126
86
|
}
|
|
127
87
|
|
|
128
88
|
const appliedVersions = new Set(
|
|
129
|
-
(applied || []).map((r: any) => r.
|
|
89
|
+
(applied || []).map((r: any) => r.filename)
|
|
130
90
|
)
|
|
131
91
|
|
|
132
|
-
const status =
|
|
133
|
-
const version =
|
|
134
|
-
const isApplied = appliedVersions.has(
|
|
92
|
+
const status = migrationFiles.map(({ name }) => {
|
|
93
|
+
const version = name.replace('.sql', '')
|
|
94
|
+
const isApplied = appliedVersions.has(name)
|
|
135
95
|
return {
|
|
136
|
-
file,
|
|
96
|
+
file: name,
|
|
137
97
|
version,
|
|
138
98
|
status: isApplied ? 'applied' : 'pending'
|
|
139
99
|
}
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
mergePackageJson,
|
|
23
23
|
writeFrozenManifest,
|
|
24
24
|
cmpSemver,
|
|
25
|
+
syncAgentFiles,
|
|
26
|
+
collectAgentFileHashes,
|
|
25
27
|
} from '../lib/project-files.ts'
|
|
26
28
|
// CIL[END stable_id="~/.framework/cli/commands/upgrade.ts:module_header:module_header"]
|
|
27
29
|
|
|
@@ -167,11 +169,27 @@ function runUpgrade(opts: { dryRun: boolean; force: boolean }): void {
|
|
|
167
169
|
.filter((rel) => rel.startsWith('.framework/migrations/') && rel.endsWith('.sql') && !(rel in oldFiles))
|
|
168
170
|
|
|
169
171
|
console.log(`\n ${added.length} added · ${changed.length} changed · ${removed.length} removed · ${kept} unchanged`)
|
|
172
|
+
|
|
173
|
+
// Agent assets are computed before the dry-run exit so the report is complete:
|
|
174
|
+
// AGENTS.md gets its marker-delimited framework block refreshed in place
|
|
175
|
+
// (customer content outside the markers is preserved); .devin/skills/spine-*
|
|
176
|
+
// dirs are replaced wholesale (customer-owned sibling dirs untouched).
|
|
177
|
+
const agentActions = syncAgentFiles({
|
|
178
|
+
pkgRoot: PACKAGE_ROOT,
|
|
179
|
+
projectRoot: PROJECT_ROOT,
|
|
180
|
+
entries: profile.agent_files ?? [],
|
|
181
|
+
version: newVersion,
|
|
182
|
+
dryRun: true,
|
|
183
|
+
})
|
|
184
|
+
|
|
170
185
|
if (opts.dryRun) {
|
|
171
186
|
for (const f of added) console.log(` + ${f}`)
|
|
172
187
|
for (const f of changed) console.log(` ~ ${f}`)
|
|
173
188
|
for (const f of removed) console.log(` - ${f}`)
|
|
174
189
|
for (const f of staleOutside) console.log(` ? ${f} (dropped upstream, left in place)`)
|
|
190
|
+
for (const a of agentActions) {
|
|
191
|
+
if (a.action !== 'unchanged') console.log(` ~ ${a.rel} (agent asset: ${a.action})`)
|
|
192
|
+
}
|
|
175
193
|
if (newMigrations.length > 0) {
|
|
176
194
|
console.log(`\n New migrations in this version (${newMigrations.length}):`)
|
|
177
195
|
for (const rel of newMigrations) console.log(` ${rel.split('/').pop()}`)
|
|
@@ -186,6 +204,22 @@ function runUpgrade(opts: { dryRun: boolean; force: boolean }): void {
|
|
|
186
204
|
for (const rel of staleOutside) console.log(` ⚠ ${rel} is no longer shipped upstream — left in place`)
|
|
187
205
|
console.log(` ✓ framework files synced (${files.length})`)
|
|
188
206
|
|
|
207
|
+
// 4b. Apply agent assets (the dry-run pass above reported; this one writes)
|
|
208
|
+
const applied = syncAgentFiles({
|
|
209
|
+
pkgRoot: PACKAGE_ROOT,
|
|
210
|
+
projectRoot: PROJECT_ROOT,
|
|
211
|
+
entries: profile.agent_files ?? [],
|
|
212
|
+
version: newVersion,
|
|
213
|
+
})
|
|
214
|
+
for (const a of applied) {
|
|
215
|
+
if (a.action === 'prepended') {
|
|
216
|
+
console.log(` ⚠ ${a.rel} had no spine-framework markers — fresh framework block prepended`)
|
|
217
|
+
} else if (a.action !== 'unchanged') {
|
|
218
|
+
console.log(` ✓ ${a.rel} (${a.action})`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
console.log(' ✓ agent assets synced (AGENTS.md + .devin/skills/spine-*)')
|
|
222
|
+
|
|
189
223
|
// 5. package.json — framework scripts/deps, exact pin
|
|
190
224
|
const pkgPath = join(PROJECT_ROOT, 'package.json')
|
|
191
225
|
const existingPkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, 'utf8')) : {}
|
|
@@ -222,6 +256,7 @@ function runUpgrade(opts: { dryRun: boolean; force: boolean }): void {
|
|
|
222
256
|
projectRoot: PROJECT_ROOT,
|
|
223
257
|
frameworkVersion: newVersion,
|
|
224
258
|
frameworkDeps: merged.frameworkDeps,
|
|
259
|
+
agentFiles: collectAgentFileHashes(PROJECT_ROOT, profile.agent_files ?? []),
|
|
225
260
|
})
|
|
226
261
|
console.log(` ✓ .spine-manifest.json regenerated (${manifest.fileCount} files)`)
|
|
227
262
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { Command } from 'commander'
|
|
22
22
|
import { readFileSync, existsSync, readdirSync, statSync, realpathSync } from 'fs'
|
|
23
|
+
import { createHash } from 'crypto'
|
|
23
24
|
import { join, dirname, resolve, isAbsolute, sep } from 'path'
|
|
24
25
|
import { fileURLToPath } from 'url'
|
|
25
26
|
// CIL[DOC-VECTOR type="context" content="purpose='Provide module-level imports for the validate-app CLI command'; No verified external callers; imports: none"]
|
|
@@ -127,6 +128,14 @@ interface Manifest {
|
|
|
127
128
|
parent_account?: string
|
|
128
129
|
default_workspace_name?: boolean
|
|
129
130
|
}
|
|
131
|
+
route_prefix?: string
|
|
132
|
+
preview_domain?: string
|
|
133
|
+
prod_domain?: string
|
|
134
|
+
preview_route_prefix?: string
|
|
135
|
+
prod_route_prefix?: string
|
|
136
|
+
agent?: {
|
|
137
|
+
skill_prefix?: string
|
|
138
|
+
}
|
|
130
139
|
}
|
|
131
140
|
// CIL[DOC-VECTOR type="context" content="purpose='Define the shape of an app manifest'; No verified external callers; imports: none"]
|
|
132
141
|
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
@@ -337,7 +346,8 @@ function validateAllowedDirectories(appPath: string): void {
|
|
|
337
346
|
const allowedDirs = [
|
|
338
347
|
'pages', 'components', 'hooks', 'utils', 'functions',
|
|
339
348
|
'seed', 'public', 'lib', 'views', 'docs', 'mcp',
|
|
340
|
-
'migrations', 'migrations-archive', 'tests'
|
|
349
|
+
'migrations', 'migrations-archive', 'tests', 'installer',
|
|
350
|
+
'agent', 'src'
|
|
341
351
|
]
|
|
342
352
|
|
|
343
353
|
const entries = readdirSync(appPath, { withFileTypes: true })
|
|
@@ -414,6 +424,23 @@ function validateDevinDirectory(appPath: string): void {
|
|
|
414
424
|
// CIL[DOC-CONFIDENCE pass="second" score="0.90" flags="none"]
|
|
415
425
|
// CIL[DOC-STABILITY stable]
|
|
416
426
|
function validateRoutes(appPath: string, manifest: Manifest): void {
|
|
427
|
+
// Host-keyed mount overrides: an override prefix only makes sense with its
|
|
428
|
+
// domain trigger, and both must be root-relative.
|
|
429
|
+
const pairs: [keyof Manifest, keyof Manifest][] = [
|
|
430
|
+
['prod_route_prefix', 'prod_domain'],
|
|
431
|
+
['preview_route_prefix', 'preview_domain'],
|
|
432
|
+
]
|
|
433
|
+
for (const [prefixKey, domainKey] of pairs) {
|
|
434
|
+
const prefix = manifest[prefixKey] as string | undefined
|
|
435
|
+
if (prefix === undefined) continue
|
|
436
|
+
if (typeof prefix !== 'string' || !prefix.startsWith('/')) {
|
|
437
|
+
throw new ValidationError(`❌ manifest.json ${prefixKey} "${prefix}" must start with /`)
|
|
438
|
+
}
|
|
439
|
+
if (!manifest[domainKey]) {
|
|
440
|
+
throw new ValidationError(`❌ manifest.json ${prefixKey} requires ${domainKey} — the override has no trigger host without it`)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
417
444
|
if (!manifest.route_prefix) {
|
|
418
445
|
console.warn('⚠️ manifest.json is missing route_prefix — install-app will default to /{slug}')
|
|
419
446
|
return
|
|
@@ -642,6 +669,7 @@ function validateOwnedTypes(appPath: string, manifest: Manifest): void {
|
|
|
642
669
|
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
643
670
|
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
644
671
|
// CIL[END stable_id="~/.framework/cli/commands/validate-app.ts:validateownedtypes:function"]
|
|
672
|
+
// CIL[BEGIN stable_id="~/.framework/cli/commands/validate-app.ts:validateknowledgeenvelope:function" type="function" identity="validateKnowledgeEnvelope" parent="~/.framework/cli/commands/validate-app.ts"]
|
|
645
673
|
// CIL[DOC-MACRO purpose="Validate the manifest knowledge envelope and seed prompt-config picks"]
|
|
646
674
|
// CIL[DOC-RESPONSIBILITY responsibility="Check config.knowledge shape and verify seeded prompt_configs' retrieval_policy picks stay inside the envelope — an agent may narrow the app envelope but never widen it"]
|
|
647
675
|
// CIL[DOC-CONTEXT module="validate-app" layer="cli" related_files=".framework/functions/_shared/kb.ts"]
|
|
@@ -740,6 +768,103 @@ function validateKnowledgeEnvelope(appPath: string, manifest: Manifest): void {
|
|
|
740
768
|
}
|
|
741
769
|
// CIL[END stable_id="~/.framework/cli/commands/validate-app.ts:validateknowledgeenvelope:function"]
|
|
742
770
|
|
|
771
|
+
// CIL[BEGIN stable_id="~/.framework/cli/commands/validate-app.ts:validateagentdirectory:function" type="function" identity="validateAgentDirectory" parent="~/.framework/cli/commands/validate-app.ts"]
|
|
772
|
+
// CIL[DOC-AUDIENCE audience="CLI engineers and framework maintainers"]
|
|
773
|
+
// CIL[DOC-MACRO purpose="Validate the app's staged agent/ directory (skills and instructions)"]
|
|
774
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Enforce skill prefix naming and SKILL.md name/dir equality, markdown-only instructions, and warn when staged assets drift from their live copies"]
|
|
775
|
+
// CIL[DOC-CONTEXT module="validate-app" layer="cli" related_files=".framework/cli/commands/install-app.ts"]
|
|
776
|
+
// CIL[DOC-CONTRACT inputs="appPath: string, manifest: Manifest" outputs="void" errors="ValidationError on missing skill_prefix, prefix mismatch, missing SKILL.md, or non-markdown instructions" side_effects="logs warnings to stdout on staged-vs-live drift"]
|
|
777
|
+
// CIL[DOC-MICRO logic_summary="agent/skills requires manifest.agent.skill_prefix; every staged dir must start <prefix>_ and its SKILL.md name must equal the dir name; agent/instructions may only contain .md; sha1 snapshots of staged trees are compared to live custom/.devin/skills and src/instructions" notable_decisions="['drift_is_a_warning_not_an_error','skill_prefix_fail_closed']"]
|
|
778
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
779
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
780
|
+
function validateAgentDirectory(appPath: string, manifest: Manifest): void {
|
|
781
|
+
const agentPath = join(appPath, 'agent')
|
|
782
|
+
if (!existsSync(agentPath)) return
|
|
783
|
+
|
|
784
|
+
const skillsPath = join(agentPath, 'skills')
|
|
785
|
+
const instructionsPath = join(agentPath, 'instructions')
|
|
786
|
+
|
|
787
|
+
if (existsSync(skillsPath)) {
|
|
788
|
+
const prefix = manifest.agent?.skill_prefix
|
|
789
|
+
if (typeof prefix !== 'string' || !/^[a-z][a-z0-9]{0,7}$/.test(prefix)) {
|
|
790
|
+
throw new ValidationError(
|
|
791
|
+
`❌ agent/skills/ requires manifest.json agent.skill_prefix matching /^[a-z][a-z0-9]{0,7}$/`
|
|
792
|
+
)
|
|
793
|
+
}
|
|
794
|
+
for (const dir of readdirSync(skillsPath, { withFileTypes: true })) {
|
|
795
|
+
if (!dir.isDirectory()) continue
|
|
796
|
+
if (!dir.name.startsWith(`${prefix}_`)) {
|
|
797
|
+
throw new ValidationError(
|
|
798
|
+
`❌ agent/skills/${dir.name} must start with the declared prefix "${prefix}_"`
|
|
799
|
+
)
|
|
800
|
+
}
|
|
801
|
+
const skillFile = join(skillsPath, dir.name, 'SKILL.md')
|
|
802
|
+
if (!existsSync(skillFile)) {
|
|
803
|
+
throw new ValidationError(`❌ agent/skills/${dir.name}/ is missing SKILL.md`)
|
|
804
|
+
}
|
|
805
|
+
const nameMatch = /^name:\s*(.+)$/m.exec(readFileSync(skillFile, 'utf8'))
|
|
806
|
+
if (!nameMatch || nameMatch[1].trim() !== dir.name) {
|
|
807
|
+
throw new ValidationError(
|
|
808
|
+
`❌ agent/skills/${dir.name}/SKILL.md name must equal its directory name "${dir.name}"`
|
|
809
|
+
)
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (existsSync(instructionsPath)) {
|
|
815
|
+
const checkMarkdownOnly = (dirPath: string): void => {
|
|
816
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
817
|
+
const full = join(dirPath, entry.name)
|
|
818
|
+
if (entry.isDirectory()) checkMarkdownOnly(full)
|
|
819
|
+
else if (!entry.name.endsWith('.md')) {
|
|
820
|
+
throw new ValidationError(
|
|
821
|
+
`❌ agent/instructions may only contain .md files — found ${entry.name}`
|
|
822
|
+
)
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
checkMarkdownOnly(instructionsPath)
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Staged copies should match their live counterparts — drift means one
|
|
830
|
+
// side was edited without re-staging (or the live install is stale).
|
|
831
|
+
const snapshot = (root: string): Map<string, string> => {
|
|
832
|
+
const out = new Map<string, string>()
|
|
833
|
+
const walk = (dir: string): void => {
|
|
834
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
835
|
+
const full = join(dir, entry.name)
|
|
836
|
+
if (entry.isDirectory()) walk(full)
|
|
837
|
+
else out.set(full.slice(root.length + 1), createHash('sha1').update(readFileSync(full)).digest('hex'))
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (existsSync(root)) walk(root)
|
|
841
|
+
return out
|
|
842
|
+
}
|
|
843
|
+
const sameTree = (a: Map<string, string>, b: Map<string, string>): boolean =>
|
|
844
|
+
a.size === b.size && [...a].every(([k, v]) => b.get(k) === v)
|
|
845
|
+
|
|
846
|
+
if (existsSync(skillsPath)) {
|
|
847
|
+
const liveSkillsRoot = join(appPath, '..', '..', '.devin', 'skills')
|
|
848
|
+
for (const dir of readdirSync(skillsPath, { withFileTypes: true })) {
|
|
849
|
+
if (!dir.isDirectory()) continue
|
|
850
|
+
if (!sameTree(snapshot(join(skillsPath, dir.name)), snapshot(join(liveSkillsRoot, dir.name)))) {
|
|
851
|
+
console.warn(`⚠️ agent/skills/${dir.name} differs from live custom/.devin/skills/${dir.name} — re-stage or reinstall`)
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (existsSync(instructionsPath)) {
|
|
857
|
+
if (!sameTree(snapshot(instructionsPath), snapshot(join(appPath, 'src', 'instructions')))) {
|
|
858
|
+
console.warn(`⚠️ agent/instructions differs from src/instructions — re-stage before packaging`)
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (existsSync(skillsPath) || existsSync(instructionsPath)) {
|
|
863
|
+
console.log(`✅ agent/ staged assets valid`)
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// CIL[END stable_id="~/.framework/cli/commands/validate-app.ts:validateagentdirectory:function"]
|
|
867
|
+
|
|
743
868
|
// CIL[BEGIN stable_id="~/.framework/cli/commands/validate-app.ts:validateAppStructure:function" type="function" identity="validateAppStructure" parent="~/.framework/cli/commands/validate-app.ts"]
|
|
744
869
|
// CIL[DOC-AUDIENCE audience="CLI engineers and framework maintainers"]
|
|
745
870
|
// CIL[DOC-MACRO purpose="Run the full app structure validation pipeline"]
|
|
@@ -755,6 +880,7 @@ export function validateAppStructure(appPath: string): void {
|
|
|
755
880
|
validateRequiredFiles(appPath, manifest)
|
|
756
881
|
validateAllowedDirectories(appPath)
|
|
757
882
|
validateDevinDirectory(appPath)
|
|
883
|
+
validateAgentDirectory(appPath, manifest)
|
|
758
884
|
validateRoutes(appPath, manifest)
|
|
759
885
|
validateManifestComponents(appPath, manifest)
|
|
760
886
|
validateSeedBlock(appPath, manifest)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// validate-schema — standalone design_schema validator.
|
|
2
|
+
//
|
|
3
|
+
// Ported from .framework/mcp/tools/architect.ts (the retired Spine MCP
|
|
4
|
+
// architect tools) so `spine-schema` and CI have a runnable validator without
|
|
5
|
+
// the MCP server. `validate-app` covers app packages (manifest/package.json/
|
|
6
|
+
// structure) — this command covers a single design_schema JSON file.
|
|
7
|
+
|
|
8
|
+
import type { Command } from 'commander'
|
|
9
|
+
import { readFileSync, existsSync } from 'fs'
|
|
10
|
+
import { resolve } from 'path'
|
|
11
|
+
|
|
12
|
+
const DATA_TYPES = new Set([
|
|
13
|
+
'text', 'textarea', 'rich_text', 'email', 'phone', 'url', 'number', 'currency', 'range',
|
|
14
|
+
'date', 'datetime', 'boolean', 'checkbox', 'select', 'multiselect', 'radio', 'color',
|
|
15
|
+
'file', 'image', 'json', 'reference', 'address',
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
const DISPLAY_TYPES = new Set([
|
|
19
|
+
'text', 'input', 'textarea', 'rich_text', 'select', 'multiselect', 'radio', 'checkbox',
|
|
20
|
+
'switch', 'date_picker', 'datetime_picker', 'color_picker', 'file_upload', 'image_upload',
|
|
21
|
+
'range_slider', 'badge', 'timestamp', 'currency', 'number',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate a design_schema object. `checkViews` additionally requires
|
|
26
|
+
* views.default_list / views.default_detail and scans every declared
|
|
27
|
+
* display_type — the generic runtime UI breaks without them.
|
|
28
|
+
*/
|
|
29
|
+
export function validateDesignSchema(
|
|
30
|
+
schema: any,
|
|
31
|
+
opts: { checkViews?: boolean } = {},
|
|
32
|
+
): { ok: boolean; errors: string[] } {
|
|
33
|
+
const checkViews = opts.checkViews ?? true
|
|
34
|
+
const errors: string[] = []
|
|
35
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
36
|
+
errors.push('design_schema must be an object')
|
|
37
|
+
return { ok: false, errors }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const scope = schema.scope ?? 'account'
|
|
41
|
+
if (scope !== 'account' && scope !== 'platform') {
|
|
42
|
+
errors.push('scope must be "account" or "platform"')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!schema.fields || typeof schema.fields !== 'object' || Object.keys(schema.fields).length === 0) {
|
|
46
|
+
errors.push('fields is required and must contain at least one field definition')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const [key, field] of Object.entries(schema.fields || {})) {
|
|
50
|
+
if (!field || typeof field !== 'object') {
|
|
51
|
+
errors.push(`fields.${key} must be an object`)
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
const f = field as any
|
|
55
|
+
if (!f.data_type) errors.push(`fields.${key} is missing data_type`)
|
|
56
|
+
else if (!DATA_TYPES.has(f.data_type)) errors.push(`fields.${key} has unknown data_type: ${f.data_type}`)
|
|
57
|
+
if (!f.label) errors.push(`fields.${key} is missing label`)
|
|
58
|
+
if (f.required === undefined) errors.push(`fields.${key} is missing required`)
|
|
59
|
+
if (['select', 'multiselect', 'radio'].includes(f.data_type) && (!f.options || !Array.isArray(f.options) || f.options.length === 0)) {
|
|
60
|
+
errors.push(`fields.${key} (${f.data_type}) requires options`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!schema.record_permissions || typeof schema.record_permissions !== 'object') {
|
|
65
|
+
errors.push('record_permissions is required')
|
|
66
|
+
} else if (!Object.keys(schema.record_permissions).includes('system-admin') && !Object.keys(schema.record_permissions).includes('system_admin')) {
|
|
67
|
+
errors.push('record_permissions must grant at least system-admin or system_admin')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (schema.views) {
|
|
71
|
+
if (schema.views.default_list) {
|
|
72
|
+
const list = schema.views.default_list
|
|
73
|
+
if (list.type !== 'list') errors.push('views.default_list.type must be "list"')
|
|
74
|
+
if (!list.fields || typeof list.fields !== 'object') errors.push('views.default_list.fields is required')
|
|
75
|
+
if (!list.default_sort) errors.push('views.default_list.default_sort is required')
|
|
76
|
+
}
|
|
77
|
+
if (schema.views.default_detail) {
|
|
78
|
+
const detail = schema.views.default_detail
|
|
79
|
+
if (detail.type !== 'detail') errors.push('views.default_detail.type must be "detail"')
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (checkViews && !schema.views) {
|
|
84
|
+
errors.push('views is required when check_views is true')
|
|
85
|
+
}
|
|
86
|
+
if (schema.views) {
|
|
87
|
+
if (checkViews && !schema.views.default_list) errors.push('views.default_list is required')
|
|
88
|
+
if (checkViews && !schema.views.default_detail) errors.push('views.default_detail is required')
|
|
89
|
+
for (const [viewName, view] of Object.entries(schema.views || {})) {
|
|
90
|
+
const v = view as any
|
|
91
|
+
if (!v || typeof v !== 'object') continue
|
|
92
|
+
for (const [fieldName, field] of Object.entries(v.fields || {})) {
|
|
93
|
+
const f = field as any
|
|
94
|
+
if (f && f.display_type && !DISPLAY_TYPES.has(f.display_type)) {
|
|
95
|
+
errors.push(`views.${viewName}.fields.${fieldName} has unknown display_type: ${f.display_type}`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (v.sections) {
|
|
99
|
+
for (const section of v.sections) {
|
|
100
|
+
for (const [fieldName, field] of Object.entries(section.fields || {})) {
|
|
101
|
+
const f = field as any
|
|
102
|
+
if (f && f.display_type && !DISPLAY_TYPES.has(f.display_type)) {
|
|
103
|
+
errors.push(`views.${viewName}.sections.*.fields.${fieldName} has unknown display_type: ${f.display_type}`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { ok: errors.length === 0, errors }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function registerValidateSchemaCommands(program: Command) {
|
|
115
|
+
program
|
|
116
|
+
.command('validate-schema')
|
|
117
|
+
.description('Validate a design_schema JSON file against the Spine design-schema rules')
|
|
118
|
+
.argument('<file>', 'Path to a design_schema JSON file')
|
|
119
|
+
.option('--no-views', 'Skip the default_list/default_detail view requirements (custom apps)')
|
|
120
|
+
.option('--json', 'Output the result as JSON')
|
|
121
|
+
.action(async (file: string, opts: { views: boolean; json: boolean }) => {
|
|
122
|
+
const abs = resolve(file)
|
|
123
|
+
if (!existsSync(abs)) {
|
|
124
|
+
console.error(`❌ file not found: ${file}`)
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
let schema: any
|
|
128
|
+
try {
|
|
129
|
+
schema = JSON.parse(readFileSync(abs, 'utf8'))
|
|
130
|
+
} catch (err: any) {
|
|
131
|
+
console.error(`❌ ${file} is not valid JSON: ${err.message}`)
|
|
132
|
+
process.exit(1)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = validateDesignSchema(schema, { checkViews: opts.views })
|
|
136
|
+
|
|
137
|
+
if (opts.json) {
|
|
138
|
+
console.log(JSON.stringify({ ok: result.ok, error_count: result.errors.length, errors: result.errors }, null, 2))
|
|
139
|
+
} else if (result.ok) {
|
|
140
|
+
console.log(`✅ ${file}: design_schema is valid`)
|
|
141
|
+
} else {
|
|
142
|
+
console.error(`❌ ${file}: ${result.errors.length} design_schema error(s)`)
|
|
143
|
+
for (const e of result.errors) console.error(` - ${e}`)
|
|
144
|
+
}
|
|
145
|
+
if (!result.ok) process.exit(1)
|
|
146
|
+
})
|
|
147
|
+
}
|