spine-framework-cortex 0.1.19 → 0.2.1

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 (46) hide show
  1. package/{functions/custom_cortex-handler.ts → api/cortex-handler.ts} +9 -9
  2. package/components/CliInstancesCard.tsx +144 -0
  3. package/components/CortexSidebar.tsx +27 -53
  4. package/hooks/useTypeRegistry.ts +74 -0
  5. package/index.tsx +13 -24
  6. package/manifest.json +1 -13
  7. package/package.json +11 -20
  8. package/pages/courses/CoursesPage.tsx +14 -4
  9. package/pages/crm/AccountDetailPage.tsx +149 -194
  10. package/pages/crm/ContactsPage.tsx +7 -7
  11. package/pages/intelligence/IntelligencePage.tsx +24 -31
  12. package/pages/kb/KBEditorPage.tsx +9 -2
  13. package/pages/operations/AuditFunnelPage.tsx +378 -0
  14. package/pages/operations/InstallFunnelPage.tsx +410 -0
  15. package/pages/operations/OperationsDashboard.tsx +275 -0
  16. package/pages/support/RedactionReview.tsx +11 -2
  17. package/seed/link-types.json +4 -42
  18. package/seed/package.json +27 -0
  19. package/seed/roles.json +1 -1
  20. package/seed/types.json +2711 -596
  21. package/CHANGELOG.md +0 -46
  22. package/LICENSE.md +0 -223
  23. package/README.md +0 -69
  24. package/functions/custom_anonymous-sessions.ts +0 -356
  25. package/functions/custom_case_analysis.ts +0 -507
  26. package/functions/custom_community-escalation.ts +0 -234
  27. package/functions/custom_cortex-chunks.ts +0 -52
  28. package/functions/custom_funnel-scoring.ts +0 -256
  29. package/functions/custom_funnel-signal.ts +0 -446
  30. package/functions/custom_funnel-timers.ts +0 -449
  31. package/functions/custom_kb-chunker-test.ts +0 -364
  32. package/functions/custom_kb-chunker.ts +0 -576
  33. package/functions/custom_kb-embeddings.ts +0 -481
  34. package/functions/custom_kb-ingestion.ts +0 -448
  35. package/functions/custom_support-triage.ts +0 -649
  36. package/functions/custom_tag_management.ts +0 -314
  37. package/functions/webhook-handlers.ts +0 -29
  38. package/lib/resolveTypeId.ts +0 -16
  39. package/pages/crm/ContactDetailPage.tsx +0 -184
  40. package/pages/ops/AuditFunnelPage.tsx +0 -191
  41. package/pages/ops/CommandCenterPage.tsx +0 -377
  42. package/pages/ops/InstallFunnelPage.tsx +0 -226
  43. package/seed/accounts.json +0 -9
  44. package/seed/integrations.json +0 -24
  45. package/seed/pipelines.json +0 -59
  46. package/seed/triggers.json +0 -125
@@ -1,448 +0,0 @@
1
- import { createHandler } from './_shared/middleware'
2
- import { adminDb } from './_shared/db'
3
- import { create as adminCreate, update as adminUpdate } from './admin-data'
4
- import { resolveTypeId } from './_shared/resolve-ids'
5
-
6
-
7
- interface ParsedChunk {
8
- identifier: string
9
- chunk_id: string
10
- version: string
11
- hash: string
12
- macro: string
13
- micro: string
14
- inputs: Record<string, string>
15
- outputs: string
16
- depends_on: string[]
17
- depended_by: string[]
18
- side_effects: string[]
19
- tags: string[]
20
- code: string
21
- metadata: {
22
- chunk_id: string
23
- file_path: string
24
- line_start: number
25
- line_end: number
26
- chunk_type: 'function' | 'class' | 'interface' | 'config' | 'object'
27
- purpose: string
28
- hash: string
29
- dependencies: string[]
30
- dependents: string[]
31
- source: {
32
- source_type: 'core'
33
- ref: string
34
- line_start: number
35
- line_end: number
36
- }
37
- }
38
- }
39
-
40
- interface IngestionRequest {
41
- chunks: ParsedChunk[]
42
- force_update?: boolean
43
- }
44
-
45
- interface IngestionResponse {
46
- success: boolean
47
- items_created: number
48
- items_updated: number
49
- embeddings_generated: number
50
- errors: string[]
51
- skipped: string[]
52
- item_ids: string[]
53
- }
54
-
55
- // Build a human-readable HTML description for developer consumption
56
- function buildDescriptionHtml(chunk: ParsedChunk, cleanCode: string): string {
57
- const sections: string[] = []
58
-
59
- // Purpose — always present
60
- sections.push(`<p><strong>Purpose</strong><br/>${chunk.macro}</p>`)
61
-
62
- if (chunk.micro && chunk.micro !== chunk.macro) {
63
- sections.push(`<p>${chunk.micro}</p>`)
64
- }
65
-
66
- // Parameters
67
- if (chunk.inputs && Object.keys(chunk.inputs).length > 0) {
68
- const rows = Object.entries(chunk.inputs)
69
- .map(([name, desc]) => `<code>${name}</code> — ${desc}`)
70
- .join('<br/>')
71
- sections.push(`<p><strong>Parameters</strong><br/>${rows}</p>`)
72
- }
73
-
74
- // Returns
75
- if (chunk.outputs) {
76
- sections.push(`<p><strong>Returns</strong><br/>${chunk.outputs}</p>`)
77
- }
78
-
79
- // Dependencies
80
- if (chunk.depends_on && chunk.depends_on.length > 0) {
81
- const depList = chunk.depends_on.map(d => `<code>${d}</code>`).join(', ')
82
- sections.push(`<p><strong>Dependencies</strong><br/>${depList}</p>`)
83
- }
84
-
85
- // Side Effects
86
- if (chunk.side_effects && chunk.side_effects.length > 0) {
87
- const effectList = chunk.side_effects.join('<br/>')
88
- sections.push(`<p><strong>Side Effects</strong><br/>${effectList}</p>`)
89
- }
90
-
91
- // Source location
92
- sections.push(`<p><strong>Source</strong><br/><code>${chunk.metadata.file_path}</code> lines ${chunk.metadata.line_start}–${chunk.metadata.line_end}</p>`)
93
-
94
- // Code block
95
- const escapedCode = cleanCode.trim().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
96
- sections.push(`<pre><code>${escapedCode}</code></pre>`)
97
-
98
- return sections.join('\n')
99
- }
100
-
101
- // Convert parsed chunk to KB article data
102
- function chunkToKBArticle(chunk: ParsedChunk): any {
103
- const title = chunk.identifier.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
104
-
105
- // Extract clean code (remove docblock if present)
106
- let cleanCode = chunk.code
107
- const docblockMatch = chunk.code.match(/^\/\*\*[\s\S]*?\*\/\s*/)
108
- if (docblockMatch) {
109
- cleanCode = chunk.code.substring(docblockMatch[0].length)
110
- }
111
-
112
- const descriptionHtml = buildDescriptionHtml(chunk, cleanCode)
113
-
114
- const searchKeywords = [
115
- chunk.identifier,
116
- chunk.macro,
117
- chunk.micro,
118
- ...chunk.tags,
119
- chunk.metadata.chunk_type,
120
- 'typescript'
121
- ].filter(Boolean)
122
-
123
- return (kbArticleTypeId: string) => ({
124
- type_id: kbArticleTypeId,
125
- title,
126
- status: 'published',
127
- description: descriptionHtml,
128
- data: {
129
- kb_type: 'code_chunk',
130
- priority: 'medium',
131
- audience: ['developer', 'ai_system'],
132
- tags: [...chunk.tags, chunk.metadata.chunk_type, 'typescript', 'core'],
133
- search_keywords: searchKeywords,
134
- category: 'technical',
135
- security_level: 'internal',
136
- source_info: {
137
- source_type: 'automated_ingestion',
138
- author: 'chunk-parser-v1.0',
139
- ingestion_timestamp: new Date().toISOString(),
140
- original_source: chunk.metadata.file_path
141
- },
142
- code_metadata: {
143
- chunk_id: chunk.chunk_id,
144
- identifier: chunk.identifier,
145
- version: chunk.version,
146
- hash: chunk.hash,
147
- macro: chunk.macro,
148
- micro: chunk.micro,
149
- inputs: chunk.inputs,
150
- outputs: chunk.outputs,
151
- depends_on: chunk.depends_on,
152
- depended_by: chunk.depended_by,
153
- side_effects: chunk.side_effects,
154
- file_path: chunk.metadata.file_path,
155
- line_start: chunk.metadata.line_start,
156
- line_end: chunk.metadata.line_end,
157
- chunk_type: chunk.metadata.chunk_type,
158
- language: 'typescript',
159
- code: cleanCode.trim()
160
- },
161
- related_articles: []
162
- },
163
- is_active: true,
164
- })
165
- }
166
-
167
- // Check if chunk already exists
168
- async function findExistingChunk(ctx: any, chunkId: string, kbArticleTypeId: string): Promise<any | null> {
169
- const { data } = await ctx.db
170
- .from('items')
171
- .select('*')
172
- .eq('type_id', kbArticleTypeId)
173
- .filter('data->>kb_type', 'eq', 'code_chunk')
174
- .filter('data->code_metadata->>chunk_id', 'eq', chunkId)
175
- .maybeSingle()
176
-
177
- return data
178
- }
179
-
180
- // Create or update KB article item using standard Spine handlers
181
- async function upsertKBArticle(ctx: any, chunk: ParsedChunk, kbArticleTypeId: string, forceUpdate: boolean = false): Promise<{ created: boolean; id: string }> {
182
- const existing = await findExistingChunk(ctx, chunk.chunk_id, kbArticleTypeId)
183
- const kbData = chunkToKBArticle(chunk)(kbArticleTypeId)
184
-
185
- if (existing) {
186
- if (!forceUpdate) {
187
- throw new Error(`Chunk ${chunk.chunk_id} already exists (use force_update to override)`)
188
- }
189
-
190
- // Update existing item via direct admin-data import (ctx passed through — nested call, no HTTP)
191
- const ctxWithQuery = { ...ctx, query: { ...ctx.query, entity: 'items', id: existing.id } }
192
- await adminUpdate(ctxWithQuery, {
193
- title: kbData.title,
194
- status: kbData.status,
195
- description: kbData.description,
196
- data: kbData.data,
197
- is_active: kbData.is_active
198
- })
199
-
200
- return { created: false, id: existing.id }
201
- } else {
202
- // Create new item via direct admin-data import (ctx passed through — nested call, no HTTP)
203
- const result: any = await adminCreate(ctx, {
204
- entity: 'items',
205
- type_id: kbData.type_id,
206
- title: kbData.title,
207
- status: kbData.status,
208
- description: kbData.description,
209
- data: kbData.data,
210
- is_active: kbData.is_active
211
- })
212
-
213
- const id = result?.id
214
- if (!id) throw new Error('Failed to create KB article: no ID returned')
215
- return { created: true, id }
216
- }
217
- }
218
-
219
- // Build plain-text semantic content for embedding — developer understanding focus
220
- function buildSemanticContent(chunk: ParsedChunk): string {
221
- const inputSummary = chunk.inputs && Object.keys(chunk.inputs).length > 0
222
- ? 'Parameters: ' + Object.entries(chunk.inputs).map(([k, v]) => `${k} (${v})`).join(', ')
223
- : ''
224
- const sideEffects = chunk.side_effects && chunk.side_effects.length > 0
225
- ? 'Side effects: ' + chunk.side_effects.join(', ')
226
- : ''
227
- const deps = chunk.depends_on && chunk.depends_on.length > 0
228
- ? 'Depends on: ' + chunk.depends_on.join(', ')
229
- : ''
230
-
231
- return [
232
- chunk.identifier,
233
- chunk.macro,
234
- chunk.micro,
235
- inputSummary,
236
- chunk.outputs ? `Returns: ${chunk.outputs}` : '',
237
- deps,
238
- sideEffects,
239
- `Tags: ${chunk.tags.join(', ')}`,
240
- `File: ${chunk.metadata.file_path} lines ${chunk.metadata.line_start}-${chunk.metadata.line_end}`
241
- ].filter(Boolean).join('\n')
242
- }
243
-
244
- // Generate embeddings for a KB item — semantic (understanding) + structure (metadata)
245
- async function generateEmbeddings(ctx: any, itemId: string, chunk: ParsedChunk): Promise<void> {
246
- const embeddingTypes = ['semantic', 'structure']
247
-
248
- for (const vectorType of embeddingTypes) {
249
- let content = ''
250
- let metadata: any = {
251
- vector_type: vectorType,
252
- item_type: 'kb_article',
253
- chunk_id: chunk.chunk_id,
254
- version: chunk.version,
255
- kb_type: 'code_chunk'
256
- }
257
-
258
- switch (vectorType) {
259
- case 'semantic':
260
- content = buildSemanticContent(chunk)
261
- metadata.chunk_type = chunk.metadata.chunk_type
262
- metadata.file_path = chunk.metadata.file_path
263
- break
264
-
265
- case 'structure':
266
- content = JSON.stringify({
267
- identifier: chunk.identifier,
268
- chunk_type: chunk.metadata.chunk_type,
269
- file_path: chunk.metadata.file_path,
270
- line_start: chunk.metadata.line_start,
271
- line_end: chunk.metadata.line_end,
272
- version: chunk.version,
273
- tags: chunk.tags,
274
- depends_on: chunk.depends_on,
275
- depended_by: chunk.depended_by,
276
- inputs: Object.keys(chunk.inputs || {}),
277
- has_side_effects: (chunk.side_effects || []).length > 0
278
- })
279
- metadata.tags = chunk.tags
280
- metadata.file_path = chunk.metadata.file_path
281
- metadata.depends_on = chunk.depends_on
282
- break
283
- }
284
-
285
- try {
286
- const embeddingVector = await generateEmbeddingVector(content)
287
- await ctx.db.from('embeddings').insert({
288
- model_id: 'text-embedding-3-small',
289
- document_id: itemId,
290
- chunk_index: vectorType === 'semantic' ? 0 : 1,
291
- content,
292
- embedding: embeddingVector,
293
- metadata
294
- })
295
- } catch (error) {
296
- console.error(`Failed to generate ${vectorType} embedding for ${chunk.chunk_id}:`, error)
297
- }
298
- }
299
- }
300
-
301
- // Generate real embedding vector via OpenAI text-embedding-3-small
302
- async function generateEmbeddingVector(content: string): Promise<number[]> {
303
- const apiKey = process.env.OPENAI_API_KEY || process.env.LLM_API_KEY
304
- const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
305
-
306
- if (!apiKey) {
307
- throw new Error('No OPENAI_API_KEY configured — cannot generate embeddings')
308
- }
309
-
310
- const res = await fetch(`${baseUrl}/embeddings`, {
311
- method: 'POST',
312
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
313
- body: JSON.stringify({
314
- model: 'text-embedding-3-small',
315
- input: content,
316
- }),
317
- })
318
-
319
- if (!res.ok) {
320
- const err = await res.text()
321
- throw new Error(`OpenAI embeddings error ${res.status}: ${err.slice(0, 200)}`)
322
- }
323
-
324
- const result: any = await res.json()
325
- return result.data?.[0]?.embedding || []
326
- }
327
-
328
- // Main ingestion handler
329
- async function handleIngestChunks(ctx: any, chunks: ParsedChunk[], kbArticleTypeId: string, forceUpdate: boolean = false): Promise<IngestionResponse> {
330
- const response: IngestionResponse = {
331
- success: true,
332
- items_created: 0,
333
- items_updated: 0,
334
- embeddings_generated: 0,
335
- errors: [],
336
- skipped: [],
337
- item_ids: []
338
- }
339
-
340
- for (const chunk of chunks) {
341
- try {
342
- // Validate chunk
343
- if (!chunk.chunk_id || !chunk.code || !chunk.metadata) {
344
- response.errors.push(`Invalid chunk data for ${chunk.identifier}`)
345
- continue
346
- }
347
-
348
- // Create/update KB article
349
- const { created, id } = await upsertKBArticle(ctx, chunk, kbArticleTypeId, forceUpdate)
350
-
351
- if (created) {
352
- response.items_created++
353
- } else {
354
- response.items_updated++
355
- }
356
- response.item_ids.push(id)
357
-
358
- // Generate embeddings
359
- await generateEmbeddings(ctx, id, chunk)
360
- response.embeddings_generated++
361
-
362
- } catch (error) {
363
- const errorMessage = error instanceof Error ? error.message : String(error)
364
- console.error(`KB ingestion error for ${chunk.identifier}:`, error)
365
-
366
- if (errorMessage.includes('already exists')) {
367
- response.skipped.push(`${chunk.identifier}: ${errorMessage}`)
368
- } else {
369
- response.errors.push(`${chunk.identifier}: ${errorMessage}`)
370
- }
371
- }
372
- }
373
-
374
- // Determine overall success
375
- if (response.errors.length > 0 && response.items_created === 0 && response.items_updated === 0) {
376
- response.success = false
377
- }
378
-
379
- return response
380
- }
381
-
382
- export const handler = createHandler(async (ctx, body) => {
383
- const { action } = ctx.query || {}
384
- const method = ctx.query?.method || 'POST'
385
- const kbArticleTypeId = await resolveTypeId('item', 'kb_article')
386
-
387
- switch (action) {
388
- case 'ingest':
389
- if (method === 'POST') {
390
- return await handleIngestChunks(ctx, body.chunks, kbArticleTypeId, body.force_update)
391
- }
392
- break
393
-
394
- case 'status':
395
- if (method === 'GET' || method === 'POST') {
396
- const existing = await findExistingChunk(ctx, body.chunk_id, kbArticleTypeId)
397
-
398
- if (!existing) {
399
- return { found: false }
400
- }
401
-
402
- // Get embedding count
403
- const { data: embeddings } = await adminDb
404
- .from('embeddings')
405
- .select('metadata->>vector_type')
406
- .eq('document_id', existing.id)
407
-
408
- return {
409
- found: true,
410
- item: existing,
411
- embeddings: embeddings?.length || 0,
412
- vector_types: embeddings?.map(e => e.metadata?.vector_type) || []
413
- }
414
- }
415
- break
416
-
417
- case 'delete':
418
- if (method === 'DELETE' || method === 'POST') {
419
- const existing = await findExistingChunk(ctx, body.chunk_id, kbArticleTypeId)
420
-
421
- if (!existing) {
422
- throw new Error('Chunk not found')
423
- }
424
-
425
- // Delete embeddings first
426
- await adminDb
427
- .from('embeddings')
428
- .delete()
429
- .eq('document_id', existing.id)
430
-
431
- // Delete the item
432
- await adminDb
433
- .from('items')
434
- .delete()
435
- .eq('id', existing.id)
436
-
437
- return { deleted: true }
438
- }
439
- break
440
-
441
- default:
442
- if (method === 'POST') {
443
- return await handleIngestChunks(ctx, body.chunks, kbArticleTypeId, body.force_update)
444
- }
445
- }
446
-
447
- throw new Error('Invalid action or method')
448
- })