@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.js CHANGED
@@ -47,6 +47,7 @@ __export(index_exports, {
47
47
  D2Parser: () => D2Parser,
48
48
  DEFAULT_BLOCKLIST: () => DEFAULT_BLOCKLIST,
49
49
  DEFAULT_PATTERNS: () => DEFAULT_PATTERNS,
50
+ DEFAULT_SKIP_DIRS: () => DEFAULT_SKIP_DIRS,
50
51
  DecisionIngestor: () => DecisionIngestor,
51
52
  DesignConstraintAdapter: () => DesignConstraintAdapter,
52
53
  DesignIngestor: () => DesignIngestor,
@@ -102,9 +103,11 @@ __export(index_exports, {
102
103
  inferDomain: () => inferDomain,
103
104
  linkToCode: () => linkToCode,
104
105
  loadGraph: () => loadGraph,
106
+ loadGraphMetadata: () => loadGraphMetadata,
105
107
  normalizeIntent: () => normalizeIntent,
106
108
  project: () => project,
107
109
  queryTraceability: () => queryTraceability,
110
+ resolveSkipDirs: () => resolveSkipDirs,
108
111
  saveGraph: () => saveGraph
109
112
  });
110
113
  module.exports = __toCommonJS(index_exports);
@@ -202,7 +205,7 @@ var EDGE_TYPES = [
202
205
  "caches"
203
206
  ];
204
207
  var OBSERVABILITY_TYPES = /* @__PURE__ */ new Set(["span", "metric", "log"]);
205
- var CURRENT_SCHEMA_VERSION = 1;
208
+ var CURRENT_SCHEMA_VERSION = 2;
206
209
  var NODE_STABILITY = {
207
210
  File: "session",
208
211
  Function: "session",
@@ -241,38 +244,99 @@ var GraphEdgeSchema = import_zod.z.object({
241
244
  // src/store/Serializer.ts
242
245
  var import_promises = require("fs/promises");
243
246
  var import_node_fs = require("fs");
247
+ var import_node_readline = require("readline");
244
248
  var import_node_path = require("path");
245
- function streamGraphJson(filePath, nodes, edges) {
249
+ function streamGraphNdjson(filePath, nodes, edges) {
246
250
  return new Promise((resolve, reject) => {
247
251
  const stream = (0, import_node_fs.createWriteStream)(filePath, { encoding: "utf-8" });
248
252
  stream.on("error", reject);
249
- stream.write('{"nodes":[');
250
- for (let i = 0; i < nodes.length; i++) {
251
- if (i > 0) stream.write(",");
252
- stream.write(JSON.stringify(nodes[i]));
253
+ for (const node of nodes) {
254
+ stream.write(JSON.stringify({ kind: "node", ...node }));
255
+ stream.write("\n");
253
256
  }
254
- stream.write('],"edges":[');
255
- for (let i = 0; i < edges.length; i++) {
256
- if (i > 0) stream.write(",");
257
- stream.write(JSON.stringify(edges[i]));
257
+ for (const edge of edges) {
258
+ stream.write(JSON.stringify({ kind: "edge", ...edge }));
259
+ stream.write("\n");
258
260
  }
259
- stream.write("]}");
260
261
  stream.end(() => resolve());
261
262
  });
262
263
  }
264
+ function countNodesByType(nodes) {
265
+ const counts = {};
266
+ for (const node of nodes) {
267
+ counts[node.type] = (counts[node.type] ?? 0) + 1;
268
+ }
269
+ return counts;
270
+ }
263
271
  async function saveGraph(dirPath, nodes, edges) {
264
272
  await (0, import_promises.mkdir)(dirPath, { recursive: true });
265
273
  const metadata = {
266
274
  schemaVersion: CURRENT_SCHEMA_VERSION,
267
275
  lastScanTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
268
276
  nodeCount: nodes.length,
269
- edgeCount: edges.length
277
+ edgeCount: edges.length,
278
+ nodesByType: countNodesByType(nodes)
270
279
  };
271
280
  await Promise.all([
272
- streamGraphJson((0, import_node_path.join)(dirPath, "graph.json"), nodes, edges),
281
+ streamGraphNdjson((0, import_node_path.join)(dirPath, "graph.json"), nodes, edges),
273
282
  (0, import_promises.writeFile)((0, import_node_path.join)(dirPath, "metadata.json"), JSON.stringify(metadata, null, 2), "utf-8")
274
283
  ]);
275
284
  }
285
+ async function loadGraphMetadata(dirPath) {
286
+ const metaPath = (0, import_node_path.join)(dirPath, "metadata.json");
287
+ try {
288
+ await (0, import_promises.access)(metaPath);
289
+ } catch {
290
+ return { status: "not_found" };
291
+ }
292
+ let metadata;
293
+ try {
294
+ const content = await (0, import_promises.readFile)(metaPath, "utf-8");
295
+ metadata = JSON.parse(content);
296
+ } catch {
297
+ return { status: "not_found" };
298
+ }
299
+ if (metadata.schemaVersion !== CURRENT_SCHEMA_VERSION) {
300
+ return {
301
+ status: "schema_mismatch",
302
+ found: metadata.schemaVersion,
303
+ expected: CURRENT_SCHEMA_VERSION
304
+ };
305
+ }
306
+ return { status: "loaded", metadata };
307
+ }
308
+ async function streamReadGraph(filePath) {
309
+ const stream = (0, import_node_fs.createReadStream)(filePath, { encoding: "utf-8" });
310
+ const rl = (0, import_node_readline.createInterface)({ input: stream, crlfDelay: Infinity });
311
+ const nodes = [];
312
+ const edges = [];
313
+ try {
314
+ for await (const rawLine of rl) {
315
+ const line = rawLine.trim();
316
+ if (line === "") continue;
317
+ const record = JSON.parse(line);
318
+ if (record.kind === "node") {
319
+ const { kind: _kind, ...rest } = record;
320
+ nodes.push(rest);
321
+ } else if (record.kind === "edge") {
322
+ const { kind: _kind, ...rest } = record;
323
+ edges.push(rest);
324
+ }
325
+ }
326
+ } catch (err) {
327
+ if (err instanceof RangeError && /invalid string length/i.test(err.message)) {
328
+ throw new Error(
329
+ `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.`,
330
+ { cause: err }
331
+ );
332
+ }
333
+ throw err;
334
+ } finally {
335
+ rl.close();
336
+ stream.close();
337
+ }
338
+ return { nodes, edges };
339
+ }
276
340
  async function loadGraph(dirPath) {
277
341
  const metaPath = (0, import_node_path.join)(dirPath, "metadata.json");
278
342
  const graphPath = (0, import_node_path.join)(dirPath, "graph.json");
@@ -291,8 +355,8 @@ async function loadGraph(dirPath) {
291
355
  expected: CURRENT_SCHEMA_VERSION
292
356
  };
293
357
  }
294
- const graphContent = await (0, import_promises.readFile)(graphPath, "utf-8");
295
- return { status: "loaded", graph: JSON.parse(graphContent) };
358
+ const graph = await streamReadGraph(graphPath);
359
+ return { status: "loaded", graph };
296
360
  }
297
361
 
298
362
  // src/store/GraphStore.ts
@@ -838,6 +902,84 @@ function groupNodesByImpact(nodes, excludeId) {
838
902
  // src/ingest/CodeIngestor.ts
839
903
  var fs = __toESM(require("fs/promises"));
840
904
  var path = __toESM(require("path"));
905
+ var import_minimatch = require("minimatch");
906
+
907
+ // src/ingest/skip-dirs.ts
908
+ var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
909
+ // VCS
910
+ ".git",
911
+ ".hg",
912
+ ".svn",
913
+ // Package managers
914
+ "node_modules",
915
+ ".pnpm-store",
916
+ ".yarn",
917
+ "vendor",
918
+ // JS/TS build outputs
919
+ "dist",
920
+ "build",
921
+ "out",
922
+ "_build",
923
+ "bin",
924
+ "obj",
925
+ "target",
926
+ "deps",
927
+ // JS/TS framework / tooling caches
928
+ ".next",
929
+ ".nuxt",
930
+ ".svelte-kit",
931
+ ".turbo",
932
+ ".vite",
933
+ ".cache",
934
+ ".parcel-cache",
935
+ ".docusaurus",
936
+ ".wrangler",
937
+ ".astro",
938
+ ".remix",
939
+ "storybook-static",
940
+ // Test / coverage / reporter outputs
941
+ "coverage",
942
+ ".nyc_output",
943
+ ".pytest_cache",
944
+ "playwright-report",
945
+ "test-results",
946
+ ".e2e",
947
+ // Python
948
+ "__pycache__",
949
+ ".venv",
950
+ "venv",
951
+ ".tox",
952
+ ".mypy_cache",
953
+ ".ruff_cache",
954
+ // JVM
955
+ ".gradle",
956
+ ".gradle-home",
957
+ // IDE / editor
958
+ ".idea",
959
+ ".vscode",
960
+ ".vs",
961
+ // Harness self
962
+ ".harness",
963
+ // AI agent sandboxes (these often contain full repo clones — skipping them
964
+ // prevents the walker from re-ingesting nested copies of the project)
965
+ ".claude",
966
+ ".cursor",
967
+ ".codex",
968
+ ".gemini",
969
+ ".aider",
970
+ ".agents",
971
+ ".agentastic",
972
+ ".playwright-mcp"
973
+ ]);
974
+ function resolveSkipDirs(options) {
975
+ const base = options?.skipDirs ? new Set(options.skipDirs) : new Set(DEFAULT_SKIP_DIRS);
976
+ if (options?.additionalSkipDirs) {
977
+ for (const name of options.additionalSkipDirs) base.add(name);
978
+ }
979
+ return base;
980
+ }
981
+
982
+ // src/ingest/CodeIngestor.ts
841
983
  var SKIP_METHOD_NAMES = /* @__PURE__ */ new Set(["constructor", "if", "for", "while", "switch"]);
842
984
  var FUNCTION_PATTERNS = {
843
985
  python: /^def\s+(\w+)\s*\(/,
@@ -907,11 +1049,55 @@ function isSupportedSourceFile(name) {
907
1049
  const ext = name.slice(name.lastIndexOf("."));
908
1050
  return SUPPORTED_EXTENSIONS.has(ext);
909
1051
  }
1052
+ function isExcluded(relPath, patterns) {
1053
+ if (patterns.length === 0) return false;
1054
+ for (const pattern of patterns) {
1055
+ if ((0, import_minimatch.minimatch)(relPath, pattern, { dot: true, matchBase: false })) return true;
1056
+ if (!pattern.includes("/") && (0, import_minimatch.minimatch)(relPath, pattern, { dot: true, matchBase: true })) {
1057
+ return true;
1058
+ }
1059
+ }
1060
+ return false;
1061
+ }
1062
+ async function readGitignorePatterns(rootDir) {
1063
+ let content;
1064
+ try {
1065
+ content = await fs.readFile(path.join(rootDir, ".gitignore"), "utf-8");
1066
+ } catch {
1067
+ return [];
1068
+ }
1069
+ const patterns = [];
1070
+ for (const rawLine of content.split(/\r?\n/)) {
1071
+ const line = rawLine.trim();
1072
+ if (line === "" || line.startsWith("#")) continue;
1073
+ if (line.startsWith("!")) continue;
1074
+ const rooted = line.startsWith("/");
1075
+ const stripped = rooted ? line.slice(1) : line;
1076
+ const isDirOnly = stripped.endsWith("/");
1077
+ const core = isDirOnly ? stripped.slice(0, -1) : stripped;
1078
+ if (core === "") continue;
1079
+ if (rooted) {
1080
+ patterns.push(isDirOnly ? `${core}/**` : core);
1081
+ if (isDirOnly) patterns.push(`${core}/`);
1082
+ } else {
1083
+ patterns.push(`**/${core}`);
1084
+ patterns.push(`**/${core}/**`);
1085
+ if (isDirOnly) patterns.push(`**/${core}/`);
1086
+ }
1087
+ }
1088
+ return patterns;
1089
+ }
910
1090
  var CodeIngestor = class {
911
- constructor(store) {
1091
+ constructor(store, options = {}) {
912
1092
  this.store = store;
1093
+ this.skipDirs = resolveSkipDirs(options);
1094
+ this.excludePatterns = options.excludePatterns ?? [];
1095
+ this.respectGitignore = options.respectGitignore ?? true;
913
1096
  }
914
1097
  store;
1098
+ skipDirs;
1099
+ excludePatterns;
1100
+ respectGitignore;
915
1101
  async ingest(rootDir) {
916
1102
  const start = Date.now();
917
1103
  const errors = [];
@@ -1001,19 +1187,55 @@ var CodeIngestor = class {
1001
1187
  }
1002
1188
  files.add(relativePath);
1003
1189
  }
1004
- async findSourceFiles(dir) {
1190
+ /**
1191
+ * Walk `rootDir` iteratively (BFS via an explicit queue) and return absolute paths
1192
+ * of every supported source file that is not excluded by the configured filters.
1193
+ *
1194
+ * Iterative on purpose: a recursive walker burns one stack frame per nested
1195
+ * directory and crashes with `Maximum call stack size exceeded` on deeply nested
1196
+ * monorepos (issue #274). The queue-based form keeps memory bounded by the
1197
+ * frontier size rather than the deepest path.
1198
+ */
1199
+ async findSourceFiles(rootDir) {
1005
1200
  const results = [];
1006
- const entries = await fs.readdir(dir, { withFileTypes: true });
1007
- for (const entry of entries) {
1008
- const fullPath = path.join(dir, entry.name);
1009
- 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") {
1010
- results.push(...await this.findSourceFiles(fullPath));
1011
- } else if (entry.isFile() && isSupportedSourceFile(entry.name)) {
1012
- results.push(fullPath);
1201
+ const matchers = await this.buildExcludeMatchers(rootDir);
1202
+ const queue = [rootDir];
1203
+ while (queue.length > 0) {
1204
+ const dir = queue.pop();
1205
+ let entries;
1206
+ try {
1207
+ entries = await fs.readdir(dir, { withFileTypes: true });
1208
+ } catch {
1209
+ continue;
1210
+ }
1211
+ for (const entry of entries) {
1212
+ const fullPath = path.join(dir, entry.name);
1213
+ const relPath = path.relative(rootDir, fullPath).replaceAll("\\", "/");
1214
+ if (entry.isDirectory()) {
1215
+ if (this.skipDirs.has(entry.name)) continue;
1216
+ if (isExcluded(`${relPath}/`, matchers)) continue;
1217
+ queue.push(fullPath);
1218
+ } else if (entry.isFile()) {
1219
+ if (!isSupportedSourceFile(entry.name)) continue;
1220
+ if (isExcluded(relPath, matchers)) continue;
1221
+ results.push(fullPath);
1222
+ }
1013
1223
  }
1014
1224
  }
1015
1225
  return results;
1016
1226
  }
1227
+ /**
1228
+ * Compose the exclude matchers from `excludePatterns` and (optionally) `.gitignore`.
1229
+ * Returns a list of minimatch-compatible patterns; an empty list means "no excludes".
1230
+ */
1231
+ async buildExcludeMatchers(rootDir) {
1232
+ const patterns = [...this.excludePatterns];
1233
+ if (this.respectGitignore) {
1234
+ const gitignorePatterns = await readGitignorePatterns(rootDir);
1235
+ patterns.push(...gitignorePatterns);
1236
+ }
1237
+ return patterns;
1238
+ }
1017
1239
  extractSymbols(content, fileId, relativePath) {
1018
1240
  const results = [];
1019
1241
  const lines = content.split("\n");
@@ -1978,6 +2200,47 @@ function parseFailureSection(section) {
1978
2200
  // src/ingest/BusinessKnowledgeIngestor.ts
1979
2201
  var fs3 = __toESM(require("fs/promises"));
1980
2202
  var path4 = __toESM(require("path"));
2203
+ var import_zod2 = require("zod");
2204
+ var BUG_TRACK_CATEGORIES = [
2205
+ "build-errors",
2206
+ "test-failures",
2207
+ "runtime-errors",
2208
+ "performance-issues",
2209
+ "database-issues",
2210
+ "security-issues",
2211
+ "ui-bugs",
2212
+ "integration-issues",
2213
+ "logic-errors"
2214
+ ];
2215
+ var KNOWLEDGE_TRACK_CATEGORIES = [
2216
+ "architecture-patterns",
2217
+ "design-patterns",
2218
+ "tooling-decisions",
2219
+ "conventions",
2220
+ "dx",
2221
+ "best-practices"
2222
+ ];
2223
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
2224
+ var SolutionBaseSchema = import_zod2.z.object({
2225
+ module: import_zod2.z.string().min(1),
2226
+ tags: import_zod2.z.array(import_zod2.z.string()),
2227
+ problem_type: import_zod2.z.string().min(1),
2228
+ last_updated: import_zod2.z.string().regex(ISO_DATE, "last_updated must be ISO date YYYY-MM-DD")
2229
+ });
2230
+ var SolutionDocFrontmatterSchema = import_zod2.z.discriminatedUnion("track", [
2231
+ SolutionBaseSchema.merge(
2232
+ import_zod2.z.object({
2233
+ track: import_zod2.z.literal("bug-track"),
2234
+ category: import_zod2.z.enum(BUG_TRACK_CATEGORIES)
2235
+ })
2236
+ ),
2237
+ SolutionBaseSchema.merge(
2238
+ import_zod2.z.object({
2239
+ track: import_zod2.z.literal("knowledge-track"),
2240
+ category: import_zod2.z.enum(KNOWLEDGE_TRACK_CATEGORIES)
2241
+ })
2242
+ )
2243
+ ]);
1981
2244
  var BUSINESS_KNOWLEDGE_TYPES = /* @__PURE__ */ new Set([
1982
2245
  "business_rule",
1983
2246
  "business_process",
@@ -2020,6 +2283,65 @@ var BusinessKnowledgeIngestor = class {
2020
2283
  durationMs: Date.now() - start
2021
2284
  };
2022
2285
  }
2286
+ async ingestSolutions(solutionsDir) {
2287
+ const start = Date.now();
2288
+ const errors = [];
2289
+ let files;
2290
+ try {
2291
+ files = await this.findMarkdownFiles(solutionsDir);
2292
+ } catch {
2293
+ return emptyResult(Date.now() - start);
2294
+ }
2295
+ let nodesAdded = 0;
2296
+ for (const filePath of files) {
2297
+ try {
2298
+ const raw = await fs3.readFile(filePath, "utf-8");
2299
+ const parsed = parseSolutionFrontmatter(raw);
2300
+ if (!parsed) {
2301
+ errors.push(`${filePath}: no frontmatter found`);
2302
+ continue;
2303
+ }
2304
+ const validation = SolutionDocFrontmatterSchema.safeParse(parsed.frontmatter);
2305
+ if (!validation.success) {
2306
+ errors.push(`${filePath}: ${validation.error.message}`);
2307
+ continue;
2308
+ }
2309
+ if (validation.data.track !== "knowledge-track") continue;
2310
+ const relPath = path4.relative(solutionsDir, filePath).replaceAll("\\", "/");
2311
+ const filename = path4.basename(filePath, ".md");
2312
+ const nodeId = `bk:solutions:${validation.data.module}:${filename}`;
2313
+ const titleMatch = parsed.body.match(/^#\s+(.+)$/m);
2314
+ const name = titleMatch ? titleMatch[1].trim() : filename;
2315
+ const node = {
2316
+ id: nodeId,
2317
+ type: "business_concept",
2318
+ name,
2319
+ path: relPath,
2320
+ content: parsed.body.trim(),
2321
+ metadata: {
2322
+ domain: validation.data.module,
2323
+ tags: validation.data.tags,
2324
+ problem_type: validation.data.problem_type,
2325
+ last_updated: validation.data.last_updated,
2326
+ source: "solutions",
2327
+ category: validation.data.category
2328
+ }
2329
+ };
2330
+ this.store.addNode(node);
2331
+ nodesAdded++;
2332
+ } catch (err) {
2333
+ errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
2334
+ }
2335
+ }
2336
+ return {
2337
+ nodesAdded,
2338
+ nodesUpdated: 0,
2339
+ edgesAdded: 0,
2340
+ edgesUpdated: 0,
2341
+ errors,
2342
+ durationMs: Date.now() - start
2343
+ };
2344
+ }
2023
2345
  async createNodes(files, knowledgeDir, errors) {
2024
2346
  const entries = [];
2025
2347
  for (const filePath of files) {
@@ -2052,6 +2374,7 @@ var BusinessKnowledgeIngestor = class {
2052
2374
  content: body.trim(),
2053
2375
  metadata: {
2054
2376
  domain,
2377
+ ...frontmatter.source && { source: frontmatter.source },
2055
2378
  ...frontmatter.tags && { tags: frontmatter.tags },
2056
2379
  ...frontmatter.related && { related: frontmatter.related }
2057
2380
  }
@@ -2141,6 +2464,25 @@ function parseFrontmatter(raw) {
2141
2464
  body
2142
2465
  };
2143
2466
  }
2467
+ function parseSolutionFrontmatter(raw) {
2468
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
2469
+ if (!match) return null;
2470
+ const yamlBlock = match[1];
2471
+ const body = match[2];
2472
+ const frontmatter = {};
2473
+ for (const line of yamlBlock.split(/\r?\n/)) {
2474
+ const kvMatch = line.match(/^(\w+):\s*(.+)$/);
2475
+ if (!kvMatch) continue;
2476
+ const key = kvMatch[1];
2477
+ const value = kvMatch[2].trim();
2478
+ if (value.startsWith("[") && value.endsWith("]")) {
2479
+ frontmatter[key] = value.slice(1, -1).split(",").map((s) => s.trim());
2480
+ } else {
2481
+ frontmatter[key] = value;
2482
+ }
2483
+ }
2484
+ return { frontmatter, body };
2485
+ }
2144
2486
 
2145
2487
  // src/ingest/DecisionIngestor.ts
2146
2488
  var fs4 = __toESM(require("fs/promises"));
@@ -5335,6 +5677,10 @@ var KnowledgeDocMaterializer = class {
5335
5677
  const mappedType = this.mapNodeType(node);
5336
5678
  const sanitize = (s) => s.replace(/[\n\r]/g, " ").replace(/:/g, "-");
5337
5679
  const lines = ["---", `type: ${sanitize(mappedType)}`, `domain: ${sanitize(domain)}`];
5680
+ const source = node.metadata?.source;
5681
+ if (typeof source === "string" && source.length > 0) {
5682
+ lines.push(`source: ${sanitize(source)}`);
5683
+ }
5338
5684
  const tags = node.metadata?.tags;
5339
5685
  if (Array.isArray(tags) && tags.length > 0) {
5340
5686
  lines.push(`tags: [${tags.map((t) => sanitize(String(t))).join(", ")}]`);
@@ -5523,6 +5869,25 @@ var KnowledgePipelineRunner = class {
5523
5869
  durationMs: 0
5524
5870
  };
5525
5871
  }
5872
+ const solutionsDir = path12.join(options.projectDir, "docs", "solutions");
5873
+ let solutionsResult;
5874
+ try {
5875
+ solutionsResult = await bkIngestor.ingestSolutions(solutionsDir);
5876
+ } catch {
5877
+ solutionsResult = {
5878
+ nodesAdded: 0,
5879
+ nodesUpdated: 0,
5880
+ edgesAdded: 0,
5881
+ edgesUpdated: 0,
5882
+ errors: [],
5883
+ durationMs: 0
5884
+ };
5885
+ }
5886
+ bkResult = {
5887
+ ...bkResult,
5888
+ nodesAdded: bkResult.nodesAdded + solutionsResult.nodesAdded,
5889
+ errors: [...bkResult.errors, ...solutionsResult.errors]
5890
+ };
5526
5891
  const decisionsDir = path12.join(options.projectDir, "docs", "knowledge", "decisions");
5527
5892
  const decisionIngestor = new DecisionIngestor(this.store);
5528
5893
  let decisionResult;
@@ -8751,7 +9116,7 @@ function queryTraceability(store, options) {
8751
9116
  }
8752
9117
 
8753
9118
  // src/constraints/GraphConstraintAdapter.ts
8754
- var import_minimatch = require("minimatch");
9119
+ var import_minimatch2 = require("minimatch");
8755
9120
  var import_node_path2 = require("path");
8756
9121
  var GraphConstraintAdapter = class {
8757
9122
  constructor(store) {
@@ -8803,7 +9168,7 @@ var GraphConstraintAdapter = class {
8803
9168
  resolveLayer(filePath, layers) {
8804
9169
  for (const layer of layers) {
8805
9170
  for (const pattern of layer.patterns) {
8806
- if ((0, import_minimatch.minimatch)(filePath, pattern)) {
9171
+ if ((0, import_minimatch2.minimatch)(filePath, pattern)) {
8807
9172
  return layer;
8808
9173
  }
8809
9174
  }
@@ -9603,6 +9968,7 @@ var VERSION = "0.6.0";
9603
9968
  D2Parser,
9604
9969
  DEFAULT_BLOCKLIST,
9605
9970
  DEFAULT_PATTERNS,
9971
+ DEFAULT_SKIP_DIRS,
9606
9972
  DecisionIngestor,
9607
9973
  DesignConstraintAdapter,
9608
9974
  DesignIngestor,
@@ -9658,8 +10024,10 @@ var VERSION = "0.6.0";
9658
10024
  inferDomain,
9659
10025
  linkToCode,
9660
10026
  loadGraph,
10027
+ loadGraphMetadata,
9661
10028
  normalizeIntent,
9662
10029
  project,
9663
10030
  queryTraceability,
10031
+ resolveSkipDirs,
9664
10032
  saveGraph
9665
10033
  });