@harness-engineering/graph 0.4.0 → 0.4.1

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.mjs CHANGED
@@ -162,6 +162,16 @@ function removeFromIndex(index, key, edge) {
162
162
  if (idx !== -1) list.splice(idx, 1);
163
163
  if (list.length === 0) index.delete(key);
164
164
  }
165
+ function filterEdges(candidates, query) {
166
+ const results = [];
167
+ for (const edge of candidates) {
168
+ if (query.from !== void 0 && edge.from !== query.from) continue;
169
+ if (query.to !== void 0 && edge.to !== query.to) continue;
170
+ if (query.type !== void 0 && edge.type !== query.type) continue;
171
+ results.push({ ...edge });
172
+ }
173
+ return results;
174
+ }
165
175
  var GraphStore = class {
166
176
  nodeMap = /* @__PURE__ */ new Map();
167
177
  edgeMap = /* @__PURE__ */ new Map();
@@ -229,27 +239,25 @@ var GraphStore = class {
229
239
  }
230
240
  }
231
241
  getEdges(query) {
232
- let candidates;
233
242
  if (query.from !== void 0 && query.to !== void 0 && query.type !== void 0) {
234
243
  const edge = this.edgeMap.get(edgeKey(query.from, query.to, query.type));
235
244
  return edge ? [{ ...edge }] : [];
236
- } else if (query.from !== void 0) {
237
- candidates = this.edgesByFrom.get(query.from) ?? [];
238
- } else if (query.to !== void 0) {
239
- candidates = this.edgesByTo.get(query.to) ?? [];
240
- } else if (query.type !== void 0) {
241
- candidates = this.edgesByType.get(query.type) ?? [];
242
- } else {
243
- candidates = this.edgeMap.values();
244
245
  }
245
- const results = [];
246
- for (const edge of candidates) {
247
- if (query.from !== void 0 && edge.from !== query.from) continue;
248
- if (query.to !== void 0 && edge.to !== query.to) continue;
249
- if (query.type !== void 0 && edge.type !== query.type) continue;
250
- results.push({ ...edge });
246
+ const candidates = this.selectCandidates(query);
247
+ return filterEdges(candidates, query);
248
+ }
249
+ /** Pick the most selective index to start from. */
250
+ selectCandidates(query) {
251
+ if (query.from !== void 0) {
252
+ return this.edgesByFrom.get(query.from) ?? [];
251
253
  }
252
- return results;
254
+ if (query.to !== void 0) {
255
+ return this.edgesByTo.get(query.to) ?? [];
256
+ }
257
+ if (query.type !== void 0) {
258
+ return this.edgesByType.get(query.type) ?? [];
259
+ }
260
+ return this.edgeMap.values();
253
261
  }
254
262
  getNeighbors(nodeId, direction = "both") {
255
263
  const neighborIds = /* @__PURE__ */ new Set();
@@ -545,6 +553,12 @@ var CODE_TYPES = /* @__PURE__ */ new Set([
545
553
  "method",
546
554
  "variable"
547
555
  ]);
556
+ function classifyNodeCategory(node) {
557
+ if (TEST_TYPES.has(node.type)) return "tests";
558
+ if (DOC_TYPES.has(node.type)) return "docs";
559
+ if (CODE_TYPES.has(node.type)) return "code";
560
+ return "other";
561
+ }
548
562
  function groupNodesByImpact(nodes, excludeId) {
549
563
  const tests = [];
550
564
  const docs = [];
@@ -552,15 +566,11 @@ function groupNodesByImpact(nodes, excludeId) {
552
566
  const other = [];
553
567
  for (const node of nodes) {
554
568
  if (excludeId && node.id === excludeId) continue;
555
- if (TEST_TYPES.has(node.type)) {
556
- tests.push(node);
557
- } else if (DOC_TYPES.has(node.type)) {
558
- docs.push(node);
559
- } else if (CODE_TYPES.has(node.type)) {
560
- code.push(node);
561
- } else {
562
- other.push(node);
563
- }
569
+ const category = classifyNodeCategory(node);
570
+ if (category === "tests") tests.push(node);
571
+ else if (category === "docs") docs.push(node);
572
+ else if (category === "code") code.push(node);
573
+ else other.push(node);
564
574
  }
565
575
  return { tests, docs, code, other };
566
576
  }
@@ -1525,7 +1535,7 @@ var RequirementIngestor = class {
1525
1535
  }
1526
1536
  for (const featureDir of featureDirs) {
1527
1537
  const featureName = path4.basename(featureDir);
1528
- const specPath = path4.join(featureDir, "proposal.md");
1538
+ const specPath = path4.join(featureDir, "proposal.md").replaceAll("\\", "/");
1529
1539
  let content;
1530
1540
  try {
1531
1541
  content = await fs3.readFile(specPath, "utf-8");
@@ -1675,7 +1685,7 @@ var RequirementIngestor = class {
1675
1685
  const escaped = node.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1676
1686
  const namePattern = new RegExp(`\\b${escaped}\\b`, "i");
1677
1687
  if (namePattern.test(reqText)) {
1678
- const edgeType = node.path && node.path.replace(/\\/g, "/").includes("/tests/") ? "verified_by" : "requires";
1688
+ const edgeType = node.path?.replace(/\\/g, "/").includes("/tests/") ? "verified_by" : "requires";
1679
1689
  this.store.addEdge({
1680
1690
  from: reqId,
1681
1691
  to: node.id,
@@ -2892,6 +2902,7 @@ var INTENT_SIGNALS = {
2892
2902
  "depend",
2893
2903
  "blast",
2894
2904
  "radius",
2905
+ "cascade",
2895
2906
  "risk",
2896
2907
  "delete",
2897
2908
  "remove"
@@ -2901,6 +2912,7 @@ var INTENT_SIGNALS = {
2901
2912
  /what\s+(breaks|happens|is affected)/,
2902
2913
  /if\s+i\s+(change|modify|remove|delete)/,
2903
2914
  /blast\s+radius/,
2915
+ /cascad/,
2904
2916
  /what\s+(depend|relies)/
2905
2917
  ]
2906
2918
  },
@@ -3386,6 +3398,10 @@ var ResponseFormatter = class {
3386
3398
  }
3387
3399
  formatImpact(entityName, data) {
3388
3400
  const d = data;
3401
+ if ("sourceNodeId" in d && "summary" in d) {
3402
+ const summary = d.summary;
3403
+ return `Blast radius of **${entityName}**: ${summary.totalAffected} affected nodes (${summary.highRisk} high risk, ${summary.mediumRisk} medium, ${summary.lowRisk} low).`;
3404
+ }
3389
3405
  const code = this.safeArrayLength(d?.code);
3390
3406
  const tests = this.safeArrayLength(d?.tests);
3391
3407
  const docs = this.safeArrayLength(d?.docs);
@@ -3447,6 +3463,246 @@ var ResponseFormatter = class {
3447
3463
  }
3448
3464
  };
3449
3465
 
3466
+ // src/blast-radius/CompositeProbabilityStrategy.ts
3467
+ var CompositeProbabilityStrategy = class _CompositeProbabilityStrategy {
3468
+ constructor(changeFreqMap, couplingMap) {
3469
+ this.changeFreqMap = changeFreqMap;
3470
+ this.couplingMap = couplingMap;
3471
+ }
3472
+ changeFreqMap;
3473
+ couplingMap;
3474
+ static BASE_WEIGHTS = {
3475
+ imports: 0.7,
3476
+ calls: 0.5,
3477
+ implements: 0.6,
3478
+ inherits: 0.6,
3479
+ co_changes_with: 0.4,
3480
+ references: 0.2,
3481
+ contains: 0.3
3482
+ };
3483
+ static FALLBACK_WEIGHT = 0.1;
3484
+ static EDGE_TYPE_BLEND = 0.5;
3485
+ static CHANGE_FREQ_BLEND = 0.3;
3486
+ static COUPLING_BLEND = 0.2;
3487
+ getEdgeProbability(edge, _fromNode, toNode) {
3488
+ const base = _CompositeProbabilityStrategy.BASE_WEIGHTS[edge.type] ?? _CompositeProbabilityStrategy.FALLBACK_WEIGHT;
3489
+ const changeFreq = this.changeFreqMap.get(toNode.id) ?? 0;
3490
+ const coupling = this.couplingMap.get(toNode.id) ?? 0;
3491
+ return Math.min(
3492
+ 1,
3493
+ base * _CompositeProbabilityStrategy.EDGE_TYPE_BLEND + changeFreq * _CompositeProbabilityStrategy.CHANGE_FREQ_BLEND + coupling * _CompositeProbabilityStrategy.COUPLING_BLEND
3494
+ );
3495
+ }
3496
+ };
3497
+
3498
+ // src/blast-radius/CascadeSimulator.ts
3499
+ var DEFAULT_PROBABILITY_FLOOR = 0.05;
3500
+ var DEFAULT_MAX_DEPTH = 10;
3501
+ var CascadeSimulator = class {
3502
+ constructor(store) {
3503
+ this.store = store;
3504
+ }
3505
+ store;
3506
+ simulate(sourceNodeId, options = {}) {
3507
+ const sourceNode = this.store.getNode(sourceNodeId);
3508
+ if (!sourceNode) {
3509
+ throw new Error(`Node not found: ${sourceNodeId}. Ensure the file has been ingested.`);
3510
+ }
3511
+ const probabilityFloor = options.probabilityFloor ?? DEFAULT_PROBABILITY_FLOOR;
3512
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
3513
+ const edgeTypeFilter = options.edgeTypes ? new Set(options.edgeTypes) : null;
3514
+ const strategy = options.strategy ?? this.buildDefaultStrategy();
3515
+ const visited = /* @__PURE__ */ new Map();
3516
+ const queue = [];
3517
+ const fanOutCount = /* @__PURE__ */ new Map();
3518
+ this.seedQueue(
3519
+ sourceNodeId,
3520
+ sourceNode,
3521
+ strategy,
3522
+ edgeTypeFilter,
3523
+ probabilityFloor,
3524
+ queue,
3525
+ fanOutCount
3526
+ );
3527
+ const truncated = this.runBfs(
3528
+ queue,
3529
+ visited,
3530
+ fanOutCount,
3531
+ sourceNodeId,
3532
+ strategy,
3533
+ edgeTypeFilter,
3534
+ probabilityFloor,
3535
+ maxDepth
3536
+ );
3537
+ return this.buildResult(sourceNodeId, sourceNode.name, visited, fanOutCount, truncated);
3538
+ }
3539
+ seedQueue(sourceNodeId, sourceNode, strategy, edgeTypeFilter, probabilityFloor, queue, fanOutCount) {
3540
+ const sourceEdges = this.store.getEdges({ from: sourceNodeId });
3541
+ for (const edge of sourceEdges) {
3542
+ if (edge.to === sourceNodeId) continue;
3543
+ if (edgeTypeFilter && !edgeTypeFilter.has(edge.type)) continue;
3544
+ const targetNode = this.store.getNode(edge.to);
3545
+ if (!targetNode) continue;
3546
+ const cumProb = strategy.getEdgeProbability(edge, sourceNode, targetNode);
3547
+ if (cumProb < probabilityFloor) continue;
3548
+ queue.push({
3549
+ nodeId: edge.to,
3550
+ cumProb,
3551
+ depth: 1,
3552
+ parentId: sourceNodeId,
3553
+ incomingEdge: edge.type
3554
+ });
3555
+ }
3556
+ fanOutCount.set(
3557
+ sourceNodeId,
3558
+ sourceEdges.filter(
3559
+ (e) => e.to !== sourceNodeId && (!edgeTypeFilter || edgeTypeFilter.has(e.type))
3560
+ ).length
3561
+ );
3562
+ }
3563
+ runBfs(queue, visited, fanOutCount, sourceNodeId, strategy, edgeTypeFilter, probabilityFloor, maxDepth) {
3564
+ const MAX_QUEUE_SIZE = 1e4;
3565
+ let head = 0;
3566
+ while (head < queue.length) {
3567
+ if (queue.length > MAX_QUEUE_SIZE) return true;
3568
+ const entry = queue[head++];
3569
+ const existing = visited.get(entry.nodeId);
3570
+ if (existing && existing.cumulativeProbability >= entry.cumProb) continue;
3571
+ const targetNode = this.store.getNode(entry.nodeId);
3572
+ if (!targetNode) continue;
3573
+ visited.set(entry.nodeId, {
3574
+ nodeId: entry.nodeId,
3575
+ name: targetNode.name,
3576
+ ...targetNode.path !== void 0 && { path: targetNode.path },
3577
+ type: targetNode.type,
3578
+ cumulativeProbability: entry.cumProb,
3579
+ depth: entry.depth,
3580
+ incomingEdge: entry.incomingEdge,
3581
+ parentId: entry.parentId
3582
+ });
3583
+ if (entry.depth < maxDepth) {
3584
+ const childCount = this.expandNode(
3585
+ entry,
3586
+ targetNode,
3587
+ sourceNodeId,
3588
+ strategy,
3589
+ edgeTypeFilter,
3590
+ probabilityFloor,
3591
+ queue
3592
+ );
3593
+ fanOutCount.set(entry.nodeId, (fanOutCount.get(entry.nodeId) ?? 0) + childCount);
3594
+ }
3595
+ }
3596
+ return false;
3597
+ }
3598
+ expandNode(entry, fromNode, sourceNodeId, strategy, edgeTypeFilter, probabilityFloor, queue) {
3599
+ const outEdges = this.store.getEdges({ from: entry.nodeId });
3600
+ let childCount = 0;
3601
+ for (const edge of outEdges) {
3602
+ if (edgeTypeFilter && !edgeTypeFilter.has(edge.type)) continue;
3603
+ if (edge.to === sourceNodeId) continue;
3604
+ const childNode = this.store.getNode(edge.to);
3605
+ if (!childNode) continue;
3606
+ const newCumProb = entry.cumProb * strategy.getEdgeProbability(edge, fromNode, childNode);
3607
+ if (newCumProb < probabilityFloor) continue;
3608
+ childCount++;
3609
+ queue.push({
3610
+ nodeId: edge.to,
3611
+ cumProb: newCumProb,
3612
+ depth: entry.depth + 1,
3613
+ parentId: entry.nodeId,
3614
+ incomingEdge: edge.type
3615
+ });
3616
+ }
3617
+ return childCount;
3618
+ }
3619
+ buildDefaultStrategy() {
3620
+ return new CompositeProbabilityStrategy(/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
3621
+ }
3622
+ buildResult(sourceNodeId, sourceName, visited, fanOutCount, truncated = false) {
3623
+ if (visited.size === 0) {
3624
+ return {
3625
+ sourceNodeId,
3626
+ sourceName,
3627
+ layers: [],
3628
+ flatSummary: [],
3629
+ summary: {
3630
+ totalAffected: 0,
3631
+ maxDepthReached: 0,
3632
+ highRisk: 0,
3633
+ mediumRisk: 0,
3634
+ lowRisk: 0,
3635
+ categoryBreakdown: { code: 0, tests: 0, docs: 0, other: 0 },
3636
+ amplificationPoints: [],
3637
+ truncated
3638
+ }
3639
+ };
3640
+ }
3641
+ const allNodes = Array.from(visited.values());
3642
+ const flatSummary = [...allNodes].sort(
3643
+ (a, b) => b.cumulativeProbability - a.cumulativeProbability
3644
+ );
3645
+ const depthMap = /* @__PURE__ */ new Map();
3646
+ for (const node of allNodes) {
3647
+ let list = depthMap.get(node.depth);
3648
+ if (!list) {
3649
+ list = [];
3650
+ depthMap.set(node.depth, list);
3651
+ }
3652
+ list.push(node);
3653
+ }
3654
+ const layers = [];
3655
+ const depths = Array.from(depthMap.keys()).sort((a, b) => a - b);
3656
+ for (const depth of depths) {
3657
+ const nodes = depthMap.get(depth);
3658
+ const breakdown = { code: 0, tests: 0, docs: 0, other: 0 };
3659
+ for (const n of nodes) {
3660
+ const graphNode = this.store.getNode(n.nodeId);
3661
+ if (graphNode) {
3662
+ breakdown[classifyNodeCategory(graphNode)]++;
3663
+ }
3664
+ }
3665
+ layers.push({ depth, nodes, categoryBreakdown: breakdown });
3666
+ }
3667
+ let highRisk = 0;
3668
+ let mediumRisk = 0;
3669
+ let lowRisk = 0;
3670
+ const catBreakdown = { code: 0, tests: 0, docs: 0, other: 0 };
3671
+ for (const node of allNodes) {
3672
+ if (node.cumulativeProbability >= 0.5) highRisk++;
3673
+ else if (node.cumulativeProbability >= 0.2) mediumRisk++;
3674
+ else lowRisk++;
3675
+ const graphNode = this.store.getNode(node.nodeId);
3676
+ if (graphNode) {
3677
+ catBreakdown[classifyNodeCategory(graphNode)]++;
3678
+ }
3679
+ }
3680
+ const amplificationPoints = [];
3681
+ for (const [nodeId, count] of fanOutCount) {
3682
+ if (count > 3) {
3683
+ amplificationPoints.push(nodeId);
3684
+ }
3685
+ }
3686
+ const maxDepthReached = allNodes.reduce((max, n) => Math.max(max, n.depth), 0);
3687
+ return {
3688
+ sourceNodeId,
3689
+ sourceName,
3690
+ layers,
3691
+ flatSummary,
3692
+ summary: {
3693
+ totalAffected: allNodes.length,
3694
+ maxDepthReached,
3695
+ highRisk,
3696
+ mediumRisk,
3697
+ lowRisk,
3698
+ categoryBreakdown: catBreakdown,
3699
+ amplificationPoints,
3700
+ truncated
3701
+ }
3702
+ };
3703
+ }
3704
+ };
3705
+
3450
3706
  // src/nlq/index.ts
3451
3707
  var ENTITY_REQUIRED_INTENTS = /* @__PURE__ */ new Set(["impact", "relationships", "explain"]);
3452
3708
  var classifier = new IntentClassifier();
@@ -3509,6 +3765,11 @@ function executeOperation(store, intent, entities, question, fusion) {
3509
3765
  switch (intent) {
3510
3766
  case "impact": {
3511
3767
  const rootId = entities[0].nodeId;
3768
+ const lowerQuestion = question.toLowerCase();
3769
+ if (lowerQuestion.includes("blast radius") || lowerQuestion.includes("cascade")) {
3770
+ const simulator = new CascadeSimulator(store);
3771
+ return simulator.simulate(rootId);
3772
+ }
3512
3773
  const result = cql.execute({
3513
3774
  rootNodeIds: [rootId],
3514
3775
  bidirectional: true,
@@ -3803,6 +4064,59 @@ var Assembler = class {
3803
4064
  };
3804
4065
 
3805
4066
  // src/query/Traceability.ts
4067
+ function extractConfidence(edge) {
4068
+ return edge.confidence ?? edge.metadata?.confidence ?? 0;
4069
+ }
4070
+ function extractMethod(edge) {
4071
+ return edge.metadata?.method ?? "convention";
4072
+ }
4073
+ function edgesToTracedFiles(store, edges) {
4074
+ return edges.map((edge) => ({
4075
+ path: store.getNode(edge.to)?.path ?? edge.to,
4076
+ confidence: extractConfidence(edge),
4077
+ method: extractMethod(edge)
4078
+ }));
4079
+ }
4080
+ function determineCoverageStatus(hasCode, hasTests) {
4081
+ if (hasCode && hasTests) return "full";
4082
+ if (hasCode) return "code-only";
4083
+ if (hasTests) return "test-only";
4084
+ return "none";
4085
+ }
4086
+ function computeMaxConfidence(codeFiles, testFiles) {
4087
+ const allConfidences = [
4088
+ ...codeFiles.map((f) => f.confidence),
4089
+ ...testFiles.map((f) => f.confidence)
4090
+ ];
4091
+ return allConfidences.length > 0 ? Math.max(...allConfidences) : 0;
4092
+ }
4093
+ function buildRequirementCoverage(store, req) {
4094
+ const codeFiles = edgesToTracedFiles(store, store.getEdges({ from: req.id, type: "requires" }));
4095
+ const testFiles = edgesToTracedFiles(
4096
+ store,
4097
+ store.getEdges({ from: req.id, type: "verified_by" })
4098
+ );
4099
+ const hasCode = codeFiles.length > 0;
4100
+ const hasTests = testFiles.length > 0;
4101
+ return {
4102
+ requirementId: req.id,
4103
+ requirementName: req.name,
4104
+ index: req.metadata?.index ?? 0,
4105
+ codeFiles,
4106
+ testFiles,
4107
+ status: determineCoverageStatus(hasCode, hasTests),
4108
+ maxConfidence: computeMaxConfidence(codeFiles, testFiles)
4109
+ };
4110
+ }
4111
+ function computeSummary(requirements) {
4112
+ const total = requirements.length;
4113
+ const withCode = requirements.filter((r) => r.codeFiles.length > 0).length;
4114
+ const withTests = requirements.filter((r) => r.testFiles.length > 0).length;
4115
+ const fullyTraced = requirements.filter((r) => r.status === "full").length;
4116
+ const untraceable = requirements.filter((r) => r.status === "none").length;
4117
+ const coveragePercent = total > 0 ? Math.round(fullyTraced / total * 100) : 0;
4118
+ return { total, withCode, withTests, fullyTraced, untraceable, coveragePercent };
4119
+ }
3806
4120
  function queryTraceability(store, options) {
3807
4121
  const allRequirements = store.findNodes({ type: "requirement" });
3808
4122
  const filtered = allRequirements.filter((node) => {
@@ -3830,56 +4144,13 @@ function queryTraceability(store, options) {
3830
4144
  const firstMeta = firstReq.metadata;
3831
4145
  const specPath = firstMeta?.specPath ?? "";
3832
4146
  const featureName = firstMeta?.featureName ?? "";
3833
- const requirements = [];
3834
- for (const req of reqs) {
3835
- const requiresEdges = store.getEdges({ from: req.id, type: "requires" });
3836
- const codeFiles = requiresEdges.map((edge) => {
3837
- const targetNode = store.getNode(edge.to);
3838
- return {
3839
- path: targetNode?.path ?? edge.to,
3840
- confidence: edge.confidence ?? edge.metadata?.confidence ?? 0,
3841
- method: edge.metadata?.method ?? "convention"
3842
- };
3843
- });
3844
- const verifiedByEdges = store.getEdges({ from: req.id, type: "verified_by" });
3845
- const testFiles = verifiedByEdges.map((edge) => {
3846
- const targetNode = store.getNode(edge.to);
3847
- return {
3848
- path: targetNode?.path ?? edge.to,
3849
- confidence: edge.confidence ?? edge.metadata?.confidence ?? 0,
3850
- method: edge.metadata?.method ?? "convention"
3851
- };
3852
- });
3853
- const hasCode = codeFiles.length > 0;
3854
- const hasTests = testFiles.length > 0;
3855
- const status = hasCode && hasTests ? "full" : hasCode ? "code-only" : hasTests ? "test-only" : "none";
3856
- const allConfidences = [
3857
- ...codeFiles.map((f) => f.confidence),
3858
- ...testFiles.map((f) => f.confidence)
3859
- ];
3860
- const maxConfidence = allConfidences.length > 0 ? Math.max(...allConfidences) : 0;
3861
- requirements.push({
3862
- requirementId: req.id,
3863
- requirementName: req.name,
3864
- index: req.metadata?.index ?? 0,
3865
- codeFiles,
3866
- testFiles,
3867
- status,
3868
- maxConfidence
3869
- });
3870
- }
4147
+ const requirements = reqs.map((req) => buildRequirementCoverage(store, req));
3871
4148
  requirements.sort((a, b) => a.index - b.index);
3872
- const total = requirements.length;
3873
- const withCode = requirements.filter((r) => r.codeFiles.length > 0).length;
3874
- const withTests = requirements.filter((r) => r.testFiles.length > 0).length;
3875
- const fullyTraced = requirements.filter((r) => r.status === "full").length;
3876
- const untraceable = requirements.filter((r) => r.status === "none").length;
3877
- const coveragePercent = total > 0 ? Math.round(fullyTraced / total * 100) : 0;
3878
4149
  results.push({
3879
4150
  specPath,
3880
4151
  featureName,
3881
4152
  requirements,
3882
- summary: { total, withCode, withTests, fullyTraced, untraceable, coveragePercent }
4153
+ summary: computeSummary(requirements)
3883
4154
  });
3884
4155
  }
3885
4156
  return results;
@@ -4703,12 +4974,14 @@ var ConflictPredictor = class {
4703
4974
  };
4704
4975
 
4705
4976
  // src/index.ts
4706
- var VERSION = "0.2.0";
4977
+ var VERSION = "0.4.0";
4707
4978
  export {
4708
4979
  Assembler,
4709
4980
  CIConnector,
4710
4981
  CURRENT_SCHEMA_VERSION,
4982
+ CascadeSimulator,
4711
4983
  CodeIngestor,
4984
+ CompositeProbabilityStrategy,
4712
4985
  ConflictPredictor,
4713
4986
  ConfluenceConnector,
4714
4987
  ContextQL,
@@ -4743,6 +5016,7 @@ export {
4743
5016
  VERSION,
4744
5017
  VectorStore,
4745
5018
  askGraph,
5019
+ classifyNodeCategory,
4746
5020
  groupNodesByImpact,
4747
5021
  linkToCode,
4748
5022
  loadGraph,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/graph",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "license": "MIT",
5
5
  "description": "Knowledge graph for context assembly in Harness Engineering",
6
6
  "main": "./dist/index.js",
@@ -19,8 +19,7 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "minimatch": "^10.2.5",
22
- "zod": "^3.25.76",
23
- "@harness-engineering/types": "0.8.1"
22
+ "zod": "^3.25.76"
24
23
  },
25
24
  "optionalDependencies": {
26
25
  "tree-sitter": "^0.22.4",