@harness-engineering/graph 0.3.5 → 0.4.0

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
@@ -35,7 +35,9 @@ var NODE_TYPES = [
35
35
  // Design
36
36
  "design_token",
37
37
  "aesthetic_intent",
38
- "design_constraint"
38
+ "design_constraint",
39
+ // Traceability
40
+ "requirement"
39
41
  ];
40
42
  var EDGE_TYPES = [
41
43
  // Code relationships
@@ -64,7 +66,11 @@ var EDGE_TYPES = [
64
66
  "uses_token",
65
67
  "declares_intent",
66
68
  "violates_design",
67
- "platform_binding"
69
+ "platform_binding",
70
+ // Traceability relationships
71
+ "requires",
72
+ "verified_by",
73
+ "tested_by"
68
74
  ];
69
75
  var OBSERVABILITY_TYPES = /* @__PURE__ */ new Set(["span", "metric", "log"]);
70
76
  var CURRENT_SCHEMA_VERSION = 1;
@@ -598,6 +604,7 @@ var CodeIngestor = class {
598
604
  this.store.addEdge(edge);
599
605
  edgesAdded++;
600
606
  }
607
+ edgesAdded += this.extractReqAnnotations(fileContents, rootDir);
601
608
  return {
602
609
  nodesAdded,
603
610
  nodesUpdated: 0,
@@ -956,6 +963,48 @@ var CodeIngestor = class {
956
963
  if (/\.jsx?$/.test(filePath)) return "javascript";
957
964
  return "unknown";
958
965
  }
966
+ /**
967
+ * Scan file contents for @req annotations and create verified_by edges
968
+ * linking requirement nodes to the annotated files.
969
+ * Format: // @req <feature-name>#<index>
970
+ */
971
+ extractReqAnnotations(fileContents, rootDir) {
972
+ const REQ_TAG = /\/\/\s*@req\s+([\w-]+)#(\d+)/g;
973
+ const reqNodes = this.store.findNodes({ type: "requirement" });
974
+ let edgesAdded = 0;
975
+ for (const [filePath, content] of fileContents) {
976
+ let match;
977
+ REQ_TAG.lastIndex = 0;
978
+ while ((match = REQ_TAG.exec(content)) !== null) {
979
+ const featureName = match[1];
980
+ const reqIndex = parseInt(match[2], 10);
981
+ const reqNode = reqNodes.find(
982
+ (n) => n.metadata.featureName === featureName && n.metadata.index === reqIndex
983
+ );
984
+ if (!reqNode) {
985
+ console.warn(
986
+ `@req annotation references non-existent requirement: ${featureName}#${reqIndex} in ${filePath}`
987
+ );
988
+ continue;
989
+ }
990
+ const relPath = path.relative(rootDir, filePath).replace(/\\/g, "/");
991
+ const fileNodeId = `file:${relPath}`;
992
+ this.store.addEdge({
993
+ from: reqNode.id,
994
+ to: fileNodeId,
995
+ type: "verified_by",
996
+ confidence: 1,
997
+ metadata: {
998
+ method: "annotation",
999
+ tag: `@req ${featureName}#${reqIndex}`,
1000
+ confidence: 1
1001
+ }
1002
+ });
1003
+ edgesAdded++;
1004
+ }
1005
+ }
1006
+ return edgesAdded;
1007
+ }
959
1008
  };
960
1009
 
961
1010
  // src/ingest/GitIngestor.ts
@@ -1432,8 +1481,218 @@ var KnowledgeIngestor = class {
1432
1481
  }
1433
1482
  };
1434
1483
 
1435
- // src/ingest/connectors/ConnectorUtils.ts
1484
+ // src/ingest/RequirementIngestor.ts
1485
+ import * as fs3 from "fs/promises";
1486
+ import * as path4 from "path";
1487
+ var REQUIREMENT_SECTIONS = [
1488
+ "Observable Truths",
1489
+ "Success Criteria",
1490
+ "Acceptance Criteria"
1491
+ ];
1492
+ var SECTION_HEADING_RE = /^#{2,3}\s+(.+)$/;
1493
+ var NUMBERED_ITEM_RE = /^\s*(\d+)\.\s+(.+)$/;
1494
+ function detectEarsPattern(text) {
1495
+ const lower = text.toLowerCase();
1496
+ if (/^if\b.+\bthen\b.+\bshall not\b/.test(lower)) return "unwanted";
1497
+ if (/^when\b/.test(lower)) return "event-driven";
1498
+ if (/^while\b/.test(lower)) return "state-driven";
1499
+ if (/^where\b/.test(lower)) return "optional";
1500
+ if (/^the\s+\w+\s+shall\b/.test(lower)) return "ubiquitous";
1501
+ return void 0;
1502
+ }
1436
1503
  var CODE_NODE_TYPES2 = ["file", "function", "class", "method", "interface", "variable"];
1504
+ var RequirementIngestor = class {
1505
+ constructor(store) {
1506
+ this.store = store;
1507
+ }
1508
+ store;
1509
+ /**
1510
+ * Scan a specs directory for `<feature>/proposal.md` files,
1511
+ * extract numbered requirements from recognized sections,
1512
+ * and create requirement nodes with convention-based edges.
1513
+ */
1514
+ async ingestSpecs(specsDir) {
1515
+ const start = Date.now();
1516
+ const errors = [];
1517
+ let nodesAdded = 0;
1518
+ let edgesAdded = 0;
1519
+ let featureDirs;
1520
+ try {
1521
+ const entries = await fs3.readdir(specsDir, { withFileTypes: true });
1522
+ featureDirs = entries.filter((e) => e.isDirectory()).map((e) => path4.join(specsDir, e.name));
1523
+ } catch {
1524
+ return emptyResult(Date.now() - start);
1525
+ }
1526
+ for (const featureDir of featureDirs) {
1527
+ const featureName = path4.basename(featureDir);
1528
+ const specPath = path4.join(featureDir, "proposal.md");
1529
+ let content;
1530
+ try {
1531
+ content = await fs3.readFile(specPath, "utf-8");
1532
+ } catch {
1533
+ continue;
1534
+ }
1535
+ try {
1536
+ const specHash = hash(specPath);
1537
+ const specNodeId = `file:${specPath}`;
1538
+ this.store.addNode({
1539
+ id: specNodeId,
1540
+ type: "document",
1541
+ name: path4.basename(specPath),
1542
+ path: specPath,
1543
+ metadata: { featureName }
1544
+ });
1545
+ const requirements = this.extractRequirements(content, specPath, specHash, featureName);
1546
+ for (const req of requirements) {
1547
+ this.store.addNode(req.node);
1548
+ nodesAdded++;
1549
+ this.store.addEdge({
1550
+ from: req.node.id,
1551
+ to: specNodeId,
1552
+ type: "specifies"
1553
+ });
1554
+ edgesAdded++;
1555
+ edgesAdded += this.linkByPathPattern(req.node.id, featureName);
1556
+ edgesAdded += this.linkByKeywordOverlap(req.node.id, req.node.name);
1557
+ }
1558
+ } catch (err) {
1559
+ errors.push(`${specPath}: ${err instanceof Error ? err.message : String(err)}`);
1560
+ }
1561
+ }
1562
+ return {
1563
+ nodesAdded,
1564
+ nodesUpdated: 0,
1565
+ edgesAdded,
1566
+ edgesUpdated: 0,
1567
+ errors,
1568
+ durationMs: Date.now() - start
1569
+ };
1570
+ }
1571
+ /**
1572
+ * Parse markdown content and extract numbered items from recognized sections.
1573
+ */
1574
+ extractRequirements(content, specPath, specHash, featureName) {
1575
+ const lines = content.split("\n");
1576
+ const results = [];
1577
+ let currentSection;
1578
+ let inRequirementSection = false;
1579
+ let globalIndex = 0;
1580
+ for (let i = 0; i < lines.length; i++) {
1581
+ const line = lines[i];
1582
+ const headingMatch = line.match(SECTION_HEADING_RE);
1583
+ if (headingMatch) {
1584
+ const heading = headingMatch[1].trim();
1585
+ const isReqSection = REQUIREMENT_SECTIONS.some(
1586
+ (s) => heading.toLowerCase() === s.toLowerCase()
1587
+ );
1588
+ if (isReqSection) {
1589
+ currentSection = heading;
1590
+ inRequirementSection = true;
1591
+ } else {
1592
+ inRequirementSection = false;
1593
+ }
1594
+ continue;
1595
+ }
1596
+ if (!inRequirementSection) continue;
1597
+ const itemMatch = line.match(NUMBERED_ITEM_RE);
1598
+ if (!itemMatch) continue;
1599
+ const index = parseInt(itemMatch[1], 10);
1600
+ const text = itemMatch[2].trim();
1601
+ const rawText = line.trim();
1602
+ const lineNumber = i + 1;
1603
+ globalIndex++;
1604
+ const nodeId = `req:${specHash}:${globalIndex}`;
1605
+ const earsPattern = detectEarsPattern(text);
1606
+ results.push({
1607
+ node: {
1608
+ id: nodeId,
1609
+ type: "requirement",
1610
+ name: text,
1611
+ path: specPath,
1612
+ location: {
1613
+ fileId: `file:${specPath}`,
1614
+ startLine: lineNumber,
1615
+ endLine: lineNumber
1616
+ },
1617
+ metadata: {
1618
+ specPath,
1619
+ index,
1620
+ section: currentSection,
1621
+ rawText,
1622
+ earsPattern,
1623
+ featureName
1624
+ }
1625
+ }
1626
+ });
1627
+ }
1628
+ return results;
1629
+ }
1630
+ /**
1631
+ * Convention-based linking: match requirement to code/test files
1632
+ * by feature name in their path.
1633
+ */
1634
+ linkByPathPattern(reqId, featureName) {
1635
+ let count = 0;
1636
+ const fileNodes = this.store.findNodes({ type: "file" });
1637
+ for (const node of fileNodes) {
1638
+ if (!node.path) continue;
1639
+ const normalizedPath = node.path.replace(/\\/g, "/");
1640
+ const isCodeMatch = normalizedPath.includes("packages/") && path4.basename(normalizedPath).includes(featureName);
1641
+ const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
1642
+ path4.basename(normalizedPath).includes(featureName);
1643
+ if (isCodeMatch && !isTestMatch) {
1644
+ this.store.addEdge({
1645
+ from: reqId,
1646
+ to: node.id,
1647
+ type: "requires",
1648
+ confidence: 0.5,
1649
+ metadata: { method: "convention", matchReason: "path-pattern" }
1650
+ });
1651
+ count++;
1652
+ } else if (isTestMatch) {
1653
+ this.store.addEdge({
1654
+ from: reqId,
1655
+ to: node.id,
1656
+ type: "verified_by",
1657
+ confidence: 0.5,
1658
+ metadata: { method: "convention", matchReason: "path-pattern" }
1659
+ });
1660
+ count++;
1661
+ }
1662
+ }
1663
+ return count;
1664
+ }
1665
+ /**
1666
+ * Convention-based linking: match requirement text to code nodes
1667
+ * by keyword overlap (function/class names appearing in requirement text).
1668
+ */
1669
+ linkByKeywordOverlap(reqId, reqText) {
1670
+ let count = 0;
1671
+ for (const nodeType of CODE_NODE_TYPES2) {
1672
+ const codeNodes = this.store.findNodes({ type: nodeType });
1673
+ for (const node of codeNodes) {
1674
+ if (node.name.length < 3) continue;
1675
+ const escaped = node.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1676
+ const namePattern = new RegExp(`\\b${escaped}\\b`, "i");
1677
+ if (namePattern.test(reqText)) {
1678
+ const edgeType = node.path && node.path.replace(/\\/g, "/").includes("/tests/") ? "verified_by" : "requires";
1679
+ this.store.addEdge({
1680
+ from: reqId,
1681
+ to: node.id,
1682
+ type: edgeType,
1683
+ confidence: 0.6,
1684
+ metadata: { method: "convention", matchReason: "keyword-overlap" }
1685
+ });
1686
+ count++;
1687
+ }
1688
+ }
1689
+ }
1690
+ return count;
1691
+ }
1692
+ };
1693
+
1694
+ // src/ingest/connectors/ConnectorUtils.ts
1695
+ var CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
1437
1696
  var SANITIZE_RULES = [
1438
1697
  // Strip XML/HTML-like instruction tags that could be interpreted as system prompts
1439
1698
  {
@@ -1468,7 +1727,7 @@ function sanitizeExternalText(text, maxLength = 2e3) {
1468
1727
  }
1469
1728
  function linkToCode(store, content, sourceNodeId, edgeType, options) {
1470
1729
  let edgesCreated = 0;
1471
- for (const type of CODE_NODE_TYPES2) {
1730
+ for (const type of CODE_NODE_TYPES3) {
1472
1731
  const nodes = store.findNodes({ type });
1473
1732
  for (const node of nodes) {
1474
1733
  if (node.name.length < 3) continue;
@@ -1488,12 +1747,12 @@ function linkToCode(store, content, sourceNodeId, edgeType, options) {
1488
1747
  }
1489
1748
 
1490
1749
  // src/ingest/connectors/SyncManager.ts
1491
- import * as fs3 from "fs/promises";
1492
- import * as path4 from "path";
1750
+ import * as fs4 from "fs/promises";
1751
+ import * as path5 from "path";
1493
1752
  var SyncManager = class {
1494
1753
  constructor(store, graphDir) {
1495
1754
  this.store = store;
1496
- this.metadataPath = path4.join(graphDir, "sync-metadata.json");
1755
+ this.metadataPath = path5.join(graphDir, "sync-metadata.json");
1497
1756
  }
1498
1757
  store;
1499
1758
  registrations = /* @__PURE__ */ new Map();
@@ -1548,15 +1807,15 @@ var SyncManager = class {
1548
1807
  }
1549
1808
  async loadMetadata() {
1550
1809
  try {
1551
- const raw = await fs3.readFile(this.metadataPath, "utf-8");
1810
+ const raw = await fs4.readFile(this.metadataPath, "utf-8");
1552
1811
  return JSON.parse(raw);
1553
1812
  } catch {
1554
1813
  return { connectors: {} };
1555
1814
  }
1556
1815
  }
1557
1816
  async saveMetadata(metadata) {
1558
- await fs3.mkdir(path4.dirname(this.metadataPath), { recursive: true });
1559
- await fs3.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1817
+ await fs4.mkdir(path5.dirname(this.metadataPath), { recursive: true });
1818
+ await fs4.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1560
1819
  }
1561
1820
  };
1562
1821
 
@@ -2085,7 +2344,7 @@ var FusionLayer = class {
2085
2344
  };
2086
2345
 
2087
2346
  // src/entropy/GraphEntropyAdapter.ts
2088
- var CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
2347
+ var CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
2089
2348
  var GraphEntropyAdapter = class {
2090
2349
  constructor(store) {
2091
2350
  this.store = store;
@@ -2152,7 +2411,7 @@ var GraphEntropyAdapter = class {
2152
2411
  }
2153
2412
  findEntryPoints() {
2154
2413
  const entryPoints = [];
2155
- for (const nodeType of CODE_NODE_TYPES3) {
2414
+ for (const nodeType of CODE_NODE_TYPES4) {
2156
2415
  const nodes = this.store.findNodes({ type: nodeType });
2157
2416
  for (const node of nodes) {
2158
2417
  const isIndexFile = nodeType === "file" && node.name === "index.ts";
@@ -2188,7 +2447,7 @@ var GraphEntropyAdapter = class {
2188
2447
  }
2189
2448
  collectUnreachableNodes(visited) {
2190
2449
  const unreachableNodes = [];
2191
- for (const nodeType of CODE_NODE_TYPES3) {
2450
+ for (const nodeType of CODE_NODE_TYPES4) {
2192
2451
  const nodes = this.store.findNodes({ type: nodeType });
2193
2452
  for (const node of nodes) {
2194
2453
  if (!visited.has(node.id)) {
@@ -3008,9 +3267,9 @@ var EntityExtractor = class {
3008
3267
  }
3009
3268
  const pathConsumed = /* @__PURE__ */ new Set();
3010
3269
  for (const match of trimmed.matchAll(FILE_PATH_RE)) {
3011
- const path6 = match[0];
3012
- add(path6);
3013
- pathConsumed.add(path6);
3270
+ const path7 = match[0];
3271
+ add(path7);
3272
+ pathConsumed.add(path7);
3014
3273
  }
3015
3274
  const allConsumed = buildConsumedSet(quotedConsumed, casingConsumed, pathConsumed);
3016
3275
  const words = trimmed.split(/\s+/);
@@ -3081,8 +3340,8 @@ var EntityResolver = class {
3081
3340
  if (isPathLike && node.path.includes(raw)) {
3082
3341
  return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
3083
3342
  }
3084
- const basename4 = node.path.split("/").pop() ?? "";
3085
- if (basename4.includes(raw)) {
3343
+ const basename5 = node.path.split("/").pop() ?? "";
3344
+ if (basename5.includes(raw)) {
3086
3345
  return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
3087
3346
  }
3088
3347
  if (raw.length >= 4 && node.path.includes(raw)) {
@@ -3157,13 +3416,13 @@ var ResponseFormatter = class {
3157
3416
  const context = Array.isArray(d?.context) ? d.context : [];
3158
3417
  const firstEntity = entities[0];
3159
3418
  const nodeType = firstEntity?.node.type ?? "node";
3160
- const path6 = firstEntity?.node.path ?? "unknown";
3419
+ const path7 = firstEntity?.node.path ?? "unknown";
3161
3420
  let neighborCount = 0;
3162
3421
  const firstContext = context[0];
3163
3422
  if (firstContext && Array.isArray(firstContext.nodes)) {
3164
3423
  neighborCount = firstContext.nodes.length;
3165
3424
  }
3166
- return `**${entityName}** is a ${nodeType} at \`${path6}\`. Connected to ${neighborCount} nodes.`;
3425
+ return `**${entityName}** is a ${nodeType} at \`${path7}\`. Connected to ${neighborCount} nodes.`;
3167
3426
  }
3168
3427
  formatAnomaly(data) {
3169
3428
  const d = data;
@@ -3304,7 +3563,7 @@ var PHASE_NODE_TYPES = {
3304
3563
  debug: ["failure", "learning", "function", "method"],
3305
3564
  plan: ["adr", "document", "module", "layer"]
3306
3565
  };
3307
- var CODE_NODE_TYPES4 = /* @__PURE__ */ new Set([
3566
+ var CODE_NODE_TYPES5 = /* @__PURE__ */ new Set([
3308
3567
  "file",
3309
3568
  "function",
3310
3569
  "class",
@@ -3519,7 +3778,7 @@ var Assembler = class {
3519
3778
  */
3520
3779
  checkCoverage() {
3521
3780
  const codeNodes = [];
3522
- for (const type of CODE_NODE_TYPES4) {
3781
+ for (const type of CODE_NODE_TYPES5) {
3523
3782
  codeNodes.push(...this.store.findNodes({ type }));
3524
3783
  }
3525
3784
  const documented = [];
@@ -3543,6 +3802,89 @@ var Assembler = class {
3543
3802
  }
3544
3803
  };
3545
3804
 
3805
+ // src/query/Traceability.ts
3806
+ function queryTraceability(store, options) {
3807
+ const allRequirements = store.findNodes({ type: "requirement" });
3808
+ const filtered = allRequirements.filter((node) => {
3809
+ if (options?.specPath && node.metadata?.specPath !== options.specPath) return false;
3810
+ if (options?.featureName && node.metadata?.featureName !== options.featureName) return false;
3811
+ return true;
3812
+ });
3813
+ if (filtered.length === 0) return [];
3814
+ const groups = /* @__PURE__ */ new Map();
3815
+ for (const req of filtered) {
3816
+ const meta = req.metadata;
3817
+ const specPath = meta?.specPath ?? "";
3818
+ const featureName = meta?.featureName ?? "";
3819
+ const key = `${specPath}\0${featureName}`;
3820
+ const list = groups.get(key);
3821
+ if (list) {
3822
+ list.push(req);
3823
+ } else {
3824
+ groups.set(key, [req]);
3825
+ }
3826
+ }
3827
+ const results = [];
3828
+ for (const [, reqs] of groups) {
3829
+ const firstReq = reqs[0];
3830
+ const firstMeta = firstReq.metadata;
3831
+ const specPath = firstMeta?.specPath ?? "";
3832
+ 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
+ }
3871
+ 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
+ results.push({
3879
+ specPath,
3880
+ featureName,
3881
+ requirements,
3882
+ summary: { total, withCode, withTests, fullyTraced, untraceable, coveragePercent }
3883
+ });
3884
+ }
3885
+ return results;
3886
+ }
3887
+
3546
3888
  // src/constraints/GraphConstraintAdapter.ts
3547
3889
  import { minimatch } from "minimatch";
3548
3890
  import { relative as relative2 } from "path";
@@ -3602,14 +3944,14 @@ var GraphConstraintAdapter = class {
3602
3944
  };
3603
3945
 
3604
3946
  // src/ingest/DesignIngestor.ts
3605
- import * as fs4 from "fs/promises";
3606
- import * as path5 from "path";
3947
+ import * as fs5 from "fs/promises";
3948
+ import * as path6 from "path";
3607
3949
  function isDTCGToken(obj) {
3608
3950
  return typeof obj === "object" && obj !== null && "$value" in obj && "$type" in obj;
3609
3951
  }
3610
3952
  async function readFileOrNull(filePath) {
3611
3953
  try {
3612
- return await fs4.readFile(filePath, "utf-8");
3954
+ return await fs5.readFile(filePath, "utf-8");
3613
3955
  } catch {
3614
3956
  return null;
3615
3957
  }
@@ -3755,8 +4097,8 @@ var DesignIngestor = class {
3755
4097
  async ingestAll(designDir) {
3756
4098
  const start = Date.now();
3757
4099
  const [tokensResult, intentResult] = await Promise.all([
3758
- this.ingestTokens(path5.join(designDir, "tokens.json")),
3759
- this.ingestDesignIntent(path5.join(designDir, "DESIGN.md"))
4100
+ this.ingestTokens(path6.join(designDir, "tokens.json")),
4101
+ this.ingestDesignIntent(path6.join(designDir, "DESIGN.md"))
3760
4102
  ]);
3761
4103
  const merged = mergeResults(tokensResult, intentResult);
3762
4104
  return { ...merged, durationMs: Date.now() - start };
@@ -4005,10 +4347,10 @@ var TaskIndependenceAnalyzer = class {
4005
4347
  includeTypes: ["file"]
4006
4348
  });
4007
4349
  for (const n of queryResult.nodes) {
4008
- const path6 = n.path ?? n.id.replace(/^file:/, "");
4009
- if (!fileSet.has(path6)) {
4010
- if (!result.has(path6)) {
4011
- result.set(path6, file);
4350
+ const path7 = n.path ?? n.id.replace(/^file:/, "");
4351
+ if (!fileSet.has(path7)) {
4352
+ if (!result.has(path7)) {
4353
+ result.set(path7, file);
4012
4354
  }
4013
4355
  }
4014
4356
  }
@@ -4392,6 +4734,7 @@ export {
4392
4734
  KnowledgeIngestor,
4393
4735
  NODE_TYPES,
4394
4736
  OBSERVABILITY_TYPES,
4737
+ RequirementIngestor,
4395
4738
  ResponseFormatter,
4396
4739
  SlackConnector,
4397
4740
  SyncManager,
@@ -4404,5 +4747,6 @@ export {
4404
4747
  linkToCode,
4405
4748
  loadGraph,
4406
4749
  project,
4750
+ queryTraceability,
4407
4751
  saveGraph
4408
4752
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/graph",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "description": "Knowledge graph for context assembly in Harness Engineering",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "minimatch": "^10.2.5",
22
22
  "zod": "^3.25.76",
23
- "@harness-engineering/types": "0.7.0"
23
+ "@harness-engineering/types": "0.8.1"
24
24
  },
25
25
  "optionalDependencies": {
26
26
  "tree-sitter": "^0.22.4",