@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,395 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import * as nodePath from 'node:path';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { EdgeKind } from "../schema/edge-kind.js";
|
|
6
|
+
import { NodeKind } from "../schema/node-kind.js";
|
|
7
|
+
import { fingerprintFile } from "../store/file-fingerprint.js";
|
|
8
|
+
import { GraphStore } from "../store/graph-store.js";
|
|
9
|
+
import { summarizeCycles } from "../query/cycle-detection.js";
|
|
10
|
+
import { summarizeUnresolvedImports } from "./unresolved-imports.js";
|
|
11
|
+
import { detectWorkspacePackages, } from "./detect-workspace.js";
|
|
12
|
+
import { EXTRACT_TS_FILE_SOURCE, extractTsFile, stitchPerFileReferences, } from "./extract-ts-file.js";
|
|
13
|
+
import { extractPythonFile } from "./extract-python-file.js";
|
|
14
|
+
import { extractGoFile } from "./extract-go-file.js";
|
|
15
|
+
import { extractJavaFile } from "./extract-java-file.js";
|
|
16
|
+
import { extractRustFile } from "./extract-rust-file.js";
|
|
17
|
+
import { extractKotlinFile } from "./extract-kotlin-file.js";
|
|
18
|
+
import { extractRubyFile } from "./extract-ruby-file.js";
|
|
19
|
+
import { extractCsharpFile } from "./extract-csharp-file.js";
|
|
20
|
+
import { extractElixirFile } from "./extract-elixir-file.js";
|
|
21
|
+
import { extractPhpFile } from "./extract-php-file.js";
|
|
22
|
+
import { extractDartFile } from "./extract-dart-file.js";
|
|
23
|
+
import { extractSwiftFile } from "./extract-swift-file.js";
|
|
24
|
+
import { createImportResolverContext, ImportResolution, resolveImport, } from "./resolve-imports.js";
|
|
25
|
+
const SOURCE_EXTS = new Set([
|
|
26
|
+
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts',
|
|
27
|
+
'.vue', '.svelte', '.astro', '.py', '.go', '.java', '.rs', '.kt', '.kts',
|
|
28
|
+
'.rb', '.cs', '.csx', '.ex', '.exs', '.php', '.dart', '.swift',
|
|
29
|
+
'.graphql', '.gql',
|
|
30
|
+
]);
|
|
31
|
+
/**
|
|
32
|
+
* Apply a delta to the on-disk index.
|
|
33
|
+
*
|
|
34
|
+
* Strategy (MVP): load the snapshot, mutate in memory, rewrite the full
|
|
35
|
+
* store. Cheap for SharkCraft-sized indexes (a few MB on disk). Per-kind
|
|
36
|
+
* append/compact is the optimisation when the cold-rewrite cost is felt.
|
|
37
|
+
*/
|
|
38
|
+
export function updateChanged(options) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
const { projectRoot } = options;
|
|
41
|
+
const store = new GraphStore(projectRoot);
|
|
42
|
+
if (!store.exists()) {
|
|
43
|
+
throw new Error(`code-graph store missing under ${store.storeDir}. Run 'shrk graph index' first.`);
|
|
44
|
+
}
|
|
45
|
+
const snap = store.loadSnapshot();
|
|
46
|
+
const nodes = new Map(snap.nodes);
|
|
47
|
+
const edges = new Map(snap.edges);
|
|
48
|
+
const files = new Map(snap.files);
|
|
49
|
+
const workspaces = detectWorkspacePackages(projectRoot);
|
|
50
|
+
const resolverCtx = createImportResolverContext(projectRoot, workspaces);
|
|
51
|
+
const packageDirIndex = buildPackageDirIndex(workspaces);
|
|
52
|
+
// Make sure the package nodes match the current workspace state. Adds
|
|
53
|
+
// new packages; doesn't drop existing ones (rare to lose a package
|
|
54
|
+
// mid-session).
|
|
55
|
+
for (const p of workspaces) {
|
|
56
|
+
const id = `package:${p.name}`;
|
|
57
|
+
if (!nodes.has(id)) {
|
|
58
|
+
nodes.set(id, {
|
|
59
|
+
id,
|
|
60
|
+
kind: NodeKind.Package,
|
|
61
|
+
label: p.name,
|
|
62
|
+
path: p.dir,
|
|
63
|
+
...(p.entry ? { data: { entry: p.entry } } : {}),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const deleted = [];
|
|
68
|
+
for (const rel of options.deletedFiles ?? []) {
|
|
69
|
+
const fileId = `file:${rel}`;
|
|
70
|
+
if (!nodes.has(fileId))
|
|
71
|
+
continue;
|
|
72
|
+
removeFileFromGraph(rel, nodes, edges, files);
|
|
73
|
+
deleted.push(rel);
|
|
74
|
+
}
|
|
75
|
+
const updated = [];
|
|
76
|
+
const skipped = [];
|
|
77
|
+
// For each file we actually re-extract, retain extracted + resolved
|
|
78
|
+
// spec so the stitcher pass can produce reference / call edges.
|
|
79
|
+
const reExtracted = new Map();
|
|
80
|
+
for (const rel of options.changedFiles ?? []) {
|
|
81
|
+
const abs = nodePath.resolve(projectRoot, rel);
|
|
82
|
+
if (!existsSync(abs) || !isFile(abs)) {
|
|
83
|
+
// Treat as deletion if the file no longer exists.
|
|
84
|
+
if (nodes.has(`file:${rel}`)) {
|
|
85
|
+
removeFileFromGraph(rel, nodes, edges, files);
|
|
86
|
+
deleted.push(rel);
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!SOURCE_EXTS.has(nodePath.extname(rel).toLowerCase()))
|
|
91
|
+
continue;
|
|
92
|
+
const newFp = fingerprintFile(abs, projectRoot);
|
|
93
|
+
const oldFp = files.get(newFp.path);
|
|
94
|
+
if (oldFp && oldFp.sha1 === newFp.sha1 && oldFp.mtime === newFp.mtime) {
|
|
95
|
+
skipped.push(newFp.path);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// Remove the file's previous contribution.
|
|
99
|
+
if (oldFp)
|
|
100
|
+
removeFileFromGraph(newFp.path, nodes, edges, files);
|
|
101
|
+
// Re-extract.
|
|
102
|
+
files.set(newFp.path, newFp);
|
|
103
|
+
const extracted = newFp.language === 'python' ? extractPythonFile(newFp, abs)
|
|
104
|
+
: newFp.language === 'go' ? extractGoFile(newFp, abs)
|
|
105
|
+
: newFp.language === 'java' ? extractJavaFile(newFp, abs)
|
|
106
|
+
: newFp.language === 'rust' ? extractRustFile(newFp, abs)
|
|
107
|
+
: newFp.language === 'kotlin' ? extractKotlinFile(newFp, abs)
|
|
108
|
+
: newFp.language === 'ruby' ? extractRubyFile(newFp, abs)
|
|
109
|
+
: newFp.language === 'csharp' ? extractCsharpFile(newFp, abs)
|
|
110
|
+
: newFp.language === 'elixir' ? extractElixirFile(newFp, abs)
|
|
111
|
+
: newFp.language === 'php' ? extractPhpFile(newFp, abs)
|
|
112
|
+
: newFp.language === 'dart' ? extractDartFile(newFp, abs)
|
|
113
|
+
: newFp.language === 'swift' ? extractSwiftFile(newFp, abs)
|
|
114
|
+
: extractTsFile(newFp, abs);
|
|
115
|
+
nodes.set(extracted.fileNode.id, extracted.fileNode);
|
|
116
|
+
for (const sym of extracted.symbolNodes)
|
|
117
|
+
nodes.set(sym.id, sym);
|
|
118
|
+
for (const e of extracted.edges)
|
|
119
|
+
edges.set(e.id, e);
|
|
120
|
+
const pkg = findOwningPackage(newFp.path, packageDirIndex);
|
|
121
|
+
if (pkg) {
|
|
122
|
+
const e = buildEdge(newFp.nodeId, `package:${pkg.name}`, EdgeKind.BelongsToPackage, EXTRACT_TS_FILE_SOURCE);
|
|
123
|
+
edges.set(e.id, e);
|
|
124
|
+
}
|
|
125
|
+
const resolvedSpec = new Map();
|
|
126
|
+
for (const raw of extracted.rawImportSpecifiers) {
|
|
127
|
+
const r = resolveImport(raw.specifier, abs, resolverCtx);
|
|
128
|
+
resolvedSpec.set(raw.specifier, r.targetPath);
|
|
129
|
+
const data = {
|
|
130
|
+
specifier: r.specifier,
|
|
131
|
+
line: raw.line,
|
|
132
|
+
importKind: raw.kind,
|
|
133
|
+
resolutionKind: r.kind,
|
|
134
|
+
};
|
|
135
|
+
const targetId = r.targetPath
|
|
136
|
+
? `file:${r.targetPath}`
|
|
137
|
+
: r.kind === ImportResolution.External
|
|
138
|
+
? `external:${r.specifier}`
|
|
139
|
+
: `unresolved:${r.specifier}`;
|
|
140
|
+
const e = buildEdge(newFp.nodeId, targetId, EdgeKind.ImportsFile, EXTRACT_TS_FILE_SOURCE, data);
|
|
141
|
+
edges.set(e.id, e);
|
|
142
|
+
}
|
|
143
|
+
reExtracted.set(newFp.path, { extracted, resolvedSpec });
|
|
144
|
+
updated.push(newFp.path);
|
|
145
|
+
}
|
|
146
|
+
// Stitch references / calls for re-extracted files. Build the default
|
|
147
|
+
// export name map from the current node table (covers both
|
|
148
|
+
// re-extracted and untouched files).
|
|
149
|
+
if (reExtracted.size > 0) {
|
|
150
|
+
const defaultExportNameByPath = new Map();
|
|
151
|
+
for (const n of nodes.values()) {
|
|
152
|
+
if (n.kind !== NodeKind.File || !n.path)
|
|
153
|
+
continue;
|
|
154
|
+
defaultExportNameByPath.set(n.path, n.data?.['defaultExportName'] ?? undefined);
|
|
155
|
+
}
|
|
156
|
+
for (const [path, { extracted, resolvedSpec }] of reExtracted) {
|
|
157
|
+
const localNames = new Map();
|
|
158
|
+
for (const sym of extracted.symbolNodes)
|
|
159
|
+
localNames.set(sym.label, sym.id);
|
|
160
|
+
const refEdges = stitchPerFileReferences({
|
|
161
|
+
fileNodeId: extracted.fileNode.id,
|
|
162
|
+
extracted,
|
|
163
|
+
resolvedSpec,
|
|
164
|
+
defaultExportNameByPath,
|
|
165
|
+
localSymbolNamesInThisFile: localNames,
|
|
166
|
+
});
|
|
167
|
+
for (const e of refEdges)
|
|
168
|
+
edges.set(e.id, e);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Rebuild package-depends-on aggregates from the (now-updated)
|
|
172
|
+
// imports-file edges. Drop the old ones first.
|
|
173
|
+
for (const id of [...edges.keys()]) {
|
|
174
|
+
const e = edges.get(id);
|
|
175
|
+
if (e.kind === EdgeKind.PackageDependsOn)
|
|
176
|
+
edges.delete(id);
|
|
177
|
+
}
|
|
178
|
+
for (const e of collectPackageDependsOn([...edges.values()], packageDirIndex)) {
|
|
179
|
+
edges.set(e.id, e);
|
|
180
|
+
}
|
|
181
|
+
const nodeList = [...nodes.values()];
|
|
182
|
+
const edgeList = [...edges.values()];
|
|
183
|
+
const cycles = summarizeCycles(nodeList, edgeList);
|
|
184
|
+
const unresolved = summarizeUnresolvedImports(edgeList);
|
|
185
|
+
const manifest = store.writeSnapshot(nodeList, edgeList, [...files.values()], {
|
|
186
|
+
projectRoot,
|
|
187
|
+
lastIndexedAt: new Date().toISOString(),
|
|
188
|
+
lastIndexDurationMs: Date.now() - start,
|
|
189
|
+
filesIndexed: files.size,
|
|
190
|
+
workspacePackages: workspaces.map((w) => w.name),
|
|
191
|
+
nodesByKind: {},
|
|
192
|
+
edgesByKind: {},
|
|
193
|
+
cycleCount: cycles.cycleCount,
|
|
194
|
+
largestCycleSize: cycles.largestCycleSize,
|
|
195
|
+
filesInCycles: cycles.filesInCycles,
|
|
196
|
+
unresolvedImportCount: unresolved.unresolvedImportCount,
|
|
197
|
+
filesWithUnresolvedImports: unresolved.filesWithUnresolvedImports,
|
|
198
|
+
unresolvedImportSamples: unresolved.unresolvedImportSamples,
|
|
199
|
+
});
|
|
200
|
+
return {
|
|
201
|
+
manifest,
|
|
202
|
+
durationMs: Date.now() - start,
|
|
203
|
+
updated,
|
|
204
|
+
deleted,
|
|
205
|
+
skipped,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Helper: detect changed files by walking the project and comparing
|
|
210
|
+
* fingerprints against the stored snapshot. Used by
|
|
211
|
+
* `shrk graph index --changed` when no explicit file list is provided.
|
|
212
|
+
*/
|
|
213
|
+
export function detectChangedAndDeleted(projectRoot) {
|
|
214
|
+
const store = new GraphStore(projectRoot);
|
|
215
|
+
if (!store.exists()) {
|
|
216
|
+
return { changed: [], deleted: [] };
|
|
217
|
+
}
|
|
218
|
+
const snap = store.loadSnapshot();
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
const changed = [];
|
|
221
|
+
const fsStack = [projectRoot];
|
|
222
|
+
const skip = new Set([
|
|
223
|
+
'node_modules',
|
|
224
|
+
'dist',
|
|
225
|
+
'build',
|
|
226
|
+
'coverage',
|
|
227
|
+
'.git',
|
|
228
|
+
'.sharkcraft',
|
|
229
|
+
'.next',
|
|
230
|
+
'.cache',
|
|
231
|
+
'.tmp-pack',
|
|
232
|
+
'out',
|
|
233
|
+
'target',
|
|
234
|
+
]);
|
|
235
|
+
while (fsStack.length > 0) {
|
|
236
|
+
const dir = fsStack.pop();
|
|
237
|
+
let entries;
|
|
238
|
+
try {
|
|
239
|
+
entries = readdirSync(dir);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
for (const name of entries) {
|
|
245
|
+
if (skip.has(name))
|
|
246
|
+
continue;
|
|
247
|
+
if (name.startsWith('.') && name !== '.')
|
|
248
|
+
continue;
|
|
249
|
+
const full = nodePath.join(dir, name);
|
|
250
|
+
let st;
|
|
251
|
+
try {
|
|
252
|
+
st = statSync(full);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (st.isDirectory()) {
|
|
258
|
+
fsStack.push(full);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!st.isFile())
|
|
262
|
+
continue;
|
|
263
|
+
if (!SOURCE_EXTS.has(nodePath.extname(full).toLowerCase()))
|
|
264
|
+
continue;
|
|
265
|
+
const rel = nodePath
|
|
266
|
+
.relative(projectRoot, full)
|
|
267
|
+
.split(nodePath.sep)
|
|
268
|
+
.join('/');
|
|
269
|
+
seen.add(rel);
|
|
270
|
+
const oldFp = snap.files.get(rel);
|
|
271
|
+
if (!oldFp) {
|
|
272
|
+
changed.push(rel);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
// Cheap check: mtime first. If equal, trust it (assumes mtime
|
|
276
|
+
// bumps on writes — true on local FS, sometimes wrong inside
|
|
277
|
+
// bind mounts). Otherwise recompute the sha and compare.
|
|
278
|
+
if (Math.floor(st.mtimeMs) === oldFp.mtime && st.size === oldFp.sizeBytes)
|
|
279
|
+
continue;
|
|
280
|
+
const newFp = fingerprintFile(full, projectRoot);
|
|
281
|
+
if (newFp.sha1 !== oldFp.sha1)
|
|
282
|
+
changed.push(rel);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const deleted = [];
|
|
286
|
+
for (const path of snap.files.keys()) {
|
|
287
|
+
if (!seen.has(path))
|
|
288
|
+
deleted.push(path);
|
|
289
|
+
}
|
|
290
|
+
return { changed, deleted };
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get the list of files changed since a git ref (e.g. `main`, `HEAD~5`,
|
|
294
|
+
* a tag). Returns project-relative POSIX paths. Errors → empty list.
|
|
295
|
+
*/
|
|
296
|
+
export function changedFilesSince(projectRoot, ref) {
|
|
297
|
+
try {
|
|
298
|
+
const raw = execSync(`git diff --name-only ${ref}`, {
|
|
299
|
+
cwd: projectRoot,
|
|
300
|
+
encoding: 'utf8',
|
|
301
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
302
|
+
});
|
|
303
|
+
return raw
|
|
304
|
+
.split('\n')
|
|
305
|
+
.map((s) => s.trim())
|
|
306
|
+
.filter((s) => s.length > 0);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return [];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// ── helpers ───────────────────────────────────────────────────────────
|
|
313
|
+
function removeFileFromGraph(rel, nodes, edges, files) {
|
|
314
|
+
const fileId = `file:${rel}`;
|
|
315
|
+
const symbolPrefix = `symbol:${rel}#`;
|
|
316
|
+
files.delete(rel);
|
|
317
|
+
// Drop the file node + all symbol nodes declared in this file.
|
|
318
|
+
for (const id of [...nodes.keys()]) {
|
|
319
|
+
if (id === fileId || id.startsWith(symbolPrefix)) {
|
|
320
|
+
nodes.delete(id);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Drop every edge that touches this file or any of its symbols.
|
|
324
|
+
for (const id of [...edges.keys()]) {
|
|
325
|
+
const e = edges.get(id);
|
|
326
|
+
if (e.from === fileId ||
|
|
327
|
+
e.to === fileId ||
|
|
328
|
+
e.from.startsWith(symbolPrefix) ||
|
|
329
|
+
e.to.startsWith(symbolPrefix)) {
|
|
330
|
+
edges.delete(id);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function isFile(p) {
|
|
335
|
+
try {
|
|
336
|
+
return statSync(p).isFile();
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function buildPackageDirIndex(packages) {
|
|
343
|
+
const entries = [...packages]
|
|
344
|
+
.map((p) => ({ dir: p.dir.replace(/\/+$/, ''), name: p.name }))
|
|
345
|
+
.sort((a, b) => b.dir.length - a.dir.length);
|
|
346
|
+
return { entries };
|
|
347
|
+
}
|
|
348
|
+
function findOwningPackage(filePath, index) {
|
|
349
|
+
for (const e of index.entries) {
|
|
350
|
+
if (filePath === e.dir || filePath.startsWith(e.dir + '/'))
|
|
351
|
+
return e;
|
|
352
|
+
}
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
function collectPackageDependsOn(allEdges, index) {
|
|
356
|
+
const pairs = new Map();
|
|
357
|
+
for (const e of allEdges) {
|
|
358
|
+
if (e.kind !== EdgeKind.ImportsFile)
|
|
359
|
+
continue;
|
|
360
|
+
const fromFile = stripPrefix(e.from, 'file:');
|
|
361
|
+
const toFile = stripPrefix(e.to, 'file:');
|
|
362
|
+
if (!fromFile || !toFile)
|
|
363
|
+
continue;
|
|
364
|
+
const fromPkg = findOwningPackage(fromFile, index);
|
|
365
|
+
const toPkg = findOwningPackage(toFile, index);
|
|
366
|
+
if (!fromPkg || !toPkg)
|
|
367
|
+
continue;
|
|
368
|
+
if (fromPkg.name === toPkg.name)
|
|
369
|
+
continue;
|
|
370
|
+
const k = `${fromPkg.name}|${toPkg.name}`;
|
|
371
|
+
const cur = pairs.get(k);
|
|
372
|
+
if (cur)
|
|
373
|
+
cur.count += 1;
|
|
374
|
+
else
|
|
375
|
+
pairs.set(k, { from: fromPkg.name, to: toPkg.name, count: 1 });
|
|
376
|
+
}
|
|
377
|
+
const out = [];
|
|
378
|
+
for (const { from, to, count } of pairs.values()) {
|
|
379
|
+
out.push(buildEdge(`package:${from}`, `package:${to}`, EdgeKind.PackageDependsOn, 'incremental-updater@v1', { count }));
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
function buildEdge(from, to, kind, source, data) {
|
|
384
|
+
return {
|
|
385
|
+
id: createHash('sha1').update(`${from}|${to}|${kind}`).digest('hex'),
|
|
386
|
+
from,
|
|
387
|
+
to,
|
|
388
|
+
kind,
|
|
389
|
+
source,
|
|
390
|
+
...(data ? { data } : {}),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function stripPrefix(id, prefix) {
|
|
394
|
+
return id.startsWith(prefix) ? id.slice(prefix.length) : undefined;
|
|
395
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IGraphManifest } from '../schema/manifest.js';
|
|
2
|
+
export interface IIndexBuilderOptions {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
/** Override the ignore set. Adds to defaults; does not replace them. */
|
|
5
|
+
extraIgnore?: readonly string[];
|
|
6
|
+
/** Cap the number of files indexed. Useful for tests; 0 = unlimited. */
|
|
7
|
+
maxFiles?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IFullIndexResult {
|
|
10
|
+
manifest: IGraphManifest;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build a full graph index and write it to disk. Overwrites any
|
|
15
|
+
* pre-existing store under `<root>/.sharkcraft/graph/`.
|
|
16
|
+
*
|
|
17
|
+
* Single-process, no worker pool yet. The compiler-API per-file extractor
|
|
18
|
+
* is fast enough at SharkCraft's size; worker-pool parallelism is a
|
|
19
|
+
* later optimisation tied to measured budgets (see code-intelligence.md
|
|
20
|
+
* §7).
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildFullIndex(options: IIndexBuilderOptions): IFullIndexResult;
|
|
23
|
+
//# sourceMappingURL=index-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-builder.d.ts","sourceRoot":"","sources":["../../src/indexer/index-builder.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AA6D5D,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,cAAc,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,gBAAgB,CA0I9E"}
|