@harness-engineering/graph 0.7.0 → 0.8.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
@@ -91,7 +91,7 @@ var EDGE_TYPES = [
91
91
  "caches"
92
92
  ];
93
93
  var OBSERVABILITY_TYPES = /* @__PURE__ */ new Set(["span", "metric", "log"]);
94
- var CURRENT_SCHEMA_VERSION = 1;
94
+ var CURRENT_SCHEMA_VERSION = 2;
95
95
  var NODE_STABILITY = {
96
96
  File: "session",
97
97
  Function: "session",
@@ -129,39 +129,100 @@ var GraphEdgeSchema = z.object({
129
129
 
130
130
  // src/store/Serializer.ts
131
131
  import { readFile, writeFile, mkdir, access } from "fs/promises";
132
- import { createWriteStream } from "fs";
132
+ import { createReadStream, createWriteStream } from "fs";
133
+ import { createInterface } from "readline";
133
134
  import { join } from "path";
134
- function streamGraphJson(filePath, nodes, edges) {
135
+ function streamGraphNdjson(filePath, nodes, edges) {
135
136
  return new Promise((resolve, reject) => {
136
137
  const stream = createWriteStream(filePath, { encoding: "utf-8" });
137
138
  stream.on("error", reject);
138
- stream.write('{"nodes":[');
139
- for (let i = 0; i < nodes.length; i++) {
140
- if (i > 0) stream.write(",");
141
- stream.write(JSON.stringify(nodes[i]));
139
+ for (const node of nodes) {
140
+ stream.write(JSON.stringify({ kind: "node", ...node }));
141
+ stream.write("\n");
142
142
  }
143
- stream.write('],"edges":[');
144
- for (let i = 0; i < edges.length; i++) {
145
- if (i > 0) stream.write(",");
146
- stream.write(JSON.stringify(edges[i]));
143
+ for (const edge of edges) {
144
+ stream.write(JSON.stringify({ kind: "edge", ...edge }));
145
+ stream.write("\n");
147
146
  }
148
- stream.write("]}");
149
147
  stream.end(() => resolve());
150
148
  });
151
149
  }
150
+ function countNodesByType(nodes) {
151
+ const counts = {};
152
+ for (const node of nodes) {
153
+ counts[node.type] = (counts[node.type] ?? 0) + 1;
154
+ }
155
+ return counts;
156
+ }
152
157
  async function saveGraph(dirPath, nodes, edges) {
153
158
  await mkdir(dirPath, { recursive: true });
154
159
  const metadata = {
155
160
  schemaVersion: CURRENT_SCHEMA_VERSION,
156
161
  lastScanTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
157
162
  nodeCount: nodes.length,
158
- edgeCount: edges.length
163
+ edgeCount: edges.length,
164
+ nodesByType: countNodesByType(nodes)
159
165
  };
160
166
  await Promise.all([
161
- streamGraphJson(join(dirPath, "graph.json"), nodes, edges),
167
+ streamGraphNdjson(join(dirPath, "graph.json"), nodes, edges),
162
168
  writeFile(join(dirPath, "metadata.json"), JSON.stringify(metadata, null, 2), "utf-8")
163
169
  ]);
164
170
  }
171
+ async function loadGraphMetadata(dirPath) {
172
+ const metaPath = join(dirPath, "metadata.json");
173
+ try {
174
+ await access(metaPath);
175
+ } catch {
176
+ return { status: "not_found" };
177
+ }
178
+ let metadata;
179
+ try {
180
+ const content = await readFile(metaPath, "utf-8");
181
+ metadata = JSON.parse(content);
182
+ } catch {
183
+ return { status: "not_found" };
184
+ }
185
+ if (metadata.schemaVersion !== CURRENT_SCHEMA_VERSION) {
186
+ return {
187
+ status: "schema_mismatch",
188
+ found: metadata.schemaVersion,
189
+ expected: CURRENT_SCHEMA_VERSION
190
+ };
191
+ }
192
+ return { status: "loaded", metadata };
193
+ }
194
+ async function streamReadGraph(filePath) {
195
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
196
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
197
+ const nodes = [];
198
+ const edges = [];
199
+ try {
200
+ for await (const rawLine of rl) {
201
+ const line = rawLine.trim();
202
+ if (line === "") continue;
203
+ const record = JSON.parse(line);
204
+ if (record.kind === "node") {
205
+ const { kind: _kind, ...rest } = record;
206
+ nodes.push(rest);
207
+ } else if (record.kind === "edge") {
208
+ const { kind: _kind, ...rest } = record;
209
+ edges.push(rest);
210
+ }
211
+ }
212
+ } catch (err) {
213
+ if (err instanceof RangeError && /invalid string length/i.test(err.message)) {
214
+ throw new Error(
215
+ `Graph contains a record larger than V8 can hold in a single string (~512 MB). This usually means a node or edge has a multi-hundred-MB content/embedding field. Inspect ${filePath} for the offending record.`,
216
+ { cause: err }
217
+ );
218
+ }
219
+ throw err;
220
+ } finally {
221
+ rl.close();
222
+ stream.close();
223
+ }
224
+ return { nodes, edges };
225
+ }
165
226
  async function loadGraph(dirPath) {
166
227
  const metaPath = join(dirPath, "metadata.json");
167
228
  const graphPath = join(dirPath, "graph.json");
@@ -180,8 +241,8 @@ async function loadGraph(dirPath) {
180
241
  expected: CURRENT_SCHEMA_VERSION
181
242
  };
182
243
  }
183
- const graphContent = await readFile(graphPath, "utf-8");
184
- return { status: "loaded", graph: JSON.parse(graphContent) };
244
+ const graph = await streamReadGraph(graphPath);
245
+ return { status: "loaded", graph };
185
246
  }
186
247
 
187
248
  // src/store/GraphStore.ts
@@ -727,6 +788,84 @@ function groupNodesByImpact(nodes, excludeId) {
727
788
  // src/ingest/CodeIngestor.ts
728
789
  import * as fs from "fs/promises";
729
790
  import * as path from "path";
791
+ import { minimatch } from "minimatch";
792
+
793
+ // src/ingest/skip-dirs.ts
794
+ var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
795
+ // VCS
796
+ ".git",
797
+ ".hg",
798
+ ".svn",
799
+ // Package managers
800
+ "node_modules",
801
+ ".pnpm-store",
802
+ ".yarn",
803
+ "vendor",
804
+ // JS/TS build outputs
805
+ "dist",
806
+ "build",
807
+ "out",
808
+ "_build",
809
+ "bin",
810
+ "obj",
811
+ "target",
812
+ "deps",
813
+ // JS/TS framework / tooling caches
814
+ ".next",
815
+ ".nuxt",
816
+ ".svelte-kit",
817
+ ".turbo",
818
+ ".vite",
819
+ ".cache",
820
+ ".parcel-cache",
821
+ ".docusaurus",
822
+ ".wrangler",
823
+ ".astro",
824
+ ".remix",
825
+ "storybook-static",
826
+ // Test / coverage / reporter outputs
827
+ "coverage",
828
+ ".nyc_output",
829
+ ".pytest_cache",
830
+ "playwright-report",
831
+ "test-results",
832
+ ".e2e",
833
+ // Python
834
+ "__pycache__",
835
+ ".venv",
836
+ "venv",
837
+ ".tox",
838
+ ".mypy_cache",
839
+ ".ruff_cache",
840
+ // JVM
841
+ ".gradle",
842
+ ".gradle-home",
843
+ // IDE / editor
844
+ ".idea",
845
+ ".vscode",
846
+ ".vs",
847
+ // Harness self
848
+ ".harness",
849
+ // AI agent sandboxes (these often contain full repo clones — skipping them
850
+ // prevents the walker from re-ingesting nested copies of the project)
851
+ ".claude",
852
+ ".cursor",
853
+ ".codex",
854
+ ".gemini",
855
+ ".aider",
856
+ ".agents",
857
+ ".agentastic",
858
+ ".playwright-mcp"
859
+ ]);
860
+ function resolveSkipDirs(options) {
861
+ const base = options?.skipDirs ? new Set(options.skipDirs) : new Set(DEFAULT_SKIP_DIRS);
862
+ if (options?.additionalSkipDirs) {
863
+ for (const name of options.additionalSkipDirs) base.add(name);
864
+ }
865
+ return base;
866
+ }
867
+
868
+ // src/ingest/CodeIngestor.ts
730
869
  var SKIP_METHOD_NAMES = /* @__PURE__ */ new Set(["constructor", "if", "for", "while", "switch"]);
731
870
  var FUNCTION_PATTERNS = {
732
871
  python: /^def\s+(\w+)\s*\(/,
@@ -796,11 +935,55 @@ function isSupportedSourceFile(name) {
796
935
  const ext = name.slice(name.lastIndexOf("."));
797
936
  return SUPPORTED_EXTENSIONS.has(ext);
798
937
  }
938
+ function isExcluded(relPath, patterns) {
939
+ if (patterns.length === 0) return false;
940
+ for (const pattern of patterns) {
941
+ if (minimatch(relPath, pattern, { dot: true, matchBase: false })) return true;
942
+ if (!pattern.includes("/") && minimatch(relPath, pattern, { dot: true, matchBase: true })) {
943
+ return true;
944
+ }
945
+ }
946
+ return false;
947
+ }
948
+ async function readGitignorePatterns(rootDir) {
949
+ let content;
950
+ try {
951
+ content = await fs.readFile(path.join(rootDir, ".gitignore"), "utf-8");
952
+ } catch {
953
+ return [];
954
+ }
955
+ const patterns = [];
956
+ for (const rawLine of content.split(/\r?\n/)) {
957
+ const line = rawLine.trim();
958
+ if (line === "" || line.startsWith("#")) continue;
959
+ if (line.startsWith("!")) continue;
960
+ const rooted = line.startsWith("/");
961
+ const stripped = rooted ? line.slice(1) : line;
962
+ const isDirOnly = stripped.endsWith("/");
963
+ const core = isDirOnly ? stripped.slice(0, -1) : stripped;
964
+ if (core === "") continue;
965
+ if (rooted) {
966
+ patterns.push(isDirOnly ? `${core}/**` : core);
967
+ if (isDirOnly) patterns.push(`${core}/`);
968
+ } else {
969
+ patterns.push(`**/${core}`);
970
+ patterns.push(`**/${core}/**`);
971
+ if (isDirOnly) patterns.push(`**/${core}/`);
972
+ }
973
+ }
974
+ return patterns;
975
+ }
799
976
  var CodeIngestor = class {
800
- constructor(store) {
977
+ constructor(store, options = {}) {
801
978
  this.store = store;
979
+ this.skipDirs = resolveSkipDirs(options);
980
+ this.excludePatterns = options.excludePatterns ?? [];
981
+ this.respectGitignore = options.respectGitignore ?? true;
802
982
  }
803
983
  store;
984
+ skipDirs;
985
+ excludePatterns;
986
+ respectGitignore;
804
987
  async ingest(rootDir) {
805
988
  const start = Date.now();
806
989
  const errors = [];
@@ -890,19 +1073,55 @@ var CodeIngestor = class {
890
1073
  }
891
1074
  files.add(relativePath);
892
1075
  }
893
- async findSourceFiles(dir) {
1076
+ /**
1077
+ * Walk `rootDir` iteratively (BFS via an explicit queue) and return absolute paths
1078
+ * of every supported source file that is not excluded by the configured filters.
1079
+ *
1080
+ * Iterative on purpose: a recursive walker burns one stack frame per nested
1081
+ * directory and crashes with `Maximum call stack size exceeded` on deeply nested
1082
+ * monorepos (issue #274). The queue-based form keeps memory bounded by the
1083
+ * frontier size rather than the deepest path.
1084
+ */
1085
+ async findSourceFiles(rootDir) {
894
1086
  const results = [];
895
- const entries = await fs.readdir(dir, { withFileTypes: true });
896
- for (const entry of entries) {
897
- const fullPath = path.join(dir, entry.name);
898
- if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== "dist" && entry.name !== "target" && entry.name !== "build" && entry.name !== ".git" && entry.name !== "__pycache__" && entry.name !== ".venv" && entry.name !== ".turbo" && entry.name !== ".harness" && entry.name !== ".next" && entry.name !== ".vscode" && entry.name !== ".idea" && entry.name !== "vendor" && entry.name !== "out" && entry.name !== ".gradle" && entry.name !== ".gradle-home" && entry.name !== "bin" && entry.name !== "obj" && entry.name !== "venv" && entry.name !== "_build" && entry.name !== "deps" && entry.name !== "coverage" && entry.name !== ".nyc_output") {
899
- results.push(...await this.findSourceFiles(fullPath));
900
- } else if (entry.isFile() && isSupportedSourceFile(entry.name)) {
901
- results.push(fullPath);
1087
+ const matchers = await this.buildExcludeMatchers(rootDir);
1088
+ const queue = [rootDir];
1089
+ while (queue.length > 0) {
1090
+ const dir = queue.pop();
1091
+ let entries;
1092
+ try {
1093
+ entries = await fs.readdir(dir, { withFileTypes: true });
1094
+ } catch {
1095
+ continue;
1096
+ }
1097
+ for (const entry of entries) {
1098
+ const fullPath = path.join(dir, entry.name);
1099
+ const relPath = path.relative(rootDir, fullPath).replaceAll("\\", "/");
1100
+ if (entry.isDirectory()) {
1101
+ if (this.skipDirs.has(entry.name)) continue;
1102
+ if (isExcluded(`${relPath}/`, matchers)) continue;
1103
+ queue.push(fullPath);
1104
+ } else if (entry.isFile()) {
1105
+ if (!isSupportedSourceFile(entry.name)) continue;
1106
+ if (isExcluded(relPath, matchers)) continue;
1107
+ results.push(fullPath);
1108
+ }
902
1109
  }
903
1110
  }
904
1111
  return results;
905
1112
  }
1113
+ /**
1114
+ * Compose the exclude matchers from `excludePatterns` and (optionally) `.gitignore`.
1115
+ * Returns a list of minimatch-compatible patterns; an empty list means "no excludes".
1116
+ */
1117
+ async buildExcludeMatchers(rootDir) {
1118
+ const patterns = [...this.excludePatterns];
1119
+ if (this.respectGitignore) {
1120
+ const gitignorePatterns = await readGitignorePatterns(rootDir);
1121
+ patterns.push(...gitignorePatterns);
1122
+ }
1123
+ return patterns;
1124
+ }
906
1125
  extractSymbols(content, fileId, relativePath) {
907
1126
  const results = [];
908
1127
  const lines = content.split("\n");
@@ -1867,6 +2086,47 @@ function parseFailureSection(section) {
1867
2086
  // src/ingest/BusinessKnowledgeIngestor.ts
1868
2087
  import * as fs3 from "fs/promises";
1869
2088
  import * as path4 from "path";
2089
+ import { z as z2 } from "zod";
2090
+ var BUG_TRACK_CATEGORIES = [
2091
+ "build-errors",
2092
+ "test-failures",
2093
+ "runtime-errors",
2094
+ "performance-issues",
2095
+ "database-issues",
2096
+ "security-issues",
2097
+ "ui-bugs",
2098
+ "integration-issues",
2099
+ "logic-errors"
2100
+ ];
2101
+ var KNOWLEDGE_TRACK_CATEGORIES = [
2102
+ "architecture-patterns",
2103
+ "design-patterns",
2104
+ "tooling-decisions",
2105
+ "conventions",
2106
+ "dx",
2107
+ "best-practices"
2108
+ ];
2109
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
2110
+ var SolutionBaseSchema = z2.object({
2111
+ module: z2.string().min(1),
2112
+ tags: z2.array(z2.string()),
2113
+ problem_type: z2.string().min(1),
2114
+ last_updated: z2.string().regex(ISO_DATE, "last_updated must be ISO date YYYY-MM-DD")
2115
+ });
2116
+ var SolutionDocFrontmatterSchema = z2.discriminatedUnion("track", [
2117
+ SolutionBaseSchema.merge(
2118
+ z2.object({
2119
+ track: z2.literal("bug-track"),
2120
+ category: z2.enum(BUG_TRACK_CATEGORIES)
2121
+ })
2122
+ ),
2123
+ SolutionBaseSchema.merge(
2124
+ z2.object({
2125
+ track: z2.literal("knowledge-track"),
2126
+ category: z2.enum(KNOWLEDGE_TRACK_CATEGORIES)
2127
+ })
2128
+ )
2129
+ ]);
1870
2130
  var BUSINESS_KNOWLEDGE_TYPES = /* @__PURE__ */ new Set([
1871
2131
  "business_rule",
1872
2132
  "business_process",
@@ -1909,6 +2169,65 @@ var BusinessKnowledgeIngestor = class {
1909
2169
  durationMs: Date.now() - start
1910
2170
  };
1911
2171
  }
2172
+ async ingestSolutions(solutionsDir) {
2173
+ const start = Date.now();
2174
+ const errors = [];
2175
+ let files;
2176
+ try {
2177
+ files = await this.findMarkdownFiles(solutionsDir);
2178
+ } catch {
2179
+ return emptyResult(Date.now() - start);
2180
+ }
2181
+ let nodesAdded = 0;
2182
+ for (const filePath of files) {
2183
+ try {
2184
+ const raw = await fs3.readFile(filePath, "utf-8");
2185
+ const parsed = parseSolutionFrontmatter(raw);
2186
+ if (!parsed) {
2187
+ errors.push(`${filePath}: no frontmatter found`);
2188
+ continue;
2189
+ }
2190
+ const validation = SolutionDocFrontmatterSchema.safeParse(parsed.frontmatter);
2191
+ if (!validation.success) {
2192
+ errors.push(`${filePath}: ${validation.error.message}`);
2193
+ continue;
2194
+ }
2195
+ if (validation.data.track !== "knowledge-track") continue;
2196
+ const relPath = path4.relative(solutionsDir, filePath).replaceAll("\\", "/");
2197
+ const filename = path4.basename(filePath, ".md");
2198
+ const nodeId = `bk:solutions:${validation.data.module}:${filename}`;
2199
+ const titleMatch = parsed.body.match(/^#\s+(.+)$/m);
2200
+ const name = titleMatch ? titleMatch[1].trim() : filename;
2201
+ const node = {
2202
+ id: nodeId,
2203
+ type: "business_concept",
2204
+ name,
2205
+ path: relPath,
2206
+ content: parsed.body.trim(),
2207
+ metadata: {
2208
+ domain: validation.data.module,
2209
+ tags: validation.data.tags,
2210
+ problem_type: validation.data.problem_type,
2211
+ last_updated: validation.data.last_updated,
2212
+ source: "solutions",
2213
+ category: validation.data.category
2214
+ }
2215
+ };
2216
+ this.store.addNode(node);
2217
+ nodesAdded++;
2218
+ } catch (err) {
2219
+ errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
2220
+ }
2221
+ }
2222
+ return {
2223
+ nodesAdded,
2224
+ nodesUpdated: 0,
2225
+ edgesAdded: 0,
2226
+ edgesUpdated: 0,
2227
+ errors,
2228
+ durationMs: Date.now() - start
2229
+ };
2230
+ }
1912
2231
  async createNodes(files, knowledgeDir, errors) {
1913
2232
  const entries = [];
1914
2233
  for (const filePath of files) {
@@ -1941,6 +2260,7 @@ var BusinessKnowledgeIngestor = class {
1941
2260
  content: body.trim(),
1942
2261
  metadata: {
1943
2262
  domain,
2263
+ ...frontmatter.source && { source: frontmatter.source },
1944
2264
  ...frontmatter.tags && { tags: frontmatter.tags },
1945
2265
  ...frontmatter.related && { related: frontmatter.related }
1946
2266
  }
@@ -2030,6 +2350,25 @@ function parseFrontmatter(raw) {
2030
2350
  body
2031
2351
  };
2032
2352
  }
2353
+ function parseSolutionFrontmatter(raw) {
2354
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
2355
+ if (!match) return null;
2356
+ const yamlBlock = match[1];
2357
+ const body = match[2];
2358
+ const frontmatter = {};
2359
+ for (const line of yamlBlock.split(/\r?\n/)) {
2360
+ const kvMatch = line.match(/^(\w+):\s*(.+)$/);
2361
+ if (!kvMatch) continue;
2362
+ const key = kvMatch[1];
2363
+ const value = kvMatch[2].trim();
2364
+ if (value.startsWith("[") && value.endsWith("]")) {
2365
+ frontmatter[key] = value.slice(1, -1).split(",").map((s) => s.trim());
2366
+ } else {
2367
+ frontmatter[key] = value;
2368
+ }
2369
+ }
2370
+ return { frontmatter, body };
2371
+ }
2033
2372
 
2034
2373
  // src/ingest/DecisionIngestor.ts
2035
2374
  import * as fs4 from "fs/promises";
@@ -5224,6 +5563,10 @@ var KnowledgeDocMaterializer = class {
5224
5563
  const mappedType = this.mapNodeType(node);
5225
5564
  const sanitize = (s) => s.replace(/[\n\r]/g, " ").replace(/:/g, "-");
5226
5565
  const lines = ["---", `type: ${sanitize(mappedType)}`, `domain: ${sanitize(domain)}`];
5566
+ const source = node.metadata?.source;
5567
+ if (typeof source === "string" && source.length > 0) {
5568
+ lines.push(`source: ${sanitize(source)}`);
5569
+ }
5227
5570
  const tags = node.metadata?.tags;
5228
5571
  if (Array.isArray(tags) && tags.length > 0) {
5229
5572
  lines.push(`tags: [${tags.map((t) => sanitize(String(t))).join(", ")}]`);
@@ -5412,6 +5755,25 @@ var KnowledgePipelineRunner = class {
5412
5755
  durationMs: 0
5413
5756
  };
5414
5757
  }
5758
+ const solutionsDir = path12.join(options.projectDir, "docs", "solutions");
5759
+ let solutionsResult;
5760
+ try {
5761
+ solutionsResult = await bkIngestor.ingestSolutions(solutionsDir);
5762
+ } catch {
5763
+ solutionsResult = {
5764
+ nodesAdded: 0,
5765
+ nodesUpdated: 0,
5766
+ edgesAdded: 0,
5767
+ edgesUpdated: 0,
5768
+ errors: [],
5769
+ durationMs: 0
5770
+ };
5771
+ }
5772
+ bkResult = {
5773
+ ...bkResult,
5774
+ nodesAdded: bkResult.nodesAdded + solutionsResult.nodesAdded,
5775
+ errors: [...bkResult.errors, ...solutionsResult.errors]
5776
+ };
5415
5777
  const decisionsDir = path12.join(options.projectDir, "docs", "knowledge", "decisions");
5416
5778
  const decisionIngestor = new DecisionIngestor(this.store);
5417
5779
  let decisionResult;
@@ -8640,7 +9002,7 @@ function queryTraceability(store, options) {
8640
9002
  }
8641
9003
 
8642
9004
  // src/constraints/GraphConstraintAdapter.ts
8643
- import { minimatch } from "minimatch";
9005
+ import { minimatch as minimatch2 } from "minimatch";
8644
9006
  import { relative as relative5 } from "path";
8645
9007
  var GraphConstraintAdapter = class {
8646
9008
  constructor(store) {
@@ -8692,7 +9054,7 @@ var GraphConstraintAdapter = class {
8692
9054
  resolveLayer(filePath, layers) {
8693
9055
  for (const layer of layers) {
8694
9056
  for (const pattern of layer.patterns) {
8695
- if (minimatch(filePath, pattern)) {
9057
+ if (minimatch2(filePath, pattern)) {
8696
9058
  return layer;
8697
9059
  }
8698
9060
  }
@@ -9491,6 +9853,7 @@ export {
9491
9853
  D2Parser,
9492
9854
  DEFAULT_BLOCKLIST,
9493
9855
  DEFAULT_PATTERNS,
9856
+ DEFAULT_SKIP_DIRS,
9494
9857
  DecisionIngestor,
9495
9858
  DesignConstraintAdapter,
9496
9859
  DesignIngestor,
@@ -9546,8 +9909,10 @@ export {
9546
9909
  inferDomain,
9547
9910
  linkToCode,
9548
9911
  loadGraph,
9912
+ loadGraphMetadata,
9549
9913
  normalizeIntent,
9550
9914
  project,
9551
9915
  queryTraceability,
9916
+ resolveSkipDirs,
9552
9917
  saveGraph
9553
9918
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-engineering/graph",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "license": "MIT",
5
5
  "description": "Knowledge graph for context assembly in Harness Engineering",
6
6
  "main": "./dist/index.js",