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.
Files changed (305) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/install-app.ts +183 -13
  8. package/.framework/cli/commands/migrate.ts +36 -30
  9. package/.framework/cli/commands/migrations.ts +16 -56
  10. package/.framework/cli/commands/upgrade.ts +35 -0
  11. package/.framework/cli/commands/validate-app.ts +127 -1
  12. package/.framework/cli/commands/validate-schema.ts +147 -0
  13. package/.framework/cli/context.ts +223 -110
  14. package/.framework/cli/index.ts +6 -0
  15. package/.framework/cli/lib/project-files.ts +241 -1
  16. package/.framework/functions/_shared/app-manifest.ts +2 -0
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/logging.ts +562 -224
  19. package/.framework/functions/_shared/middleware.ts +47 -44
  20. package/.framework/functions/_shared/permissions.ts +4 -0
  21. package/.framework/functions/admin-data.ts +51 -28
  22. package/.framework/functions/ai-agents.ts +10 -6
  23. package/.framework/functions/api-keys.ts +301 -122
  24. package/.framework/functions/apps.ts +63 -29
  25. package/.framework/functions/cil-ingest.ts +48 -15
  26. package/.framework/functions/debug-auth.ts +14 -8
  27. package/.framework/functions/deliveries.ts +39 -12
  28. package/.framework/functions/embeddings.ts +149 -78
  29. package/.framework/functions/integration-outbound.ts +67 -27
  30. package/.framework/functions/integrations.ts +254 -127
  31. package/.framework/functions/logs.ts +273 -133
  32. package/.framework/functions/observability.ts +278 -148
  33. package/.framework/functions/pipeline-executions.ts +1 -1
  34. package/.framework/functions/pipelines.ts +14 -9
  35. package/.framework/functions/prompt-configs.ts +7 -4
  36. package/.framework/functions/roles.ts +8 -5
  37. package/.framework/functions/security.ts +30 -17
  38. package/.framework/functions/system-cron.ts +240 -127
  39. package/.framework/functions/tests.ts +7 -6
  40. package/.framework/functions/timers.ts +310 -107
  41. package/.framework/functions/triggers.ts +28 -13
  42. package/.framework/functions/types.ts +13 -10
  43. package/.framework/migrations/069_log_stats_window.sql +54 -0
  44. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  45. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  46. package/.framework/migrations/072_slug_autofill.sql +77 -0
  47. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  48. package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
  49. package/.framework/migrations/075_security_definer_grants.sql +157 -0
  50. package/.framework/src/apps/admin/index.tsx +1141 -775
  51. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  52. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  53. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  54. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  55. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  56. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  57. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  58. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  59. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  60. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  61. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  62. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  63. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  64. package/.framework/src/components/ui/sheet.tsx +24 -26
  65. package/.framework/src/components/ui/textarea.tsx +74 -48
  66. package/.framework/src/contexts/AppContext.tsx +5 -12
  67. package/.framework/src/contexts/AuthContext.tsx +17 -9
  68. package/.framework/src/hooks/useApps.ts +18 -22
  69. package/.framework/src/hooks/useEntityList.ts +12 -9
  70. package/.framework/src/hooks/useListSchema.ts +108 -58
  71. package/.framework/src/instructions/README.md +74 -0
  72. package/.framework/src/instructions/accounts.md +97 -0
  73. package/.framework/src/instructions/ai-agents.md +69 -0
  74. package/.framework/src/instructions/alerts.md +80 -0
  75. package/.framework/src/instructions/api-keys.md +87 -0
  76. package/.framework/src/instructions/apps.md +77 -0
  77. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  78. package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
  79. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  80. package/.framework/src/instructions/concepts/automation.md +186 -0
  81. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  82. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  83. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  84. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  85. package/.framework/src/instructions/concepts/observability.md +116 -0
  86. package/.framework/src/instructions/concepts/permissions.md +150 -0
  87. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  88. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  89. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  90. package/.framework/src/instructions/deliveries.md +59 -0
  91. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  92. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  93. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  94. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  95. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  96. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  97. package/.framework/src/instructions/integrations.md +96 -0
  98. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  99. package/.framework/src/instructions/internal/docs-contract.md +158 -0
  100. package/.framework/src/instructions/invites.md +60 -0
  101. package/.framework/src/instructions/kb.md +80 -0
  102. package/.framework/src/instructions/logs.md +77 -0
  103. package/.framework/src/instructions/notifications.md +77 -0
  104. package/.framework/src/instructions/observability.md +61 -0
  105. package/.framework/src/instructions/operations/deployment.md +115 -0
  106. package/.framework/src/instructions/operations/migrations.md +125 -0
  107. package/.framework/src/instructions/operations/security-checklist.md +170 -0
  108. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  109. package/.framework/src/instructions/operations/upgrade.md +116 -0
  110. package/.framework/src/instructions/people.md +91 -0
  111. package/.framework/src/instructions/pipeline-executions.md +62 -0
  112. package/.framework/src/instructions/pipelines.md +78 -0
  113. package/.framework/src/instructions/prompts.md +95 -0
  114. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  115. package/.framework/src/instructions/reference/api/auth.md +113 -0
  116. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  117. package/.framework/src/instructions/reference/app-manifest.md +184 -0
  118. package/.framework/src/instructions/reference/cli.md +124 -0
  119. package/.framework/src/instructions/reference/database.md +132 -0
  120. package/.framework/src/instructions/reference/design-schema.md +280 -0
  121. package/.framework/src/instructions/reference/env-vars.md +133 -0
  122. package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
  123. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  124. package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
  125. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  126. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  127. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  128. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  129. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  130. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  131. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  132. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  133. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  134. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  135. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  136. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  137. package/.framework/src/instructions/reference/functions/index.md +103 -0
  138. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  139. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  140. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  141. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  142. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  143. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  144. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  145. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  146. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  147. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  148. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  149. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  150. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  151. package/.framework/src/instructions/reference/functions/security.md +140 -0
  152. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  153. package/.framework/src/instructions/reference/functions/system.md +106 -0
  154. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  155. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  156. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  157. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  158. package/.framework/src/instructions/reference/functions/types.md +146 -0
  159. package/.framework/src/instructions/reference/seeds.md +126 -0
  160. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  161. package/.framework/src/instructions/reference/spine-config.md +114 -0
  162. package/.framework/src/instructions/roles.md +80 -0
  163. package/.framework/src/instructions/runtime-collaboration.md +106 -0
  164. package/.framework/src/instructions/runtime-detail.md +70 -0
  165. package/.framework/src/instructions/runtime-list.md +77 -0
  166. package/.framework/src/instructions/security.md +67 -0
  167. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  168. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  169. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  170. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  171. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  172. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  173. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  174. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  175. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  176. package/.framework/src/instructions/tasks/document-your-app.md +117 -0
  177. package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
  178. package/.framework/src/instructions/tasks/register-an-app.md +137 -0
  179. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  180. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  181. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  182. package/.framework/src/instructions/testing.md +59 -0
  183. package/.framework/src/instructions/timers.md +84 -0
  184. package/.framework/src/instructions/triggers.md +69 -0
  185. package/.framework/src/instructions/types.md +93 -0
  186. package/.framework/src/lib/app-mount.ts +88 -0
  187. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  188. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  189. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  190. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  191. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  192. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  193. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  194. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  195. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  196. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  197. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  198. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  199. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  200. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  201. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  202. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  203. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  204. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  205. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  206. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  207. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  208. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  209. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  210. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  211. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  212. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  213. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  214. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  215. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  216. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  217. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  218. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  219. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  220. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  221. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  222. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  223. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  224. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  225. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  226. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  227. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  228. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  229. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  230. package/.framework/src/pages/auth/LoginPage.tsx +3 -2
  231. package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
  232. package/.framework/templates/agent/AGENTS.md +167 -0
  233. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  234. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  235. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  236. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  237. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
  238. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  239. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
  240. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
  241. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
  242. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  243. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  244. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  245. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  246. package/.framework/templates/project.json +4 -1
  247. package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
  248. package/.framework/tests/unit/agent-files.test.ts +355 -0
  249. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  250. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  251. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  252. package/.framework/tests/unit/app-mount.test.ts +80 -0
  253. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  254. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  255. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  256. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  257. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  258. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  259. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  260. package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
  261. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  262. package/.framework/tests/unit/system-cron.test.ts +240 -0
  263. package/.framework/tests/unit/timers.test.ts +159 -0
  264. package/README.md +125 -166
  265. package/config/tsconfig.cli.json +2 -2
  266. package/package.json +15 -4
  267. package/templates/project/package-lock.json +2 -2
  268. package/.framework/docs/admin-routes.md +0 -76
  269. package/.framework/docs/api-docs-progress.md +0 -38
  270. package/.framework/docs/api-governance.md +0 -146
  271. package/.framework/docs/api-testing-results.md +0 -212
  272. package/.framework/docs/assembly-launch-guide.md +0 -138
  273. package/.framework/docs/audit-results.md +0 -590
  274. package/.framework/docs/db-api-inventory.md +0 -95
  275. package/.framework/docs/gap-remediation-backlog.md +0 -103
  276. package/.framework/docs/ui-adoption-verification.md +0 -111
  277. package/.framework/docs/ui-api-coverage.md +0 -84
  278. package/.framework/docs/v2-compatibility-audit.md +0 -228
  279. package/.framework/mcp/index.ts +0 -133
  280. package/.framework/mcp/load-env.ts +0 -60
  281. package/.framework/mcp/shared/app-manifest.ts +0 -275
  282. package/.framework/mcp/shared/db.ts +0 -477
  283. package/.framework/mcp/shared/kb.ts +0 -118
  284. package/.framework/mcp/tools/architect.ts +0 -431
  285. package/.framework/mcp/tools/cil/context.ts +0 -345
  286. package/.framework/mcp/tools/cil/graph.ts +0 -242
  287. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  288. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  289. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  290. package/.framework/mcp/tools/cil/project.ts +0 -791
  291. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  292. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  293. package/.framework/mcp/tools/cil/search.ts +0 -97
  294. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  295. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  296. package/.framework/mcp/tools/kb/delete.ts +0 -139
  297. package/.framework/mcp/tools/kb/index.ts +0 -52
  298. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  299. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  300. package/.framework/mcp/tools/kb/publish.ts +0 -100
  301. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  302. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  303. package/.framework/mcp/tools/kb/search.ts +0 -101
  304. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  305. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,355 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
+ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync, existsSync, readdirSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join, resolve, dirname } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import {
7
+ loadProjectProfile,
8
+ syncAgentFiles,
9
+ collectAgentFileHashes,
10
+ mergeAgentsFile,
11
+ findAgentsBlock,
12
+ agentsBlockVersion,
13
+ renderAgentTemplate,
14
+ writeFrozenManifest,
15
+ AGENTS_BEGIN_RE,
16
+ AGENTS_END_RE,
17
+ } from '../../cli/lib/project-files.ts'
18
+ import { validateDesignSchema } from '../../cli/commands/validate-schema.ts'
19
+
20
+ // The repo root doubles as the package root — the shipped templates live at
21
+ // .framework/templates/agent/ relative to it.
22
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
23
+ const AGENT_SRC = join(REPO_ROOT, '.framework/templates/agent')
24
+ const TEMPLATE_PATH = join(AGENT_SRC, 'AGENTS.md')
25
+ const SKILLS_SRC = join(AGENT_SRC, 'skills')
26
+
27
+ let workdir: string
28
+
29
+ beforeEach(() => {
30
+ workdir = mkdtempSync(join(tmpdir(), 'spine-agent-files-'))
31
+ vi.spyOn(console, 'log').mockImplementation(() => {})
32
+ })
33
+
34
+ afterEach(() => {
35
+ rmSync(workdir, { recursive: true, force: true })
36
+ vi.restoreAllMocks()
37
+ })
38
+
39
+ /* ---------------- template + skill asset lint ---------------- */
40
+
41
+ describe('AGENTS.md template', () => {
42
+ const template = readFileSync(TEMPLATE_PATH, 'utf8')
43
+
44
+ it('carries the marker pair and the version placeholder', () => {
45
+ expect(template).toContain('<!-- spine-framework:begin version={{SPINE_VERSION}} -->')
46
+ expect(template).toContain('<!-- spine-framework:end -->')
47
+ expect(template).toContain('never overwritten by spine-framework upgrade')
48
+ })
49
+
50
+ it('contains the required framework-rule sections', () => {
51
+ for (const required of [
52
+ 'design_schema',
53
+ 'record_permissions',
54
+ 'admin-data',
55
+ 'custom/',
56
+ 'data_type',
57
+ 'display_type',
58
+ '.framework/src/instructions/',
59
+ '.devin/skills/',
60
+ 'npm run typecheck',
61
+ 'npm run test',
62
+ 'npm run verify',
63
+ 'npx spine-framework validate-schema',
64
+ ]) {
65
+ expect(template, required).toContain(required)
66
+ }
67
+ })
68
+
69
+ it('excludes CIL-specific content (not shipped to customers)', () => {
70
+ expect(template).not.toMatch(/\bcil_\w+/)
71
+ expect(template).not.toContain('DOC-')
72
+ expect(template).not.toContain('CIL[')
73
+ })
74
+
75
+ it('cites only .framework/.devin paths that exist in the package', () => {
76
+ // Mirror of docs-lint check 5 — only vendored-tree paths are checked;
77
+ // custom/, .env, and app-relative paths live in the customer project, not
78
+ // this repo. Globs/placeholders are skipped.
79
+ for (const m of template.matchAll(/`([^\s`]+)`/g)) {
80
+ const p = m[1].replace(/[.,;:)]+$/, '')
81
+ if (p.includes('*') || p.includes('<') || p.includes('{')) continue
82
+ if (!/^\.(framework|devin)\//.test(p)) continue
83
+ expect(existsSync(join(REPO_ROOT, p)), p).toBe(true)
84
+ }
85
+ })
86
+ })
87
+
88
+ describe('spine-* skill files', () => {
89
+ const skillDirs = readdirSync(SKILLS_SRC, { withFileTypes: true })
90
+ .filter((d) => d.isDirectory() && d.name.startsWith('spine-'))
91
+ .map((d) => d.name)
92
+
93
+ it('ships the expected skill set', () => {
94
+ expect(skillDirs.sort()).toEqual([
95
+ 'spine-add-ai-agent',
96
+ 'spine-add-automation',
97
+ 'spine-add-integration',
98
+ 'spine-architect',
99
+ 'spine-install-app',
100
+ 'spine-migration',
101
+ 'spine-new-app',
102
+ 'spine-new-function',
103
+ 'spine-new-type',
104
+ 'spine-schema',
105
+ 'spine-test',
106
+ 'spine-troubleshoot',
107
+ 'spine-upgrade',
108
+ ])
109
+ })
110
+
111
+ for (const dir of skillDirs) {
112
+ it(`${dir}/SKILL.md has valid frontmatter and cites real paths`, () => {
113
+ const file = join(SKILLS_SRC, dir, 'SKILL.md')
114
+ const src = readFileSync(file, 'utf8')
115
+ expect(src.startsWith('---'), 'missing frontmatter').toBe(true)
116
+ const fmEnd = src.indexOf('\n---', 3)
117
+ expect(fmEnd, 'unterminated frontmatter').toBeGreaterThan(-1)
118
+ const fm = src.slice(3, fmEnd)
119
+ expect(fm).toMatch(/^name:\s*.+$/m)
120
+ expect(fm).toMatch(/^description:\s*.+$/m)
121
+ expect(fm).toMatch(/^allowed-tools:/m)
122
+ expect(fm).toContain(`name: ${dir}`)
123
+ // Procedural body — a Steps/procedure section and sane length.
124
+ const body = src.slice(fmEnd + 4)
125
+ expect(body.length).toBeGreaterThan(400)
126
+ // Backticked .framework/.devin paths must exist (docs-lint check 5).
127
+ for (const m of src.matchAll(/`([^\s`]+)`/g)) {
128
+ const p = m[1].replace(/[.,;:)]+$/, '')
129
+ if (p.includes('*') || p.includes('<') || p.includes('{')) continue
130
+ if (!/^\.(framework|devin)\//.test(p)) continue
131
+ expect(existsSync(join(REPO_ROOT, p)), `${dir} cites missing path ${p}`).toBe(true)
132
+ }
133
+ // No MCP / CIL tooling references — those don't ship.
134
+ expect(src).not.toMatch(/\bcil_\w+/)
135
+ expect(src).not.toContain('mcp_call_tool')
136
+ })
137
+ }
138
+ })
139
+
140
+ /* ---------------- init-side sync + manifest ---------------- */
141
+
142
+ describe('syncAgentFiles — init path (real package templates)', () => {
143
+ it('writes AGENTS.md with the version substituted and copies all skills', () => {
144
+ const profile = loadProjectProfile(REPO_ROOT)
145
+ const actions = syncAgentFiles({
146
+ pkgRoot: REPO_ROOT,
147
+ projectRoot: workdir,
148
+ entries: profile.agent_files ?? [],
149
+ version: '9.9.9',
150
+ })
151
+
152
+ expect(actions.map((a) => a.rel).sort()).toEqual(['.devin/skills', 'AGENTS.md'])
153
+
154
+ const agents = readFileSync(join(workdir, 'AGENTS.md'), 'utf8')
155
+ expect(agents).toContain('<!-- spine-framework:begin version=9.9.9 -->')
156
+ expect(agents).not.toContain('{{SPINE_VERSION}}')
157
+
158
+ const skills = readdirSync(join(workdir, '.devin/skills')).sort()
159
+ expect(skills.length).toBe(13)
160
+ for (const name of skills) {
161
+ expect(name.startsWith('spine-')).toBe(true)
162
+ expect(existsSync(join(workdir, '.devin/skills', name, 'SKILL.md'))).toBe(true)
163
+ }
164
+ })
165
+
166
+ it('records every installed asset in the manifest agent_files map', () => {
167
+ const profile = loadProjectProfile(REPO_ROOT)
168
+ syncAgentFiles({
169
+ pkgRoot: REPO_ROOT,
170
+ projectRoot: workdir,
171
+ entries: profile.agent_files ?? [],
172
+ version: '9.9.9',
173
+ })
174
+ const hashes = collectAgentFileHashes(workdir, profile.agent_files ?? [])
175
+
176
+ expect(hashes['AGENTS.md']).toMatch(/^sha256:[0-9a-f]{64}$/)
177
+ const skillEntries = Object.keys(hashes).filter((k) => k.startsWith('.devin/skills/spine-'))
178
+ expect(skillEntries.length).toBe(13)
179
+
180
+ mkdirSync(join(workdir, '.framework/cli'), { recursive: true })
181
+ writeFileSync(join(workdir, '.framework/cli/x.ts'), 'export {}\n')
182
+ writeFrozenManifest({
183
+ projectRoot: workdir,
184
+ frameworkVersion: '9.9.9',
185
+ frameworkDeps: [],
186
+ agentFiles: hashes,
187
+ })
188
+ const manifest = JSON.parse(readFileSync(join(workdir, '.spine-manifest.json'), 'utf8'))
189
+ // agent files live outside `files` — sealing them would break verify.
190
+ expect(manifest.files['AGENTS.md']).toBeUndefined()
191
+ expect(Object.keys(manifest.files).every((k) => !k.startsWith('.devin/'))).toBe(true)
192
+ expect(manifest.agent_files['AGENTS.md']).toMatch(/^sha256:/)
193
+ expect(Object.keys(manifest.agent_files).filter((k) => k.startsWith('.devin/skills/')).length).toBe(13)
194
+ })
195
+ })
196
+
197
+ /* ---------------- merge semantics (upgrade path) ---------------- */
198
+
199
+ describe('mergeAgentsFile / upgrade block refresh', () => {
200
+ const renderedNew = renderAgentTemplate(readFileSync(TEMPLATE_PATH, 'utf8'), '9.9.9')
201
+
202
+ function agentsWithVersion(version: string, tail = ''): string {
203
+ const rendered = renderAgentTemplate(readFileSync(TEMPLATE_PATH, 'utf8'), version)
204
+ return tail ? `${rendered}\n${tail}` : rendered
205
+ }
206
+
207
+ it('replaces only the framework block and preserves the user section', () => {
208
+ const existing = agentsWithVersion('0.0.1', '\n# My project rules\n\n- never do the thing\n')
209
+ const merged = mergeAgentsFile(existing, renderedNew)
210
+ expect(merged.status).toBe('replaced')
211
+ expect(merged.text).toContain('version=9.9.9')
212
+ expect(merged.text).not.toContain('version=0.0.1')
213
+ expect(merged.text).toContain('# My project rules')
214
+ expect(merged.text).toContain('- never do the thing')
215
+ })
216
+
217
+ it('prepends a fresh block when markers are absent', () => {
218
+ const existing = '# Existing rules\n\nMy own AGENTS file.\n'
219
+ const merged = mergeAgentsFile(existing, renderedNew)
220
+ expect(merged.status).toBe('prepended')
221
+ expect(merged.text.startsWith('<!-- spine-framework:begin version=9.9.9 -->')).toBe(true)
222
+ expect(merged.text).toContain('# Existing rules')
223
+ // Exactly one begin marker afterwards.
224
+ expect(merged.text.match(AGENTS_BEGIN_RE)!.length).toBeGreaterThan(0)
225
+ expect((merged.text.match(/spine-framework:begin/g) ?? []).length).toBe(1)
226
+ })
227
+
228
+ it('preserves CRLF line endings in the merged file', () => {
229
+ const existing = agentsWithVersion('0.0.1', '\nuser tail\n').replace(/\n/g, '\r\n')
230
+ const merged = mergeAgentsFile(existing, renderedNew)
231
+ expect(merged.text).toContain('\r\n')
232
+ expect(merged.text).not.toMatch(/[^\r]\n/) // no bare LF survives
233
+ expect(merged.text).toContain('version=9.9.9')
234
+ expect(merged.text).toContain('user tail')
235
+ })
236
+
237
+ it('writes the full template when AGENTS.md is missing (via syncAgentFiles)', () => {
238
+ const profile = loadProjectProfile(REPO_ROOT)
239
+ const actions = syncAgentFiles({
240
+ pkgRoot: REPO_ROOT,
241
+ projectRoot: workdir,
242
+ entries: profile.agent_files ?? [],
243
+ version: '9.9.9',
244
+ })
245
+ expect(actions.find((a) => a.rel === 'AGENTS.md')?.action).toBe('written')
246
+ const text = readFileSync(join(workdir, 'AGENTS.md'), 'utf8')
247
+ expect(findAgentsBlock(text.split('\n'))).not.toBeNull()
248
+ expect(agentsBlockVersion(text)).toBe('9.9.9')
249
+ })
250
+
251
+ it('syncAgentFiles refreshes the block on rerun and drops removed spine-* dirs', () => {
252
+ const profile = loadProjectProfile(REPO_ROOT)
253
+ // Seed an "old" install: old block version + a stale spine-* dir + a customer dir.
254
+ const oldRendered = renderAgentTemplate(readFileSync(TEMPLATE_PATH, 'utf8'), '0.0.1')
255
+ writeFileSync(join(workdir, 'AGENTS.md'), `${oldRendered}\nuser-owned section\n`)
256
+ mkdirSync(join(workdir, '.devin/skills/spine-removed'), { recursive: true })
257
+ writeFileSync(join(workdir, '.devin/skills/spine-removed/SKILL.md'), 'stale\n')
258
+ mkdirSync(join(workdir, '.devin/skills/my-own'), { recursive: true })
259
+ writeFileSync(join(workdir, '.devin/skills/my-own/SKILL.md'), 'customer-owned\n')
260
+
261
+ const actions = syncAgentFiles({
262
+ pkgRoot: REPO_ROOT,
263
+ projectRoot: workdir,
264
+ entries: profile.agent_files ?? [],
265
+ version: '9.9.9',
266
+ })
267
+ expect(actions.find((a) => a.rel === 'AGENTS.md')?.action).toBe('replaced')
268
+
269
+ const text = readFileSync(join(workdir, 'AGENTS.md'), 'utf8')
270
+ expect(agentsBlockVersion(text)).toBe('9.9.9')
271
+ expect(text).toContain('user-owned section')
272
+
273
+ expect(existsSync(join(workdir, '.devin/skills/spine-removed'))).toBe(false)
274
+ expect(readFileSync(join(workdir, '.devin/skills/my-own/SKILL.md'), 'utf8')).toBe('customer-owned\n')
275
+ expect(existsSync(join(workdir, '.devin/skills/spine-architect/SKILL.md'))).toBe(true)
276
+ })
277
+
278
+ it('dry-run reports actions without writing', () => {
279
+ const profile = loadProjectProfile(REPO_ROOT)
280
+ writeFileSync(join(workdir, 'AGENTS.md'), '# mine\n')
281
+ const actions = syncAgentFiles({
282
+ pkgRoot: REPO_ROOT,
283
+ projectRoot: workdir,
284
+ entries: profile.agent_files ?? [],
285
+ version: '9.9.9',
286
+ dryRun: true,
287
+ })
288
+ expect(actions.find((a) => a.rel === 'AGENTS.md')?.action).toBe('prepended')
289
+ expect(readFileSync(join(workdir, 'AGENTS.md'), 'utf8')).toBe('# mine\n')
290
+ expect(existsSync(join(workdir, '.devin'))).toBe(false)
291
+ })
292
+ })
293
+
294
+ /* ---------------- validate-schema ---------------- */
295
+
296
+ const MIN_SCHEMA = {
297
+ scope: 'account',
298
+ fields: {
299
+ title: { data_type: 'text', label: 'Title', required: true, system: true },
300
+ },
301
+ views: {
302
+ default_list: {
303
+ type: 'list',
304
+ fields: { title: { display_type: 'text' } },
305
+ default_sort: { field: 'created_at', direction: 'desc' },
306
+ },
307
+ default_detail: {
308
+ type: 'detail',
309
+ sections: [{ title: 'Details', fields: { title: { display_type: 'input' } } }],
310
+ },
311
+ },
312
+ record_permissions: { 'system-admin': ['create', 'read', 'update', 'delete'] },
313
+ }
314
+
315
+ describe('validateDesignSchema', () => {
316
+ it('accepts the minimal valid schema', () => {
317
+ const r = validateDesignSchema(MIN_SCHEMA)
318
+ expect(r.ok).toBe(true)
319
+ expect(r.errors).toEqual([])
320
+ })
321
+
322
+ it('rejects non-object roots, missing fields, and missing record_permissions', () => {
323
+ expect(validateDesignSchema(null).errors).toContain('design_schema must be an object')
324
+ const noFields = validateDesignSchema({ record_permissions: { 'system-admin': ['read'] } })
325
+ expect(noFields.errors.some((e) => e.startsWith('fields is required'))).toBe(true)
326
+ const noPerms = validateDesignSchema({ fields: MIN_SCHEMA.fields })
327
+ expect(noPerms.errors.some((e) => e.startsWith('record_permissions'))).toBe(true)
328
+ })
329
+
330
+ it('flags unknown data_type and missing options/select variants', () => {
331
+ const schema = {
332
+ ...MIN_SCHEMA,
333
+ fields: {
334
+ bad: { data_type: 'wat', label: 'X', required: true },
335
+ sel: { data_type: 'select', label: 'S', required: true },
336
+ },
337
+ }
338
+ const r = validateDesignSchema(schema)
339
+ expect(r.errors).toContain('fields.bad has unknown data_type: wat')
340
+ expect(r.errors.some((e) => e.includes('fields.sel') && e.includes('options'))).toBe(true)
341
+ })
342
+
343
+ it('flags unknown display_type inside views', () => {
344
+ const schema = JSON.parse(JSON.stringify(MIN_SCHEMA))
345
+ schema.views.default_list.fields.title.display_type = 'sparkles'
346
+ const r = validateDesignSchema(schema)
347
+ expect(r.errors.some((e) => e.includes('unknown display_type: sparkles'))).toBe(true)
348
+ })
349
+
350
+ it('requires views unless checkViews is disabled', () => {
351
+ const schema = { fields: MIN_SCHEMA.fields, record_permissions: MIN_SCHEMA.record_permissions }
352
+ expect(validateDesignSchema(schema).ok).toBe(false)
353
+ expect(validateDesignSchema(schema, { checkViews: false }).ok).toBe(true)
354
+ })
355
+ })