@dreamtree-org/graphify 1.0.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/LICENSE +21 -0
- package/README.md +69 -0
- package/bin/graphify-mcp.js +7 -0
- package/bin/graphify.js +2 -0
- package/dist/chunk-5ANIDX3G.js +364 -0
- package/dist/chunk-5ANIDX3G.js.map +1 -0
- package/dist/chunk-DG5FECXV.js +2474 -0
- package/dist/chunk-DG5FECXV.js.map +1 -0
- package/dist/cli/index.cjs +3250 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +267 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.cjs +2759 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +301 -0
- package/dist/index.d.ts +301 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.cjs +336 -0
- package/dist/mcp/server.cjs.map +1 -0
- package/dist/mcp/server.d.cts +22 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.js +88 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +81 -0
- package/src/skill/SKILL.md +74 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import Graph from 'graphology';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
type Confidence = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';
|
|
5
|
+
type Relation = 'calls' | 'imports' | 'imports_from' | 'inherits' | 'implements' | 'mixes_in' | 'embeds' | 'references' | 'contains' | 'method' | 're_exports';
|
|
6
|
+
interface GraphNode {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
sourceFile: string;
|
|
10
|
+
sourceLocation: string;
|
|
11
|
+
}
|
|
12
|
+
interface GraphEdge {
|
|
13
|
+
source: string;
|
|
14
|
+
target: string;
|
|
15
|
+
relation: Relation;
|
|
16
|
+
confidence: Confidence;
|
|
17
|
+
}
|
|
18
|
+
/** Per-file extractor output. Validate against this shape before buildGraph() consumes it. */
|
|
19
|
+
interface ExtractionResult {
|
|
20
|
+
nodes: GraphNode[];
|
|
21
|
+
edges: GraphEdge[];
|
|
22
|
+
}
|
|
23
|
+
type FileCategory = 'code' | 'document' | 'paper' | 'image' | 'video';
|
|
24
|
+
interface FileManifest {
|
|
25
|
+
scanRoot: string;
|
|
26
|
+
files: Record<FileCategory, string[]>;
|
|
27
|
+
totalFiles: number;
|
|
28
|
+
totalWords: number;
|
|
29
|
+
skippedSensitive: string[];
|
|
30
|
+
}
|
|
31
|
+
interface Analysis {
|
|
32
|
+
godNodes: string[];
|
|
33
|
+
surprises: string[];
|
|
34
|
+
openQuestions: string[];
|
|
35
|
+
}
|
|
36
|
+
interface ExportOptions {
|
|
37
|
+
outDir: string;
|
|
38
|
+
html?: boolean;
|
|
39
|
+
svg?: boolean;
|
|
40
|
+
graphml?: boolean;
|
|
41
|
+
neo4j?: boolean;
|
|
42
|
+
obsidian?: boolean;
|
|
43
|
+
obsidianDir?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Walk `root` and categorize files into code/document/paper/image/video
|
|
48
|
+
* buckets. Does not follow symlinks (security.ts §5 — symlink traversal).
|
|
49
|
+
* Sensitive-looking files (.env, credentials, key material) are recorded in
|
|
50
|
+
* `skippedSensitive` and excluded from every other bucket/count.
|
|
51
|
+
*/
|
|
52
|
+
declare function collectFiles(root: string): FileManifest;
|
|
53
|
+
|
|
54
|
+
type Extractor = (path: string) => Promise<ExtractionResult>;
|
|
55
|
+
/** File extension -> extractor module. Register new languages here. */
|
|
56
|
+
declare const EXTRACTOR_REGISTRY: Record<string, Extractor>;
|
|
57
|
+
/**
|
|
58
|
+
* Dispatch a single file to its language extractor. Returns an empty
|
|
59
|
+
* extraction (not a thrown error) for unregistered extensions so detect's
|
|
60
|
+
* file list and extract's coverage can diverge without crashing a run.
|
|
61
|
+
*/
|
|
62
|
+
declare function extract(filePath: string): Promise<ExtractionResult>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merge per-file extraction results into one graphology graph.
|
|
66
|
+
*
|
|
67
|
+
* Every extraction is validated against the ExtractionResult schema before
|
|
68
|
+
* anything is added to the graph — a single malformed extraction throws
|
|
69
|
+
* (via validateExtraction()/ExtractionValidationError) before any node from
|
|
70
|
+
* *any* extraction is merged, so a bad extractor can never silently
|
|
71
|
+
* corrupt a graph that also contains good data.
|
|
72
|
+
*
|
|
73
|
+
* Nodes and edges are sorted before being added so the resulting graph (and
|
|
74
|
+
* the graph.json ultimately exported from it) is deterministic regardless
|
|
75
|
+
* of the order files were extracted in. Duplicate edges — same
|
|
76
|
+
* source/target/relation, e.g. two different call sites resolving to the
|
|
77
|
+
* same callee — are merged into a single edge, keeping the strongest
|
|
78
|
+
* confidence seen (EXTRACTED > INFERRED > AMBIGUOUS) rather than being
|
|
79
|
+
* counted as parallel edges (the schema has no call-count/weight field, so
|
|
80
|
+
* "A calls B" is either true or it isn't). Multiple distinct relations
|
|
81
|
+
* between the same pair (e.g. a file both `imports` and `imports_from` the
|
|
82
|
+
* same module) are kept as separate edges.
|
|
83
|
+
*/
|
|
84
|
+
declare function buildGraph(extractions: ExtractionResult[]): Graph;
|
|
85
|
+
|
|
86
|
+
type ClusterAlgorithm = 'louvain' | 'leiden';
|
|
87
|
+
interface ClusterOptions {
|
|
88
|
+
/**
|
|
89
|
+
* `louvain` (default, v1 behavior, unchanged) or `leiden` (v2: better
|
|
90
|
+
* community quality / guaranteed well-connectedness, via
|
|
91
|
+
* `@aflsolutions/graphology-communities-leiden` — see master prompt's
|
|
92
|
+
* v2 upgrade ideas, "Real Leiden clustering"). Both run deterministically
|
|
93
|
+
* against a sorted working copy with the same fixed-seed PRNG.
|
|
94
|
+
*/
|
|
95
|
+
algorithm?: ClusterAlgorithm;
|
|
96
|
+
/**
|
|
97
|
+
* Leiden-only: hard cap on outer iterations, for very large graphs where
|
|
98
|
+
* full convergence is expensive and the last iterations buy little
|
|
99
|
+
* additional modularity. Ignored for `algorithm: 'louvain'`.
|
|
100
|
+
*/
|
|
101
|
+
maxIterations?: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Community detection — Louvain by default (v1, unchanged), or Leiden
|
|
105
|
+
* (v2, `{ algorithm: 'leiden' }`) for better-quality, guaranteed
|
|
106
|
+
* well-connected communities on the same graph. Deterministic: runs
|
|
107
|
+
* against a sorted working copy with a fixed RNG seed, then copies the
|
|
108
|
+
* resulting `community` assignment back onto the original graph by node id
|
|
109
|
+
* (so `cluster()` still mutates and returns the graph instance it was
|
|
110
|
+
* given). Labels each community after its highest-degree ("hub") member;
|
|
111
|
+
* no LLM required for a usable baseline. Also stamps a `communityHash`
|
|
112
|
+
* (sha256 of the community's sorted member ids) so `--cluster-only` can
|
|
113
|
+
* tell which communities actually changed vs. merely being relabeled with
|
|
114
|
+
* a different arbitrary index next run.
|
|
115
|
+
*/
|
|
116
|
+
declare function cluster(graph: Graph, options?: ClusterOptions): Graph;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Surface god-nodes (excessive fan-in/out), structural surprises (self
|
|
120
|
+
* references, AMBIGUOUS edges), and open questions (isolated nodes,
|
|
121
|
+
* disconnected components) for a human to review in GRAPH_REPORT.md.
|
|
122
|
+
*/
|
|
123
|
+
declare function analyze(graph: Graph): Analysis;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Render the plain-language GRAPH_REPORT.md content: a short summary,
|
|
127
|
+
* community breakdown, god nodes, structural surprises, and open
|
|
128
|
+
* questions — everything a human (or an agent) needs to sanity-check a
|
|
129
|
+
* freshly built graph before trusting it.
|
|
130
|
+
*/
|
|
131
|
+
declare function renderReport(graph: Graph, analysis: Analysis, now?: Date): string;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Write graph.json (graphology's node-link serialization, GraphRAG-ready)
|
|
135
|
+
* and, per options, graph.html (vis-network, bundled inline — no CDN, works
|
|
136
|
+
* fully offline) and GRAPH_REPORT.md. Every label/title embedded in
|
|
137
|
+
* graph.html is passed through security.sanitizeLabel() + escapeHtml()
|
|
138
|
+
* first (XSS control, see SECURITY.md).
|
|
139
|
+
*
|
|
140
|
+
* Obsidian vault / .graphml / Neo4j cypher export are NOT implemented in
|
|
141
|
+
* v1 — if requested, exportGraph logs a clear warning and skips them
|
|
142
|
+
* rather than silently doing nothing or faking output.
|
|
143
|
+
*/
|
|
144
|
+
declare function exportGraph(graph: Graph, options: ExportOptions, report?: string): Promise<void>;
|
|
145
|
+
|
|
146
|
+
interface PipelineOptions {
|
|
147
|
+
/** Where to write graph.json/graph.html/GRAPH_REPORT.md. Defaults to `<root>/graphify-out`. */
|
|
148
|
+
outDir?: string;
|
|
149
|
+
html?: boolean;
|
|
150
|
+
svg?: boolean;
|
|
151
|
+
graphml?: boolean;
|
|
152
|
+
neo4j?: boolean;
|
|
153
|
+
obsidian?: boolean;
|
|
154
|
+
/** `louvain` (default, v1) or `leiden` (v2 — better community quality, see cluster.ts). */
|
|
155
|
+
algorithm?: ClusterAlgorithm;
|
|
156
|
+
/** Leiden-only: cap on outer iterations for very large graphs. */
|
|
157
|
+
maxIterations?: number;
|
|
158
|
+
/** Called with a short progress message after each stage (e.g. for CLI stderr output). */
|
|
159
|
+
onProgress?: (message: string) => void;
|
|
160
|
+
}
|
|
161
|
+
interface PipelineResult {
|
|
162
|
+
manifest: FileManifest;
|
|
163
|
+
graph: Graph;
|
|
164
|
+
analysis: Analysis;
|
|
165
|
+
report: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The full `detect -> extract -> build -> cluster -> analyze -> report ->
|
|
169
|
+
* export` pipeline as a single library call — the CLI's default command is
|
|
170
|
+
* a thin wrapper over this. Every extension registered in
|
|
171
|
+
* EXTRACTOR_REGISTRY (see extract.ts) is run; files with no registered
|
|
172
|
+
* extractor are skipped (not an error — see extract()'s doc comment).
|
|
173
|
+
*/
|
|
174
|
+
declare function runPipeline(root: string, options?: PipelineOptions): Promise<PipelineResult>;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Load a previously-exported graph.json back into a graphology Graph.
|
|
178
|
+
* Always goes through security.validateGraphPath() (path-traversal guard —
|
|
179
|
+
* see SECURITY.md) so a caller-supplied `outDir`/CLI arg can never make
|
|
180
|
+
* this read outside the graphify-out/ directory it resolves to.
|
|
181
|
+
*/
|
|
182
|
+
declare function loadGraph(outDir?: string): Promise<Graph>;
|
|
183
|
+
|
|
184
|
+
interface NodeMatch {
|
|
185
|
+
id: string;
|
|
186
|
+
label: string;
|
|
187
|
+
score: number;
|
|
188
|
+
}
|
|
189
|
+
/** Score every node by how many query tokens appear in its label or id. */
|
|
190
|
+
declare function scoreNodes(graph: Graph, query: string): NodeMatch[];
|
|
191
|
+
/**
|
|
192
|
+
* Best-effort single-node resolution: the highest-scoring lexical match,
|
|
193
|
+
* or null if nothing in the graph matches at all. Deliberately does *not*
|
|
194
|
+
* fall back to "the most connected node" — path/explain name a specific
|
|
195
|
+
* node by intent, and silently substituting an unrelated one would be
|
|
196
|
+
* more misleading than clearly saying "no match".
|
|
197
|
+
*/
|
|
198
|
+
declare function resolveNode(graph: Graph, query: string): NodeMatch | null;
|
|
199
|
+
interface VisitedNode {
|
|
200
|
+
id: string;
|
|
201
|
+
label: string;
|
|
202
|
+
sourceFile: string;
|
|
203
|
+
sourceLocation: string;
|
|
204
|
+
depth: number;
|
|
205
|
+
}
|
|
206
|
+
interface TraversalEdge {
|
|
207
|
+
source: string;
|
|
208
|
+
target: string;
|
|
209
|
+
relation: string;
|
|
210
|
+
confidence: string;
|
|
211
|
+
}
|
|
212
|
+
interface QueryResult {
|
|
213
|
+
seeds: NodeMatch[];
|
|
214
|
+
visited: VisitedNode[];
|
|
215
|
+
edges: TraversalEdge[];
|
|
216
|
+
}
|
|
217
|
+
interface QueryOptions {
|
|
218
|
+
dfs?: boolean;
|
|
219
|
+
/** Approximate cap on how many nodes to include (a stand-in for a true token budget — see docs). */
|
|
220
|
+
budget?: number;
|
|
221
|
+
maxDepth?: number;
|
|
222
|
+
maxSeeds?: number;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* BFS (default, broad context) or DFS (--dfs, trace a specific path)
|
|
226
|
+
* traversal from the nodes whose label best matches `question`'s
|
|
227
|
+
* keywords. This is lexical retrieval, not natural-language
|
|
228
|
+
* understanding — it surfaces the neighborhood of the graph an agent (or
|
|
229
|
+
* a human) should look at to answer the question, it does not itself
|
|
230
|
+
* compose an answer.
|
|
231
|
+
*/
|
|
232
|
+
declare function queryGraph(graph: Graph, question: string, options?: QueryOptions): QueryResult;
|
|
233
|
+
interface PathResult {
|
|
234
|
+
from: NodeMatch;
|
|
235
|
+
to: NodeMatch;
|
|
236
|
+
found: boolean;
|
|
237
|
+
path: string[];
|
|
238
|
+
edges: TraversalEdge[];
|
|
239
|
+
}
|
|
240
|
+
/** Shortest path between the nodes that best match `fromQuery` and `toQuery` (undirected BFS — connectivity, not call direction). */
|
|
241
|
+
declare function shortestPath(graph: Graph, fromQuery: string, toQuery: string): PathResult | null;
|
|
242
|
+
interface ExplainResult {
|
|
243
|
+
id: string;
|
|
244
|
+
label: string;
|
|
245
|
+
sourceFile: string;
|
|
246
|
+
sourceLocation: string;
|
|
247
|
+
communityLabel?: string;
|
|
248
|
+
outgoing: TraversalEdge[];
|
|
249
|
+
incoming: TraversalEdge[];
|
|
250
|
+
}
|
|
251
|
+
/** Plain-language-ready explanation of the node that best matches `query`. */
|
|
252
|
+
declare function explainNode(graph: Graph, query: string): ExplainResult | null;
|
|
253
|
+
|
|
254
|
+
declare const ExtractionResultSchema: z.ZodObject<{
|
|
255
|
+
nodes: z.ZodArray<z.ZodObject<{
|
|
256
|
+
id: z.ZodString;
|
|
257
|
+
label: z.ZodString;
|
|
258
|
+
sourceFile: z.ZodString;
|
|
259
|
+
sourceLocation: z.ZodString;
|
|
260
|
+
}, z.core.$strip>>;
|
|
261
|
+
edges: z.ZodArray<z.ZodObject<{
|
|
262
|
+
source: z.ZodString;
|
|
263
|
+
target: z.ZodString;
|
|
264
|
+
relation: z.ZodEnum<{
|
|
265
|
+
calls: "calls";
|
|
266
|
+
imports: "imports";
|
|
267
|
+
imports_from: "imports_from";
|
|
268
|
+
inherits: "inherits";
|
|
269
|
+
implements: "implements";
|
|
270
|
+
mixes_in: "mixes_in";
|
|
271
|
+
embeds: "embeds";
|
|
272
|
+
references: "references";
|
|
273
|
+
contains: "contains";
|
|
274
|
+
method: "method";
|
|
275
|
+
re_exports: "re_exports";
|
|
276
|
+
}>;
|
|
277
|
+
confidence: z.ZodEnum<{
|
|
278
|
+
EXTRACTED: "EXTRACTED";
|
|
279
|
+
INFERRED: "INFERRED";
|
|
280
|
+
AMBIGUOUS: "AMBIGUOUS";
|
|
281
|
+
}>;
|
|
282
|
+
}, z.core.$strip>>;
|
|
283
|
+
}, z.core.$strip>;
|
|
284
|
+
interface ExtractionValidationIssue {
|
|
285
|
+
path: Array<string | number>;
|
|
286
|
+
message: string;
|
|
287
|
+
}
|
|
288
|
+
/** Thrown by validateExtraction() on schema mismatch. Carries the raw zod issues. */
|
|
289
|
+
declare class ExtractionValidationError extends Error {
|
|
290
|
+
readonly issues: ExtractionValidationIssue[];
|
|
291
|
+
constructor(message: string, issues: ExtractionValidationIssue[]);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Validate an unknown value (typically an extractor's return value) against
|
|
295
|
+
* the ExtractionResult schema. Throws ExtractionValidationError with a clear,
|
|
296
|
+
* human-readable message on mismatch instead of letting malformed data reach
|
|
297
|
+
* buildGraph().
|
|
298
|
+
*/
|
|
299
|
+
declare function validateExtraction(value: unknown, context?: string): ExtractionResult;
|
|
300
|
+
|
|
301
|
+
export { type Analysis, type ClusterAlgorithm, type ClusterOptions, type Confidence, EXTRACTOR_REGISTRY, type ExplainResult, type ExportOptions, type ExtractionResult, ExtractionResultSchema, ExtractionValidationError, type ExtractionValidationIssue, type FileCategory, type FileManifest, type GraphEdge, type GraphNode, type NodeMatch, type PathResult, type PipelineOptions, type PipelineResult, type QueryOptions, type QueryResult, type Relation, type TraversalEdge, type VisitedNode, analyze, buildGraph, cluster, collectFiles, explainNode, exportGraph, extract, loadGraph, queryGraph, renderReport, resolveNode, runPipeline, scoreNodes, shortestPath, validateExtraction };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import Graph from 'graphology';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
type Confidence = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';
|
|
5
|
+
type Relation = 'calls' | 'imports' | 'imports_from' | 'inherits' | 'implements' | 'mixes_in' | 'embeds' | 'references' | 'contains' | 'method' | 're_exports';
|
|
6
|
+
interface GraphNode {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
sourceFile: string;
|
|
10
|
+
sourceLocation: string;
|
|
11
|
+
}
|
|
12
|
+
interface GraphEdge {
|
|
13
|
+
source: string;
|
|
14
|
+
target: string;
|
|
15
|
+
relation: Relation;
|
|
16
|
+
confidence: Confidence;
|
|
17
|
+
}
|
|
18
|
+
/** Per-file extractor output. Validate against this shape before buildGraph() consumes it. */
|
|
19
|
+
interface ExtractionResult {
|
|
20
|
+
nodes: GraphNode[];
|
|
21
|
+
edges: GraphEdge[];
|
|
22
|
+
}
|
|
23
|
+
type FileCategory = 'code' | 'document' | 'paper' | 'image' | 'video';
|
|
24
|
+
interface FileManifest {
|
|
25
|
+
scanRoot: string;
|
|
26
|
+
files: Record<FileCategory, string[]>;
|
|
27
|
+
totalFiles: number;
|
|
28
|
+
totalWords: number;
|
|
29
|
+
skippedSensitive: string[];
|
|
30
|
+
}
|
|
31
|
+
interface Analysis {
|
|
32
|
+
godNodes: string[];
|
|
33
|
+
surprises: string[];
|
|
34
|
+
openQuestions: string[];
|
|
35
|
+
}
|
|
36
|
+
interface ExportOptions {
|
|
37
|
+
outDir: string;
|
|
38
|
+
html?: boolean;
|
|
39
|
+
svg?: boolean;
|
|
40
|
+
graphml?: boolean;
|
|
41
|
+
neo4j?: boolean;
|
|
42
|
+
obsidian?: boolean;
|
|
43
|
+
obsidianDir?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Walk `root` and categorize files into code/document/paper/image/video
|
|
48
|
+
* buckets. Does not follow symlinks (security.ts §5 — symlink traversal).
|
|
49
|
+
* Sensitive-looking files (.env, credentials, key material) are recorded in
|
|
50
|
+
* `skippedSensitive` and excluded from every other bucket/count.
|
|
51
|
+
*/
|
|
52
|
+
declare function collectFiles(root: string): FileManifest;
|
|
53
|
+
|
|
54
|
+
type Extractor = (path: string) => Promise<ExtractionResult>;
|
|
55
|
+
/** File extension -> extractor module. Register new languages here. */
|
|
56
|
+
declare const EXTRACTOR_REGISTRY: Record<string, Extractor>;
|
|
57
|
+
/**
|
|
58
|
+
* Dispatch a single file to its language extractor. Returns an empty
|
|
59
|
+
* extraction (not a thrown error) for unregistered extensions so detect's
|
|
60
|
+
* file list and extract's coverage can diverge without crashing a run.
|
|
61
|
+
*/
|
|
62
|
+
declare function extract(filePath: string): Promise<ExtractionResult>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merge per-file extraction results into one graphology graph.
|
|
66
|
+
*
|
|
67
|
+
* Every extraction is validated against the ExtractionResult schema before
|
|
68
|
+
* anything is added to the graph — a single malformed extraction throws
|
|
69
|
+
* (via validateExtraction()/ExtractionValidationError) before any node from
|
|
70
|
+
* *any* extraction is merged, so a bad extractor can never silently
|
|
71
|
+
* corrupt a graph that also contains good data.
|
|
72
|
+
*
|
|
73
|
+
* Nodes and edges are sorted before being added so the resulting graph (and
|
|
74
|
+
* the graph.json ultimately exported from it) is deterministic regardless
|
|
75
|
+
* of the order files were extracted in. Duplicate edges — same
|
|
76
|
+
* source/target/relation, e.g. two different call sites resolving to the
|
|
77
|
+
* same callee — are merged into a single edge, keeping the strongest
|
|
78
|
+
* confidence seen (EXTRACTED > INFERRED > AMBIGUOUS) rather than being
|
|
79
|
+
* counted as parallel edges (the schema has no call-count/weight field, so
|
|
80
|
+
* "A calls B" is either true or it isn't). Multiple distinct relations
|
|
81
|
+
* between the same pair (e.g. a file both `imports` and `imports_from` the
|
|
82
|
+
* same module) are kept as separate edges.
|
|
83
|
+
*/
|
|
84
|
+
declare function buildGraph(extractions: ExtractionResult[]): Graph;
|
|
85
|
+
|
|
86
|
+
type ClusterAlgorithm = 'louvain' | 'leiden';
|
|
87
|
+
interface ClusterOptions {
|
|
88
|
+
/**
|
|
89
|
+
* `louvain` (default, v1 behavior, unchanged) or `leiden` (v2: better
|
|
90
|
+
* community quality / guaranteed well-connectedness, via
|
|
91
|
+
* `@aflsolutions/graphology-communities-leiden` — see master prompt's
|
|
92
|
+
* v2 upgrade ideas, "Real Leiden clustering"). Both run deterministically
|
|
93
|
+
* against a sorted working copy with the same fixed-seed PRNG.
|
|
94
|
+
*/
|
|
95
|
+
algorithm?: ClusterAlgorithm;
|
|
96
|
+
/**
|
|
97
|
+
* Leiden-only: hard cap on outer iterations, for very large graphs where
|
|
98
|
+
* full convergence is expensive and the last iterations buy little
|
|
99
|
+
* additional modularity. Ignored for `algorithm: 'louvain'`.
|
|
100
|
+
*/
|
|
101
|
+
maxIterations?: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Community detection — Louvain by default (v1, unchanged), or Leiden
|
|
105
|
+
* (v2, `{ algorithm: 'leiden' }`) for better-quality, guaranteed
|
|
106
|
+
* well-connected communities on the same graph. Deterministic: runs
|
|
107
|
+
* against a sorted working copy with a fixed RNG seed, then copies the
|
|
108
|
+
* resulting `community` assignment back onto the original graph by node id
|
|
109
|
+
* (so `cluster()` still mutates and returns the graph instance it was
|
|
110
|
+
* given). Labels each community after its highest-degree ("hub") member;
|
|
111
|
+
* no LLM required for a usable baseline. Also stamps a `communityHash`
|
|
112
|
+
* (sha256 of the community's sorted member ids) so `--cluster-only` can
|
|
113
|
+
* tell which communities actually changed vs. merely being relabeled with
|
|
114
|
+
* a different arbitrary index next run.
|
|
115
|
+
*/
|
|
116
|
+
declare function cluster(graph: Graph, options?: ClusterOptions): Graph;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Surface god-nodes (excessive fan-in/out), structural surprises (self
|
|
120
|
+
* references, AMBIGUOUS edges), and open questions (isolated nodes,
|
|
121
|
+
* disconnected components) for a human to review in GRAPH_REPORT.md.
|
|
122
|
+
*/
|
|
123
|
+
declare function analyze(graph: Graph): Analysis;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Render the plain-language GRAPH_REPORT.md content: a short summary,
|
|
127
|
+
* community breakdown, god nodes, structural surprises, and open
|
|
128
|
+
* questions — everything a human (or an agent) needs to sanity-check a
|
|
129
|
+
* freshly built graph before trusting it.
|
|
130
|
+
*/
|
|
131
|
+
declare function renderReport(graph: Graph, analysis: Analysis, now?: Date): string;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Write graph.json (graphology's node-link serialization, GraphRAG-ready)
|
|
135
|
+
* and, per options, graph.html (vis-network, bundled inline — no CDN, works
|
|
136
|
+
* fully offline) and GRAPH_REPORT.md. Every label/title embedded in
|
|
137
|
+
* graph.html is passed through security.sanitizeLabel() + escapeHtml()
|
|
138
|
+
* first (XSS control, see SECURITY.md).
|
|
139
|
+
*
|
|
140
|
+
* Obsidian vault / .graphml / Neo4j cypher export are NOT implemented in
|
|
141
|
+
* v1 — if requested, exportGraph logs a clear warning and skips them
|
|
142
|
+
* rather than silently doing nothing or faking output.
|
|
143
|
+
*/
|
|
144
|
+
declare function exportGraph(graph: Graph, options: ExportOptions, report?: string): Promise<void>;
|
|
145
|
+
|
|
146
|
+
interface PipelineOptions {
|
|
147
|
+
/** Where to write graph.json/graph.html/GRAPH_REPORT.md. Defaults to `<root>/graphify-out`. */
|
|
148
|
+
outDir?: string;
|
|
149
|
+
html?: boolean;
|
|
150
|
+
svg?: boolean;
|
|
151
|
+
graphml?: boolean;
|
|
152
|
+
neo4j?: boolean;
|
|
153
|
+
obsidian?: boolean;
|
|
154
|
+
/** `louvain` (default, v1) or `leiden` (v2 — better community quality, see cluster.ts). */
|
|
155
|
+
algorithm?: ClusterAlgorithm;
|
|
156
|
+
/** Leiden-only: cap on outer iterations for very large graphs. */
|
|
157
|
+
maxIterations?: number;
|
|
158
|
+
/** Called with a short progress message after each stage (e.g. for CLI stderr output). */
|
|
159
|
+
onProgress?: (message: string) => void;
|
|
160
|
+
}
|
|
161
|
+
interface PipelineResult {
|
|
162
|
+
manifest: FileManifest;
|
|
163
|
+
graph: Graph;
|
|
164
|
+
analysis: Analysis;
|
|
165
|
+
report: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The full `detect -> extract -> build -> cluster -> analyze -> report ->
|
|
169
|
+
* export` pipeline as a single library call — the CLI's default command is
|
|
170
|
+
* a thin wrapper over this. Every extension registered in
|
|
171
|
+
* EXTRACTOR_REGISTRY (see extract.ts) is run; files with no registered
|
|
172
|
+
* extractor are skipped (not an error — see extract()'s doc comment).
|
|
173
|
+
*/
|
|
174
|
+
declare function runPipeline(root: string, options?: PipelineOptions): Promise<PipelineResult>;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Load a previously-exported graph.json back into a graphology Graph.
|
|
178
|
+
* Always goes through security.validateGraphPath() (path-traversal guard —
|
|
179
|
+
* see SECURITY.md) so a caller-supplied `outDir`/CLI arg can never make
|
|
180
|
+
* this read outside the graphify-out/ directory it resolves to.
|
|
181
|
+
*/
|
|
182
|
+
declare function loadGraph(outDir?: string): Promise<Graph>;
|
|
183
|
+
|
|
184
|
+
interface NodeMatch {
|
|
185
|
+
id: string;
|
|
186
|
+
label: string;
|
|
187
|
+
score: number;
|
|
188
|
+
}
|
|
189
|
+
/** Score every node by how many query tokens appear in its label or id. */
|
|
190
|
+
declare function scoreNodes(graph: Graph, query: string): NodeMatch[];
|
|
191
|
+
/**
|
|
192
|
+
* Best-effort single-node resolution: the highest-scoring lexical match,
|
|
193
|
+
* or null if nothing in the graph matches at all. Deliberately does *not*
|
|
194
|
+
* fall back to "the most connected node" — path/explain name a specific
|
|
195
|
+
* node by intent, and silently substituting an unrelated one would be
|
|
196
|
+
* more misleading than clearly saying "no match".
|
|
197
|
+
*/
|
|
198
|
+
declare function resolveNode(graph: Graph, query: string): NodeMatch | null;
|
|
199
|
+
interface VisitedNode {
|
|
200
|
+
id: string;
|
|
201
|
+
label: string;
|
|
202
|
+
sourceFile: string;
|
|
203
|
+
sourceLocation: string;
|
|
204
|
+
depth: number;
|
|
205
|
+
}
|
|
206
|
+
interface TraversalEdge {
|
|
207
|
+
source: string;
|
|
208
|
+
target: string;
|
|
209
|
+
relation: string;
|
|
210
|
+
confidence: string;
|
|
211
|
+
}
|
|
212
|
+
interface QueryResult {
|
|
213
|
+
seeds: NodeMatch[];
|
|
214
|
+
visited: VisitedNode[];
|
|
215
|
+
edges: TraversalEdge[];
|
|
216
|
+
}
|
|
217
|
+
interface QueryOptions {
|
|
218
|
+
dfs?: boolean;
|
|
219
|
+
/** Approximate cap on how many nodes to include (a stand-in for a true token budget — see docs). */
|
|
220
|
+
budget?: number;
|
|
221
|
+
maxDepth?: number;
|
|
222
|
+
maxSeeds?: number;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* BFS (default, broad context) or DFS (--dfs, trace a specific path)
|
|
226
|
+
* traversal from the nodes whose label best matches `question`'s
|
|
227
|
+
* keywords. This is lexical retrieval, not natural-language
|
|
228
|
+
* understanding — it surfaces the neighborhood of the graph an agent (or
|
|
229
|
+
* a human) should look at to answer the question, it does not itself
|
|
230
|
+
* compose an answer.
|
|
231
|
+
*/
|
|
232
|
+
declare function queryGraph(graph: Graph, question: string, options?: QueryOptions): QueryResult;
|
|
233
|
+
interface PathResult {
|
|
234
|
+
from: NodeMatch;
|
|
235
|
+
to: NodeMatch;
|
|
236
|
+
found: boolean;
|
|
237
|
+
path: string[];
|
|
238
|
+
edges: TraversalEdge[];
|
|
239
|
+
}
|
|
240
|
+
/** Shortest path between the nodes that best match `fromQuery` and `toQuery` (undirected BFS — connectivity, not call direction). */
|
|
241
|
+
declare function shortestPath(graph: Graph, fromQuery: string, toQuery: string): PathResult | null;
|
|
242
|
+
interface ExplainResult {
|
|
243
|
+
id: string;
|
|
244
|
+
label: string;
|
|
245
|
+
sourceFile: string;
|
|
246
|
+
sourceLocation: string;
|
|
247
|
+
communityLabel?: string;
|
|
248
|
+
outgoing: TraversalEdge[];
|
|
249
|
+
incoming: TraversalEdge[];
|
|
250
|
+
}
|
|
251
|
+
/** Plain-language-ready explanation of the node that best matches `query`. */
|
|
252
|
+
declare function explainNode(graph: Graph, query: string): ExplainResult | null;
|
|
253
|
+
|
|
254
|
+
declare const ExtractionResultSchema: z.ZodObject<{
|
|
255
|
+
nodes: z.ZodArray<z.ZodObject<{
|
|
256
|
+
id: z.ZodString;
|
|
257
|
+
label: z.ZodString;
|
|
258
|
+
sourceFile: z.ZodString;
|
|
259
|
+
sourceLocation: z.ZodString;
|
|
260
|
+
}, z.core.$strip>>;
|
|
261
|
+
edges: z.ZodArray<z.ZodObject<{
|
|
262
|
+
source: z.ZodString;
|
|
263
|
+
target: z.ZodString;
|
|
264
|
+
relation: z.ZodEnum<{
|
|
265
|
+
calls: "calls";
|
|
266
|
+
imports: "imports";
|
|
267
|
+
imports_from: "imports_from";
|
|
268
|
+
inherits: "inherits";
|
|
269
|
+
implements: "implements";
|
|
270
|
+
mixes_in: "mixes_in";
|
|
271
|
+
embeds: "embeds";
|
|
272
|
+
references: "references";
|
|
273
|
+
contains: "contains";
|
|
274
|
+
method: "method";
|
|
275
|
+
re_exports: "re_exports";
|
|
276
|
+
}>;
|
|
277
|
+
confidence: z.ZodEnum<{
|
|
278
|
+
EXTRACTED: "EXTRACTED";
|
|
279
|
+
INFERRED: "INFERRED";
|
|
280
|
+
AMBIGUOUS: "AMBIGUOUS";
|
|
281
|
+
}>;
|
|
282
|
+
}, z.core.$strip>>;
|
|
283
|
+
}, z.core.$strip>;
|
|
284
|
+
interface ExtractionValidationIssue {
|
|
285
|
+
path: Array<string | number>;
|
|
286
|
+
message: string;
|
|
287
|
+
}
|
|
288
|
+
/** Thrown by validateExtraction() on schema mismatch. Carries the raw zod issues. */
|
|
289
|
+
declare class ExtractionValidationError extends Error {
|
|
290
|
+
readonly issues: ExtractionValidationIssue[];
|
|
291
|
+
constructor(message: string, issues: ExtractionValidationIssue[]);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Validate an unknown value (typically an extractor's return value) against
|
|
295
|
+
* the ExtractionResult schema. Throws ExtractionValidationError with a clear,
|
|
296
|
+
* human-readable message on mismatch instead of letting malformed data reach
|
|
297
|
+
* buildGraph().
|
|
298
|
+
*/
|
|
299
|
+
declare function validateExtraction(value: unknown, context?: string): ExtractionResult;
|
|
300
|
+
|
|
301
|
+
export { type Analysis, type ClusterAlgorithm, type ClusterOptions, type Confidence, EXTRACTOR_REGISTRY, type ExplainResult, type ExportOptions, type ExtractionResult, ExtractionResultSchema, ExtractionValidationError, type ExtractionValidationIssue, type FileCategory, type FileManifest, type GraphEdge, type GraphNode, type NodeMatch, type PathResult, type PipelineOptions, type PipelineResult, type QueryOptions, type QueryResult, type Relation, type TraversalEdge, type VisitedNode, analyze, buildGraph, cluster, collectFiles, explainNode, exportGraph, extract, loadGraph, queryGraph, renderReport, resolveNode, runPipeline, scoreNodes, shortestPath, validateExtraction };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXTRACTOR_REGISTRY,
|
|
3
|
+
ExtractionResultSchema,
|
|
4
|
+
ExtractionValidationError,
|
|
5
|
+
analyze,
|
|
6
|
+
buildGraph,
|
|
7
|
+
cluster,
|
|
8
|
+
collectFiles,
|
|
9
|
+
exportGraph,
|
|
10
|
+
extract,
|
|
11
|
+
renderReport,
|
|
12
|
+
runPipeline,
|
|
13
|
+
validateExtraction
|
|
14
|
+
} from "./chunk-DG5FECXV.js";
|
|
15
|
+
import {
|
|
16
|
+
explainNode,
|
|
17
|
+
loadGraph,
|
|
18
|
+
queryGraph,
|
|
19
|
+
resolveNode,
|
|
20
|
+
scoreNodes,
|
|
21
|
+
shortestPath
|
|
22
|
+
} from "./chunk-5ANIDX3G.js";
|
|
23
|
+
export {
|
|
24
|
+
EXTRACTOR_REGISTRY,
|
|
25
|
+
ExtractionResultSchema,
|
|
26
|
+
ExtractionValidationError,
|
|
27
|
+
analyze,
|
|
28
|
+
buildGraph,
|
|
29
|
+
cluster,
|
|
30
|
+
collectFiles,
|
|
31
|
+
explainNode,
|
|
32
|
+
exportGraph,
|
|
33
|
+
extract,
|
|
34
|
+
loadGraph,
|
|
35
|
+
queryGraph,
|
|
36
|
+
renderReport,
|
|
37
|
+
resolveNode,
|
|
38
|
+
runPipeline,
|
|
39
|
+
scoreNodes,
|
|
40
|
+
shortestPath,
|
|
41
|
+
validateExtraction
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|