spine-framework 0.4.4 → 0.5.0-rc.4

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 (283) 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/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  15. package/.framework/functions/_shared/logging.ts +562 -224
  16. package/.framework/functions/_shared/middleware.ts +47 -44
  17. package/.framework/functions/admin-data.ts +39 -28
  18. package/.framework/functions/ai-agents.ts +10 -6
  19. package/.framework/functions/api-keys.ts +301 -122
  20. package/.framework/functions/apps.ts +43 -26
  21. package/.framework/functions/cil-ingest.ts +48 -15
  22. package/.framework/functions/debug-auth.ts +14 -8
  23. package/.framework/functions/deliveries.ts +39 -12
  24. package/.framework/functions/embeddings.ts +145 -76
  25. package/.framework/functions/integration-outbound.ts +67 -27
  26. package/.framework/functions/integrations.ts +254 -127
  27. package/.framework/functions/logs.ts +273 -133
  28. package/.framework/functions/observability.ts +277 -147
  29. package/.framework/functions/pipeline-executions.ts +1 -1
  30. package/.framework/functions/pipelines.ts +14 -9
  31. package/.framework/functions/prompt-configs.ts +7 -4
  32. package/.framework/functions/roles.ts +8 -5
  33. package/.framework/functions/security.ts +30 -17
  34. package/.framework/functions/system-cron.ts +240 -127
  35. package/.framework/functions/tests.ts +7 -6
  36. package/.framework/functions/timers.ts +310 -107
  37. package/.framework/functions/triggers.ts +28 -13
  38. package/.framework/functions/types.ts +13 -10
  39. package/.framework/migrations/069_log_stats_window.sql +54 -0
  40. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  41. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  42. package/.framework/migrations/072_slug_autofill.sql +77 -0
  43. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  44. package/.framework/src/apps/admin/index.tsx +1141 -775
  45. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  46. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  47. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  48. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  49. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  50. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  51. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  52. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  53. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  54. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  55. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  56. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  57. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  58. package/.framework/src/components/ui/sheet.tsx +24 -26
  59. package/.framework/src/components/ui/textarea.tsx +74 -48
  60. package/.framework/src/hooks/useEntityList.ts +12 -9
  61. package/.framework/src/hooks/useListSchema.ts +108 -58
  62. package/.framework/src/instructions/README.md +57 -0
  63. package/.framework/src/instructions/ai-agents.md +69 -0
  64. package/.framework/src/instructions/alerts.md +80 -0
  65. package/.framework/src/instructions/api-keys.md +87 -0
  66. package/.framework/src/instructions/apps.md +77 -0
  67. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  68. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  69. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  70. package/.framework/src/instructions/concepts/automation.md +186 -0
  71. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  72. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  73. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  74. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  75. package/.framework/src/instructions/concepts/observability.md +116 -0
  76. package/.framework/src/instructions/concepts/permissions.md +150 -0
  77. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  78. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  79. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  80. package/.framework/src/instructions/deliveries.md +59 -0
  81. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  82. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  83. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  84. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  85. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  86. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  87. package/.framework/src/instructions/integrations.md +96 -0
  88. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  89. package/.framework/src/instructions/invites.md +60 -0
  90. package/.framework/src/instructions/kb.md +80 -0
  91. package/.framework/src/instructions/logs.md +77 -0
  92. package/.framework/src/instructions/observability.md +61 -0
  93. package/.framework/src/instructions/operations/deployment.md +115 -0
  94. package/.framework/src/instructions/operations/migrations.md +125 -0
  95. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  96. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  97. package/.framework/src/instructions/operations/upgrade.md +116 -0
  98. package/.framework/src/instructions/pipeline-executions.md +62 -0
  99. package/.framework/src/instructions/pipelines.md +78 -0
  100. package/.framework/src/instructions/prompts.md +95 -0
  101. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  102. package/.framework/src/instructions/reference/api/auth.md +113 -0
  103. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  104. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  105. package/.framework/src/instructions/reference/cli.md +124 -0
  106. package/.framework/src/instructions/reference/database.md +132 -0
  107. package/.framework/src/instructions/reference/design-schema.md +280 -0
  108. package/.framework/src/instructions/reference/env-vars.md +133 -0
  109. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  110. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  111. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  112. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  113. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  114. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  115. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  116. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  117. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  118. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  119. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  120. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  121. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  122. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  123. package/.framework/src/instructions/reference/functions/index.md +103 -0
  124. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  125. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  126. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  127. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  128. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  129. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  130. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  131. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  132. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  133. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  134. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  135. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  136. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  137. package/.framework/src/instructions/reference/functions/security.md +140 -0
  138. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  139. package/.framework/src/instructions/reference/functions/system.md +106 -0
  140. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  141. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  142. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  143. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  144. package/.framework/src/instructions/reference/functions/types.md +146 -0
  145. package/.framework/src/instructions/reference/seeds.md +126 -0
  146. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  147. package/.framework/src/instructions/reference/spine-config.md +114 -0
  148. package/.framework/src/instructions/roles.md +80 -0
  149. package/.framework/src/instructions/runtime-detail.md +70 -0
  150. package/.framework/src/instructions/runtime-list.md +77 -0
  151. package/.framework/src/instructions/security.md +67 -0
  152. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  153. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  154. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  155. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  156. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  157. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  158. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  159. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  160. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  161. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  162. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  163. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  164. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  165. package/.framework/src/instructions/testing.md +59 -0
  166. package/.framework/src/instructions/timers.md +82 -0
  167. package/.framework/src/instructions/triggers.md +69 -0
  168. package/.framework/src/instructions/types.md +93 -0
  169. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  170. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  171. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  172. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  173. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  174. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  175. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  176. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  177. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  178. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  179. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  180. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  181. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  182. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  183. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  184. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  185. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  186. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  187. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  188. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  189. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  190. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  191. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  192. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  193. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  194. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  195. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  196. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  197. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  198. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  199. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  200. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  201. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  202. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  203. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  204. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  205. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  206. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  207. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  208. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  209. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  210. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  211. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  212. package/.framework/templates/agent/AGENTS.md +150 -0
  213. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  214. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  215. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  216. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  217. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  218. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  219. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  220. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  221. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  222. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  223. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  224. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  225. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  226. package/.framework/templates/project.json +4 -1
  227. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  228. package/.framework/tests/unit/agent-files.test.ts +355 -0
  229. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  230. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  231. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  232. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  233. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  234. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  235. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  236. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  237. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  238. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  239. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  240. package/.framework/tests/unit/system-cron.test.ts +240 -0
  241. package/.framework/tests/unit/timers.test.ts +159 -0
  242. package/README.md +125 -166
  243. package/config/tsconfig.cli.json +2 -2
  244. package/package.json +15 -4
  245. package/templates/project/package-lock.json +2 -2
  246. package/.framework/docs/admin-routes.md +0 -76
  247. package/.framework/docs/api-docs-progress.md +0 -38
  248. package/.framework/docs/api-governance.md +0 -146
  249. package/.framework/docs/api-testing-results.md +0 -212
  250. package/.framework/docs/assembly-launch-guide.md +0 -138
  251. package/.framework/docs/audit-results.md +0 -590
  252. package/.framework/docs/db-api-inventory.md +0 -95
  253. package/.framework/docs/gap-remediation-backlog.md +0 -103
  254. package/.framework/docs/ui-adoption-verification.md +0 -111
  255. package/.framework/docs/ui-api-coverage.md +0 -84
  256. package/.framework/docs/v2-compatibility-audit.md +0 -228
  257. package/.framework/mcp/index.ts +0 -133
  258. package/.framework/mcp/load-env.ts +0 -60
  259. package/.framework/mcp/shared/app-manifest.ts +0 -275
  260. package/.framework/mcp/shared/db.ts +0 -477
  261. package/.framework/mcp/shared/kb.ts +0 -118
  262. package/.framework/mcp/tools/architect.ts +0 -431
  263. package/.framework/mcp/tools/cil/context.ts +0 -345
  264. package/.framework/mcp/tools/cil/graph.ts +0 -242
  265. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  266. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  267. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  268. package/.framework/mcp/tools/cil/project.ts +0 -791
  269. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  270. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  271. package/.framework/mcp/tools/cil/search.ts +0 -97
  272. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  273. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  274. package/.framework/mcp/tools/kb/delete.ts +0 -139
  275. package/.framework/mcp/tools/kb/index.ts +0 -52
  276. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  277. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  278. package/.framework/mcp/tools/kb/publish.ts +0 -100
  279. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  280. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  281. package/.framework/mcp/tools/kb/search.ts +0 -101
  282. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  283. 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
+ })