@shrkcrft/graph 0.1.0-alpha.10
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.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/indexer/detect-workspace.d.ts +18 -0
- package/dist/indexer/detect-workspace.d.ts.map +1 -0
- package/dist/indexer/detect-workspace.js +80 -0
- package/dist/indexer/extract-csharp-file.d.ts +27 -0
- package/dist/indexer/extract-csharp-file.d.ts.map +1 -0
- package/dist/indexer/extract-csharp-file.js +163 -0
- package/dist/indexer/extract-dart-file.d.ts +28 -0
- package/dist/indexer/extract-dart-file.d.ts.map +1 -0
- package/dist/indexer/extract-dart-file.js +167 -0
- package/dist/indexer/extract-elixir-file.d.ts +27 -0
- package/dist/indexer/extract-elixir-file.d.ts.map +1 -0
- package/dist/indexer/extract-elixir-file.js +164 -0
- package/dist/indexer/extract-go-file.d.ts +28 -0
- package/dist/indexer/extract-go-file.d.ts.map +1 -0
- package/dist/indexer/extract-go-file.js +156 -0
- package/dist/indexer/extract-java-file.d.ts +25 -0
- package/dist/indexer/extract-java-file.d.ts.map +1 -0
- package/dist/indexer/extract-java-file.js +140 -0
- package/dist/indexer/extract-kotlin-file.d.ts +20 -0
- package/dist/indexer/extract-kotlin-file.d.ts.map +1 -0
- package/dist/indexer/extract-kotlin-file.js +158 -0
- package/dist/indexer/extract-php-file.d.ts +26 -0
- package/dist/indexer/extract-php-file.d.ts.map +1 -0
- package/dist/indexer/extract-php-file.js +161 -0
- package/dist/indexer/extract-python-file.d.ts +30 -0
- package/dist/indexer/extract-python-file.d.ts.map +1 -0
- package/dist/indexer/extract-python-file.js +196 -0
- package/dist/indexer/extract-ruby-file.d.ts +29 -0
- package/dist/indexer/extract-ruby-file.d.ts.map +1 -0
- package/dist/indexer/extract-ruby-file.js +151 -0
- package/dist/indexer/extract-rust-file.d.ts +27 -0
- package/dist/indexer/extract-rust-file.d.ts.map +1 -0
- package/dist/indexer/extract-rust-file.js +186 -0
- package/dist/indexer/extract-swift-file.d.ts +27 -0
- package/dist/indexer/extract-swift-file.d.ts.map +1 -0
- package/dist/indexer/extract-swift-file.js +168 -0
- package/dist/indexer/extract-ts-file.d.ts +79 -0
- package/dist/indexer/extract-ts-file.d.ts.map +1 -0
- package/dist/indexer/extract-ts-file.js +403 -0
- package/dist/indexer/incremental-updater.d.ts +41 -0
- package/dist/indexer/incremental-updater.d.ts.map +1 -0
- package/dist/indexer/incremental-updater.js +395 -0
- package/dist/indexer/index-builder.d.ts +23 -0
- package/dist/indexer/index-builder.d.ts.map +1 -0
- package/dist/indexer/index-builder.js +289 -0
- package/dist/indexer/resolve-imports.d.ts +36 -0
- package/dist/indexer/resolve-imports.d.ts.map +1 -0
- package/dist/indexer/resolve-imports.js +144 -0
- package/dist/indexer/unresolved-imports.d.ts +20 -0
- package/dist/indexer/unresolved-imports.d.ts.map +1 -0
- package/dist/indexer/unresolved-imports.js +32 -0
- package/dist/query/cycle-detection.d.ts +40 -0
- package/dist/query/cycle-detection.d.ts.map +1 -0
- package/dist/query/cycle-detection.js +135 -0
- package/dist/query/query-api.d.ts +87 -0
- package/dist/query/query-api.d.ts.map +1 -0
- package/dist/query/query-api.js +232 -0
- package/dist/schema/edge-kind.d.ts +31 -0
- package/dist/schema/edge-kind.d.ts.map +1 -0
- package/dist/schema/edge-kind.js +35 -0
- package/dist/schema/edge.d.ts +22 -0
- package/dist/schema/edge.d.ts.map +1 -0
- package/dist/schema/edge.js +1 -0
- package/dist/schema/file-fingerprint.d.ts +22 -0
- package/dist/schema/file-fingerprint.d.ts.map +1 -0
- package/dist/schema/file-fingerprint.js +1 -0
- package/dist/schema/graph-snapshot.d.ts +18 -0
- package/dist/schema/graph-snapshot.d.ts.map +1 -0
- package/dist/schema/graph-snapshot.js +1 -0
- package/dist/schema/manifest.d.ts +47 -0
- package/dist/schema/manifest.d.ts.map +1 -0
- package/dist/schema/manifest.js +1 -0
- package/dist/schema/node-kind.d.ts +21 -0
- package/dist/schema/node-kind.d.ts.map +1 -0
- package/dist/schema/node-kind.js +27 -0
- package/dist/schema/node.d.ts +26 -0
- package/dist/schema/node.d.ts.map +1 -0
- package/dist/schema/node.js +1 -0
- package/dist/schema/schema-version.d.ts +10 -0
- package/dist/schema/schema-version.d.ts.map +1 -0
- package/dist/schema/schema-version.js +8 -0
- package/dist/store/file-fingerprint.d.ts +8 -0
- package/dist/store/file-fingerprint.d.ts.map +1 -0
- package/dist/store/file-fingerprint.js +64 -0
- package/dist/store/graph-store.d.ts +48 -0
- package/dist/store/graph-store.d.ts.map +1 -0
- package/dist/store/graph-store.js +194 -0
- package/package.json +54 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { EdgeKind } from "../schema/edge-kind.js";
|
|
2
|
+
/**
|
|
3
|
+
* Find every import cycle in the file-import directed graph. Returns
|
|
4
|
+
* one entry per SCC of size ≥ 2. Iterative Tarjan SCC over the
|
|
5
|
+
* `imports-file` subgraph; O(V+E) and stack-safe.
|
|
6
|
+
*
|
|
7
|
+
* `pathById` is optional — when supplied, the returned `paths` array
|
|
8
|
+
* is populated so callers don't have to re-resolve file ids.
|
|
9
|
+
*/
|
|
10
|
+
export function findFileCycles(nodes, edges, pathById) {
|
|
11
|
+
const adj = buildFileAdjacency(nodes, edges);
|
|
12
|
+
const sccs = stronglyConnectedComponentsIterative(adj);
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const scc of sccs) {
|
|
15
|
+
if (scc.length < 2)
|
|
16
|
+
continue;
|
|
17
|
+
const cycle = {
|
|
18
|
+
nodeIds: [...scc],
|
|
19
|
+
size: scc.length,
|
|
20
|
+
};
|
|
21
|
+
if (pathById) {
|
|
22
|
+
cycle.paths = scc.map((id) => pathById.get(id) ?? id.replace(/^file:/, ''));
|
|
23
|
+
}
|
|
24
|
+
out.push(cycle);
|
|
25
|
+
}
|
|
26
|
+
// Stable order: by size DESC, then by first id ASC. Makes "show me
|
|
27
|
+
// the worst cycle first" deterministic across runs.
|
|
28
|
+
out.sort((a, b) => {
|
|
29
|
+
if (b.size !== a.size)
|
|
30
|
+
return b.size - a.size;
|
|
31
|
+
return (a.nodeIds[0] ?? '').localeCompare(b.nodeIds[0] ?? '');
|
|
32
|
+
});
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Roll-up over `findFileCycles` results. Kept for downstream callers
|
|
37
|
+
* (the indexer, doctor) that only care about counts.
|
|
38
|
+
*/
|
|
39
|
+
export function summarizeCycles(nodes, edges) {
|
|
40
|
+
const cycles = findFileCycles(nodes, edges);
|
|
41
|
+
let largestCycleSize = 0;
|
|
42
|
+
let filesInCycles = 0;
|
|
43
|
+
for (const c of cycles) {
|
|
44
|
+
if (c.size > largestCycleSize)
|
|
45
|
+
largestCycleSize = c.size;
|
|
46
|
+
filesInCycles += c.size;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
cycleCount: cycles.length,
|
|
50
|
+
largestCycleSize,
|
|
51
|
+
filesInCycles,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function buildFileAdjacency(nodes, edges) {
|
|
55
|
+
const fileIds = new Set();
|
|
56
|
+
for (const n of nodes) {
|
|
57
|
+
if (n.id.startsWith('file:'))
|
|
58
|
+
fileIds.add(n.id);
|
|
59
|
+
}
|
|
60
|
+
const adj = new Map();
|
|
61
|
+
for (const id of fileIds)
|
|
62
|
+
adj.set(id, []);
|
|
63
|
+
for (const e of edges) {
|
|
64
|
+
if (e.kind !== EdgeKind.ImportsFile)
|
|
65
|
+
continue;
|
|
66
|
+
if (!fileIds.has(e.from) || !fileIds.has(e.to))
|
|
67
|
+
continue;
|
|
68
|
+
adj.get(e.from).push(e.to);
|
|
69
|
+
}
|
|
70
|
+
return adj;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Iterative Tarjan's strongly connected components. Returns one array
|
|
74
|
+
* per SCC; singletons are included so callers that care about all of
|
|
75
|
+
* them (e.g. `IGraphQueryApi.cycles()` once that ships) can filter
|
|
76
|
+
* themselves. Tarjan is single-pass O(V+E), and unlike the recursive
|
|
77
|
+
* form this version doesn't blow the stack on long import chains.
|
|
78
|
+
*/
|
|
79
|
+
function stronglyConnectedComponentsIterative(adj) {
|
|
80
|
+
const nodeIds = [...adj.keys()];
|
|
81
|
+
const indices = new Map();
|
|
82
|
+
const lowlinks = new Map();
|
|
83
|
+
const onStack = new Set();
|
|
84
|
+
const stack = [];
|
|
85
|
+
const result = [];
|
|
86
|
+
let index = 0;
|
|
87
|
+
for (const start of nodeIds) {
|
|
88
|
+
if (indices.has(start))
|
|
89
|
+
continue;
|
|
90
|
+
const callStack = [{ v: start, iter: 0 }];
|
|
91
|
+
indices.set(start, index);
|
|
92
|
+
lowlinks.set(start, index);
|
|
93
|
+
index += 1;
|
|
94
|
+
stack.push(start);
|
|
95
|
+
onStack.add(start);
|
|
96
|
+
while (callStack.length > 0) {
|
|
97
|
+
const top = callStack[callStack.length - 1];
|
|
98
|
+
const successors = adj.get(top.v) ?? [];
|
|
99
|
+
if (top.iter < successors.length) {
|
|
100
|
+
const w = successors[top.iter];
|
|
101
|
+
top.iter += 1;
|
|
102
|
+
if (!indices.has(w)) {
|
|
103
|
+
indices.set(w, index);
|
|
104
|
+
lowlinks.set(w, index);
|
|
105
|
+
index += 1;
|
|
106
|
+
stack.push(w);
|
|
107
|
+
onStack.add(w);
|
|
108
|
+
callStack.push({ v: w, iter: 0 });
|
|
109
|
+
}
|
|
110
|
+
else if (onStack.has(w)) {
|
|
111
|
+
lowlinks.set(top.v, Math.min(lowlinks.get(top.v), indices.get(w)));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
if (lowlinks.get(top.v) === indices.get(top.v)) {
|
|
116
|
+
const scc = [];
|
|
117
|
+
while (true) {
|
|
118
|
+
const w = stack.pop();
|
|
119
|
+
onStack.delete(w);
|
|
120
|
+
scc.push(w);
|
|
121
|
+
if (w === top.v)
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
result.push(scc);
|
|
125
|
+
}
|
|
126
|
+
callStack.pop();
|
|
127
|
+
if (callStack.length > 0) {
|
|
128
|
+
const parent = callStack[callStack.length - 1];
|
|
129
|
+
lowlinks.set(parent.v, Math.min(lowlinks.get(parent.v), lowlinks.get(top.v)));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { IEdge } from '../schema/edge.js';
|
|
2
|
+
import type { IGraphSnapshot } from '../schema/graph-snapshot.js';
|
|
3
|
+
import type { INode } from '../schema/node.js';
|
|
4
|
+
import { type IFileCycle } from './cycle-detection.js';
|
|
5
|
+
export interface IGraphStatus {
|
|
6
|
+
exists: boolean;
|
|
7
|
+
state: 'fresh' | 'stale' | 'missing' | 'corrupt';
|
|
8
|
+
fileCount: number;
|
|
9
|
+
nodeCount: number;
|
|
10
|
+
edgeCount: number;
|
|
11
|
+
lastIndexedAt?: string;
|
|
12
|
+
digestOk?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface IGraphNeighbours {
|
|
15
|
+
node: INode;
|
|
16
|
+
/** Outgoing edges (this → other). */
|
|
17
|
+
out: readonly {
|
|
18
|
+
edge: IEdge;
|
|
19
|
+
target: INode | {
|
|
20
|
+
id: string;
|
|
21
|
+
resolved: false;
|
|
22
|
+
};
|
|
23
|
+
}[];
|
|
24
|
+
/** Incoming edges (other → this). */
|
|
25
|
+
in: readonly {
|
|
26
|
+
edge: IEdge;
|
|
27
|
+
source: INode | {
|
|
28
|
+
id: string;
|
|
29
|
+
resolved: false;
|
|
30
|
+
};
|
|
31
|
+
}[];
|
|
32
|
+
}
|
|
33
|
+
export interface IFindSymbolOptions {
|
|
34
|
+
/** Filter by symbol package. */
|
|
35
|
+
package?: string;
|
|
36
|
+
/** Hard cap on returned matches. Default 50. */
|
|
37
|
+
limit?: number;
|
|
38
|
+
/** Exact match required (no case-insensitive / prefix matching). */
|
|
39
|
+
exact?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Read-only query layer over an in-memory graph snapshot.
|
|
43
|
+
*
|
|
44
|
+
* The CLI/MCP layer constructs one instance per request from a freshly
|
|
45
|
+
* loaded snapshot. The query API never writes; only the indexer does.
|
|
46
|
+
*/
|
|
47
|
+
export declare class GraphQueryApi {
|
|
48
|
+
private readonly snap;
|
|
49
|
+
private readonly fileByPath;
|
|
50
|
+
private readonly symbolByName;
|
|
51
|
+
private readonly outByFrom;
|
|
52
|
+
private readonly inByTo;
|
|
53
|
+
constructor(snap: IGraphSnapshot);
|
|
54
|
+
/** Load a snapshot from disk and construct the query API. */
|
|
55
|
+
static fromStore(projectRoot: string): GraphQueryApi;
|
|
56
|
+
status(): IGraphStatus;
|
|
57
|
+
findFile(path: string): INode | undefined;
|
|
58
|
+
/** Iterate every file node in the snapshot. Cheap; in-memory walk. */
|
|
59
|
+
allFiles(): IterableIterator<INode>;
|
|
60
|
+
/** Iterate every package node in the snapshot. */
|
|
61
|
+
allPackages(): IterableIterator<INode>;
|
|
62
|
+
findSymbol(name: string, opts?: IFindSymbolOptions): readonly INode[];
|
|
63
|
+
/** Files that import `nodeId` (directly). */
|
|
64
|
+
importersOf(nodeId: string): readonly INode[];
|
|
65
|
+
/** Files imported by `nodeId` (directly). Resolves only File-kind targets. */
|
|
66
|
+
importsFrom(nodeId: string): readonly INode[];
|
|
67
|
+
/** Symbols declared by a given file. */
|
|
68
|
+
symbolsIn(fileNodeId: string): readonly INode[];
|
|
69
|
+
/** Files that *call* the given symbol (Wave 3 — file-level precision). */
|
|
70
|
+
callersOf(symbolNodeId: string): readonly INode[];
|
|
71
|
+
/** Files that *reference* the given symbol (any use, including calls). */
|
|
72
|
+
referencesOf(symbolNodeId: string): readonly INode[];
|
|
73
|
+
/** Packages that this package depends on (PackageDependsOn). */
|
|
74
|
+
packageDeps(packageName: string): readonly INode[];
|
|
75
|
+
/**
|
|
76
|
+
* Every import cycle in the snapshot (SCC ≥ 2). Recomputes from the
|
|
77
|
+
* in-memory snapshot — file paths are filled in from the snapshot's
|
|
78
|
+
* file nodes. Sorted by size descending then id ascending so callers
|
|
79
|
+
* get a stable "worst first" ordering. Roadmap §3.1 long-promised
|
|
80
|
+
* this method on the query API; backed by `findFileCycles` so the
|
|
81
|
+
* indexer's manifest counts stay consistent with what callers see.
|
|
82
|
+
*/
|
|
83
|
+
cycles(): readonly IFileCycle[];
|
|
84
|
+
/** 1-hop neighbours of a node, both in + out. */
|
|
85
|
+
neighbours(nodeId: string): IGraphNeighbours | undefined;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=query-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-api.d.ts","sourceRoot":"","sources":["../../src/query/query-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { EdgeKind } from "../schema/edge-kind.js";
|
|
2
|
+
import { NodeKind } from "../schema/node-kind.js";
|
|
3
|
+
import { GraphStore } from "../store/graph-store.js";
|
|
4
|
+
import { findFileCycles } from "./cycle-detection.js";
|
|
5
|
+
/**
|
|
6
|
+
* Read-only query layer over an in-memory graph snapshot.
|
|
7
|
+
*
|
|
8
|
+
* The CLI/MCP layer constructs one instance per request from a freshly
|
|
9
|
+
* loaded snapshot. The query API never writes; only the indexer does.
|
|
10
|
+
*/
|
|
11
|
+
export class GraphQueryApi {
|
|
12
|
+
snap;
|
|
13
|
+
fileByPath;
|
|
14
|
+
symbolByName;
|
|
15
|
+
outByFrom;
|
|
16
|
+
inByTo;
|
|
17
|
+
constructor(snap) {
|
|
18
|
+
this.snap = snap;
|
|
19
|
+
const fileByPath = new Map();
|
|
20
|
+
const symbolByName = new Map();
|
|
21
|
+
for (const n of snap.nodes.values()) {
|
|
22
|
+
if (n.kind === NodeKind.File && n.path) {
|
|
23
|
+
fileByPath.set(n.path, n);
|
|
24
|
+
}
|
|
25
|
+
else if (n.kind === NodeKind.Symbol) {
|
|
26
|
+
const arr = symbolByName.get(n.label);
|
|
27
|
+
if (arr)
|
|
28
|
+
arr.push(n);
|
|
29
|
+
else
|
|
30
|
+
symbolByName.set(n.label, [n]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const outByFrom = new Map();
|
|
34
|
+
const inByTo = new Map();
|
|
35
|
+
for (const e of snap.edges.values()) {
|
|
36
|
+
const ofrom = outByFrom.get(e.from);
|
|
37
|
+
if (ofrom)
|
|
38
|
+
ofrom.push(e);
|
|
39
|
+
else
|
|
40
|
+
outByFrom.set(e.from, [e]);
|
|
41
|
+
const ito = inByTo.get(e.to);
|
|
42
|
+
if (ito)
|
|
43
|
+
ito.push(e);
|
|
44
|
+
else
|
|
45
|
+
inByTo.set(e.to, [e]);
|
|
46
|
+
}
|
|
47
|
+
this.fileByPath = fileByPath;
|
|
48
|
+
this.symbolByName = symbolByName;
|
|
49
|
+
this.outByFrom = outByFrom;
|
|
50
|
+
this.inByTo = inByTo;
|
|
51
|
+
}
|
|
52
|
+
/** Load a snapshot from disk and construct the query API. */
|
|
53
|
+
static fromStore(projectRoot) {
|
|
54
|
+
const store = new GraphStore(projectRoot);
|
|
55
|
+
return new GraphQueryApi(store.loadSnapshot());
|
|
56
|
+
}
|
|
57
|
+
status() {
|
|
58
|
+
const m = this.snap.manifest;
|
|
59
|
+
return {
|
|
60
|
+
exists: true,
|
|
61
|
+
state: 'fresh',
|
|
62
|
+
fileCount: m.filesIndexed,
|
|
63
|
+
nodeCount: this.snap.nodes.size,
|
|
64
|
+
edgeCount: this.snap.edges.size,
|
|
65
|
+
lastIndexedAt: m.lastIndexedAt,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
findFile(path) {
|
|
69
|
+
return this.fileByPath.get(path);
|
|
70
|
+
}
|
|
71
|
+
/** Iterate every file node in the snapshot. Cheap; in-memory walk. */
|
|
72
|
+
*allFiles() {
|
|
73
|
+
for (const n of this.fileByPath.values())
|
|
74
|
+
yield n;
|
|
75
|
+
}
|
|
76
|
+
/** Iterate every package node in the snapshot. */
|
|
77
|
+
*allPackages() {
|
|
78
|
+
for (const n of this.snap.nodes.values()) {
|
|
79
|
+
if (n.kind === NodeKind.Package)
|
|
80
|
+
yield n;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
findSymbol(name, opts = {}) {
|
|
84
|
+
const limit = opts.limit ?? 50;
|
|
85
|
+
if (opts.exact !== false) {
|
|
86
|
+
const exact = this.symbolByName.get(name) ?? [];
|
|
87
|
+
return filterByPackage(exact, opts.package).slice(0, limit);
|
|
88
|
+
}
|
|
89
|
+
const lower = name.toLowerCase();
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const [k, list] of this.symbolByName) {
|
|
92
|
+
if (!k.toLowerCase().includes(lower))
|
|
93
|
+
continue;
|
|
94
|
+
for (const n of list)
|
|
95
|
+
out.push(n);
|
|
96
|
+
if (out.length >= limit * 2)
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
return filterByPackage(out, opts.package).slice(0, limit);
|
|
100
|
+
}
|
|
101
|
+
/** Files that import `nodeId` (directly). */
|
|
102
|
+
importersOf(nodeId) {
|
|
103
|
+
const edges = this.inByTo.get(nodeId) ?? [];
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const e of edges) {
|
|
106
|
+
if (e.kind !== EdgeKind.ImportsFile)
|
|
107
|
+
continue;
|
|
108
|
+
const n = this.snap.nodes.get(e.from);
|
|
109
|
+
if (n)
|
|
110
|
+
out.push(n);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
/** Files imported by `nodeId` (directly). Resolves only File-kind targets. */
|
|
115
|
+
importsFrom(nodeId) {
|
|
116
|
+
const edges = this.outByFrom.get(nodeId) ?? [];
|
|
117
|
+
const out = [];
|
|
118
|
+
for (const e of edges) {
|
|
119
|
+
if (e.kind !== EdgeKind.ImportsFile)
|
|
120
|
+
continue;
|
|
121
|
+
const n = this.snap.nodes.get(e.to);
|
|
122
|
+
if (n)
|
|
123
|
+
out.push(n);
|
|
124
|
+
}
|
|
125
|
+
return out;
|
|
126
|
+
}
|
|
127
|
+
/** Symbols declared by a given file. */
|
|
128
|
+
symbolsIn(fileNodeId) {
|
|
129
|
+
const edges = this.outByFrom.get(fileNodeId) ?? [];
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const e of edges) {
|
|
132
|
+
if (e.kind !== EdgeKind.DeclaresSymbol)
|
|
133
|
+
continue;
|
|
134
|
+
const n = this.snap.nodes.get(e.to);
|
|
135
|
+
if (n)
|
|
136
|
+
out.push(n);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
/** Files that *call* the given symbol (Wave 3 — file-level precision). */
|
|
141
|
+
callersOf(symbolNodeId) {
|
|
142
|
+
const edges = this.inByTo.get(symbolNodeId) ?? [];
|
|
143
|
+
const out = [];
|
|
144
|
+
for (const e of edges) {
|
|
145
|
+
if (e.kind !== EdgeKind.CallsSymbol)
|
|
146
|
+
continue;
|
|
147
|
+
const n = this.snap.nodes.get(e.from);
|
|
148
|
+
if (n)
|
|
149
|
+
out.push(n);
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
/** Files that *reference* the given symbol (any use, including calls). */
|
|
154
|
+
referencesOf(symbolNodeId) {
|
|
155
|
+
const edges = this.inByTo.get(symbolNodeId) ?? [];
|
|
156
|
+
const out = [];
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
for (const e of edges) {
|
|
159
|
+
if (e.kind !== EdgeKind.ReferencesSymbol && e.kind !== EdgeKind.CallsSymbol)
|
|
160
|
+
continue;
|
|
161
|
+
if (seen.has(e.from))
|
|
162
|
+
continue;
|
|
163
|
+
seen.add(e.from);
|
|
164
|
+
const n = this.snap.nodes.get(e.from);
|
|
165
|
+
if (n)
|
|
166
|
+
out.push(n);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
/** Packages that this package depends on (PackageDependsOn). */
|
|
171
|
+
packageDeps(packageName) {
|
|
172
|
+
const edges = this.outByFrom.get(`package:${packageName}`) ?? [];
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const e of edges) {
|
|
175
|
+
if (e.kind !== EdgeKind.PackageDependsOn)
|
|
176
|
+
continue;
|
|
177
|
+
const n = this.snap.nodes.get(e.to);
|
|
178
|
+
if (n)
|
|
179
|
+
out.push(n);
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Every import cycle in the snapshot (SCC ≥ 2). Recomputes from the
|
|
185
|
+
* in-memory snapshot — file paths are filled in from the snapshot's
|
|
186
|
+
* file nodes. Sorted by size descending then id ascending so callers
|
|
187
|
+
* get a stable "worst first" ordering. Roadmap §3.1 long-promised
|
|
188
|
+
* this method on the query API; backed by `findFileCycles` so the
|
|
189
|
+
* indexer's manifest counts stay consistent with what callers see.
|
|
190
|
+
*/
|
|
191
|
+
cycles() {
|
|
192
|
+
const pathById = new Map();
|
|
193
|
+
for (const [id, n] of this.snap.nodes) {
|
|
194
|
+
if (n.kind === NodeKind.File && n.path)
|
|
195
|
+
pathById.set(id, n.path);
|
|
196
|
+
}
|
|
197
|
+
return findFileCycles([...this.snap.nodes.values()], [...this.snap.edges.values()], pathById);
|
|
198
|
+
}
|
|
199
|
+
/** 1-hop neighbours of a node, both in + out. */
|
|
200
|
+
neighbours(nodeId) {
|
|
201
|
+
const node = this.snap.nodes.get(nodeId);
|
|
202
|
+
if (!node)
|
|
203
|
+
return undefined;
|
|
204
|
+
const outEdges = this.outByFrom.get(nodeId) ?? [];
|
|
205
|
+
const inEdges = this.inByTo.get(nodeId) ?? [];
|
|
206
|
+
return {
|
|
207
|
+
node,
|
|
208
|
+
out: outEdges.map((edge) => {
|
|
209
|
+
const target = this.snap.nodes.get(edge.to);
|
|
210
|
+
return { edge, target: target ?? { id: edge.to, resolved: false } };
|
|
211
|
+
}),
|
|
212
|
+
in: inEdges.map((edge) => {
|
|
213
|
+
const source = this.snap.nodes.get(edge.from);
|
|
214
|
+
return { edge, source: source ?? { id: edge.from, resolved: false } };
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function filterByPackage(list, pkg) {
|
|
220
|
+
if (!pkg)
|
|
221
|
+
return [...list];
|
|
222
|
+
const prefix = pkg + '/';
|
|
223
|
+
return list.filter((n) => {
|
|
224
|
+
if (!n.path)
|
|
225
|
+
return false;
|
|
226
|
+
// Workspace packages live at `packages/<slug>/...`. Without a path-
|
|
227
|
+
// alias map at query time, the cheap proxy is "path starts with the
|
|
228
|
+
// package directory prefix". Caller passes the directory, not the
|
|
229
|
+
// package name, for now.
|
|
230
|
+
return n.path === pkg || n.path.startsWith(prefix);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge kinds in the code graph.
|
|
3
|
+
*
|
|
4
|
+
* MVP populates the "Code structure" group. Symbol-reference edges are
|
|
5
|
+
* reserved for Wave 3; bridge edges are reserved for Wave 2.
|
|
6
|
+
*/
|
|
7
|
+
export declare enum EdgeKind {
|
|
8
|
+
ImportsFile = "imports-file",
|
|
9
|
+
DeclaresSymbol = "declares-symbol",
|
|
10
|
+
ReExportsSymbol = "re-exports-symbol",
|
|
11
|
+
BelongsToPackage = "belongs-to-package",
|
|
12
|
+
PackageDependsOn = "package-depends-on",
|
|
13
|
+
ReferencesSymbol = "references-symbol",
|
|
14
|
+
CallsSymbol = "calls-symbol",
|
|
15
|
+
ExtendsSymbol = "extends-symbol",
|
|
16
|
+
ImplementsSymbol = "implements-symbol",
|
|
17
|
+
AppliesRule = "applies-rule",
|
|
18
|
+
ViolatesBoundary = "violates-boundary",
|
|
19
|
+
MatchesPath = "matches-path",
|
|
20
|
+
CoveredByTemplate = "covered-by-template",
|
|
21
|
+
CoveredByPipeline = "covered-by-pipeline",
|
|
22
|
+
ContributedByPack = "contributed-by-pack",
|
|
23
|
+
ContainsKnowledge = "contains-knowledge",
|
|
24
|
+
/** File declares a framework entity (e.g. controller/component/module). */
|
|
25
|
+
FrameworkDeclares = "framework-declares",
|
|
26
|
+
/** Controller / route handler → HTTP route. */
|
|
27
|
+
HandlesRoute = "handles-route",
|
|
28
|
+
/** Component → hook it uses. */
|
|
29
|
+
UsesHook = "uses-hook"
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=edge-kind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-kind.d.ts","sourceRoot":"","sources":["../../src/schema/edge-kind.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,oBAAY,QAAQ;IAElB,WAAW,iBAAiB;IAC5B,cAAc,oBAAoB;IAClC,eAAe,sBAAsB;IACrC,gBAAgB,uBAAuB;IACvC,gBAAgB,uBAAuB;IAGvC,gBAAgB,sBAAsB;IACtC,WAAW,iBAAiB;IAC5B,aAAa,mBAAmB;IAChC,gBAAgB,sBAAsB;IAGtC,WAAW,iBAAiB;IAC5B,gBAAgB,sBAAsB;IACtC,WAAW,iBAAiB;IAC5B,iBAAiB,wBAAwB;IACzC,iBAAiB,wBAAwB;IACzC,iBAAiB,wBAAwB;IACzC,iBAAiB,uBAAuB;IAGxC,2EAA2E;IAC3E,iBAAiB,uBAAuB;IACxC,+CAA+C;IAC/C,YAAY,kBAAkB;IAC9B,gCAAgC;IAChC,QAAQ,cAAc;CACvB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge kinds in the code graph.
|
|
3
|
+
*
|
|
4
|
+
* MVP populates the "Code structure" group. Symbol-reference edges are
|
|
5
|
+
* reserved for Wave 3; bridge edges are reserved for Wave 2.
|
|
6
|
+
*/
|
|
7
|
+
export var EdgeKind;
|
|
8
|
+
(function (EdgeKind) {
|
|
9
|
+
// ── Code structure (MVP) ─────────────────────────────────────────────
|
|
10
|
+
EdgeKind["ImportsFile"] = "imports-file";
|
|
11
|
+
EdgeKind["DeclaresSymbol"] = "declares-symbol";
|
|
12
|
+
EdgeKind["ReExportsSymbol"] = "re-exports-symbol";
|
|
13
|
+
EdgeKind["BelongsToPackage"] = "belongs-to-package";
|
|
14
|
+
EdgeKind["PackageDependsOn"] = "package-depends-on";
|
|
15
|
+
// ── Symbol references (reserved, Wave 3) ─────────────────────────────
|
|
16
|
+
EdgeKind["ReferencesSymbol"] = "references-symbol";
|
|
17
|
+
EdgeKind["CallsSymbol"] = "calls-symbol";
|
|
18
|
+
EdgeKind["ExtendsSymbol"] = "extends-symbol";
|
|
19
|
+
EdgeKind["ImplementsSymbol"] = "implements-symbol";
|
|
20
|
+
// ── Bridge to assets (reserved, Wave 2) ──────────────────────────────
|
|
21
|
+
EdgeKind["AppliesRule"] = "applies-rule";
|
|
22
|
+
EdgeKind["ViolatesBoundary"] = "violates-boundary";
|
|
23
|
+
EdgeKind["MatchesPath"] = "matches-path";
|
|
24
|
+
EdgeKind["CoveredByTemplate"] = "covered-by-template";
|
|
25
|
+
EdgeKind["CoveredByPipeline"] = "covered-by-pipeline";
|
|
26
|
+
EdgeKind["ContributedByPack"] = "contributed-by-pack";
|
|
27
|
+
EdgeKind["ContainsKnowledge"] = "contains-knowledge";
|
|
28
|
+
// ── Framework-aware edges (Wave 7) ───────────────────────────────────
|
|
29
|
+
/** File declares a framework entity (e.g. controller/component/module). */
|
|
30
|
+
EdgeKind["FrameworkDeclares"] = "framework-declares";
|
|
31
|
+
/** Controller / route handler → HTTP route. */
|
|
32
|
+
EdgeKind["HandlesRoute"] = "handles-route";
|
|
33
|
+
/** Component → hook it uses. */
|
|
34
|
+
EdgeKind["UsesHook"] = "uses-hook";
|
|
35
|
+
})(EdgeKind || (EdgeKind = {}));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EdgeKind } from './edge-kind.js';
|
|
2
|
+
/**
|
|
3
|
+
* An edge in the code graph.
|
|
4
|
+
*
|
|
5
|
+
* `id` is deterministic: sha1(from || '|' || to || '|' || kind). Two
|
|
6
|
+
* extractors emitting the same logical edge collapse to one row in the
|
|
7
|
+
* store.
|
|
8
|
+
*
|
|
9
|
+
* `source` records which extractor created the edge, with version. Used
|
|
10
|
+
* to invalidate edges when an extractor's behaviour changes.
|
|
11
|
+
*/
|
|
12
|
+
export interface IEdge {
|
|
13
|
+
id: string;
|
|
14
|
+
from: string;
|
|
15
|
+
to: string;
|
|
16
|
+
kind: EdgeKind;
|
|
17
|
+
/** e.g. 'extract-ts-file@v1'. */
|
|
18
|
+
source: string;
|
|
19
|
+
/** Kind-specific structured payload. */
|
|
20
|
+
data?: Readonly<Record<string, unknown>>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=edge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge.d.ts","sourceRoot":"","sources":["../../src/schema/edge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-file fingerprint used for incremental indexing.
|
|
3
|
+
*
|
|
4
|
+
* MVP uses mtime + sha1(content). Falling back to sha1-only is fine when
|
|
5
|
+
* mtime is unreliable (e.g., docker bind mounts on Linux). The store keeps
|
|
6
|
+
* both so the comparison is cheap.
|
|
7
|
+
*/
|
|
8
|
+
export interface IFileFingerprint {
|
|
9
|
+
/** Project-relative path. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** ms since epoch. */
|
|
12
|
+
mtime: number;
|
|
13
|
+
/** SHA-1 of file contents, hex. */
|
|
14
|
+
sha1: string;
|
|
15
|
+
/** File size in bytes. */
|
|
16
|
+
sizeBytes: number;
|
|
17
|
+
/** Resolved language tag, e.g. 'typescript' | 'javascript'. */
|
|
18
|
+
language: string;
|
|
19
|
+
/** Node id this file owns in the graph. */
|
|
20
|
+
nodeId: string;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=file-fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-fingerprint.d.ts","sourceRoot":"","sources":["../../src/schema/file-fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IEdge } from './edge.js';
|
|
2
|
+
import type { IFileFingerprint } from './file-fingerprint.js';
|
|
3
|
+
import type { IGraphManifest } from './manifest.js';
|
|
4
|
+
import type { INode } from './node.js';
|
|
5
|
+
/**
|
|
6
|
+
* In-memory snapshot of the graph — the unit the query API operates on.
|
|
7
|
+
*
|
|
8
|
+
* `nodes` and `edges` are flat maps keyed by id. The store loads JSONL
|
|
9
|
+
* into this shape; the indexer builds it before writing. Callers should
|
|
10
|
+
* treat it as immutable after construction.
|
|
11
|
+
*/
|
|
12
|
+
export interface IGraphSnapshot {
|
|
13
|
+
manifest: IGraphManifest;
|
|
14
|
+
nodes: ReadonlyMap<string, INode>;
|
|
15
|
+
edges: ReadonlyMap<string, IEdge>;
|
|
16
|
+
files: ReadonlyMap<string, IFileFingerprint>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=graph-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-snapshot.d.ts","sourceRoot":"","sources":["../../src/schema/graph-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAClC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { GraphSchemaVersion } from './schema-version.js';
|
|
2
|
+
/**
|
|
3
|
+
* `.sharkcraft/graph/meta.json` shape.
|
|
4
|
+
*
|
|
5
|
+
* `digest` is SHA-256 over the concatenated JSONL files (alphabetical),
|
|
6
|
+
* computed at write time. A stale digest signals a tampered or partial
|
|
7
|
+
* store and must trigger a rebuild rather than silent use.
|
|
8
|
+
*/
|
|
9
|
+
export interface IGraphManifest {
|
|
10
|
+
schema: GraphSchemaVersion;
|
|
11
|
+
projectRoot: string;
|
|
12
|
+
lastIndexedAt: string;
|
|
13
|
+
/** Wall-clock duration of the last full index, in ms. */
|
|
14
|
+
lastIndexDurationMs: number;
|
|
15
|
+
filesIndexed: number;
|
|
16
|
+
nodesByKind: Readonly<Record<string, number>>;
|
|
17
|
+
edgesByKind: Readonly<Record<string, number>>;
|
|
18
|
+
/** SHA-256 of all JSONL files. */
|
|
19
|
+
digest: string;
|
|
20
|
+
/** Workspace packages discovered at index time. */
|
|
21
|
+
workspacePackages: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* Count of strongly-connected components of size ≥ 2 in the
|
|
24
|
+
* `imports-file` subgraph, computed at index time. Optional for
|
|
25
|
+
* forward-compat with manifests written before this field existed
|
|
26
|
+
* (added 2026-05). Roadmap §3.1 promised cycle-aware queries; this
|
|
27
|
+
* is the persisted counter the doctor + dashboard read from.
|
|
28
|
+
*/
|
|
29
|
+
cycleCount?: number;
|
|
30
|
+
/** Size of the largest SCC of size ≥ 2 (0 when no cycles). */
|
|
31
|
+
largestCycleSize?: number;
|
|
32
|
+
/** Total file nodes participating in any cycle. */
|
|
33
|
+
filesInCycles?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Number of `imports-file` edges that resolved to the
|
|
36
|
+
* `unresolved:<spec>` sentinel (relative / alias / workspace path
|
|
37
|
+
* the resolver could not match against an on-disk file). Optional
|
|
38
|
+
* for forward-compat. The first ten distinct specifiers are kept
|
|
39
|
+
* in `unresolvedImportSamples` for doctor + dashboard.
|
|
40
|
+
*/
|
|
41
|
+
unresolvedImportCount?: number;
|
|
42
|
+
/** Distinct files with at least one unresolved import. */
|
|
43
|
+
filesWithUnresolvedImports?: number;
|
|
44
|
+
/** First N distinct unresolved specifiers (sample for human/agent). */
|
|
45
|
+
unresolvedImportSamples?: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/schema/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,uEAAuE;IACvE,uBAAuB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|