spine-framework 0.3.124 → 0.3.125

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.
@@ -1,44 +1,12 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/doctor
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine doctor` command — comprehensive system validation for agentic IDE setup.
9
- * Checks environment, database, migrations, integrity, and custom code.
10
- *
11
- * **Commands:**
12
- * | Subcommand | Description |
13
- * |-------------------|-------------------------------------------------------|
14
- * | `doctor` | Run all checks and report status |
15
- * | `doctor --json` | Output structured JSON for agent consumption |
16
- * | `doctor --fix` | Attempt automatic remediation where possible |
17
- *
18
- * **Checks performed:**
19
- * 1. Environment variables (SUPABASE_URL, SERVICE_ROLE_KEY)
20
- * 2. Database connectivity and latency
21
- * 3. Migration status (local vs applied)
22
- * 4. Core integrity (manifest hash verification)
23
- * 5. Custom code TypeScript compilation
24
- * 6. Test suite status (last run results)
25
- *
26
- * **Exit codes:**
27
- * - 0: All checks passed (healthy)
28
- * - 1: One or more checks failed
29
- * - 2: Check passed but warnings present (degraded)
30
- *
31
- * **Usage:**
32
- * ```bash
33
- * spine doctor
34
- * spine doctor --json
35
- * spine doctor --fix
36
- * ```
37
- *
38
- * @seeAlso cli/context.ts (buildCliContext)
39
- * @seeAlso functions/system.ts (health endpoint)
40
- */
41
1
 
2
+
3
+ // CIL[BEGIN chunk_id="doctor_module_header_1783405679" type="module_header" identity="module_header" parent="none"]
4
+ // CIL[DOC-MACRO purpose="Setup imports and path constants for doctor command"]
5
+ // CIL[DOC-RESPONSIBILITY responsibility="Configure module dependencies and file paths"]
6
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
7
+ // CIL[DOC-CONTRACT inputs="none" outputs="imports, paths" errors="none" side_effects="none"]
8
+ // CIL[DOC-MICRO logic_summary="Import CLI framework, database, and file system utilities" notable_decisions="[]"]
9
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
42
10
  import type { Command } from 'commander'
43
11
  import { buildCliContext, handleError } from '../context.ts'
44
12
  import { adminDb } from '../../functions/_shared/index.ts'
@@ -48,9 +16,17 @@ import { fileURLToPath } from 'url'
48
16
 
49
17
  const __filename = fileURLToPath(import.meta.url)
50
18
  const __dirname = dirname(__filename)
19
+ // CIL[END chunk_id="doctor_module_header_1783405679"]
20
+
51
21
 
52
- // ─── TYPES ─────────────────────────────────────────────────────────────────
53
22
 
23
+ // CIL[BEGIN chunk_id="doctor_CheckResult_1783405679" type="interface" identity="CheckResult" parent="none"]
24
+ // CIL[DOC-MACRO purpose="Define structure for individual health check results"]
25
+ // CIL[DOC-RESPONSIBILITY responsibility="Standardize check result data format"]
26
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
27
+ // CIL[DOC-CONTRACT inputs="none" outputs="CheckResult interface" errors="none" side_effects="none"]
28
+ // CIL[DOC-MICRO logic_summary="Type definition for check status and metadata" notable_decisions="[]"]
29
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
54
30
  interface CheckResult {
55
31
  name: string
56
32
  status: 'pass' | 'fail' | 'warn'
@@ -58,7 +34,15 @@ interface CheckResult {
58
34
  details?: Record<string, any>
59
35
  fixable?: boolean
60
36
  }
61
-
37
+ // CIL[END chunk_id="doctor_CheckResult_1783405679"]
38
+
39
+ // CIL[BEGIN chunk_id="doctor_DoctorReport_1783405679" type="interface" identity="DoctorReport" parent="none"]
40
+ // CIL[DOC-MACRO purpose="Define structure for complete doctor health report"]
41
+ // CIL[DOC-RESPONSIBILITY responsibility="Aggregate all check results into summary report"]
42
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
43
+ // CIL[DOC-CONTRACT inputs="none" outputs="DoctorReport interface" errors="none" side_effects="none"]
44
+ // CIL[DOC-MICRO logic_summary="Type definition for overall health status and summary" notable_decisions="[]"]
45
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
62
46
  interface DoctorReport {
63
47
  overall: 'healthy' | 'degraded' | 'unhealthy'
64
48
  checks: CheckResult[]
@@ -69,9 +53,17 @@ interface DoctorReport {
69
53
  fixable: number
70
54
  }
71
55
  }
56
+ // CIL[END chunk_id="doctor_DoctorReport_1783405679"]
72
57
 
73
- // ─── CHECK IMPLEMENTATIONS ─────────────────────────────────────────────────
74
58
 
59
+
60
+ // CIL[BEGIN chunk_id="doctor_checkEnvironment_1783405679" type="function" identity="checkEnvironment" parent="none"]
61
+ // CIL[DOC-MACRO purpose="Validate required environment variables are set"]
62
+ // CIL[DOC-RESPONSIBILITY responsibility="Check SUPABASE_URL and SERVICE_ROLE_KEY presence"]
63
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
64
+ // CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="none" side_effects="none"]
65
+ // CIL[DOC-MICRO logic_summary="Check process.env for required vars and .xenv file existence" notable_decisions="[]"]
66
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
75
67
  async function checkEnvironment(): Promise<CheckResult> {
76
68
  const required = ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY']
77
69
  const missing = required.filter(v => !process.env[v])
@@ -86,7 +78,7 @@ async function checkEnvironment(): Promise<CheckResult> {
86
78
  }
87
79
  }
88
80
 
89
- // Check if .xenv exists
81
+
90
82
  const xenvPath = resolve(__dirname, '../../.xenv')
91
83
  const hasXenv = existsSync(xenvPath)
92
84
 
@@ -97,7 +89,15 @@ async function checkEnvironment(): Promise<CheckResult> {
97
89
  details: { has_xenv: hasXenv, vars_present: required.length }
98
90
  }
99
91
  }
100
-
92
+ // CIL[END chunk_id="doctor_checkEnvironment_1783405679"]
93
+
94
+ // CIL[BEGIN chunk_id="doctor_checkDatabase_1783405679" type="function" identity="checkDatabase" parent="none"]
95
+ // CIL[DOC-MACRO purpose="Test database connectivity and measure latency"]
96
+ // CIL[DOC-RESPONSIBILITY responsibility="Verify database connection and performance"]
97
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
98
+ // CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="database connection errors" side_effects="none"]
99
+ // CIL[DOC-MICRO logic_summary="Query accounts table to test connection and measure response time" notable_decisions="[]"]
100
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
101
101
  async function checkDatabase(): Promise<CheckResult> {
102
102
  const start = Date.now()
103
103
  try {
@@ -129,7 +129,15 @@ async function checkDatabase(): Promise<CheckResult> {
129
129
  }
130
130
  }
131
131
  }
132
-
132
+ // CIL[END chunk_id="doctor_checkDatabase_1783405679"]
133
+
134
+ // CIL[BEGIN chunk_id="doctor_checkMigrations_1783405679" type="function" identity="checkMigrations" parent="none"]
135
+ // CIL[DOC-MACRO purpose="Compare local migration files with applied database migrations"]
136
+ // CIL[DOC-RESPONSIBILITY responsibility="Detect pending migrations and migration status"]
137
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
138
+ // CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="database query errors" side_effects="none"]
139
+ // CIL[DOC-MICRO logic_summary="List local SQL files and compare with schema_migrations table" notable_decisions="[]"]
140
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
133
141
  async function checkMigrations(): Promise<CheckResult> {
134
142
  const migrationsDir = resolve(__dirname, '../../migrations_dayzero')
135
143
 
@@ -191,7 +199,15 @@ async function checkMigrations(): Promise<CheckResult> {
191
199
  }
192
200
  }
193
201
  }
194
-
202
+ // CIL[END chunk_id="doctor_checkMigrations_1783405679"]
203
+
204
+ // CIL[BEGIN chunk_id="doctor_checkIntegrity_1783405679" type="function" identity="checkIntegrity" parent="none"]
205
+ // CIL[DOC-MACRO purpose="Validate core manifest file and integrity hash presence"]
206
+ // CIL[DOC-RESPONSIBILITY responsibility="Check manifest file exists and has integrity data"]
207
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
208
+ // CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="file read/parse errors" side_effects="none"]
209
+ // CIL[DOC-MICRO logic_summary="Read manifest.json and verify integrity hash exists" notable_decisions="[]"]
210
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
195
211
  async function checkIntegrity(): Promise<CheckResult> {
196
212
  const manifestPath = resolve(__dirname, '../../.spine-manifest.json')
197
213
 
@@ -218,9 +234,7 @@ async function checkIntegrity(): Promise<CheckResult> {
218
234
  }
219
235
  }
220
236
 
221
- // Note: We don't actually verify the hash here - that would require
222
- // computing SHA256 of all core files, which is slow. We just check
223
- // that a manifest exists and looks valid.
237
+
224
238
  return {
225
239
  name: 'integrity',
226
240
  status: 'pass',
@@ -236,7 +250,15 @@ async function checkIntegrity(): Promise<CheckResult> {
236
250
  }
237
251
  }
238
252
  }
239
-
253
+ // CIL[END chunk_id="doctor_checkIntegrity_1783405679"]
254
+
255
+ // CIL[BEGIN chunk_id="doctor_checkTestRuns_1783405679" type="function" identity="checkTestRuns" parent="none"]
256
+ // CIL[DOC-MACRO purpose="Check latest test run status and results"]
257
+ // CIL[DOC-RESPONSIBILITY responsibility="Verify test suite execution and report status"]
258
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
259
+ // CIL[DOC-CONTRACT inputs="none" outputs="CheckResult" errors="database query errors" side_effects="none"]
260
+ // CIL[DOC-MICRO logic_summary="Query test_runs table for most recent test execution" notable_decisions="[]"]
261
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
240
262
  async function checkTestRuns(): Promise<CheckResult> {
241
263
  try {
242
264
  const { data, error } = await adminDb
@@ -281,9 +303,17 @@ async function checkTestRuns(): Promise<CheckResult> {
281
303
  }
282
304
  }
283
305
  }
306
+ // CIL[END chunk_id="doctor_checkTestRuns_1783405679"]
284
307
 
285
- // ─── FIX IMPLEMENTATIONS ───────────────────────────────────────────────────
286
308
 
309
+
310
+ // CIL[BEGIN chunk_id="doctor_tryFix_1783405679" type="function" identity="tryFix" parent="none"]
311
+ // CIL[DOC-MACRO purpose="Provide fix suggestions for failed health checks"]
312
+ // CIL[DOC-RESPONSIBILITY responsibility="Generate remediation instructions for each check type"]
313
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
314
+ // CIL[DOC-CONTRACT inputs="CheckResult" outputs="string fix instructions" errors="none" side_effects="none"]
315
+ // CIL[DOC-MICRO logic_summary="Switch statement mapping check names to fix commands" notable_decisions="[]"]
316
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
287
317
  async function tryFix(check: CheckResult): Promise<string> {
288
318
  switch (check.name) {
289
319
  case 'environment':
@@ -302,9 +332,17 @@ async function tryFix(check: CheckResult): Promise<string> {
302
332
  return 'No automatic fix available'
303
333
  }
304
334
  }
335
+ // CIL[END chunk_id="doctor_tryFix_1783405679"]
336
+
305
337
 
306
- // ─── COMMAND REGISTRATION ──────────────────────────────────────────────────
307
338
 
339
+ // CIL[BEGIN chunk_id="doctor_registerDoctorCommands_1783405679" type="function" identity="registerDoctorCommands" parent="none"]
340
+ // CIL[DOC-MACRO purpose="Register doctor CLI command and orchestrate health checks"]
341
+ // CIL[DOC-RESPONSIBILITY responsibility="Execute all health checks and format results"]
342
+ // CIL[DOC-CONTEXT module="cli/commands/doctor" layer="cli" related_files="[]"]
343
+ // CIL[DOC-CONTRACT inputs="Command program" outputs="registered CLI command" errors="CLI errors" side_effects="process exit"]
344
+ // CIL[DOC-MICRO logic_summary="Run checks sequentially, aggregate results, output formatted report" notable_decisions="[]"]
345
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
308
346
  export function registerDoctorCommands(program: Command) {
309
347
  program
310
348
  .command('doctor')
@@ -313,8 +351,7 @@ export function registerDoctorCommands(program: Command) {
313
351
  .option('--fix', 'Show fix instructions for failed checks')
314
352
  .action(async (opts) => {
315
353
  try {
316
- // Don't require full context for doctor - we want to diagnose issues
317
- // even when env vars are missing
354
+
318
355
  let ctx
319
356
  try {
320
357
  ctx = await buildCliContext()
@@ -322,16 +359,16 @@ export function registerDoctorCommands(program: Command) {
322
359
  ctx = null
323
360
  }
324
361
 
325
- // Run all checks
362
+
326
363
  const checks: CheckResult[] = []
327
364
 
328
365
  checks.push(await checkEnvironment())
329
366
 
330
- // Only proceed with DB checks if env is valid
367
+
331
368
  if (checks[0].status === 'pass') {
332
369
  checks.push(await checkDatabase())
333
370
 
334
- // Only proceed with data checks if DB is connected
371
+
335
372
  if (checks[1].status === 'pass') {
336
373
  checks.push(await checkMigrations())
337
374
  checks.push(await checkTestRuns())
@@ -372,7 +409,7 @@ export function registerDoctorCommands(program: Command) {
372
409
 
373
410
  checks.push(await checkIntegrity())
374
411
 
375
- // Calculate overall status
412
+
376
413
  const failed = checks.filter(c => c.status === 'fail').length
377
414
  const warnings = checks.filter(c => c.status === 'warn').length
378
415
  const fixable = checks.filter(c => c.fixable).length
@@ -397,7 +434,7 @@ export function registerDoctorCommands(program: Command) {
397
434
  }
398
435
  }
399
436
 
400
- // Output
437
+
401
438
  if (opts.json) {
402
439
  console.log(JSON.stringify(report, null, 2))
403
440
  } else {
@@ -426,7 +463,7 @@ export function registerDoctorCommands(program: Command) {
426
463
  console.log()
427
464
  }
428
465
 
429
- // Exit code
466
+
430
467
  if (overall === 'unhealthy') {
431
468
  process.exit(1)
432
469
  } else if (overall === 'degraded') {
@@ -440,3 +477,4 @@ export function registerDoctorCommands(program: Command) {
440
477
  }
441
478
  })
442
479
  }
480
+ // CIL[END chunk_id="doctor_registerDoctorCommands_1783405679"]
@@ -1,38 +1,12 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/generate
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine generate` command — code scaffolding for extending Spine safely.
9
- * Generates type definitions, handlers, and UI pages in v2-custom/ only.
10
- *
11
- * **Commands:**
12
- * | Subcommand | Description |
13
- * |-------------------------|-------------------------------------------------------|
14
- * | `generate type <slug>` | Create new type with handler and UI stubs |
15
- * | `generate function <name>` | Create new Netlify function stub |
16
- *
17
- * **What gets generated for `type`:**
18
- * 1. Type record in database (item type by default)
19
- * 2. Handler stub: `v2-custom/functions/{slug}.ts`
20
- * 3. UI page stub: `v2-custom/src/pages/{slug}/index.tsx`
21
- *
22
- * **What gets generated for `function`:**
23
- * 1. Netlify function stub: `v2-custom/functions/{name}.ts`
24
- * 2. Basic handler structure with middleware imports
25
- *
26
- * **Usage:**
27
- * ```bash
28
- * spine generate type ticket-v2
29
- * spine generate function webhook-handler
30
- * ```
31
- *
32
- * @seeAlso functions/types.ts (type CRUD API)
33
- * @seeAlso functions/_shared/middleware.ts (handler structure)
34
- */
35
1
 
2
+
3
+ // CIL[BEGIN chunk_id="generate_module_header_1783475972" type="module_header" identity="module_header" parent="none"]
4
+ // CIL[DOC-MACRO purpose="Setup imports and path constants for generate command"]
5
+ // CIL[DOC-RESPONSIBILITY responsibility="Configure module dependencies and file paths"]
6
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
7
+ // CIL[DOC-CONTRACT inputs="none" outputs="imports, paths" errors="none" side_effects="none"]
8
+ // CIL[DOC-MICRO logic_summary="Import CLI framework, database, and file system utilities" notable_decisions="[]"]
9
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
36
10
  import type { Command } from 'commander'
37
11
  import { buildCliContext, handleError } from '../context.ts'
38
12
  import { adminDb } from '../../functions/_shared/index.ts'
@@ -43,9 +17,17 @@ import { fileURLToPath } from 'url'
43
17
  const __filename = fileURLToPath(import.meta.url)
44
18
  const __dirname = dirname(__filename)
45
19
  const PROJECT_ROOT = resolve(__dirname, '../../../')
20
+ // CIL[END chunk_id="generate_module_header_1783475972"]
46
21
 
47
- // ─── TYPE GENERATION ─────────────────────────────────────────────────────────
48
22
 
23
+
24
+ // CIL[BEGIN chunk_id="generate_generatetype_1783475972" type="function" identity="generateType" parent="none"]
25
+ // CIL[DOC-MACRO purpose="Generate complete type with database record and code stubs"]
26
+ // CIL[DOC-RESPONSIBILITY responsibility="Create type in DB and generate handler/UI files"]
27
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
28
+ // CIL[DOC-CONTRACT inputs="ctx, slug, options" outputs="none" errors="database errors, file errors" side_effects="file creation, DB writes"]
29
+ // CIL[DOC-MICRO logic_summary="Create DB type record, generate handler stub, generate UI page stub" notable_decisions="[]"]
30
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
49
31
  async function generateType(ctx: any, slug: string, options: any): Promise<void> {
50
32
  const name = options.name || slug.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
51
33
  const kind = options.kind || 'item'
@@ -53,8 +35,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
53
35
 
54
36
  console.log(`\n🔧 Generating type '${slug}'...`)
55
37
 
56
- // Step 1: Create type record in database
57
- console.log(' Step 1: Creating type record in database...')
38
+
58
39
 
59
40
  const { data: existing, error: checkErr } = await adminDb
60
41
  .from('types')
@@ -63,7 +44,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
63
44
  .eq('kind', kind)
64
45
  .single()
65
46
 
66
- if (checkErr && checkErr.code !== 'PGRST116') { // PGRST116 = not found
47
+ if (checkErr && checkErr.code !== 'PGRST116') {
67
48
  throw new Error(`Failed to check existing type: ${checkErr.message}`)
68
49
  }
69
50
 
@@ -111,8 +92,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
111
92
  console.log(' ✓ Type record created')
112
93
  }
113
94
 
114
- // Step 2: Generate handler stub
115
- console.log(' Step 2: Generating handler stub...')
95
+
116
96
  const handlerPath = resolve(PROJECT_ROOT, `v2-custom/functions/${slug}.ts`)
117
97
 
118
98
  if (existsSync(handlerPath) && !options.force) {
@@ -123,8 +103,7 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
123
103
  console.log(` ✓ Handler stub created: v2-custom/functions/${slug}.ts`)
124
104
  }
125
105
 
126
- // Step 3: Generate UI page stub
127
- console.log(' Step 3: Generating UI page stub...')
106
+
128
107
  const pageDir = resolve(PROJECT_ROOT, `v2-custom/src/pages/${slug}`)
129
108
  const pagePath = resolve(pageDir, 'index.tsx')
130
109
 
@@ -143,7 +122,15 @@ async function generateType(ctx: any, slug: string, options: any): Promise<void>
143
122
  console.log(` 2. Edit v2-custom/src/pages/${slug}/index.tsx to customize the UI`)
144
123
  console.log(` 3. Run 'spine dev' to see your changes`)
145
124
  }
146
-
125
+ // CIL[END chunk_id="generate_generatetype_1783475972"]
126
+
127
+ // CIL[BEGIN chunk_id="generate_generatehandlerstub_1783475972" type="function" identity="generateHandlerStub" parent="none"]
128
+ // CIL[DOC-MACRO purpose="Generate TypeScript handler stub with CRUD methods"]
129
+ // CIL[DOC-RESPONSIBILITY responsibility="Create template handler code for new types"]
130
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
131
+ // CIL[DOC-CONTRACT inputs="slug, kind, name" outputs="string handler code" errors="none" side_effects="none"]
132
+ // CIL[DOC-MICRO logic_summary="Return template string with handler structure and HTTP methods" notable_decisions="[]"]
133
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
147
134
  function generateHandlerStub(slug: string, kind: string, name: string): string {
148
135
  return `/**
149
136
  * @module ${slug}
@@ -185,7 +172,15 @@ export const handler = createHandler(async (ctx) => {
185
172
  })
186
173
  `
187
174
  }
188
-
175
+ // CIL[END chunk_id="generate_generatehandlerstub_1783475972"]
176
+
177
+ // CIL[BEGIN chunk_id="generate_generatepagestub_1783475972" type="function" identity="generatePageStub" parent="none"]
178
+ // CIL[DOC-MACRO purpose="Generate React UI page stub for type management"]
179
+ // CIL[DOC-RESPONSIBILITY responsibility="Create template React component with API integration"]
180
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
181
+ // CIL[DOC-CONTRACT inputs="slug, name, kind" outputs="string React component code" errors="none" side_effects="none"]
182
+ // CIL[DOC-MICRO logic_summary="Return template string with React component and API hooks" notable_decisions="[]"]
183
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
189
184
  function generatePageStub(slug: string, name: string, kind: string): string {
190
185
  return `/**
191
186
  * @page ${slug}/index
@@ -227,9 +222,17 @@ export default function ${name.replace(/\s+/g, '')}Page() {
227
222
  }
228
223
  `
229
224
  }
225
+ // CIL[END chunk_id="generate_generatepagestub_1783475972"]
226
+
230
227
 
231
- // ─── FUNCTION GENERATION ─────────────────────────────────────────────────────
232
228
 
229
+ // CIL[BEGIN chunk_id="generate_generatefunction_1783475972" type="function" identity="generateFunction" parent="none"]
230
+ // CIL[DOC-MACRO purpose="Generate standalone Netlify function stub"]
231
+ // CIL[DOC-RESPONSIBILITY responsibility="Create custom function file with basic structure"]
232
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
233
+ // CIL[DOC-CONTRACT inputs="name, options" outputs="none" errors="file errors" side_effects="file creation"]
234
+ // CIL[DOC-MICRO logic_summary="Check existing file, generate function stub, write to disk" notable_decisions="[]"]
235
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
233
236
  async function generateFunction(name: string, options: any): Promise<void> {
234
237
  console.log(`\n🔧 Generating function '${name}'...`)
235
238
 
@@ -250,7 +253,15 @@ async function generateFunction(name: string, options: any): Promise<void> {
250
253
  console.log(` 1. Edit the handler to implement your logic`)
251
254
  console.log(` 2. Run 'spine dev' to see your changes`)
252
255
  }
253
-
256
+ // CIL[END chunk_id="generate_generatefunction_1783555400"]
257
+
258
+ // CIL[BEGIN chunk_id="generate_generatefunctionstub_1783555400" type="function" identity="generateFunctionStub" parent="none"]
259
+ // CIL[DOC-MACRO purpose="Generate standalone Netlify function template"]
260
+ // CIL[DOC-RESPONSIBILITY responsibility="Create template function with action routing"]
261
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
262
+ // CIL[DOC-CONTRACT inputs="name, description" outputs="string function code" errors="none" side_effects="none"]
263
+ // CIL[DOC-MICRO logic_summary="Return template string with handler structure and action switch" notable_decisions="[]"]
264
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
254
265
  function generateFunctionStub(name: string, description?: string): string {
255
266
  return `/**
256
267
  * @module ${name}
@@ -293,9 +304,15 @@ export const handler = createHandler(async (ctx) => {
293
304
  })
294
305
  `
295
306
  }
296
-
297
- // ─── COMMAND REGISTRATION ────────────────────────────────────────────────────
298
-
307
+ // CIL[END chunk_id="generate_generatefunctionstub_1783555400"]
308
+
309
+ // CIL[BEGIN chunk_id="generate_registergeneratecommands_1783555400" type="function" identity="registerGenerateCommands" parent="none"]
310
+ // CIL[DOC-MACRO purpose="Register generate CLI commands for type and function scaffolding"]
311
+ // CIL[DOC-RESPONSIBILITY responsibility="Setup CLI interface for code generation tools"]
312
+ // CIL[DOC-CONTEXT module="cli/commands/generate" layer="cli" related_files="[]"]
313
+ // CIL[DOC-CONTRACT inputs="Command program" outputs="registered CLI commands" errors="CLI errors" side_effects="none"]
314
+ // CIL[DOC-MICRO logic_summary="Register 'generate type' and 'generate function' subcommands with options" notable_decisions="[]"]
315
+ // CIL[DOC-CONFIDENCE pass="first" score="0.90" flags="none"]
299
316
  export function registerGenerateCommands(program: Command) {
300
317
  const generate = program
301
318
  .command('generate')
@@ -330,3 +347,4 @@ export function registerGenerateCommands(program: Command) {
330
347
  }
331
348
  })
332
349
  }
350
+ // CIL[END chunk_id="generate_registergeneratecommands_1783555400"]
@@ -9,7 +9,16 @@
9
9
 
10
10
  import { adminDb } from './db'
11
11
 
12
- export type FileStatus = 'discovered' | 'pass2_complete' | 'pass3_complete' | 'pass4_complete' | 'pass4b_complete' | 'pass5_complete'
12
+ export type FileStatus =
13
+ | '0-discovered'
14
+ | '1-selected'
15
+ | '2-chunked'
16
+ | '3-first-pass-complete'
17
+ | '4-first-pass-embedded'
18
+ | '5-first-pass-mapped'
19
+ | '6-second-pass-complete'
20
+ | '7-second-pass-embedded'
21
+ | '8-second-pass-mapped'
13
22
 
14
23
  export interface FileTreeNode {
15
24
  id?: string | null
@@ -40,7 +49,7 @@ export function isSelectedPath(filePath: string, selectedPaths: string[]): boole
40
49
  * Extract the file status from a cil_file item.
41
50
  */
42
51
  export function getFileStatus(fileItem: any): FileStatus {
43
- return fileItem.data?.status || 'discovered'
52
+ return fileItem.data?.status || '0-discovered'
44
53
  }
45
54
 
46
55
  /**
@@ -258,6 +258,20 @@ export { sanitizeRecordData, validateUpdatePermissions, canAccessRecord } from '
258
258
  export { generateValidationSchema } from './schema-utils'
259
259
  export type { ValidationSchema } from './schema-utils'
260
260
 
261
+ // ============================================
262
+ // ID Resolution — type, link type, account, agent lookups
263
+ // ============================================
264
+
265
+ /**
266
+ * resolveTypeId — resolve a single type UUID by kind + slug.
267
+ * resolveTypeIds — batch resolve multiple type UUIDs.
268
+ * resolveTypeRecord — resolve a type's full record (id + design_schema + validation_schema).
269
+ * Use this when creating items that need to be stamped with schema data.
270
+ * resolveLinkTypeId / resolveLinkTypeIds — resolve link type UUIDs by slug.
271
+ */
272
+ export { resolveTypeId, resolveTypeIds, resolveTypeRecord, resolveLinkTypeId, resolveLinkTypeIds, resolveAccountId, resolveAgentId, resolvePromptConfigId } from './resolve-ids'
273
+ export type { TypeRecord } from './resolve-ids'
274
+
261
275
  // ============================================
262
276
  // Audit
263
277
  // ============================================
@@ -1,10 +1,5 @@
1
- // Slug-based ID resolution utilities
2
- // Custom app code must never hardcode UUIDs — IDs differ on every fresh install.
3
- // All functions that need entity IDs must call resolveIds() at request time.
4
-
5
1
  import { adminDb } from './db'
6
2
 
7
- // Resolve type IDs by (kind, slug) pairs
8
3
  export async function resolveTypeIds(
9
4
  pairs: Array<{ kind: string; slug: string }>
10
5
  ): Promise<Record<string, string>> {
@@ -32,13 +27,46 @@ export async function resolveTypeIds(
32
27
  return result
33
28
  }
34
29
 
35
- // Resolve a single type ID — throws if not found
36
30
  export async function resolveTypeId(kind: string, slug: string): Promise<string> {
37
31
  const ids = await resolveTypeIds([{ kind, slug }])
38
32
  return ids[`${kind}/${slug}`]
39
33
  }
40
34
 
41
- // Resolve link type IDs by slug
35
+ export interface TypeRecord {
36
+ id: string
37
+ slug: string
38
+ kind: string
39
+ design_schema: Record<string, any>
40
+ validation_schema: Record<string, any>
41
+ is_active: boolean
42
+ }
43
+
44
+ /**
45
+ * Resolve a type record by kind + slug in a single query.
46
+ * Returns id, design_schema, and validation_schema — everything needed
47
+ * to stamp a new item record (mirrors the admin-data.ts create handler pattern).
48
+ */
49
+ export async function resolveTypeRecord(kind: string, slug: string): Promise<TypeRecord> {
50
+ const { data, error } = await adminDb
51
+ .from('types')
52
+ .select('id, slug, kind, design_schema, validation_schema, is_active')
53
+ .eq('kind', kind)
54
+ .eq('slug', slug)
55
+ .single()
56
+
57
+ if (error || !data) throw new Error(`Type not found: ${kind}/${slug}`)
58
+ if (!data.is_active) throw new Error(`Type is inactive: ${kind}/${slug}`)
59
+
60
+ return {
61
+ id: data.id,
62
+ slug: data.slug,
63
+ kind: data.kind,
64
+ design_schema: data.design_schema || {},
65
+ validation_schema: data.validation_schema || {},
66
+ is_active: data.is_active,
67
+ }
68
+ }
69
+
42
70
  export async function resolveLinkTypeIds(slugs: string[]): Promise<Record<string, string>> {
43
71
  if (slugs.length === 0) return {}
44
72
 
@@ -61,13 +89,11 @@ export async function resolveLinkTypeIds(slugs: string[]): Promise<Record<string
61
89
  return result
62
90
  }
63
91
 
64
- // Resolve a single link type ID — throws if not found
65
92
  export async function resolveLinkTypeId(slug: string): Promise<string> {
66
93
  const ids = await resolveLinkTypeIds([slug])
67
94
  return ids[slug]
68
95
  }
69
96
 
70
- // Resolve account ID by slug
71
97
  export async function resolveAccountId(slug: string): Promise<string> {
72
98
  const { data, error } = await adminDb
73
99
  .from('accounts')
@@ -79,7 +105,6 @@ export async function resolveAccountId(slug: string): Promise<string> {
79
105
  return data.id
80
106
  }
81
107
 
82
- // Resolve AI agent ID by name
83
108
  export async function resolveAgentId(name: string): Promise<string> {
84
109
  const { data, error } = await adminDb
85
110
  .from('ai_agents')
@@ -92,7 +117,6 @@ export async function resolveAgentId(name: string): Promise<string> {
92
117
  return data.id
93
118
  }
94
119
 
95
- // Resolve prompt config ID by slug
96
120
  export async function resolvePromptConfigId(slug: string): Promise<string> {
97
121
  const { data, error } = await adminDb
98
122
  .from('prompt_configs')
@@ -45,7 +45,7 @@
45
45
  import { createHandler } from './_shared/middleware'
46
46
  import { sanitizeRecordData } from './_shared/permissions'
47
47
  import { adminDb } from './_shared/db'
48
- import { resolveTypeId } from './_shared/resolve-ids'
48
+ import { resolveTypeId, resolveTypeRecord } from './_shared/resolve-ids'
49
49
  import { fireCreateTriggers, fireUpdateTriggers, fireDeleteTriggers } from './_shared/trigger-engine'
50
50
  // ─── CHUNK_END: ADMIN_DATA_OVERVIEW ──────────────────────────────────────────────
51
51
 
@@ -299,36 +299,30 @@ export const create = createHandler(async (ctx, body) => {
299
299
  const e: any = new Error('Valid entity parameter is required'); e.statusCode = 400; throw e
300
300
  }
301
301
 
302
- // Resolve type_id from type_slug if provided, otherwise require type_id directly
303
- let typeId = recordData.type_id
304
- if (!typeId && body?.type_slug) {
302
+ // Resolve type record (id + design_schema + validation_schema) in one query.
303
+ // Accepts either type_slug (resolved by kind) or a direct type_id already in recordData.
304
+ let typeRecord: { id: string; design_schema: Record<string, any>; validation_schema: Record<string, any> }
305
+
306
+ if (body?.type_slug) {
305
307
  const kind = ENTITY_TO_KIND[entity]
306
308
  if (!kind) {
307
309
  const e: any = new Error(`type_slug is not supported for entity: ${entity}`); e.statusCode = 400; throw e
308
310
  }
309
- typeId = await resolveTypeId(kind, body.type_slug)
310
- }
311
-
312
- // type_id is required on all runtime record creation
313
- if (!typeId) {
311
+ typeRecord = await resolveTypeRecord(kind, body.type_slug)
312
+ } else if (recordData.type_id) {
313
+ const { data, error: typeErr } = await adminDb
314
+ .from('types')
315
+ .select('id, design_schema, validation_schema, is_active')
316
+ .eq('id', recordData.type_id)
317
+ .single()
318
+ if (typeErr || !data) throw new Error(`type_id not found: ${recordData.type_id}`)
319
+ if (!data.is_active) throw new Error(`type_id references an inactive type: ${recordData.type_id}`)
320
+ typeRecord = { id: data.id, design_schema: data.design_schema || {}, validation_schema: data.validation_schema || {} }
321
+ } else {
314
322
  const e: any = new Error('type_id or type_slug is required — every runtime record must reference a type'); e.statusCode = 400; throw e
315
323
  }
316
- recordData.type_id = typeId
317
324
 
318
- // Look up the type to stamp design_schema and validation_schema
319
- const { data: typeRecord, error: typeErr } = await adminDb
320
- .from('types')
321
- .select('id, design_schema, validation_schema, is_active')
322
- .eq('id', typeId)
323
- .single()
324
-
325
- if (typeErr || !typeRecord) {
326
- throw new Error(`type_id not found: ${typeId}`)
327
- }
328
-
329
- if (!typeRecord.is_active) {
330
- throw new Error(`type_id references an inactive type: ${typeId}`)
331
- }
325
+ recordData.type_id = typeRecord.id
332
326
 
333
327
  // Ensure the type has at least permissions=ALL (defensive — migration 062 guarantees this)
334
328
  let designSchema = typeRecord.design_schema || {}
@@ -42,7 +42,8 @@ import { User } from '../types/auth'
42
42
  const STORAGE_VERSION = 1
43
43
  const STORAGE_KEY = 'spine_user'
44
44
  /** Max age (ms) before stored user is considered stale and discarded */
45
- const STORAGE_TTL_MS = 5 * 60 * 1000 // 5 minutes
45
+ const STORAGE_TTL_MS = 24 * 60 * 60 * 1000 // 24 hours
46
+ const SESSION_TIMEOUT_MS = 15000 // guard against stale Supabase sessions hanging on reload
46
47
 
47
48
  interface StoredUserEnvelope {
48
49
  version: number
@@ -80,6 +81,23 @@ function writeStoredUser(user: User | null) {
80
81
  }
81
82
  }
82
83
 
84
+ /**
85
+ * Wrapper around supabase.auth.getSession() that times out to prevent the
86
+ * app from getting stuck on a corrupted/stale stored session.
87
+ */
88
+ async function getSessionWithTimeout(): Promise<{ data: { session: any | null } }> {
89
+ try {
90
+ return await Promise.race([
91
+ supabase.auth.getSession(),
92
+ new Promise<{ data: { session: null } }>((resolve) =>
93
+ setTimeout(() => resolve({ data: { session: null } }), SESSION_TIMEOUT_MS)
94
+ )
95
+ ])
96
+ } catch (error) {
97
+ return { data: { session: null } }
98
+ }
99
+ }
100
+
83
101
  // ─── TYPES ───────────────────────────────────────────────────────────────────
84
102
 
85
103
  /**
@@ -167,7 +185,6 @@ async function fetchUserContext(maxRetries = 2): Promise<User | null> {
167
185
 
168
186
  const data = await response.json()
169
187
  if (data.error || !data.data) return null
170
-
171
188
  return data.data
172
189
  } catch (error) {
173
190
  lastError = error
@@ -177,8 +194,6 @@ async function fetchUserContext(maxRetries = 2): Promise<User | null> {
177
194
  return null
178
195
  }
179
196
  }
180
-
181
- console.error('fetchUserContext failed after retries:', lastError)
182
197
  return null
183
198
  }
184
199
 
@@ -274,7 +289,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
274
289
  // ── Refresh ────────────────────────────────────────────────────────────────
275
290
 
276
291
  const refreshUser = useCallback(async () => {
277
- const { data: { session } } = await supabase.auth.getSession()
292
+ const { data: { session } } = await getSessionWithTimeout()
278
293
 
279
294
  if (!session?.user) {
280
295
  clearUser()
@@ -330,7 +345,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
330
345
 
331
346
  const checkAuth = async () => {
332
347
  try {
333
- const { data: { session } } = await supabase.auth.getSession()
348
+ const { data: { session } } = await getSessionWithTimeout()
334
349
 
335
350
  if (!session?.user) {
336
351
  if (!cancelled) clearUser()
@@ -58,7 +58,7 @@ export function getAccountId(): string | null {
58
58
  // ─── 401 INTERCEPTOR ─────────────────────────────────────────────────────────
59
59
 
60
60
  /** Paths that are expected to return 401 (don't trigger the interceptor) */
61
- const AUTH_PATHS = ['/api/auth']
61
+ const AUTH_PATHS = ['/api/auth', '/api/admin-data']
62
62
 
63
63
  let _onUnauthorizedCallback: (() => void) | null = null
64
64
 
@@ -118,6 +118,8 @@ export async function getAuthHeaders(): Promise<Record<string, string>> {
118
118
  const { data: { session } } = await supabase.auth.getSession()
119
119
  if (session?.access_token) {
120
120
  headers['Authorization'] = `Bearer ${session.access_token}`
121
+ } else {
122
+
121
123
  }
122
124
 
123
125
  if (_accountId) {
package/netlify.toml CHANGED
@@ -30,6 +30,9 @@
30
30
  [functions."custom_cil-ingest"]
31
31
  timeout = 300
32
32
 
33
+ [functions."custom_cil-embeddings"]
34
+ timeout = 300
35
+
33
36
  [[redirects]]
34
37
  from = "/*"
35
38
  to = "/index.html"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.124",
3
+ "version": "0.3.125",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {