@harness-engineering/graph 0.3.4 → 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 CHANGED
@@ -163,10 +163,11 @@ interface EdgeQuery {
163
163
  readonly type?: EdgeType;
164
164
  }
165
165
  declare class GraphStore {
166
- private readonly db;
167
- private nodes;
168
- private edges;
169
- constructor();
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 stripLokiMeta;
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 readonly db;
167
- private nodes;
168
- private edges;
169
- constructor();
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 stripLokiMeta;
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
- var GraphStore = class {
222
- db;
223
- nodes;
224
- edges;
225
- constructor() {
226
- this.db = new import_lokijs.default("graph.db");
227
- this.nodes = this.db.addCollection("nodes", {
228
- unique: ["id"],
229
- indices: ["type", "name"]
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.nodes.by("id", node.id);
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.nodes.insert({ ...node });
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 doc = this.nodes.by("id", id);
252
- if (!doc) return null;
253
- return this.stripLokiMeta(doc);
258
+ const node = this.nodeMap.get(id);
259
+ if (!node) return null;
260
+ return { ...node };
254
261
  }
255
262
  findNodes(query) {
256
- const lokiQuery = {};
257
- if (query.type !== void 0) lokiQuery["type"] = query.type;
258
- if (query.name !== void 0) lokiQuery["name"] = query.name;
259
- if (query.path !== void 0) lokiQuery["path"] = query.path;
260
- return this.nodes.find(lokiQuery).map((doc) => this.stripLokiMeta(doc));
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
- const doc = this.nodes.by("id", id);
264
- if (doc) {
265
- this.nodes.remove(doc);
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.edges.remove(edge);
278
+ this.removeEdgeInternal(edge);
272
279
  }
273
280
  }
274
281
  // --- Edge operations ---
275
282
  addEdge(edge) {
276
- const existing = this.edges.findOne({
277
- from: edge.from,
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
- this.edges.insert({ ...edge });
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
- const lokiQuery = {};
297
- if (query.from !== void 0) lokiQuery["from"] = query.from;
298
- if (query.to !== void 0) lokiQuery["to"] = query.to;
299
- if (query.type !== void 0) lokiQuery["type"] = query.type;
300
- return this.edges.find(lokiQuery).map((doc) => this.stripLokiMeta(doc));
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.edges.find({ from: nodeId });
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.edges.find({ to: nodeId });
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.nodes.count();
348
+ return this.nodeMap.size;
326
349
  }
327
350
  get edgeCount() {
328
- return this.edges.count();
351
+ return this.edgeMap.size;
329
352
  }
330
353
  // --- Clear ---
331
354
  clear() {
332
- this.nodes.clear();
333
- this.edges.clear();
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.nodes.find().map((doc) => this.stripLokiMeta(doc));
338
- const allEdges = this.edges.find().map((doc) => this.stripLokiMeta(doc));
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.nodes.insert({ ...node });
372
+ this.nodeMap.set(node.id, { ...node });
347
373
  }
348
374
  for (const edge of data.edges) {
349
- this.edges.insert({ ...edge });
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
- stripLokiMeta(doc) {
355
- const { $loki: _, meta: _meta, ...rest } = doc;
356
- return rest;
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 edgeKey(e) {
471
+ function edgeKey2(e) {
438
472
  return `${e.from}|${e.to}|${e.type}`;
439
473
  }
440
474
  function addEdge(state, edge) {
441
- const key = edgeKey(edge);
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
- var GraphStore = class {
145
- db;
146
- nodes;
147
- edges;
148
- constructor() {
149
- this.db = new loki("graph.db");
150
- this.nodes = this.db.addCollection("nodes", {
151
- unique: ["id"],
152
- indices: ["type", "name"]
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.nodes.by("id", node.id);
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.nodes.insert({ ...node });
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 doc = this.nodes.by("id", id);
175
- if (!doc) return null;
176
- return this.stripLokiMeta(doc);
181
+ const node = this.nodeMap.get(id);
182
+ if (!node) return null;
183
+ return { ...node };
177
184
  }
178
185
  findNodes(query) {
179
- const lokiQuery = {};
180
- if (query.type !== void 0) lokiQuery["type"] = query.type;
181
- if (query.name !== void 0) lokiQuery["name"] = query.name;
182
- if (query.path !== void 0) lokiQuery["path"] = query.path;
183
- return this.nodes.find(lokiQuery).map((doc) => this.stripLokiMeta(doc));
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
- const doc = this.nodes.by("id", id);
187
- if (doc) {
188
- this.nodes.remove(doc);
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.edges.remove(edge);
201
+ this.removeEdgeInternal(edge);
195
202
  }
196
203
  }
197
204
  // --- Edge operations ---
198
205
  addEdge(edge) {
199
- const existing = this.edges.findOne({
200
- from: edge.from,
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
- this.edges.insert({ ...edge });
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
- const lokiQuery = {};
220
- if (query.from !== void 0) lokiQuery["from"] = query.from;
221
- if (query.to !== void 0) lokiQuery["to"] = query.to;
222
- if (query.type !== void 0) lokiQuery["type"] = query.type;
223
- return this.edges.find(lokiQuery).map((doc) => this.stripLokiMeta(doc));
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.edges.find({ from: nodeId });
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.edges.find({ to: nodeId });
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.nodes.count();
271
+ return this.nodeMap.size;
249
272
  }
250
273
  get edgeCount() {
251
- return this.edges.count();
274
+ return this.edgeMap.size;
252
275
  }
253
276
  // --- Clear ---
254
277
  clear() {
255
- this.nodes.clear();
256
- this.edges.clear();
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.nodes.find().map((doc) => this.stripLokiMeta(doc));
261
- const allEdges = this.edges.find().map((doc) => this.stripLokiMeta(doc));
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.nodes.insert({ ...node });
295
+ this.nodeMap.set(node.id, { ...node });
270
296
  }
271
297
  for (const edge of data.edges) {
272
- this.edges.insert({ ...edge });
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
- stripLokiMeta(doc) {
278
- const { $loki: _, meta: _meta, ...rest } = doc;
279
- return rest;
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 edgeKey(e) {
394
+ function edgeKey2(e) {
361
395
  return `${e.from}|${e.to}|${e.type}`;
362
396
  }
363
397
  function addEdge(state, edge) {
364
- const key = edgeKey(edge);
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.4",
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
- "lokijs": "^1.5.12",
22
- "minimatch": "^10.2.4",
23
- "zod": "^3.24.1",
24
- "@harness-engineering/types": "0.6.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.0.0",
45
- "@vitest/coverage-v8": "^4.0.18",
46
- "tsup": "^8.0.0",
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.0.18"
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",