@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.js CHANGED
@@ -59,6 +59,7 @@ __export(index_exports, {
59
59
  KnowledgeIngestor: () => KnowledgeIngestor,
60
60
  NODE_TYPES: () => NODE_TYPES,
61
61
  OBSERVABILITY_TYPES: () => OBSERVABILITY_TYPES,
62
+ RequirementIngestor: () => RequirementIngestor,
62
63
  ResponseFormatter: () => ResponseFormatter,
63
64
  SlackConnector: () => SlackConnector,
64
65
  SyncManager: () => SyncManager,
@@ -71,6 +72,7 @@ __export(index_exports, {
71
72
  linkToCode: () => linkToCode,
72
73
  loadGraph: () => loadGraph,
73
74
  project: () => project,
75
+ queryTraceability: () => queryTraceability,
74
76
  saveGraph: () => saveGraph
75
77
  });
76
78
  module.exports = __toCommonJS(index_exports);
@@ -112,7 +114,9 @@ var NODE_TYPES = [
112
114
  // Design
113
115
  "design_token",
114
116
  "aesthetic_intent",
115
- "design_constraint"
117
+ "design_constraint",
118
+ // Traceability
119
+ "requirement"
116
120
  ];
117
121
  var EDGE_TYPES = [
118
122
  // Code relationships
@@ -141,7 +145,11 @@ var EDGE_TYPES = [
141
145
  "uses_token",
142
146
  "declares_intent",
143
147
  "violates_design",
144
- "platform_binding"
148
+ "platform_binding",
149
+ // Traceability relationships
150
+ "requires",
151
+ "verified_by",
152
+ "tested_by"
145
153
  ];
146
154
  var OBSERVABILITY_TYPES = /* @__PURE__ */ new Set(["span", "metric", "log"]);
147
155
  var CURRENT_SCHEMA_VERSION = 1;
@@ -675,6 +683,7 @@ var CodeIngestor = class {
675
683
  this.store.addEdge(edge);
676
684
  edgesAdded++;
677
685
  }
686
+ edgesAdded += this.extractReqAnnotations(fileContents, rootDir);
678
687
  return {
679
688
  nodesAdded,
680
689
  nodesUpdated: 0,
@@ -1033,6 +1042,48 @@ var CodeIngestor = class {
1033
1042
  if (/\.jsx?$/.test(filePath)) return "javascript";
1034
1043
  return "unknown";
1035
1044
  }
1045
+ /**
1046
+ * Scan file contents for @req annotations and create verified_by edges
1047
+ * linking requirement nodes to the annotated files.
1048
+ * Format: // @req <feature-name>#<index>
1049
+ */
1050
+ extractReqAnnotations(fileContents, rootDir) {
1051
+ const REQ_TAG = /\/\/\s*@req\s+([\w-]+)#(\d+)/g;
1052
+ const reqNodes = this.store.findNodes({ type: "requirement" });
1053
+ let edgesAdded = 0;
1054
+ for (const [filePath, content] of fileContents) {
1055
+ let match;
1056
+ REQ_TAG.lastIndex = 0;
1057
+ while ((match = REQ_TAG.exec(content)) !== null) {
1058
+ const featureName = match[1];
1059
+ const reqIndex = parseInt(match[2], 10);
1060
+ const reqNode = reqNodes.find(
1061
+ (n) => n.metadata.featureName === featureName && n.metadata.index === reqIndex
1062
+ );
1063
+ if (!reqNode) {
1064
+ console.warn(
1065
+ `@req annotation references non-existent requirement: ${featureName}#${reqIndex} in ${filePath}`
1066
+ );
1067
+ continue;
1068
+ }
1069
+ const relPath = path.relative(rootDir, filePath).replace(/\\/g, "/");
1070
+ const fileNodeId = `file:${relPath}`;
1071
+ this.store.addEdge({
1072
+ from: reqNode.id,
1073
+ to: fileNodeId,
1074
+ type: "verified_by",
1075
+ confidence: 1,
1076
+ metadata: {
1077
+ method: "annotation",
1078
+ tag: `@req ${featureName}#${reqIndex}`,
1079
+ confidence: 1
1080
+ }
1081
+ });
1082
+ edgesAdded++;
1083
+ }
1084
+ }
1085
+ return edgesAdded;
1086
+ }
1036
1087
  };
1037
1088
 
1038
1089
  // src/ingest/GitIngestor.ts
@@ -1509,8 +1560,218 @@ var KnowledgeIngestor = class {
1509
1560
  }
1510
1561
  };
1511
1562
 
1512
- // src/ingest/connectors/ConnectorUtils.ts
1563
+ // src/ingest/RequirementIngestor.ts
1564
+ var fs3 = __toESM(require("fs/promises"));
1565
+ var path4 = __toESM(require("path"));
1566
+ var REQUIREMENT_SECTIONS = [
1567
+ "Observable Truths",
1568
+ "Success Criteria",
1569
+ "Acceptance Criteria"
1570
+ ];
1571
+ var SECTION_HEADING_RE = /^#{2,3}\s+(.+)$/;
1572
+ var NUMBERED_ITEM_RE = /^\s*(\d+)\.\s+(.+)$/;
1573
+ function detectEarsPattern(text) {
1574
+ const lower = text.toLowerCase();
1575
+ if (/^if\b.+\bthen\b.+\bshall not\b/.test(lower)) return "unwanted";
1576
+ if (/^when\b/.test(lower)) return "event-driven";
1577
+ if (/^while\b/.test(lower)) return "state-driven";
1578
+ if (/^where\b/.test(lower)) return "optional";
1579
+ if (/^the\s+\w+\s+shall\b/.test(lower)) return "ubiquitous";
1580
+ return void 0;
1581
+ }
1513
1582
  var CODE_NODE_TYPES2 = ["file", "function", "class", "method", "interface", "variable"];
1583
+ var RequirementIngestor = class {
1584
+ constructor(store) {
1585
+ this.store = store;
1586
+ }
1587
+ store;
1588
+ /**
1589
+ * Scan a specs directory for `<feature>/proposal.md` files,
1590
+ * extract numbered requirements from recognized sections,
1591
+ * and create requirement nodes with convention-based edges.
1592
+ */
1593
+ async ingestSpecs(specsDir) {
1594
+ const start = Date.now();
1595
+ const errors = [];
1596
+ let nodesAdded = 0;
1597
+ let edgesAdded = 0;
1598
+ let featureDirs;
1599
+ try {
1600
+ const entries = await fs3.readdir(specsDir, { withFileTypes: true });
1601
+ featureDirs = entries.filter((e) => e.isDirectory()).map((e) => path4.join(specsDir, e.name));
1602
+ } catch {
1603
+ return emptyResult(Date.now() - start);
1604
+ }
1605
+ for (const featureDir of featureDirs) {
1606
+ const featureName = path4.basename(featureDir);
1607
+ const specPath = path4.join(featureDir, "proposal.md");
1608
+ let content;
1609
+ try {
1610
+ content = await fs3.readFile(specPath, "utf-8");
1611
+ } catch {
1612
+ continue;
1613
+ }
1614
+ try {
1615
+ const specHash = hash(specPath);
1616
+ const specNodeId = `file:${specPath}`;
1617
+ this.store.addNode({
1618
+ id: specNodeId,
1619
+ type: "document",
1620
+ name: path4.basename(specPath),
1621
+ path: specPath,
1622
+ metadata: { featureName }
1623
+ });
1624
+ const requirements = this.extractRequirements(content, specPath, specHash, featureName);
1625
+ for (const req of requirements) {
1626
+ this.store.addNode(req.node);
1627
+ nodesAdded++;
1628
+ this.store.addEdge({
1629
+ from: req.node.id,
1630
+ to: specNodeId,
1631
+ type: "specifies"
1632
+ });
1633
+ edgesAdded++;
1634
+ edgesAdded += this.linkByPathPattern(req.node.id, featureName);
1635
+ edgesAdded += this.linkByKeywordOverlap(req.node.id, req.node.name);
1636
+ }
1637
+ } catch (err) {
1638
+ errors.push(`${specPath}: ${err instanceof Error ? err.message : String(err)}`);
1639
+ }
1640
+ }
1641
+ return {
1642
+ nodesAdded,
1643
+ nodesUpdated: 0,
1644
+ edgesAdded,
1645
+ edgesUpdated: 0,
1646
+ errors,
1647
+ durationMs: Date.now() - start
1648
+ };
1649
+ }
1650
+ /**
1651
+ * Parse markdown content and extract numbered items from recognized sections.
1652
+ */
1653
+ extractRequirements(content, specPath, specHash, featureName) {
1654
+ const lines = content.split("\n");
1655
+ const results = [];
1656
+ let currentSection;
1657
+ let inRequirementSection = false;
1658
+ let globalIndex = 0;
1659
+ for (let i = 0; i < lines.length; i++) {
1660
+ const line = lines[i];
1661
+ const headingMatch = line.match(SECTION_HEADING_RE);
1662
+ if (headingMatch) {
1663
+ const heading = headingMatch[1].trim();
1664
+ const isReqSection = REQUIREMENT_SECTIONS.some(
1665
+ (s) => heading.toLowerCase() === s.toLowerCase()
1666
+ );
1667
+ if (isReqSection) {
1668
+ currentSection = heading;
1669
+ inRequirementSection = true;
1670
+ } else {
1671
+ inRequirementSection = false;
1672
+ }
1673
+ continue;
1674
+ }
1675
+ if (!inRequirementSection) continue;
1676
+ const itemMatch = line.match(NUMBERED_ITEM_RE);
1677
+ if (!itemMatch) continue;
1678
+ const index = parseInt(itemMatch[1], 10);
1679
+ const text = itemMatch[2].trim();
1680
+ const rawText = line.trim();
1681
+ const lineNumber = i + 1;
1682
+ globalIndex++;
1683
+ const nodeId = `req:${specHash}:${globalIndex}`;
1684
+ const earsPattern = detectEarsPattern(text);
1685
+ results.push({
1686
+ node: {
1687
+ id: nodeId,
1688
+ type: "requirement",
1689
+ name: text,
1690
+ path: specPath,
1691
+ location: {
1692
+ fileId: `file:${specPath}`,
1693
+ startLine: lineNumber,
1694
+ endLine: lineNumber
1695
+ },
1696
+ metadata: {
1697
+ specPath,
1698
+ index,
1699
+ section: currentSection,
1700
+ rawText,
1701
+ earsPattern,
1702
+ featureName
1703
+ }
1704
+ }
1705
+ });
1706
+ }
1707
+ return results;
1708
+ }
1709
+ /**
1710
+ * Convention-based linking: match requirement to code/test files
1711
+ * by feature name in their path.
1712
+ */
1713
+ linkByPathPattern(reqId, featureName) {
1714
+ let count = 0;
1715
+ const fileNodes = this.store.findNodes({ type: "file" });
1716
+ for (const node of fileNodes) {
1717
+ if (!node.path) continue;
1718
+ const normalizedPath = node.path.replace(/\\/g, "/");
1719
+ const isCodeMatch = normalizedPath.includes("packages/") && path4.basename(normalizedPath).includes(featureName);
1720
+ const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
1721
+ path4.basename(normalizedPath).includes(featureName);
1722
+ if (isCodeMatch && !isTestMatch) {
1723
+ this.store.addEdge({
1724
+ from: reqId,
1725
+ to: node.id,
1726
+ type: "requires",
1727
+ confidence: 0.5,
1728
+ metadata: { method: "convention", matchReason: "path-pattern" }
1729
+ });
1730
+ count++;
1731
+ } else if (isTestMatch) {
1732
+ this.store.addEdge({
1733
+ from: reqId,
1734
+ to: node.id,
1735
+ type: "verified_by",
1736
+ confidence: 0.5,
1737
+ metadata: { method: "convention", matchReason: "path-pattern" }
1738
+ });
1739
+ count++;
1740
+ }
1741
+ }
1742
+ return count;
1743
+ }
1744
+ /**
1745
+ * Convention-based linking: match requirement text to code nodes
1746
+ * by keyword overlap (function/class names appearing in requirement text).
1747
+ */
1748
+ linkByKeywordOverlap(reqId, reqText) {
1749
+ let count = 0;
1750
+ for (const nodeType of CODE_NODE_TYPES2) {
1751
+ const codeNodes = this.store.findNodes({ type: nodeType });
1752
+ for (const node of codeNodes) {
1753
+ if (node.name.length < 3) continue;
1754
+ const escaped = node.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1755
+ const namePattern = new RegExp(`\\b${escaped}\\b`, "i");
1756
+ if (namePattern.test(reqText)) {
1757
+ const edgeType = node.path && node.path.replace(/\\/g, "/").includes("/tests/") ? "verified_by" : "requires";
1758
+ this.store.addEdge({
1759
+ from: reqId,
1760
+ to: node.id,
1761
+ type: edgeType,
1762
+ confidence: 0.6,
1763
+ metadata: { method: "convention", matchReason: "keyword-overlap" }
1764
+ });
1765
+ count++;
1766
+ }
1767
+ }
1768
+ }
1769
+ return count;
1770
+ }
1771
+ };
1772
+
1773
+ // src/ingest/connectors/ConnectorUtils.ts
1774
+ var CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
1514
1775
  var SANITIZE_RULES = [
1515
1776
  // Strip XML/HTML-like instruction tags that could be interpreted as system prompts
1516
1777
  {
@@ -1545,7 +1806,7 @@ function sanitizeExternalText(text, maxLength = 2e3) {
1545
1806
  }
1546
1807
  function linkToCode(store, content, sourceNodeId, edgeType, options) {
1547
1808
  let edgesCreated = 0;
1548
- for (const type of CODE_NODE_TYPES2) {
1809
+ for (const type of CODE_NODE_TYPES3) {
1549
1810
  const nodes = store.findNodes({ type });
1550
1811
  for (const node of nodes) {
1551
1812
  if (node.name.length < 3) continue;
@@ -1565,12 +1826,12 @@ function linkToCode(store, content, sourceNodeId, edgeType, options) {
1565
1826
  }
1566
1827
 
1567
1828
  // src/ingest/connectors/SyncManager.ts
1568
- var fs3 = __toESM(require("fs/promises"));
1569
- var path4 = __toESM(require("path"));
1829
+ var fs4 = __toESM(require("fs/promises"));
1830
+ var path5 = __toESM(require("path"));
1570
1831
  var SyncManager = class {
1571
1832
  constructor(store, graphDir) {
1572
1833
  this.store = store;
1573
- this.metadataPath = path4.join(graphDir, "sync-metadata.json");
1834
+ this.metadataPath = path5.join(graphDir, "sync-metadata.json");
1574
1835
  }
1575
1836
  store;
1576
1837
  registrations = /* @__PURE__ */ new Map();
@@ -1625,15 +1886,15 @@ var SyncManager = class {
1625
1886
  }
1626
1887
  async loadMetadata() {
1627
1888
  try {
1628
- const raw = await fs3.readFile(this.metadataPath, "utf-8");
1889
+ const raw = await fs4.readFile(this.metadataPath, "utf-8");
1629
1890
  return JSON.parse(raw);
1630
1891
  } catch {
1631
1892
  return { connectors: {} };
1632
1893
  }
1633
1894
  }
1634
1895
  async saveMetadata(metadata) {
1635
- await fs3.mkdir(path4.dirname(this.metadataPath), { recursive: true });
1636
- await fs3.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1896
+ await fs4.mkdir(path5.dirname(this.metadataPath), { recursive: true });
1897
+ await fs4.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
1637
1898
  }
1638
1899
  };
1639
1900
 
@@ -2162,7 +2423,7 @@ var FusionLayer = class {
2162
2423
  };
2163
2424
 
2164
2425
  // src/entropy/GraphEntropyAdapter.ts
2165
- var CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
2426
+ var CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
2166
2427
  var GraphEntropyAdapter = class {
2167
2428
  constructor(store) {
2168
2429
  this.store = store;
@@ -2229,7 +2490,7 @@ var GraphEntropyAdapter = class {
2229
2490
  }
2230
2491
  findEntryPoints() {
2231
2492
  const entryPoints = [];
2232
- for (const nodeType of CODE_NODE_TYPES3) {
2493
+ for (const nodeType of CODE_NODE_TYPES4) {
2233
2494
  const nodes = this.store.findNodes({ type: nodeType });
2234
2495
  for (const node of nodes) {
2235
2496
  const isIndexFile = nodeType === "file" && node.name === "index.ts";
@@ -2265,7 +2526,7 @@ var GraphEntropyAdapter = class {
2265
2526
  }
2266
2527
  collectUnreachableNodes(visited) {
2267
2528
  const unreachableNodes = [];
2268
- for (const nodeType of CODE_NODE_TYPES3) {
2529
+ for (const nodeType of CODE_NODE_TYPES4) {
2269
2530
  const nodes = this.store.findNodes({ type: nodeType });
2270
2531
  for (const node of nodes) {
2271
2532
  if (!visited.has(node.id)) {
@@ -3085,9 +3346,9 @@ var EntityExtractor = class {
3085
3346
  }
3086
3347
  const pathConsumed = /* @__PURE__ */ new Set();
3087
3348
  for (const match of trimmed.matchAll(FILE_PATH_RE)) {
3088
- const path6 = match[0];
3089
- add(path6);
3090
- pathConsumed.add(path6);
3349
+ const path7 = match[0];
3350
+ add(path7);
3351
+ pathConsumed.add(path7);
3091
3352
  }
3092
3353
  const allConsumed = buildConsumedSet(quotedConsumed, casingConsumed, pathConsumed);
3093
3354
  const words = trimmed.split(/\s+/);
@@ -3158,8 +3419,8 @@ var EntityResolver = class {
3158
3419
  if (isPathLike && node.path.includes(raw)) {
3159
3420
  return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
3160
3421
  }
3161
- const basename4 = node.path.split("/").pop() ?? "";
3162
- if (basename4.includes(raw)) {
3422
+ const basename5 = node.path.split("/").pop() ?? "";
3423
+ if (basename5.includes(raw)) {
3163
3424
  return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
3164
3425
  }
3165
3426
  if (raw.length >= 4 && node.path.includes(raw)) {
@@ -3234,13 +3495,13 @@ var ResponseFormatter = class {
3234
3495
  const context = Array.isArray(d?.context) ? d.context : [];
3235
3496
  const firstEntity = entities[0];
3236
3497
  const nodeType = firstEntity?.node.type ?? "node";
3237
- const path6 = firstEntity?.node.path ?? "unknown";
3498
+ const path7 = firstEntity?.node.path ?? "unknown";
3238
3499
  let neighborCount = 0;
3239
3500
  const firstContext = context[0];
3240
3501
  if (firstContext && Array.isArray(firstContext.nodes)) {
3241
3502
  neighborCount = firstContext.nodes.length;
3242
3503
  }
3243
- return `**${entityName}** is a ${nodeType} at \`${path6}\`. Connected to ${neighborCount} nodes.`;
3504
+ return `**${entityName}** is a ${nodeType} at \`${path7}\`. Connected to ${neighborCount} nodes.`;
3244
3505
  }
3245
3506
  formatAnomaly(data) {
3246
3507
  const d = data;
@@ -3381,7 +3642,7 @@ var PHASE_NODE_TYPES = {
3381
3642
  debug: ["failure", "learning", "function", "method"],
3382
3643
  plan: ["adr", "document", "module", "layer"]
3383
3644
  };
3384
- var CODE_NODE_TYPES4 = /* @__PURE__ */ new Set([
3645
+ var CODE_NODE_TYPES5 = /* @__PURE__ */ new Set([
3385
3646
  "file",
3386
3647
  "function",
3387
3648
  "class",
@@ -3596,7 +3857,7 @@ var Assembler = class {
3596
3857
  */
3597
3858
  checkCoverage() {
3598
3859
  const codeNodes = [];
3599
- for (const type of CODE_NODE_TYPES4) {
3860
+ for (const type of CODE_NODE_TYPES5) {
3600
3861
  codeNodes.push(...this.store.findNodes({ type }));
3601
3862
  }
3602
3863
  const documented = [];
@@ -3620,6 +3881,89 @@ var Assembler = class {
3620
3881
  }
3621
3882
  };
3622
3883
 
3884
+ // src/query/Traceability.ts
3885
+ function queryTraceability(store, options) {
3886
+ const allRequirements = store.findNodes({ type: "requirement" });
3887
+ const filtered = allRequirements.filter((node) => {
3888
+ if (options?.specPath && node.metadata?.specPath !== options.specPath) return false;
3889
+ if (options?.featureName && node.metadata?.featureName !== options.featureName) return false;
3890
+ return true;
3891
+ });
3892
+ if (filtered.length === 0) return [];
3893
+ const groups = /* @__PURE__ */ new Map();
3894
+ for (const req of filtered) {
3895
+ const meta = req.metadata;
3896
+ const specPath = meta?.specPath ?? "";
3897
+ const featureName = meta?.featureName ?? "";
3898
+ const key = `${specPath}\0${featureName}`;
3899
+ const list = groups.get(key);
3900
+ if (list) {
3901
+ list.push(req);
3902
+ } else {
3903
+ groups.set(key, [req]);
3904
+ }
3905
+ }
3906
+ const results = [];
3907
+ for (const [, reqs] of groups) {
3908
+ const firstReq = reqs[0];
3909
+ const firstMeta = firstReq.metadata;
3910
+ const specPath = firstMeta?.specPath ?? "";
3911
+ const featureName = firstMeta?.featureName ?? "";
3912
+ const requirements = [];
3913
+ for (const req of reqs) {
3914
+ const requiresEdges = store.getEdges({ from: req.id, type: "requires" });
3915
+ const codeFiles = requiresEdges.map((edge) => {
3916
+ const targetNode = store.getNode(edge.to);
3917
+ return {
3918
+ path: targetNode?.path ?? edge.to,
3919
+ confidence: edge.confidence ?? edge.metadata?.confidence ?? 0,
3920
+ method: edge.metadata?.method ?? "convention"
3921
+ };
3922
+ });
3923
+ const verifiedByEdges = store.getEdges({ from: req.id, type: "verified_by" });
3924
+ const testFiles = verifiedByEdges.map((edge) => {
3925
+ const targetNode = store.getNode(edge.to);
3926
+ return {
3927
+ path: targetNode?.path ?? edge.to,
3928
+ confidence: edge.confidence ?? edge.metadata?.confidence ?? 0,
3929
+ method: edge.metadata?.method ?? "convention"
3930
+ };
3931
+ });
3932
+ const hasCode = codeFiles.length > 0;
3933
+ const hasTests = testFiles.length > 0;
3934
+ const status = hasCode && hasTests ? "full" : hasCode ? "code-only" : hasTests ? "test-only" : "none";
3935
+ const allConfidences = [
3936
+ ...codeFiles.map((f) => f.confidence),
3937
+ ...testFiles.map((f) => f.confidence)
3938
+ ];
3939
+ const maxConfidence = allConfidences.length > 0 ? Math.max(...allConfidences) : 0;
3940
+ requirements.push({
3941
+ requirementId: req.id,
3942
+ requirementName: req.name,
3943
+ index: req.metadata?.index ?? 0,
3944
+ codeFiles,
3945
+ testFiles,
3946
+ status,
3947
+ maxConfidence
3948
+ });
3949
+ }
3950
+ requirements.sort((a, b) => a.index - b.index);
3951
+ const total = requirements.length;
3952
+ const withCode = requirements.filter((r) => r.codeFiles.length > 0).length;
3953
+ const withTests = requirements.filter((r) => r.testFiles.length > 0).length;
3954
+ const fullyTraced = requirements.filter((r) => r.status === "full").length;
3955
+ const untraceable = requirements.filter((r) => r.status === "none").length;
3956
+ const coveragePercent = total > 0 ? Math.round(fullyTraced / total * 100) : 0;
3957
+ results.push({
3958
+ specPath,
3959
+ featureName,
3960
+ requirements,
3961
+ summary: { total, withCode, withTests, fullyTraced, untraceable, coveragePercent }
3962
+ });
3963
+ }
3964
+ return results;
3965
+ }
3966
+
3623
3967
  // src/constraints/GraphConstraintAdapter.ts
3624
3968
  var import_minimatch = require("minimatch");
3625
3969
  var import_node_path2 = require("path");
@@ -3679,14 +4023,14 @@ var GraphConstraintAdapter = class {
3679
4023
  };
3680
4024
 
3681
4025
  // src/ingest/DesignIngestor.ts
3682
- var fs4 = __toESM(require("fs/promises"));
3683
- var path5 = __toESM(require("path"));
4026
+ var fs5 = __toESM(require("fs/promises"));
4027
+ var path6 = __toESM(require("path"));
3684
4028
  function isDTCGToken(obj) {
3685
4029
  return typeof obj === "object" && obj !== null && "$value" in obj && "$type" in obj;
3686
4030
  }
3687
4031
  async function readFileOrNull(filePath) {
3688
4032
  try {
3689
- return await fs4.readFile(filePath, "utf-8");
4033
+ return await fs5.readFile(filePath, "utf-8");
3690
4034
  } catch {
3691
4035
  return null;
3692
4036
  }
@@ -3832,8 +4176,8 @@ var DesignIngestor = class {
3832
4176
  async ingestAll(designDir) {
3833
4177
  const start = Date.now();
3834
4178
  const [tokensResult, intentResult] = await Promise.all([
3835
- this.ingestTokens(path5.join(designDir, "tokens.json")),
3836
- this.ingestDesignIntent(path5.join(designDir, "DESIGN.md"))
4179
+ this.ingestTokens(path6.join(designDir, "tokens.json")),
4180
+ this.ingestDesignIntent(path6.join(designDir, "DESIGN.md"))
3837
4181
  ]);
3838
4182
  const merged = mergeResults(tokensResult, intentResult);
3839
4183
  return { ...merged, durationMs: Date.now() - start };
@@ -4082,10 +4426,10 @@ var TaskIndependenceAnalyzer = class {
4082
4426
  includeTypes: ["file"]
4083
4427
  });
4084
4428
  for (const n of queryResult.nodes) {
4085
- const path6 = n.path ?? n.id.replace(/^file:/, "");
4086
- if (!fileSet.has(path6)) {
4087
- if (!result.has(path6)) {
4088
- result.set(path6, file);
4429
+ const path7 = n.path ?? n.id.replace(/^file:/, "");
4430
+ if (!fileSet.has(path7)) {
4431
+ if (!result.has(path7)) {
4432
+ result.set(path7, file);
4089
4433
  }
4090
4434
  }
4091
4435
  }
@@ -4470,6 +4814,7 @@ var VERSION = "0.2.0";
4470
4814
  KnowledgeIngestor,
4471
4815
  NODE_TYPES,
4472
4816
  OBSERVABILITY_TYPES,
4817
+ RequirementIngestor,
4473
4818
  ResponseFormatter,
4474
4819
  SlackConnector,
4475
4820
  SyncManager,
@@ -4482,5 +4827,6 @@ var VERSION = "0.2.0";
4482
4827
  linkToCode,
4483
4828
  loadGraph,
4484
4829
  project,
4830
+ queryTraceability,
4485
4831
  saveGraph
4486
4832
  });