@dreamtree-org/graphify 1.3.0 → 1.5.0

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.
package/dist/index.d.cts CHANGED
@@ -2,12 +2,18 @@ import Graph from 'graphology';
2
2
  import { z } from 'zod';
3
3
 
4
4
  type Confidence = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';
5
- type Relation = 'calls' | 'imports' | 'imports_from' | 'inherits' | 'implements' | 'mixes_in' | 'embeds' | 'references' | 'contains' | 'method' | 're_exports';
5
+ type Relation = 'calls' | 'imports' | 'imports_from' | 'inherits' | 'implements' | 'mixes_in' | 'embeds' | 'references' | 'contains' | 'method' | 're_exports' | 'follows';
6
6
  interface GraphNode {
7
7
  id: string;
8
8
  label: string;
9
9
  sourceFile: string;
10
10
  sourceLocation: string;
11
+ /**
12
+ * Node kind for document graphs (document|section|chunk|entity — see
13
+ * docs/KB-PROVIDER.md §5). Optional and schema-additive: classic code
14
+ * nodes encode kind inside the label ("class Foo") and omit this.
15
+ */
16
+ kind?: string;
11
17
  }
12
18
  interface GraphEdge {
13
19
  source: string;
@@ -89,28 +95,111 @@ declare function extract(filePath: string, displayPath?: string): Promise<Extrac
89
95
  * same module) are kept as separate edges.
90
96
  */
91
97
  declare function buildGraph(extractions: ExtractionResult[]): Graph;
98
+ /**
99
+ * The merge loop shared by buildGraph() (fresh graph) and
100
+ * updateCorpusGraph() (incremental, src/ingest/corpus.ts). Callers validate
101
+ * first — this assumes well-formed extractions. A node that already exists
102
+ * only as an auto-vivified placeholder (sourceFile '<unknown>') is upgraded
103
+ * in place when an extraction supplies the real node.
104
+ */
105
+ declare function mergeExtractionsInto(graph: Graph, validated: ExtractionResult[]): void;
92
106
 
93
- interface ResolveOptions {
107
+ /**
108
+ * Provider-agnostic interface for the semantic (LLM-driven) extraction
109
+ * pass. The first-class, tested path is Anthropic (see anthropic.ts) —
110
+ * this interface exists so an OpenAI-compatible endpoint can be swapped in
111
+ * later without redesigning callers. Do not build out a large provider
112
+ * matrix for v1 (see master prompt §7 v2 ideas).
113
+ */
114
+ interface SemanticExtractor {
115
+ extractSemantic(path: string, content: string): Promise<ExtractionResult>;
116
+ }
117
+
118
+ /**
119
+ * Smart document ingestion (docs/KB-PROVIDER.md §5): replaces the flat
120
+ * `text -> string[]` chunker contract with `document -> { chunks + graph
121
+ * fragment }`. One document per call — providers ingest from queue workers
122
+ * a page at a time; updateCorpusGraph() (corpus.ts) folds each fragment
123
+ * into the corpus graph. Structural pass only: sections, chunks,
124
+ * contains/follows edges, and explicit relative markdown links — fully
125
+ * offline and deterministic. Entities in prose need the optional
126
+ * caller-injected SemanticExtractor.
127
+ */
128
+ interface DocumentInput {
94
129
  /**
95
- * The scanned package's own name(s) (from package.json). Tests very
96
- * commonly import the package's PUBLIC entry (`import { persist } from
97
- * 'zustand/middleware'`) rather than a relative path — without this,
98
- * those land on an opaque `module:` node and the test never connects to
99
- * the source it exercises (so `graphify tests` under-selects).
130
+ * Plain text or markdown. Binary parsing (PDF/DOCX/...) is the caller's
131
+ * job this API takes extracted text.
100
132
  */
101
- selfNames?: string[];
133
+ text: string;
134
+ /**
135
+ * Stable caller-side identity — becomes GraphNode.sourceFile and the id
136
+ * prefix for every node from this document. Re-ingesting the same
137
+ * sourceRef through updateCorpusGraph() replaces that document's subgraph.
138
+ */
139
+ sourceRef: string;
140
+ /** Routes the structural pass. Markdown headings are honored unless this says text/plain. */
141
+ mimeType?: string;
142
+ title?: string;
143
+ /** Echoed onto every chunk, never interpreted. */
144
+ metadata?: Record<string, string>;
145
+ }
146
+ interface ChunkOptions {
147
+ /** Soft target per chunk, in estimated tokens (~4 chars each). Default 400. */
148
+ targetTokens?: number;
149
+ /** Hard cap — over-long paragraphs are split to fit. Default 512. */
150
+ maxTokens?: number;
151
+ /**
152
+ * Overlap is OFF by default: `follows` edges + expandRetrieval() replace
153
+ * what sliding-window overlap approximates. Callers migrating from
154
+ * window-chunkers can turn it back on.
155
+ */
156
+ overlapTokens?: number;
102
157
  }
103
- interface ResolveStats {
104
- /** Edge endpoints re-pointed from a guessed id to a real node id. */
105
- resolvedEndpoints: number;
106
- /** Placeholder module nodes dropped because the real file node replaced them. */
107
- droppedPlaceholders: number;
158
+ interface IngestOptions {
159
+ chunking?: ChunkOptions;
160
+ /**
161
+ * Optional semantic pass for entities/relationships in prose. Injected,
162
+ * never built-in — same stance as embeddings (docs/KB-PROVIDER.md §6).
163
+ * Omitted = structural-only, no LLM, no API key.
164
+ */
165
+ semanticExtractor?: SemanticExtractor;
166
+ }
167
+ interface IngestedChunk {
168
+ /**
169
+ * Graph node id of this chunk — store it next to the chunk row: it is
170
+ * what makes expandRetrieval() hits O(1) instead of lexical.
171
+ */
172
+ nodeId: string;
173
+ /** What the caller embeds and stores. */
174
+ content: string;
175
+ tokenEstimate: number;
176
+ /** Reading order within the document. */
177
+ index: number;
178
+ /** Heading trail, e.g. ["Q3 Results", "Revenue"]. Empty for plain text. */
179
+ sectionPath: string[];
180
+ /** sha256 of normalized content — caller-side dedup across sources. */
181
+ contentHash: string;
182
+ metadata: Record<string, string>;
183
+ }
184
+ interface IngestResult {
185
+ chunks: IngestedChunk[];
186
+ /** Graph fragment for this document — feed to updateCorpusGraph() or runPipeline's extraExtractions. */
187
+ extraction: ExtractionResult;
188
+ stats: {
189
+ sections: number;
190
+ entities: number;
191
+ tokenTotal: number;
192
+ };
108
193
  }
109
194
  /**
110
- * Mutates `extractions` in place: re-points guessed edge endpoints at real
111
- * node ids and drops the placeholder module nodes those guesses created.
195
+ * Turn one document into structure-aware chunks plus a typed graph
196
+ * fragment: a `document` node, `section` nodes nested via `contains`,
197
+ * `chunk` nodes under their section (`contains`), chained in reading order
198
+ * (`follows`), with relative markdown links as `references` edges. The
199
+ * chunks are graph nodes — the caller stores each chunk's nodeId, which is
200
+ * what fuses their vector search with expandRetrieval() at query time.
112
201
  */
113
- declare function resolveCrossFileReferences(extractions: ExtractionResult[], options?: ResolveOptions): ResolveStats;
202
+ declare function ingestDocument(input: DocumentInput, options?: IngestOptions): Promise<IngestResult>;
114
203
 
115
204
  type ClusterAlgorithm = 'louvain' | 'leiden';
116
205
  interface ClusterOptions {
@@ -144,6 +233,60 @@ interface ClusterOptions {
144
233
  */
145
234
  declare function cluster(graph: Graph, options?: ClusterOptions): Graph;
146
235
 
236
+ interface UpdateCorpusOptions {
237
+ algorithm?: ClusterAlgorithm;
238
+ /**
239
+ * Clustering strategy per update. `'auto'` (default) keeps bulk loading
240
+ * safe without the caller thinking about it: new nodes adopt the majority
241
+ * community of their neighbors (cheap, local, deterministic), and a full
242
+ * global re-cluster runs only when accumulated churn since the last full
243
+ * pass exceeds `reclusterRatio` of the graph — or on a never-clustered
244
+ * graph. `true` forces a full re-cluster now (e.g. once after a batch);
245
+ * `false` skips community maintenance entirely for this call (churn is
246
+ * still tracked, so a later `'auto'` call sees the backlog).
247
+ */
248
+ recluster?: boolean | 'auto';
249
+ /**
250
+ * Auto mode only: fraction of the graph's nodes that must have churned
251
+ * since the last full clustering before one is triggered. Default 0.1.
252
+ */
253
+ reclusterRatio?: number;
254
+ }
255
+ /**
256
+ * Fold one document's extraction into an existing corpus graph — the
257
+ * streaming-ingestion companion to ingestDocument() (docs/KB-PROVIDER.md
258
+ * §5). Pass null for the store.load() no-graph-yet case. Re-ingesting a
259
+ * document is replace-not-duplicate: every node whose sourceFile matches
260
+ * one of the extraction's sourceFiles is dropped (with its edges) before
261
+ * the fragment is merged. Placeholder nodes that earlier documents' link
262
+ * edges auto-vivified are upgraded in place when this document is the real
263
+ * thing. Community upkeep is incremental by default and self-tunes to bulk
264
+ * loads — see UpdateCorpusOptions.recluster. Mutates and returns the graph.
265
+ */
266
+ declare function updateCorpusGraph(graph: Graph | null, extraction: ExtractionResult, options?: UpdateCorpusOptions): Graph;
267
+
268
+ interface ResolveOptions {
269
+ /**
270
+ * The scanned package's own name(s) (from package.json). Tests very
271
+ * commonly import the package's PUBLIC entry (`import { persist } from
272
+ * 'zustand/middleware'`) rather than a relative path — without this,
273
+ * those land on an opaque `module:` node and the test never connects to
274
+ * the source it exercises (so `graphify tests` under-selects).
275
+ */
276
+ selfNames?: string[];
277
+ }
278
+ interface ResolveStats {
279
+ /** Edge endpoints re-pointed from a guessed id to a real node id. */
280
+ resolvedEndpoints: number;
281
+ /** Placeholder module nodes dropped because the real file node replaced them. */
282
+ droppedPlaceholders: number;
283
+ }
284
+ /**
285
+ * Mutates `extractions` in place: re-points guessed edge endpoints at real
286
+ * node ids and drops the placeholder module nodes those guesses created.
287
+ */
288
+ declare function resolveCrossFileReferences(extractions: ExtractionResult[], options?: ResolveOptions): ResolveStats;
289
+
147
290
  /**
148
291
  * Surface god-nodes (excessive fan-in/out), structural surprises (self
149
292
  * references, AMBIGUOUS edges), and open questions (isolated nodes,
@@ -358,6 +501,73 @@ interface ExplainResult {
358
501
  /** Plain-language-ready explanation of the node that best matches `query`. */
359
502
  declare function explainNode(graph: Graph, query: string): ExplainResult | null;
360
503
 
504
+ /**
505
+ * Graph-augmented retrieval (docs/KB-PROVIDER.md §4): the caller has
506
+ * already done similarity search (vector, keyword, hybrid — their
507
+ * pipeline, their embeddings); expandRetrieval() takes those hits and adds
508
+ * what similarity can't see — the nodes *structurally connected* to them.
509
+ * Pure and synchronous like every query function: Graph in, data out; no
510
+ * store access, no embedding calls.
511
+ */
512
+ /**
513
+ * One hit from the caller's own search. `nodeId` when the caller stored
514
+ * graph node ids alongside its records at ingest time; otherwise `text`,
515
+ * resolved against the graph lexically (same resolver queryGraph uses).
516
+ */
517
+ interface RetrievalHit {
518
+ nodeId?: string;
519
+ text?: string;
520
+ /** The caller's similarity score, echoed through to rank seeds. */
521
+ score?: number;
522
+ }
523
+ interface ExpandOptions {
524
+ /** Approximate cap, in tokens (~4 chars each), on the result. Default 2000. */
525
+ tokenBudget?: number;
526
+ maxHops?: number;
527
+ /** Restrict traversal (and reported edges) to these relations, e.g. ['contains', 'references']. */
528
+ relations?: Relation[];
529
+ }
530
+ interface ExpandedNode {
531
+ id: string;
532
+ label: string;
533
+ /** Node kind attr (chunk|section|entity|document|...) — absent on classic code nodes. */
534
+ kind?: string;
535
+ sourceFile: string;
536
+ sourceLocation: string;
537
+ /** 0 = was a seed (or an unresolvable hit echoed through). */
538
+ hops: number;
539
+ /** The edge that first led the traversal here (absent for seeds). */
540
+ via?: TraversalEdge;
541
+ /** The caller's similarity score — seeds only. */
542
+ seedScore?: number;
543
+ }
544
+ interface ExpandedContext {
545
+ /**
546
+ * Union of seed nodes and discovered neighbors, ranked: seeds first (by
547
+ * caller score desc), then by (hops asc, degree desc). Deduped across
548
+ * seeds. Hits that resolve to no graph node are echoed through with
549
+ * hops 0 and no expansion, so adopting this on a partially-ingested
550
+ * corpus never does worse than flat retrieval.
551
+ */
552
+ nodes: ExpandedNode[];
553
+ /** Every edge between included nodes — provenance for citations. */
554
+ edges: TraversalEdge[];
555
+ communities: Array<{
556
+ id: number;
557
+ label: string;
558
+ seedCount: number;
559
+ }>;
560
+ /** True when the token budget stopped the traversal before the frontier was exhausted. */
561
+ truncated: boolean;
562
+ }
563
+ /**
564
+ * Expand the caller's top-K hits through the graph: one joint,
565
+ * token-budgeted BFS seeded from every hit at once, so overlapping
566
+ * neighborhoods dedupe and the budget is shared across seeds. Deterministic
567
+ * (lexical neighbor order, stable ranking) like the rest of the query layer.
568
+ */
569
+ declare function expandRetrieval(graph: Graph, hits: RetrievalHit[], options?: ExpandOptions): ExpandedContext;
570
+
361
571
  interface AffectedNode {
362
572
  id: string;
363
573
  label: string;
@@ -664,6 +874,7 @@ declare const ExtractionResultSchema: z.ZodObject<{
664
874
  label: z.ZodString;
665
875
  sourceFile: z.ZodString;
666
876
  sourceLocation: z.ZodString;
877
+ kind: z.ZodOptional<z.ZodString>;
667
878
  }, z.core.$strip>>;
668
879
  edges: z.ZodArray<z.ZodObject<{
669
880
  source: z.ZodString;
@@ -680,6 +891,7 @@ declare const ExtractionResultSchema: z.ZodObject<{
680
891
  contains: "contains";
681
892
  method: "method";
682
893
  re_exports: "re_exports";
894
+ follows: "follows";
683
895
  }>;
684
896
  confidence: z.ZodEnum<{
685
897
  EXTRACTED: "EXTRACTED";
@@ -705,4 +917,4 @@ declare class ExtractionValidationError extends Error {
705
917
  */
706
918
  declare function validateExtraction(value: unknown, context?: string): ExtractionResult;
707
919
 
708
- export { type AffectedNode, type Analysis, type ChangedSymbol, type ClusterAlgorithm, type ClusterOptions, type Confidence, type ContextOptions, type ContextPack, type ContextSnippet, type DependencyRule, type DsnQueryFn, EXTRACTOR_REGISTRY, type ExplainResult, type ExportOptions, ExtractionCache, type ExtractionResult, ExtractionResultSchema, ExtractionValidationError, type ExtractionValidationIssue, type FileCategory, type FileManifest, type GraphDiff, type GraphEdge, type GraphNode, type GraphStore, type ImpactOptions, type ImpactResult, LocalFileGraphStore, type MergeEntry, type NodeMatch, type PathResult, type PipelineOptions, type PipelineResult, type QueryOptions, type QueryResult, type ReflectOptions, type Relation, type ResolveStats, type ResultOutcome, type ResultType, type RewiredSymbol, type RulesConfig, type SavedResult, type SerializedGraph, type TestSelection, type TraversalEdge, type Violation, type VisitedNode, affectedBy, analyze, buildContextPack, buildGraph, checkRules, cluster, collectFiles, deserializeGraph, diffGraphs, explainNode, exportGraph, extract, extractMysql, globToRegExp, graphForDirectory, isTestFile, loadGraph, loadResults, mergeGraphs, queryGraph, rankForTask, renderContextPack, renderLessons, renderReport, renderReview, resolveCrossFileReferences, resolveNode, reviewRevisions, runPipeline, saveResult, scoreNodes, serializeGraph, shortestPath, testsForChangedFiles, testsForNode, validateExtraction, validateRules };
920
+ export { type AffectedNode, type Analysis, type ChangedSymbol, type ChunkOptions, type ClusterAlgorithm, type ClusterOptions, type Confidence, type ContextOptions, type ContextPack, type ContextSnippet, type DependencyRule, type DocumentInput, type DsnQueryFn, EXTRACTOR_REGISTRY, type ExpandOptions, type ExpandedContext, type ExpandedNode, type ExplainResult, type ExportOptions, ExtractionCache, type ExtractionResult, ExtractionResultSchema, ExtractionValidationError, type ExtractionValidationIssue, type FileCategory, type FileManifest, type GraphDiff, type GraphEdge, type GraphNode, type GraphStore, type ImpactOptions, type ImpactResult, type IngestOptions, type IngestResult, type IngestedChunk, LocalFileGraphStore, type MergeEntry, type NodeMatch, type PathResult, type PipelineOptions, type PipelineResult, type QueryOptions, type QueryResult, type ReflectOptions, type Relation, type ResolveStats, type ResultOutcome, type ResultType, type RetrievalHit, type RewiredSymbol, type RulesConfig, type SavedResult, type SemanticExtractor, type SerializedGraph, type TestSelection, type TraversalEdge, type UpdateCorpusOptions, type Violation, type VisitedNode, affectedBy, analyze, buildContextPack, buildGraph, checkRules, cluster, collectFiles, deserializeGraph, diffGraphs, expandRetrieval, explainNode, exportGraph, extract, extractMysql, globToRegExp, graphForDirectory, ingestDocument, isTestFile, loadGraph, loadResults, mergeExtractionsInto, mergeGraphs, queryGraph, rankForTask, renderContextPack, renderLessons, renderReport, renderReview, resolveCrossFileReferences, resolveNode, reviewRevisions, runPipeline, saveResult, scoreNodes, serializeGraph, shortestPath, testsForChangedFiles, testsForNode, updateCorpusGraph, validateExtraction, validateRules };
package/dist/index.d.ts CHANGED
@@ -2,12 +2,18 @@ import Graph from 'graphology';
2
2
  import { z } from 'zod';
3
3
 
4
4
  type Confidence = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';
5
- type Relation = 'calls' | 'imports' | 'imports_from' | 'inherits' | 'implements' | 'mixes_in' | 'embeds' | 'references' | 'contains' | 'method' | 're_exports';
5
+ type Relation = 'calls' | 'imports' | 'imports_from' | 'inherits' | 'implements' | 'mixes_in' | 'embeds' | 'references' | 'contains' | 'method' | 're_exports' | 'follows';
6
6
  interface GraphNode {
7
7
  id: string;
8
8
  label: string;
9
9
  sourceFile: string;
10
10
  sourceLocation: string;
11
+ /**
12
+ * Node kind for document graphs (document|section|chunk|entity — see
13
+ * docs/KB-PROVIDER.md §5). Optional and schema-additive: classic code
14
+ * nodes encode kind inside the label ("class Foo") and omit this.
15
+ */
16
+ kind?: string;
11
17
  }
12
18
  interface GraphEdge {
13
19
  source: string;
@@ -89,28 +95,111 @@ declare function extract(filePath: string, displayPath?: string): Promise<Extrac
89
95
  * same module) are kept as separate edges.
90
96
  */
91
97
  declare function buildGraph(extractions: ExtractionResult[]): Graph;
98
+ /**
99
+ * The merge loop shared by buildGraph() (fresh graph) and
100
+ * updateCorpusGraph() (incremental, src/ingest/corpus.ts). Callers validate
101
+ * first — this assumes well-formed extractions. A node that already exists
102
+ * only as an auto-vivified placeholder (sourceFile '<unknown>') is upgraded
103
+ * in place when an extraction supplies the real node.
104
+ */
105
+ declare function mergeExtractionsInto(graph: Graph, validated: ExtractionResult[]): void;
92
106
 
93
- interface ResolveOptions {
107
+ /**
108
+ * Provider-agnostic interface for the semantic (LLM-driven) extraction
109
+ * pass. The first-class, tested path is Anthropic (see anthropic.ts) —
110
+ * this interface exists so an OpenAI-compatible endpoint can be swapped in
111
+ * later without redesigning callers. Do not build out a large provider
112
+ * matrix for v1 (see master prompt §7 v2 ideas).
113
+ */
114
+ interface SemanticExtractor {
115
+ extractSemantic(path: string, content: string): Promise<ExtractionResult>;
116
+ }
117
+
118
+ /**
119
+ * Smart document ingestion (docs/KB-PROVIDER.md §5): replaces the flat
120
+ * `text -> string[]` chunker contract with `document -> { chunks + graph
121
+ * fragment }`. One document per call — providers ingest from queue workers
122
+ * a page at a time; updateCorpusGraph() (corpus.ts) folds each fragment
123
+ * into the corpus graph. Structural pass only: sections, chunks,
124
+ * contains/follows edges, and explicit relative markdown links — fully
125
+ * offline and deterministic. Entities in prose need the optional
126
+ * caller-injected SemanticExtractor.
127
+ */
128
+ interface DocumentInput {
94
129
  /**
95
- * The scanned package's own name(s) (from package.json). Tests very
96
- * commonly import the package's PUBLIC entry (`import { persist } from
97
- * 'zustand/middleware'`) rather than a relative path — without this,
98
- * those land on an opaque `module:` node and the test never connects to
99
- * the source it exercises (so `graphify tests` under-selects).
130
+ * Plain text or markdown. Binary parsing (PDF/DOCX/...) is the caller's
131
+ * job this API takes extracted text.
100
132
  */
101
- selfNames?: string[];
133
+ text: string;
134
+ /**
135
+ * Stable caller-side identity — becomes GraphNode.sourceFile and the id
136
+ * prefix for every node from this document. Re-ingesting the same
137
+ * sourceRef through updateCorpusGraph() replaces that document's subgraph.
138
+ */
139
+ sourceRef: string;
140
+ /** Routes the structural pass. Markdown headings are honored unless this says text/plain. */
141
+ mimeType?: string;
142
+ title?: string;
143
+ /** Echoed onto every chunk, never interpreted. */
144
+ metadata?: Record<string, string>;
145
+ }
146
+ interface ChunkOptions {
147
+ /** Soft target per chunk, in estimated tokens (~4 chars each). Default 400. */
148
+ targetTokens?: number;
149
+ /** Hard cap — over-long paragraphs are split to fit. Default 512. */
150
+ maxTokens?: number;
151
+ /**
152
+ * Overlap is OFF by default: `follows` edges + expandRetrieval() replace
153
+ * what sliding-window overlap approximates. Callers migrating from
154
+ * window-chunkers can turn it back on.
155
+ */
156
+ overlapTokens?: number;
102
157
  }
103
- interface ResolveStats {
104
- /** Edge endpoints re-pointed from a guessed id to a real node id. */
105
- resolvedEndpoints: number;
106
- /** Placeholder module nodes dropped because the real file node replaced them. */
107
- droppedPlaceholders: number;
158
+ interface IngestOptions {
159
+ chunking?: ChunkOptions;
160
+ /**
161
+ * Optional semantic pass for entities/relationships in prose. Injected,
162
+ * never built-in — same stance as embeddings (docs/KB-PROVIDER.md §6).
163
+ * Omitted = structural-only, no LLM, no API key.
164
+ */
165
+ semanticExtractor?: SemanticExtractor;
166
+ }
167
+ interface IngestedChunk {
168
+ /**
169
+ * Graph node id of this chunk — store it next to the chunk row: it is
170
+ * what makes expandRetrieval() hits O(1) instead of lexical.
171
+ */
172
+ nodeId: string;
173
+ /** What the caller embeds and stores. */
174
+ content: string;
175
+ tokenEstimate: number;
176
+ /** Reading order within the document. */
177
+ index: number;
178
+ /** Heading trail, e.g. ["Q3 Results", "Revenue"]. Empty for plain text. */
179
+ sectionPath: string[];
180
+ /** sha256 of normalized content — caller-side dedup across sources. */
181
+ contentHash: string;
182
+ metadata: Record<string, string>;
183
+ }
184
+ interface IngestResult {
185
+ chunks: IngestedChunk[];
186
+ /** Graph fragment for this document — feed to updateCorpusGraph() or runPipeline's extraExtractions. */
187
+ extraction: ExtractionResult;
188
+ stats: {
189
+ sections: number;
190
+ entities: number;
191
+ tokenTotal: number;
192
+ };
108
193
  }
109
194
  /**
110
- * Mutates `extractions` in place: re-points guessed edge endpoints at real
111
- * node ids and drops the placeholder module nodes those guesses created.
195
+ * Turn one document into structure-aware chunks plus a typed graph
196
+ * fragment: a `document` node, `section` nodes nested via `contains`,
197
+ * `chunk` nodes under their section (`contains`), chained in reading order
198
+ * (`follows`), with relative markdown links as `references` edges. The
199
+ * chunks are graph nodes — the caller stores each chunk's nodeId, which is
200
+ * what fuses their vector search with expandRetrieval() at query time.
112
201
  */
113
- declare function resolveCrossFileReferences(extractions: ExtractionResult[], options?: ResolveOptions): ResolveStats;
202
+ declare function ingestDocument(input: DocumentInput, options?: IngestOptions): Promise<IngestResult>;
114
203
 
115
204
  type ClusterAlgorithm = 'louvain' | 'leiden';
116
205
  interface ClusterOptions {
@@ -144,6 +233,60 @@ interface ClusterOptions {
144
233
  */
145
234
  declare function cluster(graph: Graph, options?: ClusterOptions): Graph;
146
235
 
236
+ interface UpdateCorpusOptions {
237
+ algorithm?: ClusterAlgorithm;
238
+ /**
239
+ * Clustering strategy per update. `'auto'` (default) keeps bulk loading
240
+ * safe without the caller thinking about it: new nodes adopt the majority
241
+ * community of their neighbors (cheap, local, deterministic), and a full
242
+ * global re-cluster runs only when accumulated churn since the last full
243
+ * pass exceeds `reclusterRatio` of the graph — or on a never-clustered
244
+ * graph. `true` forces a full re-cluster now (e.g. once after a batch);
245
+ * `false` skips community maintenance entirely for this call (churn is
246
+ * still tracked, so a later `'auto'` call sees the backlog).
247
+ */
248
+ recluster?: boolean | 'auto';
249
+ /**
250
+ * Auto mode only: fraction of the graph's nodes that must have churned
251
+ * since the last full clustering before one is triggered. Default 0.1.
252
+ */
253
+ reclusterRatio?: number;
254
+ }
255
+ /**
256
+ * Fold one document's extraction into an existing corpus graph — the
257
+ * streaming-ingestion companion to ingestDocument() (docs/KB-PROVIDER.md
258
+ * §5). Pass null for the store.load() no-graph-yet case. Re-ingesting a
259
+ * document is replace-not-duplicate: every node whose sourceFile matches
260
+ * one of the extraction's sourceFiles is dropped (with its edges) before
261
+ * the fragment is merged. Placeholder nodes that earlier documents' link
262
+ * edges auto-vivified are upgraded in place when this document is the real
263
+ * thing. Community upkeep is incremental by default and self-tunes to bulk
264
+ * loads — see UpdateCorpusOptions.recluster. Mutates and returns the graph.
265
+ */
266
+ declare function updateCorpusGraph(graph: Graph | null, extraction: ExtractionResult, options?: UpdateCorpusOptions): Graph;
267
+
268
+ interface ResolveOptions {
269
+ /**
270
+ * The scanned package's own name(s) (from package.json). Tests very
271
+ * commonly import the package's PUBLIC entry (`import { persist } from
272
+ * 'zustand/middleware'`) rather than a relative path — without this,
273
+ * those land on an opaque `module:` node and the test never connects to
274
+ * the source it exercises (so `graphify tests` under-selects).
275
+ */
276
+ selfNames?: string[];
277
+ }
278
+ interface ResolveStats {
279
+ /** Edge endpoints re-pointed from a guessed id to a real node id. */
280
+ resolvedEndpoints: number;
281
+ /** Placeholder module nodes dropped because the real file node replaced them. */
282
+ droppedPlaceholders: number;
283
+ }
284
+ /**
285
+ * Mutates `extractions` in place: re-points guessed edge endpoints at real
286
+ * node ids and drops the placeholder module nodes those guesses created.
287
+ */
288
+ declare function resolveCrossFileReferences(extractions: ExtractionResult[], options?: ResolveOptions): ResolveStats;
289
+
147
290
  /**
148
291
  * Surface god-nodes (excessive fan-in/out), structural surprises (self
149
292
  * references, AMBIGUOUS edges), and open questions (isolated nodes,
@@ -358,6 +501,73 @@ interface ExplainResult {
358
501
  /** Plain-language-ready explanation of the node that best matches `query`. */
359
502
  declare function explainNode(graph: Graph, query: string): ExplainResult | null;
360
503
 
504
+ /**
505
+ * Graph-augmented retrieval (docs/KB-PROVIDER.md §4): the caller has
506
+ * already done similarity search (vector, keyword, hybrid — their
507
+ * pipeline, their embeddings); expandRetrieval() takes those hits and adds
508
+ * what similarity can't see — the nodes *structurally connected* to them.
509
+ * Pure and synchronous like every query function: Graph in, data out; no
510
+ * store access, no embedding calls.
511
+ */
512
+ /**
513
+ * One hit from the caller's own search. `nodeId` when the caller stored
514
+ * graph node ids alongside its records at ingest time; otherwise `text`,
515
+ * resolved against the graph lexically (same resolver queryGraph uses).
516
+ */
517
+ interface RetrievalHit {
518
+ nodeId?: string;
519
+ text?: string;
520
+ /** The caller's similarity score, echoed through to rank seeds. */
521
+ score?: number;
522
+ }
523
+ interface ExpandOptions {
524
+ /** Approximate cap, in tokens (~4 chars each), on the result. Default 2000. */
525
+ tokenBudget?: number;
526
+ maxHops?: number;
527
+ /** Restrict traversal (and reported edges) to these relations, e.g. ['contains', 'references']. */
528
+ relations?: Relation[];
529
+ }
530
+ interface ExpandedNode {
531
+ id: string;
532
+ label: string;
533
+ /** Node kind attr (chunk|section|entity|document|...) — absent on classic code nodes. */
534
+ kind?: string;
535
+ sourceFile: string;
536
+ sourceLocation: string;
537
+ /** 0 = was a seed (or an unresolvable hit echoed through). */
538
+ hops: number;
539
+ /** The edge that first led the traversal here (absent for seeds). */
540
+ via?: TraversalEdge;
541
+ /** The caller's similarity score — seeds only. */
542
+ seedScore?: number;
543
+ }
544
+ interface ExpandedContext {
545
+ /**
546
+ * Union of seed nodes and discovered neighbors, ranked: seeds first (by
547
+ * caller score desc), then by (hops asc, degree desc). Deduped across
548
+ * seeds. Hits that resolve to no graph node are echoed through with
549
+ * hops 0 and no expansion, so adopting this on a partially-ingested
550
+ * corpus never does worse than flat retrieval.
551
+ */
552
+ nodes: ExpandedNode[];
553
+ /** Every edge between included nodes — provenance for citations. */
554
+ edges: TraversalEdge[];
555
+ communities: Array<{
556
+ id: number;
557
+ label: string;
558
+ seedCount: number;
559
+ }>;
560
+ /** True when the token budget stopped the traversal before the frontier was exhausted. */
561
+ truncated: boolean;
562
+ }
563
+ /**
564
+ * Expand the caller's top-K hits through the graph: one joint,
565
+ * token-budgeted BFS seeded from every hit at once, so overlapping
566
+ * neighborhoods dedupe and the budget is shared across seeds. Deterministic
567
+ * (lexical neighbor order, stable ranking) like the rest of the query layer.
568
+ */
569
+ declare function expandRetrieval(graph: Graph, hits: RetrievalHit[], options?: ExpandOptions): ExpandedContext;
570
+
361
571
  interface AffectedNode {
362
572
  id: string;
363
573
  label: string;
@@ -664,6 +874,7 @@ declare const ExtractionResultSchema: z.ZodObject<{
664
874
  label: z.ZodString;
665
875
  sourceFile: z.ZodString;
666
876
  sourceLocation: z.ZodString;
877
+ kind: z.ZodOptional<z.ZodString>;
667
878
  }, z.core.$strip>>;
668
879
  edges: z.ZodArray<z.ZodObject<{
669
880
  source: z.ZodString;
@@ -680,6 +891,7 @@ declare const ExtractionResultSchema: z.ZodObject<{
680
891
  contains: "contains";
681
892
  method: "method";
682
893
  re_exports: "re_exports";
894
+ follows: "follows";
683
895
  }>;
684
896
  confidence: z.ZodEnum<{
685
897
  EXTRACTED: "EXTRACTED";
@@ -705,4 +917,4 @@ declare class ExtractionValidationError extends Error {
705
917
  */
706
918
  declare function validateExtraction(value: unknown, context?: string): ExtractionResult;
707
919
 
708
- export { type AffectedNode, type Analysis, type ChangedSymbol, type ClusterAlgorithm, type ClusterOptions, type Confidence, type ContextOptions, type ContextPack, type ContextSnippet, type DependencyRule, type DsnQueryFn, EXTRACTOR_REGISTRY, type ExplainResult, type ExportOptions, ExtractionCache, type ExtractionResult, ExtractionResultSchema, ExtractionValidationError, type ExtractionValidationIssue, type FileCategory, type FileManifest, type GraphDiff, type GraphEdge, type GraphNode, type GraphStore, type ImpactOptions, type ImpactResult, LocalFileGraphStore, type MergeEntry, type NodeMatch, type PathResult, type PipelineOptions, type PipelineResult, type QueryOptions, type QueryResult, type ReflectOptions, type Relation, type ResolveStats, type ResultOutcome, type ResultType, type RewiredSymbol, type RulesConfig, type SavedResult, type SerializedGraph, type TestSelection, type TraversalEdge, type Violation, type VisitedNode, affectedBy, analyze, buildContextPack, buildGraph, checkRules, cluster, collectFiles, deserializeGraph, diffGraphs, explainNode, exportGraph, extract, extractMysql, globToRegExp, graphForDirectory, isTestFile, loadGraph, loadResults, mergeGraphs, queryGraph, rankForTask, renderContextPack, renderLessons, renderReport, renderReview, resolveCrossFileReferences, resolveNode, reviewRevisions, runPipeline, saveResult, scoreNodes, serializeGraph, shortestPath, testsForChangedFiles, testsForNode, validateExtraction, validateRules };
920
+ export { type AffectedNode, type Analysis, type ChangedSymbol, type ChunkOptions, type ClusterAlgorithm, type ClusterOptions, type Confidence, type ContextOptions, type ContextPack, type ContextSnippet, type DependencyRule, type DocumentInput, type DsnQueryFn, EXTRACTOR_REGISTRY, type ExpandOptions, type ExpandedContext, type ExpandedNode, type ExplainResult, type ExportOptions, ExtractionCache, type ExtractionResult, ExtractionResultSchema, ExtractionValidationError, type ExtractionValidationIssue, type FileCategory, type FileManifest, type GraphDiff, type GraphEdge, type GraphNode, type GraphStore, type ImpactOptions, type ImpactResult, type IngestOptions, type IngestResult, type IngestedChunk, LocalFileGraphStore, type MergeEntry, type NodeMatch, type PathResult, type PipelineOptions, type PipelineResult, type QueryOptions, type QueryResult, type ReflectOptions, type Relation, type ResolveStats, type ResultOutcome, type ResultType, type RetrievalHit, type RewiredSymbol, type RulesConfig, type SavedResult, type SemanticExtractor, type SerializedGraph, type TestSelection, type TraversalEdge, type UpdateCorpusOptions, type Violation, type VisitedNode, affectedBy, analyze, buildContextPack, buildGraph, checkRules, cluster, collectFiles, deserializeGraph, diffGraphs, expandRetrieval, explainNode, exportGraph, extract, extractMysql, globToRegExp, graphForDirectory, ingestDocument, isTestFile, loadGraph, loadResults, mergeExtractionsInto, mergeGraphs, queryGraph, rankForTask, renderContextPack, renderLessons, renderReport, renderReview, resolveCrossFileReferences, resolveNode, reviewRevisions, runPipeline, saveResult, scoreNodes, serializeGraph, shortestPath, testsForChangedFiles, testsForNode, updateCorpusGraph, validateExtraction, validateRules };