@harness-engineering/graph 0.7.1 → 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.d.mts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +389 -26
- package/dist/index.mjs +387 -27
- package/package.json +1 -1
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 =
|
|
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
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
|
295
|
-
return { status: "loaded", graph
|
|
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
|
-
|
|
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
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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) {
|
|
@@ -2142,6 +2464,25 @@ function parseFrontmatter(raw) {
|
|
|
2142
2464
|
body
|
|
2143
2465
|
};
|
|
2144
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
|
+
}
|
|
2145
2486
|
|
|
2146
2487
|
// src/ingest/DecisionIngestor.ts
|
|
2147
2488
|
var fs4 = __toESM(require("fs/promises"));
|
|
@@ -5528,6 +5869,25 @@ var KnowledgePipelineRunner = class {
|
|
|
5528
5869
|
durationMs: 0
|
|
5529
5870
|
};
|
|
5530
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
|
+
};
|
|
5531
5891
|
const decisionsDir = path12.join(options.projectDir, "docs", "knowledge", "decisions");
|
|
5532
5892
|
const decisionIngestor = new DecisionIngestor(this.store);
|
|
5533
5893
|
let decisionResult;
|
|
@@ -8756,7 +9116,7 @@ function queryTraceability(store, options) {
|
|
|
8756
9116
|
}
|
|
8757
9117
|
|
|
8758
9118
|
// src/constraints/GraphConstraintAdapter.ts
|
|
8759
|
-
var
|
|
9119
|
+
var import_minimatch2 = require("minimatch");
|
|
8760
9120
|
var import_node_path2 = require("path");
|
|
8761
9121
|
var GraphConstraintAdapter = class {
|
|
8762
9122
|
constructor(store) {
|
|
@@ -8808,7 +9168,7 @@ var GraphConstraintAdapter = class {
|
|
|
8808
9168
|
resolveLayer(filePath, layers) {
|
|
8809
9169
|
for (const layer of layers) {
|
|
8810
9170
|
for (const pattern of layer.patterns) {
|
|
8811
|
-
if ((0,
|
|
9171
|
+
if ((0, import_minimatch2.minimatch)(filePath, pattern)) {
|
|
8812
9172
|
return layer;
|
|
8813
9173
|
}
|
|
8814
9174
|
}
|
|
@@ -9608,6 +9968,7 @@ var VERSION = "0.6.0";
|
|
|
9608
9968
|
D2Parser,
|
|
9609
9969
|
DEFAULT_BLOCKLIST,
|
|
9610
9970
|
DEFAULT_PATTERNS,
|
|
9971
|
+
DEFAULT_SKIP_DIRS,
|
|
9611
9972
|
DecisionIngestor,
|
|
9612
9973
|
DesignConstraintAdapter,
|
|
9613
9974
|
DesignIngestor,
|
|
@@ -9663,8 +10024,10 @@ var VERSION = "0.6.0";
|
|
|
9663
10024
|
inferDomain,
|
|
9664
10025
|
linkToCode,
|
|
9665
10026
|
loadGraph,
|
|
10027
|
+
loadGraphMetadata,
|
|
9666
10028
|
normalizeIntent,
|
|
9667
10029
|
project,
|
|
9668
10030
|
queryTraceability,
|
|
10031
|
+
resolveSkipDirs,
|
|
9669
10032
|
saveGraph
|
|
9670
10033
|
});
|