diff --git a/gitnexus/src/core/ingestion/model/registration-table.ts b/gitnexus/src/core/ingestion/model/registration-table.ts index a5bb14f21..8102926ba 100644 --- a/gitnexus/src/core/ingestion/model/registration-table.ts +++ b/gitnexus/src/core/ingestion/model/registration-table.ts @@ -182,6 +182,7 @@ const LABEL_BEHAVIOR = { Section: 'inert', Route: 'inert', Tool: 'inert', + StateSlot: 'inert', } as const satisfies Record & // Cross-invariant 1 — every class-like label (participates in // qualifiedName fallback in `SymbolTable.add()`) MUST be classified as diff --git a/gitnexus/src/core/ingestion/pipeline-phases/state-slots.ts b/gitnexus/src/core/ingestion/pipeline-phases/state-slots.ts new file mode 100644 index 000000000..06c6ff72d --- /dev/null +++ b/gitnexus/src/core/ingestion/pipeline-phases/state-slots.ts @@ -0,0 +1,78 @@ +/** + * Phase: stateSlots + * + * Detects state slot patterns (React Query, SWR, Redux, Zustand, tRPC, etc.) + * across all source files and creates StateSlot nodes + PRODUCES/CONSUMES edges. + * + * @deps crossFile + * @reads scannedFiles (file paths + sizes), graph (symbol nodes for edge creation) + * @writes graph (StateSlot nodes, PRODUCES edges, CONSUMES edges) + * @output slotsCreated, producesEdges, consumesEdges, overlapWarnings + */ + +import { readFile } from 'node:fs/promises'; +import type { PipelinePhase, PipelineContext, PhaseResult } from './types.js'; +import { getPhaseOutput } from './types.js'; +import type { StructureOutput } from './structure.js'; +import { detectStateSlots } from '../state-slot-detectors/index.js'; +import { processStateSlots, type StateSlotProcessorResult } from '../state-slot-processor.js'; +import { isDev } from '../utils/env.js'; + +export interface StateSlotsOutput { + stateSlotsResult: StateSlotProcessorResult; +} + +export const stateSlotsPhase: PipelinePhase = { + name: 'stateSlots', + deps: ['crossFile'], + + async execute( + ctx: PipelineContext, + deps: ReadonlyMap>, + ): Promise { + const { scannedFiles, totalFiles } = getPhaseOutput(deps, 'structure'); + + ctx.onProgress({ + phase: 'state-slots', + percent: 92, + message: 'Detecting state slots...', + stats: { filesProcessed: 0, totalFiles, nodesCreated: ctx.graph.nodeCount }, + }); + + // Run detectors over all files + const allSlots = []; + let filesProcessed = 0; + + for (const file of scannedFiles) { + let content: string; + try { + content = await readFile(file.path, 'utf8'); + } catch { + continue; + } + const slots = detectStateSlots(content, file.path); + if (slots.length > 0) allSlots.push(...slots); + filesProcessed++; + } + + ctx.onProgress({ + phase: 'state-slots', + percent: 93, + message: `Processing ${allSlots.length} state slot(s)...`, + stats: { filesProcessed, totalFiles, nodesCreated: ctx.graph.nodeCount }, + }); + + const stateSlotsResult = processStateSlots(allSlots, ctx.graph); + + if (isDev) { + console.log( + `🗂 State slots: ${stateSlotsResult.slotsCreated} slots, ` + + `${stateSlotsResult.producesEdges} PRODUCES, ` + + `${stateSlotsResult.consumesEdges} CONSUMES, ` + + `${stateSlotsResult.overlapWarnings.length} overlap warnings`, + ); + } + + return { stateSlotsResult }; + }, +}; diff --git a/gitnexus/test/unit/schema.test.ts b/gitnexus/test/unit/schema.test.ts index 050ae954e..b15c800a3 100644 --- a/gitnexus/test/unit/schema.test.ts +++ b/gitnexus/test/unit/schema.test.ts @@ -68,8 +68,8 @@ describe('LadybugDB Schema', () => { }); it('has expected total count', () => { - // 9 core + 18 multi-language + Route + Tool + StateSlot = 31 - expect(NODE_TABLES).toHaveLength(31); + // 9 core + 18 multi-language + Route + Tool + StateSlot = 32 + expect(NODE_TABLES).toHaveLength(32); }); }); @@ -202,7 +202,7 @@ describe('LadybugDB Schema', () => { describe('schema query ordering', () => { it('NODE_SCHEMA_QUERIES has correct count', () => { - expect(NODE_SCHEMA_QUERIES).toHaveLength(31); + expect(NODE_SCHEMA_QUERIES).toHaveLength(32); }); it('REL_SCHEMA_QUERIES has one relation table', () => { @@ -210,8 +210,8 @@ describe('LadybugDB Schema', () => { }); it('SCHEMA_QUERIES includes all node + rel + embedding schemas', () => { - // 31 node + 1 rel + 1 embedding = 33 - expect(SCHEMA_QUERIES).toHaveLength(33); + // 32 node + 1 rel + 1 embedding = 34 + expect(SCHEMA_QUERIES).toHaveLength(34); }); it('node schemas come before relation schemas in SCHEMA_QUERIES', () => { diff --git a/gitnexus/test/unit/tools.test.ts b/gitnexus/test/unit/tools.test.ts index 4274716a7..618abc2c5 100644 --- a/gitnexus/test/unit/tools.test.ts +++ b/gitnexus/test/unit/tools.test.ts @@ -19,8 +19,8 @@ const GROUP_TOOLS = new Set([ ]); describe('GITNEXUS_TOOLS', () => { - it('exports all tools (7 base + 3 route/tool/shape + 1 api_impact + 5 group)', () => { - expect(GITNEXUS_TOOLS).toHaveLength(16); + it('exports all tools (7 base + 3 route/tool/shape + 1 api_impact + 1 data_flow + 5 group)', () => { + expect(GITNEXUS_TOOLS).toHaveLength(17); }); it('contains all expected tool names', () => {