@harness-engineering/graph 0.3.3 → 0.3.5
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.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.js +110 -62
- package/dist/index.mjs +110 -62
- package/package.json +10 -11
package/dist/index.d.mts
CHANGED
|
@@ -163,10 +163,11 @@ interface EdgeQuery {
|
|
|
163
163
|
readonly type?: EdgeType;
|
|
164
164
|
}
|
|
165
165
|
declare class GraphStore {
|
|
166
|
-
private
|
|
167
|
-
private
|
|
168
|
-
private
|
|
169
|
-
|
|
166
|
+
private nodeMap;
|
|
167
|
+
private edgeMap;
|
|
168
|
+
private edgesByFrom;
|
|
169
|
+
private edgesByTo;
|
|
170
|
+
private edgesByType;
|
|
170
171
|
addNode(node: GraphNode): void;
|
|
171
172
|
batchAddNodes(nodes: readonly GraphNode[]): void;
|
|
172
173
|
getNode(id: string): GraphNode | null;
|
|
@@ -181,7 +182,7 @@ declare class GraphStore {
|
|
|
181
182
|
clear(): void;
|
|
182
183
|
save(dirPath: string): Promise<void>;
|
|
183
184
|
load(dirPath: string): Promise<boolean>;
|
|
184
|
-
private
|
|
185
|
+
private removeEdgeInternal;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -163,10 +163,11 @@ interface EdgeQuery {
|
|
|
163
163
|
readonly type?: EdgeType;
|
|
164
164
|
}
|
|
165
165
|
declare class GraphStore {
|
|
166
|
-
private
|
|
167
|
-
private
|
|
168
|
-
private
|
|
169
|
-
|
|
166
|
+
private nodeMap;
|
|
167
|
+
private edgeMap;
|
|
168
|
+
private edgesByFrom;
|
|
169
|
+
private edgesByTo;
|
|
170
|
+
private edgesByType;
|
|
170
171
|
addNode(node: GraphNode): void;
|
|
171
172
|
batchAddNodes(nodes: readonly GraphNode[]): void;
|
|
172
173
|
getNode(id: string): GraphNode | null;
|
|
@@ -181,7 +182,7 @@ declare class GraphStore {
|
|
|
181
182
|
clear(): void;
|
|
182
183
|
save(dirPath: string): Promise<void>;
|
|
183
184
|
load(dirPath: string): Promise<boolean>;
|
|
184
|
-
private
|
|
185
|
+
private removeEdgeInternal;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/**
|
package/dist/index.js
CHANGED
|
@@ -171,9 +171,6 @@ var GraphEdgeSchema = import_zod.z.object({
|
|
|
171
171
|
metadata: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
// src/store/GraphStore.ts
|
|
175
|
-
var import_lokijs = __toESM(require("lokijs"));
|
|
176
|
-
|
|
177
174
|
// src/store/Serializer.ts
|
|
178
175
|
var import_promises = require("fs/promises");
|
|
179
176
|
var import_node_path = require("path");
|
|
@@ -218,28 +215,38 @@ function safeMerge(target, source) {
|
|
|
218
215
|
}
|
|
219
216
|
}
|
|
220
217
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
231
|
-
this.edges = this.db.addCollection("edges", {
|
|
232
|
-
indices: ["from", "to", "type"]
|
|
233
|
-
});
|
|
218
|
+
function edgeKey(from, to, type) {
|
|
219
|
+
return `${from}\0${to}\0${type}`;
|
|
220
|
+
}
|
|
221
|
+
function addToIndex(index, key, edge) {
|
|
222
|
+
const list = index.get(key);
|
|
223
|
+
if (list) {
|
|
224
|
+
list.push(edge);
|
|
225
|
+
} else {
|
|
226
|
+
index.set(key, [edge]);
|
|
234
227
|
}
|
|
228
|
+
}
|
|
229
|
+
function removeFromIndex(index, key, edge) {
|
|
230
|
+
const list = index.get(key);
|
|
231
|
+
if (!list) return;
|
|
232
|
+
const idx = list.indexOf(edge);
|
|
233
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
234
|
+
if (list.length === 0) index.delete(key);
|
|
235
|
+
}
|
|
236
|
+
var GraphStore = class {
|
|
237
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
238
|
+
edgeMap = /* @__PURE__ */ new Map();
|
|
239
|
+
// keyed by from\0to\0type
|
|
240
|
+
edgesByFrom = /* @__PURE__ */ new Map();
|
|
241
|
+
edgesByTo = /* @__PURE__ */ new Map();
|
|
242
|
+
edgesByType = /* @__PURE__ */ new Map();
|
|
235
243
|
// --- Node operations ---
|
|
236
244
|
addNode(node) {
|
|
237
|
-
const existing = this.
|
|
245
|
+
const existing = this.nodeMap.get(node.id);
|
|
238
246
|
if (existing) {
|
|
239
247
|
safeMerge(existing, node);
|
|
240
|
-
this.nodes.update(existing);
|
|
241
248
|
} else {
|
|
242
|
-
this.
|
|
249
|
+
this.nodeMap.set(node.id, { ...node });
|
|
243
250
|
}
|
|
244
251
|
}
|
|
245
252
|
batchAddNodes(nodes) {
|
|
@@ -248,44 +255,44 @@ var GraphStore = class {
|
|
|
248
255
|
}
|
|
249
256
|
}
|
|
250
257
|
getNode(id) {
|
|
251
|
-
const
|
|
252
|
-
if (!
|
|
253
|
-
return
|
|
258
|
+
const node = this.nodeMap.get(id);
|
|
259
|
+
if (!node) return null;
|
|
260
|
+
return { ...node };
|
|
254
261
|
}
|
|
255
262
|
findNodes(query) {
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
263
|
+
const results = [];
|
|
264
|
+
for (const node of this.nodeMap.values()) {
|
|
265
|
+
if (query.type !== void 0 && node.type !== query.type) continue;
|
|
266
|
+
if (query.name !== void 0 && node.name !== query.name) continue;
|
|
267
|
+
if (query.path !== void 0 && node.path !== query.path) continue;
|
|
268
|
+
results.push({ ...node });
|
|
269
|
+
}
|
|
270
|
+
return results;
|
|
261
271
|
}
|
|
262
272
|
removeNode(id) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const edgesToRemove = this.edges.find({
|
|
268
|
-
$or: [{ from: id }, { to: id }]
|
|
269
|
-
});
|
|
273
|
+
this.nodeMap.delete(id);
|
|
274
|
+
const fromEdges = this.edgesByFrom.get(id) ?? [];
|
|
275
|
+
const toEdges = this.edgesByTo.get(id) ?? [];
|
|
276
|
+
const edgesToRemove = /* @__PURE__ */ new Set([...fromEdges, ...toEdges]);
|
|
270
277
|
for (const edge of edgesToRemove) {
|
|
271
|
-
this.
|
|
278
|
+
this.removeEdgeInternal(edge);
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
// --- Edge operations ---
|
|
275
282
|
addEdge(edge) {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
to: edge.to,
|
|
279
|
-
type: edge.type
|
|
280
|
-
});
|
|
283
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
284
|
+
const existing = this.edgeMap.get(key);
|
|
281
285
|
if (existing) {
|
|
282
286
|
if (edge.metadata) {
|
|
283
287
|
safeMerge(existing, edge);
|
|
284
|
-
this.edges.update(existing);
|
|
285
288
|
}
|
|
286
289
|
return;
|
|
287
290
|
}
|
|
288
|
-
|
|
291
|
+
const copy = { ...edge };
|
|
292
|
+
this.edgeMap.set(key, copy);
|
|
293
|
+
addToIndex(this.edgesByFrom, edge.from, copy);
|
|
294
|
+
addToIndex(this.edgesByTo, edge.to, copy);
|
|
295
|
+
addToIndex(this.edgesByType, edge.type, copy);
|
|
289
296
|
}
|
|
290
297
|
batchAddEdges(edges) {
|
|
291
298
|
for (const edge of edges) {
|
|
@@ -293,22 +300,38 @@ var GraphStore = class {
|
|
|
293
300
|
}
|
|
294
301
|
}
|
|
295
302
|
getEdges(query) {
|
|
296
|
-
|
|
297
|
-
if (query.from !== void 0
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
let candidates;
|
|
304
|
+
if (query.from !== void 0 && query.to !== void 0 && query.type !== void 0) {
|
|
305
|
+
const edge = this.edgeMap.get(edgeKey(query.from, query.to, query.type));
|
|
306
|
+
return edge ? [{ ...edge }] : [];
|
|
307
|
+
} else if (query.from !== void 0) {
|
|
308
|
+
candidates = this.edgesByFrom.get(query.from) ?? [];
|
|
309
|
+
} else if (query.to !== void 0) {
|
|
310
|
+
candidates = this.edgesByTo.get(query.to) ?? [];
|
|
311
|
+
} else if (query.type !== void 0) {
|
|
312
|
+
candidates = this.edgesByType.get(query.type) ?? [];
|
|
313
|
+
} else {
|
|
314
|
+
candidates = this.edgeMap.values();
|
|
315
|
+
}
|
|
316
|
+
const results = [];
|
|
317
|
+
for (const edge of candidates) {
|
|
318
|
+
if (query.from !== void 0 && edge.from !== query.from) continue;
|
|
319
|
+
if (query.to !== void 0 && edge.to !== query.to) continue;
|
|
320
|
+
if (query.type !== void 0 && edge.type !== query.type) continue;
|
|
321
|
+
results.push({ ...edge });
|
|
322
|
+
}
|
|
323
|
+
return results;
|
|
301
324
|
}
|
|
302
325
|
getNeighbors(nodeId, direction = "both") {
|
|
303
326
|
const neighborIds = /* @__PURE__ */ new Set();
|
|
304
327
|
if (direction === "outbound" || direction === "both") {
|
|
305
|
-
const outEdges = this.
|
|
328
|
+
const outEdges = this.edgesByFrom.get(nodeId) ?? [];
|
|
306
329
|
for (const edge of outEdges) {
|
|
307
330
|
neighborIds.add(edge.to);
|
|
308
331
|
}
|
|
309
332
|
}
|
|
310
333
|
if (direction === "inbound" || direction === "both") {
|
|
311
|
-
const inEdges = this.
|
|
334
|
+
const inEdges = this.edgesByTo.get(nodeId) ?? [];
|
|
312
335
|
for (const edge of inEdges) {
|
|
313
336
|
neighborIds.add(edge.from);
|
|
314
337
|
}
|
|
@@ -322,20 +345,23 @@ var GraphStore = class {
|
|
|
322
345
|
}
|
|
323
346
|
// --- Counts ---
|
|
324
347
|
get nodeCount() {
|
|
325
|
-
return this.
|
|
348
|
+
return this.nodeMap.size;
|
|
326
349
|
}
|
|
327
350
|
get edgeCount() {
|
|
328
|
-
return this.
|
|
351
|
+
return this.edgeMap.size;
|
|
329
352
|
}
|
|
330
353
|
// --- Clear ---
|
|
331
354
|
clear() {
|
|
332
|
-
this.
|
|
333
|
-
this.
|
|
355
|
+
this.nodeMap.clear();
|
|
356
|
+
this.edgeMap.clear();
|
|
357
|
+
this.edgesByFrom.clear();
|
|
358
|
+
this.edgesByTo.clear();
|
|
359
|
+
this.edgesByType.clear();
|
|
334
360
|
}
|
|
335
361
|
// --- Persistence ---
|
|
336
362
|
async save(dirPath) {
|
|
337
|
-
const allNodes = this.
|
|
338
|
-
const allEdges = this.
|
|
363
|
+
const allNodes = Array.from(this.nodeMap.values()).map((n) => ({ ...n }));
|
|
364
|
+
const allEdges = Array.from(this.edgeMap.values()).map((e) => ({ ...e }));
|
|
339
365
|
await saveGraph(dirPath, allNodes, allEdges);
|
|
340
366
|
}
|
|
341
367
|
async load(dirPath) {
|
|
@@ -343,17 +369,25 @@ var GraphStore = class {
|
|
|
343
369
|
if (!data) return false;
|
|
344
370
|
this.clear();
|
|
345
371
|
for (const node of data.nodes) {
|
|
346
|
-
this.
|
|
372
|
+
this.nodeMap.set(node.id, { ...node });
|
|
347
373
|
}
|
|
348
374
|
for (const edge of data.edges) {
|
|
349
|
-
|
|
375
|
+
const copy = { ...edge };
|
|
376
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
377
|
+
this.edgeMap.set(key, copy);
|
|
378
|
+
addToIndex(this.edgesByFrom, edge.from, copy);
|
|
379
|
+
addToIndex(this.edgesByTo, edge.to, copy);
|
|
380
|
+
addToIndex(this.edgesByType, edge.type, copy);
|
|
350
381
|
}
|
|
351
382
|
return true;
|
|
352
383
|
}
|
|
353
384
|
// --- Internal ---
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
385
|
+
removeEdgeInternal(edge) {
|
|
386
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
387
|
+
this.edgeMap.delete(key);
|
|
388
|
+
removeFromIndex(this.edgesByFrom, edge.from, edge);
|
|
389
|
+
removeFromIndex(this.edgesByTo, edge.to, edge);
|
|
390
|
+
removeFromIndex(this.edgesByType, edge.type, edge);
|
|
357
391
|
}
|
|
358
392
|
};
|
|
359
393
|
|
|
@@ -434,11 +468,11 @@ var VectorStore = class _VectorStore {
|
|
|
434
468
|
};
|
|
435
469
|
|
|
436
470
|
// src/query/ContextQL.ts
|
|
437
|
-
function
|
|
471
|
+
function edgeKey2(e) {
|
|
438
472
|
return `${e.from}|${e.to}|${e.type}`;
|
|
439
473
|
}
|
|
440
474
|
function addEdge(state, edge) {
|
|
441
|
-
const key =
|
|
475
|
+
const key = edgeKey2(edge);
|
|
442
476
|
if (!state.edgeSet.has(key)) {
|
|
443
477
|
state.edgeSet.add(key);
|
|
444
478
|
state.resultEdges.push(edge);
|
|
@@ -618,6 +652,7 @@ var CodeIngestor = class {
|
|
|
618
652
|
constructor(store) {
|
|
619
653
|
this.store = store;
|
|
620
654
|
}
|
|
655
|
+
store;
|
|
621
656
|
async ingest(rootDir) {
|
|
622
657
|
const start = Date.now();
|
|
623
658
|
const errors = [];
|
|
@@ -1009,6 +1044,8 @@ var GitIngestor = class {
|
|
|
1009
1044
|
this.store = store;
|
|
1010
1045
|
this.gitRunner = gitRunner;
|
|
1011
1046
|
}
|
|
1047
|
+
store;
|
|
1048
|
+
gitRunner;
|
|
1012
1049
|
async ingest(rootDir) {
|
|
1013
1050
|
const start = Date.now();
|
|
1014
1051
|
const errors = [];
|
|
@@ -1188,6 +1225,7 @@ var TopologicalLinker = class {
|
|
|
1188
1225
|
constructor(store) {
|
|
1189
1226
|
this.store = store;
|
|
1190
1227
|
}
|
|
1228
|
+
store;
|
|
1191
1229
|
link() {
|
|
1192
1230
|
let edgesAdded = 0;
|
|
1193
1231
|
const files = this.store.findNodes({ type: "file" });
|
|
@@ -1285,6 +1323,7 @@ var KnowledgeIngestor = class {
|
|
|
1285
1323
|
constructor(store) {
|
|
1286
1324
|
this.store = store;
|
|
1287
1325
|
}
|
|
1326
|
+
store;
|
|
1288
1327
|
async ingestADRs(adrDir) {
|
|
1289
1328
|
const start = Date.now();
|
|
1290
1329
|
const errors = [];
|
|
@@ -1533,6 +1572,7 @@ var SyncManager = class {
|
|
|
1533
1572
|
this.store = store;
|
|
1534
1573
|
this.metadataPath = path4.join(graphDir, "sync-metadata.json");
|
|
1535
1574
|
}
|
|
1575
|
+
store;
|
|
1536
1576
|
registrations = /* @__PURE__ */ new Map();
|
|
1537
1577
|
metadataPath;
|
|
1538
1578
|
registerConnector(connector, config) {
|
|
@@ -2127,6 +2167,7 @@ var GraphEntropyAdapter = class {
|
|
|
2127
2167
|
constructor(store) {
|
|
2128
2168
|
this.store = store;
|
|
2129
2169
|
}
|
|
2170
|
+
store;
|
|
2130
2171
|
/**
|
|
2131
2172
|
* Find all `documents` edges and classify them as stale or missing-target.
|
|
2132
2173
|
*
|
|
@@ -2267,6 +2308,7 @@ var GraphComplexityAdapter = class {
|
|
|
2267
2308
|
constructor(store) {
|
|
2268
2309
|
this.store = store;
|
|
2269
2310
|
}
|
|
2311
|
+
store;
|
|
2270
2312
|
/**
|
|
2271
2313
|
* Compute complexity hotspots by combining cyclomatic complexity with change frequency.
|
|
2272
2314
|
*
|
|
@@ -2354,6 +2396,7 @@ var GraphCouplingAdapter = class {
|
|
|
2354
2396
|
constructor(store) {
|
|
2355
2397
|
this.store = store;
|
|
2356
2398
|
}
|
|
2399
|
+
store;
|
|
2357
2400
|
/**
|
|
2358
2401
|
* Compute coupling data for all file nodes in the graph.
|
|
2359
2402
|
*
|
|
@@ -2423,6 +2466,7 @@ var GraphAnomalyAdapter = class {
|
|
|
2423
2466
|
constructor(store) {
|
|
2424
2467
|
this.store = store;
|
|
2425
2468
|
}
|
|
2469
|
+
store;
|
|
2426
2470
|
detect(options) {
|
|
2427
2471
|
const threshold = options?.threshold != null && options.threshold > 0 ? options.threshold : DEFAULT_THRESHOLD;
|
|
2428
2472
|
const requestedMetrics = options?.metrics ?? [...DEFAULT_METRICS];
|
|
@@ -3583,6 +3627,7 @@ var GraphConstraintAdapter = class {
|
|
|
3583
3627
|
constructor(store) {
|
|
3584
3628
|
this.store = store;
|
|
3585
3629
|
}
|
|
3630
|
+
store;
|
|
3586
3631
|
computeDependencyGraph() {
|
|
3587
3632
|
const fileNodes = this.store.findNodes({ type: "file" });
|
|
3588
3633
|
const nodes = fileNodes.map((n) => n.path ?? n.id);
|
|
@@ -3727,6 +3772,7 @@ var DesignIngestor = class {
|
|
|
3727
3772
|
constructor(store) {
|
|
3728
3773
|
this.store = store;
|
|
3729
3774
|
}
|
|
3775
|
+
store;
|
|
3730
3776
|
async ingestTokens(tokensPath) {
|
|
3731
3777
|
const start = Date.now();
|
|
3732
3778
|
const content = await readFileOrNull(tokensPath);
|
|
@@ -3799,6 +3845,7 @@ var DesignConstraintAdapter = class {
|
|
|
3799
3845
|
constructor(store) {
|
|
3800
3846
|
this.store = store;
|
|
3801
3847
|
}
|
|
3848
|
+
store;
|
|
3802
3849
|
checkForHardcodedColors(source, file, strictness) {
|
|
3803
3850
|
const severity = this.mapSeverity(strictness);
|
|
3804
3851
|
const tokenNodes = this.store.findNodes({ type: "design_token" });
|
|
@@ -3882,6 +3929,7 @@ var GraphFeedbackAdapter = class {
|
|
|
3882
3929
|
constructor(store) {
|
|
3883
3930
|
this.store = store;
|
|
3884
3931
|
}
|
|
3932
|
+
store;
|
|
3885
3933
|
computeImpactData(changedFiles) {
|
|
3886
3934
|
const affectedTests = [];
|
|
3887
3935
|
const affectedDocs = [];
|
package/dist/index.mjs
CHANGED
|
@@ -94,9 +94,6 @@ var GraphEdgeSchema = z.object({
|
|
|
94
94
|
metadata: z.record(z.unknown()).optional()
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
// src/store/GraphStore.ts
|
|
98
|
-
import loki from "lokijs";
|
|
99
|
-
|
|
100
97
|
// src/store/Serializer.ts
|
|
101
98
|
import { readFile, writeFile, mkdir, access } from "fs/promises";
|
|
102
99
|
import { join } from "path";
|
|
@@ -141,28 +138,38 @@ function safeMerge(target, source) {
|
|
|
141
138
|
}
|
|
142
139
|
}
|
|
143
140
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
this.edges = this.db.addCollection("edges", {
|
|
155
|
-
indices: ["from", "to", "type"]
|
|
156
|
-
});
|
|
141
|
+
function edgeKey(from, to, type) {
|
|
142
|
+
return `${from}\0${to}\0${type}`;
|
|
143
|
+
}
|
|
144
|
+
function addToIndex(index, key, edge) {
|
|
145
|
+
const list = index.get(key);
|
|
146
|
+
if (list) {
|
|
147
|
+
list.push(edge);
|
|
148
|
+
} else {
|
|
149
|
+
index.set(key, [edge]);
|
|
157
150
|
}
|
|
151
|
+
}
|
|
152
|
+
function removeFromIndex(index, key, edge) {
|
|
153
|
+
const list = index.get(key);
|
|
154
|
+
if (!list) return;
|
|
155
|
+
const idx = list.indexOf(edge);
|
|
156
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
157
|
+
if (list.length === 0) index.delete(key);
|
|
158
|
+
}
|
|
159
|
+
var GraphStore = class {
|
|
160
|
+
nodeMap = /* @__PURE__ */ new Map();
|
|
161
|
+
edgeMap = /* @__PURE__ */ new Map();
|
|
162
|
+
// keyed by from\0to\0type
|
|
163
|
+
edgesByFrom = /* @__PURE__ */ new Map();
|
|
164
|
+
edgesByTo = /* @__PURE__ */ new Map();
|
|
165
|
+
edgesByType = /* @__PURE__ */ new Map();
|
|
158
166
|
// --- Node operations ---
|
|
159
167
|
addNode(node) {
|
|
160
|
-
const existing = this.
|
|
168
|
+
const existing = this.nodeMap.get(node.id);
|
|
161
169
|
if (existing) {
|
|
162
170
|
safeMerge(existing, node);
|
|
163
|
-
this.nodes.update(existing);
|
|
164
171
|
} else {
|
|
165
|
-
this.
|
|
172
|
+
this.nodeMap.set(node.id, { ...node });
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
batchAddNodes(nodes) {
|
|
@@ -171,44 +178,44 @@ var GraphStore = class {
|
|
|
171
178
|
}
|
|
172
179
|
}
|
|
173
180
|
getNode(id) {
|
|
174
|
-
const
|
|
175
|
-
if (!
|
|
176
|
-
return
|
|
181
|
+
const node = this.nodeMap.get(id);
|
|
182
|
+
if (!node) return null;
|
|
183
|
+
return { ...node };
|
|
177
184
|
}
|
|
178
185
|
findNodes(query) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
const results = [];
|
|
187
|
+
for (const node of this.nodeMap.values()) {
|
|
188
|
+
if (query.type !== void 0 && node.type !== query.type) continue;
|
|
189
|
+
if (query.name !== void 0 && node.name !== query.name) continue;
|
|
190
|
+
if (query.path !== void 0 && node.path !== query.path) continue;
|
|
191
|
+
results.push({ ...node });
|
|
192
|
+
}
|
|
193
|
+
return results;
|
|
184
194
|
}
|
|
185
195
|
removeNode(id) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const edgesToRemove = this.edges.find({
|
|
191
|
-
$or: [{ from: id }, { to: id }]
|
|
192
|
-
});
|
|
196
|
+
this.nodeMap.delete(id);
|
|
197
|
+
const fromEdges = this.edgesByFrom.get(id) ?? [];
|
|
198
|
+
const toEdges = this.edgesByTo.get(id) ?? [];
|
|
199
|
+
const edgesToRemove = /* @__PURE__ */ new Set([...fromEdges, ...toEdges]);
|
|
193
200
|
for (const edge of edgesToRemove) {
|
|
194
|
-
this.
|
|
201
|
+
this.removeEdgeInternal(edge);
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
204
|
// --- Edge operations ---
|
|
198
205
|
addEdge(edge) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
to: edge.to,
|
|
202
|
-
type: edge.type
|
|
203
|
-
});
|
|
206
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
207
|
+
const existing = this.edgeMap.get(key);
|
|
204
208
|
if (existing) {
|
|
205
209
|
if (edge.metadata) {
|
|
206
210
|
safeMerge(existing, edge);
|
|
207
|
-
this.edges.update(existing);
|
|
208
211
|
}
|
|
209
212
|
return;
|
|
210
213
|
}
|
|
211
|
-
|
|
214
|
+
const copy = { ...edge };
|
|
215
|
+
this.edgeMap.set(key, copy);
|
|
216
|
+
addToIndex(this.edgesByFrom, edge.from, copy);
|
|
217
|
+
addToIndex(this.edgesByTo, edge.to, copy);
|
|
218
|
+
addToIndex(this.edgesByType, edge.type, copy);
|
|
212
219
|
}
|
|
213
220
|
batchAddEdges(edges) {
|
|
214
221
|
for (const edge of edges) {
|
|
@@ -216,22 +223,38 @@ var GraphStore = class {
|
|
|
216
223
|
}
|
|
217
224
|
}
|
|
218
225
|
getEdges(query) {
|
|
219
|
-
|
|
220
|
-
if (query.from !== void 0
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
226
|
+
let candidates;
|
|
227
|
+
if (query.from !== void 0 && query.to !== void 0 && query.type !== void 0) {
|
|
228
|
+
const edge = this.edgeMap.get(edgeKey(query.from, query.to, query.type));
|
|
229
|
+
return edge ? [{ ...edge }] : [];
|
|
230
|
+
} else if (query.from !== void 0) {
|
|
231
|
+
candidates = this.edgesByFrom.get(query.from) ?? [];
|
|
232
|
+
} else if (query.to !== void 0) {
|
|
233
|
+
candidates = this.edgesByTo.get(query.to) ?? [];
|
|
234
|
+
} else if (query.type !== void 0) {
|
|
235
|
+
candidates = this.edgesByType.get(query.type) ?? [];
|
|
236
|
+
} else {
|
|
237
|
+
candidates = this.edgeMap.values();
|
|
238
|
+
}
|
|
239
|
+
const results = [];
|
|
240
|
+
for (const edge of candidates) {
|
|
241
|
+
if (query.from !== void 0 && edge.from !== query.from) continue;
|
|
242
|
+
if (query.to !== void 0 && edge.to !== query.to) continue;
|
|
243
|
+
if (query.type !== void 0 && edge.type !== query.type) continue;
|
|
244
|
+
results.push({ ...edge });
|
|
245
|
+
}
|
|
246
|
+
return results;
|
|
224
247
|
}
|
|
225
248
|
getNeighbors(nodeId, direction = "both") {
|
|
226
249
|
const neighborIds = /* @__PURE__ */ new Set();
|
|
227
250
|
if (direction === "outbound" || direction === "both") {
|
|
228
|
-
const outEdges = this.
|
|
251
|
+
const outEdges = this.edgesByFrom.get(nodeId) ?? [];
|
|
229
252
|
for (const edge of outEdges) {
|
|
230
253
|
neighborIds.add(edge.to);
|
|
231
254
|
}
|
|
232
255
|
}
|
|
233
256
|
if (direction === "inbound" || direction === "both") {
|
|
234
|
-
const inEdges = this.
|
|
257
|
+
const inEdges = this.edgesByTo.get(nodeId) ?? [];
|
|
235
258
|
for (const edge of inEdges) {
|
|
236
259
|
neighborIds.add(edge.from);
|
|
237
260
|
}
|
|
@@ -245,20 +268,23 @@ var GraphStore = class {
|
|
|
245
268
|
}
|
|
246
269
|
// --- Counts ---
|
|
247
270
|
get nodeCount() {
|
|
248
|
-
return this.
|
|
271
|
+
return this.nodeMap.size;
|
|
249
272
|
}
|
|
250
273
|
get edgeCount() {
|
|
251
|
-
return this.
|
|
274
|
+
return this.edgeMap.size;
|
|
252
275
|
}
|
|
253
276
|
// --- Clear ---
|
|
254
277
|
clear() {
|
|
255
|
-
this.
|
|
256
|
-
this.
|
|
278
|
+
this.nodeMap.clear();
|
|
279
|
+
this.edgeMap.clear();
|
|
280
|
+
this.edgesByFrom.clear();
|
|
281
|
+
this.edgesByTo.clear();
|
|
282
|
+
this.edgesByType.clear();
|
|
257
283
|
}
|
|
258
284
|
// --- Persistence ---
|
|
259
285
|
async save(dirPath) {
|
|
260
|
-
const allNodes = this.
|
|
261
|
-
const allEdges = this.
|
|
286
|
+
const allNodes = Array.from(this.nodeMap.values()).map((n) => ({ ...n }));
|
|
287
|
+
const allEdges = Array.from(this.edgeMap.values()).map((e) => ({ ...e }));
|
|
262
288
|
await saveGraph(dirPath, allNodes, allEdges);
|
|
263
289
|
}
|
|
264
290
|
async load(dirPath) {
|
|
@@ -266,17 +292,25 @@ var GraphStore = class {
|
|
|
266
292
|
if (!data) return false;
|
|
267
293
|
this.clear();
|
|
268
294
|
for (const node of data.nodes) {
|
|
269
|
-
this.
|
|
295
|
+
this.nodeMap.set(node.id, { ...node });
|
|
270
296
|
}
|
|
271
297
|
for (const edge of data.edges) {
|
|
272
|
-
|
|
298
|
+
const copy = { ...edge };
|
|
299
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
300
|
+
this.edgeMap.set(key, copy);
|
|
301
|
+
addToIndex(this.edgesByFrom, edge.from, copy);
|
|
302
|
+
addToIndex(this.edgesByTo, edge.to, copy);
|
|
303
|
+
addToIndex(this.edgesByType, edge.type, copy);
|
|
273
304
|
}
|
|
274
305
|
return true;
|
|
275
306
|
}
|
|
276
307
|
// --- Internal ---
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
|
|
308
|
+
removeEdgeInternal(edge) {
|
|
309
|
+
const key = edgeKey(edge.from, edge.to, edge.type);
|
|
310
|
+
this.edgeMap.delete(key);
|
|
311
|
+
removeFromIndex(this.edgesByFrom, edge.from, edge);
|
|
312
|
+
removeFromIndex(this.edgesByTo, edge.to, edge);
|
|
313
|
+
removeFromIndex(this.edgesByType, edge.type, edge);
|
|
280
314
|
}
|
|
281
315
|
};
|
|
282
316
|
|
|
@@ -357,11 +391,11 @@ var VectorStore = class _VectorStore {
|
|
|
357
391
|
};
|
|
358
392
|
|
|
359
393
|
// src/query/ContextQL.ts
|
|
360
|
-
function
|
|
394
|
+
function edgeKey2(e) {
|
|
361
395
|
return `${e.from}|${e.to}|${e.type}`;
|
|
362
396
|
}
|
|
363
397
|
function addEdge(state, edge) {
|
|
364
|
-
const key =
|
|
398
|
+
const key = edgeKey2(edge);
|
|
365
399
|
if (!state.edgeSet.has(key)) {
|
|
366
400
|
state.edgeSet.add(key);
|
|
367
401
|
state.resultEdges.push(edge);
|
|
@@ -541,6 +575,7 @@ var CodeIngestor = class {
|
|
|
541
575
|
constructor(store) {
|
|
542
576
|
this.store = store;
|
|
543
577
|
}
|
|
578
|
+
store;
|
|
544
579
|
async ingest(rootDir) {
|
|
545
580
|
const start = Date.now();
|
|
546
581
|
const errors = [];
|
|
@@ -932,6 +967,8 @@ var GitIngestor = class {
|
|
|
932
967
|
this.store = store;
|
|
933
968
|
this.gitRunner = gitRunner;
|
|
934
969
|
}
|
|
970
|
+
store;
|
|
971
|
+
gitRunner;
|
|
935
972
|
async ingest(rootDir) {
|
|
936
973
|
const start = Date.now();
|
|
937
974
|
const errors = [];
|
|
@@ -1111,6 +1148,7 @@ var TopologicalLinker = class {
|
|
|
1111
1148
|
constructor(store) {
|
|
1112
1149
|
this.store = store;
|
|
1113
1150
|
}
|
|
1151
|
+
store;
|
|
1114
1152
|
link() {
|
|
1115
1153
|
let edgesAdded = 0;
|
|
1116
1154
|
const files = this.store.findNodes({ type: "file" });
|
|
@@ -1208,6 +1246,7 @@ var KnowledgeIngestor = class {
|
|
|
1208
1246
|
constructor(store) {
|
|
1209
1247
|
this.store = store;
|
|
1210
1248
|
}
|
|
1249
|
+
store;
|
|
1211
1250
|
async ingestADRs(adrDir) {
|
|
1212
1251
|
const start = Date.now();
|
|
1213
1252
|
const errors = [];
|
|
@@ -1456,6 +1495,7 @@ var SyncManager = class {
|
|
|
1456
1495
|
this.store = store;
|
|
1457
1496
|
this.metadataPath = path4.join(graphDir, "sync-metadata.json");
|
|
1458
1497
|
}
|
|
1498
|
+
store;
|
|
1459
1499
|
registrations = /* @__PURE__ */ new Map();
|
|
1460
1500
|
metadataPath;
|
|
1461
1501
|
registerConnector(connector, config) {
|
|
@@ -2050,6 +2090,7 @@ var GraphEntropyAdapter = class {
|
|
|
2050
2090
|
constructor(store) {
|
|
2051
2091
|
this.store = store;
|
|
2052
2092
|
}
|
|
2093
|
+
store;
|
|
2053
2094
|
/**
|
|
2054
2095
|
* Find all `documents` edges and classify them as stale or missing-target.
|
|
2055
2096
|
*
|
|
@@ -2190,6 +2231,7 @@ var GraphComplexityAdapter = class {
|
|
|
2190
2231
|
constructor(store) {
|
|
2191
2232
|
this.store = store;
|
|
2192
2233
|
}
|
|
2234
|
+
store;
|
|
2193
2235
|
/**
|
|
2194
2236
|
* Compute complexity hotspots by combining cyclomatic complexity with change frequency.
|
|
2195
2237
|
*
|
|
@@ -2277,6 +2319,7 @@ var GraphCouplingAdapter = class {
|
|
|
2277
2319
|
constructor(store) {
|
|
2278
2320
|
this.store = store;
|
|
2279
2321
|
}
|
|
2322
|
+
store;
|
|
2280
2323
|
/**
|
|
2281
2324
|
* Compute coupling data for all file nodes in the graph.
|
|
2282
2325
|
*
|
|
@@ -2346,6 +2389,7 @@ var GraphAnomalyAdapter = class {
|
|
|
2346
2389
|
constructor(store) {
|
|
2347
2390
|
this.store = store;
|
|
2348
2391
|
}
|
|
2392
|
+
store;
|
|
2349
2393
|
detect(options) {
|
|
2350
2394
|
const threshold = options?.threshold != null && options.threshold > 0 ? options.threshold : DEFAULT_THRESHOLD;
|
|
2351
2395
|
const requestedMetrics = options?.metrics ?? [...DEFAULT_METRICS];
|
|
@@ -3506,6 +3550,7 @@ var GraphConstraintAdapter = class {
|
|
|
3506
3550
|
constructor(store) {
|
|
3507
3551
|
this.store = store;
|
|
3508
3552
|
}
|
|
3553
|
+
store;
|
|
3509
3554
|
computeDependencyGraph() {
|
|
3510
3555
|
const fileNodes = this.store.findNodes({ type: "file" });
|
|
3511
3556
|
const nodes = fileNodes.map((n) => n.path ?? n.id);
|
|
@@ -3650,6 +3695,7 @@ var DesignIngestor = class {
|
|
|
3650
3695
|
constructor(store) {
|
|
3651
3696
|
this.store = store;
|
|
3652
3697
|
}
|
|
3698
|
+
store;
|
|
3653
3699
|
async ingestTokens(tokensPath) {
|
|
3654
3700
|
const start = Date.now();
|
|
3655
3701
|
const content = await readFileOrNull(tokensPath);
|
|
@@ -3722,6 +3768,7 @@ var DesignConstraintAdapter = class {
|
|
|
3722
3768
|
constructor(store) {
|
|
3723
3769
|
this.store = store;
|
|
3724
3770
|
}
|
|
3771
|
+
store;
|
|
3725
3772
|
checkForHardcodedColors(source, file, strictness) {
|
|
3726
3773
|
const severity = this.mapSeverity(strictness);
|
|
3727
3774
|
const tokenNodes = this.store.findNodes({ type: "design_token" });
|
|
@@ -3805,6 +3852,7 @@ var GraphFeedbackAdapter = class {
|
|
|
3805
3852
|
constructor(store) {
|
|
3806
3853
|
this.store = store;
|
|
3807
3854
|
}
|
|
3855
|
+
store;
|
|
3808
3856
|
computeImpactData(changedFiles) {
|
|
3809
3857
|
const affectedTests = [];
|
|
3810
3858
|
const affectedDocs = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harness-engineering/graph",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Knowledge graph for context assembly in Harness Engineering",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,13 +18,11 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"@harness-engineering/types": "0.4.0"
|
|
21
|
+
"minimatch": "^10.2.5",
|
|
22
|
+
"zod": "^3.25.76",
|
|
23
|
+
"@harness-engineering/types": "0.7.0"
|
|
25
24
|
},
|
|
26
25
|
"optionalDependencies": {
|
|
27
|
-
"hnswlib-node": "^3.0.0",
|
|
28
26
|
"tree-sitter": "^0.22.4",
|
|
29
27
|
"tree-sitter-typescript": "^0.23.2"
|
|
30
28
|
},
|
|
@@ -41,11 +39,11 @@
|
|
|
41
39
|
},
|
|
42
40
|
"homepage": "https://github.com/Intense-Visions/harness-engineering/tree/main/packages/graph#readme",
|
|
43
41
|
"devDependencies": {
|
|
44
|
-
"@types/node": "^22.
|
|
45
|
-
"@vitest/coverage-v8": "^4.
|
|
46
|
-
"tsup": "^8.
|
|
42
|
+
"@types/node": "^22.19.15",
|
|
43
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
47
45
|
"typescript": "^5.9.3",
|
|
48
|
-
"vitest": "^4.
|
|
46
|
+
"vitest": "^4.1.2"
|
|
49
47
|
},
|
|
50
48
|
"scripts": {
|
|
51
49
|
"build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.build.json",
|
|
@@ -55,6 +53,7 @@
|
|
|
55
53
|
"clean": "node ../../scripts/clean.mjs dist",
|
|
56
54
|
"test": "vitest run",
|
|
57
55
|
"test:watch": "vitest",
|
|
58
|
-
"test:coverage": "vitest run --coverage"
|
|
56
|
+
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"bench": "vitest bench"
|
|
59
58
|
}
|
|
60
59
|
}
|