@harness-engineering/graph 0.9.0 → 0.11.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 +107 -2
- package/dist/index.d.ts +107 -2
- package/dist/index.js +393 -39
- package/dist/index.mjs +396 -42
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2327,6 +2327,23 @@ var CODE_NODE_TYPES2 = [
|
|
|
2327
2327
|
"variable"
|
|
2328
2328
|
];
|
|
2329
2329
|
var MEASURABLE_TYPES = /* @__PURE__ */ new Set(["business_process", "business_concept"]);
|
|
2330
|
+
var STRATEGY_REQUIRED_SECTIONS = [
|
|
2331
|
+
"Target problem",
|
|
2332
|
+
"Our approach",
|
|
2333
|
+
"Who it's for",
|
|
2334
|
+
"Key metrics",
|
|
2335
|
+
"Tracks"
|
|
2336
|
+
];
|
|
2337
|
+
var STRATEGY_OPTIONAL_SECTIONS = [
|
|
2338
|
+
"Milestones",
|
|
2339
|
+
"Not working on",
|
|
2340
|
+
"Marketing"
|
|
2341
|
+
];
|
|
2342
|
+
var STRATEGY_KNOWN_SECTIONS = /* @__PURE__ */ new Set([
|
|
2343
|
+
...STRATEGY_REQUIRED_SECTIONS,
|
|
2344
|
+
...STRATEGY_OPTIONAL_SECTIONS
|
|
2345
|
+
]);
|
|
2346
|
+
var STRATEGY_PLACEHOLDER_RE = /^<[^>]+>\s*$/;
|
|
2330
2347
|
var BusinessKnowledgeIngestor = class {
|
|
2331
2348
|
constructor(store) {
|
|
2332
2349
|
this.store = store;
|
|
@@ -2411,6 +2428,75 @@ var BusinessKnowledgeIngestor = class {
|
|
|
2411
2428
|
durationMs: Date.now() - start
|
|
2412
2429
|
};
|
|
2413
2430
|
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Ingest the repo-root STRATEGY.md anchor as `business_fact` nodes — one per
|
|
2433
|
+
* non-empty section. Soft-fails when the file is absent (returns empty result
|
|
2434
|
+
* with no errors) so existing projects without a strategy doc keep working.
|
|
2435
|
+
*
|
|
2436
|
+
* Each emitted node carries `metadata.domain === 'strategy'` and
|
|
2437
|
+
* `metadata.source === 'STRATEGY.md'`, making the strategy domain
|
|
2438
|
+
* discoverable through the same filters as other business-knowledge nodes.
|
|
2439
|
+
*/
|
|
2440
|
+
async ingestStrategy(strategyPath) {
|
|
2441
|
+
const start = Date.now();
|
|
2442
|
+
const errors = [];
|
|
2443
|
+
let raw;
|
|
2444
|
+
try {
|
|
2445
|
+
raw = await fs3.readFile(strategyPath, "utf-8");
|
|
2446
|
+
} catch {
|
|
2447
|
+
return emptyResult(Date.now() - start);
|
|
2448
|
+
}
|
|
2449
|
+
const parsed = parseStrategyMarkdown(raw);
|
|
2450
|
+
if (!parsed) {
|
|
2451
|
+
errors.push(`${strategyPath}: no frontmatter found`);
|
|
2452
|
+
return { ...emptyResult(Date.now() - start), errors };
|
|
2453
|
+
}
|
|
2454
|
+
const relPath = path4.basename(strategyPath);
|
|
2455
|
+
let nodesAdded = 0;
|
|
2456
|
+
for (const section of parsed.sections) {
|
|
2457
|
+
const node = this.buildStrategyNode(section, parsed.frontmatter, relPath);
|
|
2458
|
+
if (node === null) continue;
|
|
2459
|
+
this.store.addNode(node);
|
|
2460
|
+
nodesAdded++;
|
|
2461
|
+
}
|
|
2462
|
+
return {
|
|
2463
|
+
nodesAdded,
|
|
2464
|
+
nodesUpdated: 0,
|
|
2465
|
+
edgesAdded: 0,
|
|
2466
|
+
edgesUpdated: 0,
|
|
2467
|
+
errors,
|
|
2468
|
+
durationMs: Date.now() - start
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Build a single STRATEGY.md `business_fact` node from a parsed section.
|
|
2473
|
+
* Returns null when the section is unknown, empty, or carries unfilled
|
|
2474
|
+
* template placeholder text — callers iterate and skip nulls.
|
|
2475
|
+
*/
|
|
2476
|
+
buildStrategyNode(section, frontmatter, relPath) {
|
|
2477
|
+
if (!STRATEGY_KNOWN_SECTIONS.has(section.name)) return null;
|
|
2478
|
+
const body = section.body.trim();
|
|
2479
|
+
if (body.length === 0) return null;
|
|
2480
|
+
if (STRATEGY_PLACEHOLDER_RE.test(body)) return null;
|
|
2481
|
+
const productName = typeof frontmatter.name === "string" ? frontmatter.name : "unnamed-product";
|
|
2482
|
+
return {
|
|
2483
|
+
id: `bk:strategy:${slugifyStrategySection(section.name)}`,
|
|
2484
|
+
type: "business_fact",
|
|
2485
|
+
name: section.name,
|
|
2486
|
+
path: relPath,
|
|
2487
|
+
content: body,
|
|
2488
|
+
metadata: {
|
|
2489
|
+
domain: "strategy",
|
|
2490
|
+
source: "STRATEGY.md",
|
|
2491
|
+
section_name: section.name,
|
|
2492
|
+
product_name: productName,
|
|
2493
|
+
...typeof frontmatter.last_updated === "string" && {
|
|
2494
|
+
last_updated: frontmatter.last_updated
|
|
2495
|
+
},
|
|
2496
|
+
...typeof frontmatter.version === "number" && { version: frontmatter.version }
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2414
2500
|
async createNodes(files, knowledgeDir, errors) {
|
|
2415
2501
|
const entries = [];
|
|
2416
2502
|
for (const filePath of files) {
|
|
@@ -2533,6 +2619,49 @@ function parseFrontmatter(raw) {
|
|
|
2533
2619
|
body
|
|
2534
2620
|
};
|
|
2535
2621
|
}
|
|
2622
|
+
function slugifyStrategySection(name) {
|
|
2623
|
+
return name.toLowerCase().replace(/'/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
2624
|
+
}
|
|
2625
|
+
function parseStrategyMarkdown(raw) {
|
|
2626
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
2627
|
+
if (!match) return null;
|
|
2628
|
+
const yamlBlock = match[1];
|
|
2629
|
+
const body = match[2];
|
|
2630
|
+
const frontmatter = {};
|
|
2631
|
+
for (const line of yamlBlock.split(/\r?\n/)) {
|
|
2632
|
+
const kvMatch = line.match(/^(\w+):\s*(.+)$/);
|
|
2633
|
+
if (!kvMatch) continue;
|
|
2634
|
+
const key = kvMatch[1];
|
|
2635
|
+
const rawValue = kvMatch[2].trim();
|
|
2636
|
+
const unquoted = rawValue.replace(/^["']|["']$/g, "");
|
|
2637
|
+
const asNumber = Number(unquoted);
|
|
2638
|
+
if (unquoted !== "" && !Number.isNaN(asNumber) && /^-?\d+(?:\.\d+)?$/.test(unquoted)) {
|
|
2639
|
+
frontmatter[key] = asNumber;
|
|
2640
|
+
} else {
|
|
2641
|
+
frontmatter[key] = unquoted;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
const sections = [];
|
|
2645
|
+
const h2Re = /^##[ \t]+(.+?)[ \t]*$/gm;
|
|
2646
|
+
const matches = [];
|
|
2647
|
+
let m;
|
|
2648
|
+
while ((m = h2Re.exec(body)) !== null) {
|
|
2649
|
+
matches.push({
|
|
2650
|
+
name: (m[1] ?? "").trim(),
|
|
2651
|
+
headingStart: m.index,
|
|
2652
|
+
bodyStart: m.index + m[0].length
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
for (let i = 0; i < matches.length; i++) {
|
|
2656
|
+
const current = matches[i];
|
|
2657
|
+
const sliceEnd = matches[i + 1]?.headingStart ?? body.length;
|
|
2658
|
+
sections.push({
|
|
2659
|
+
name: current.name,
|
|
2660
|
+
body: body.slice(current.bodyStart, sliceEnd).trim()
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
return { frontmatter, sections };
|
|
2664
|
+
}
|
|
2536
2665
|
function parseSolutionFrontmatter(raw) {
|
|
2537
2666
|
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
2538
2667
|
if (!match) return null;
|
|
@@ -2564,6 +2693,12 @@ var CODE_NODE_TYPES3 = [
|
|
|
2564
2693
|
"interface",
|
|
2565
2694
|
"variable"
|
|
2566
2695
|
];
|
|
2696
|
+
var ARCHITECTURE_ADR_H1 = /^\s*#\s+ADR[-\s]*(\d+)\s*[:\-—]\s*(.+?)\s*$/im;
|
|
2697
|
+
function matchField(body, field) {
|
|
2698
|
+
const re = new RegExp(`\\*\\*${field}:\\*\\*\\s*(.+)`, "i");
|
|
2699
|
+
const m = body.match(re);
|
|
2700
|
+
return m ? m[1].trim() : void 0;
|
|
2701
|
+
}
|
|
2567
2702
|
var DecisionIngestor = class {
|
|
2568
2703
|
constructor(store) {
|
|
2569
2704
|
this.store = store;
|
|
@@ -2620,6 +2755,71 @@ var DecisionIngestor = class {
|
|
|
2620
2755
|
durationMs: Date.now() - start
|
|
2621
2756
|
};
|
|
2622
2757
|
}
|
|
2758
|
+
/**
|
|
2759
|
+
* Ingest ADRs written by `harness-architecture-advisor` from
|
|
2760
|
+
* `docs/architecture/<topic>/ADR-<n>.md`. These files do not carry YAML
|
|
2761
|
+
* frontmatter — the canonical format is:
|
|
2762
|
+
*
|
|
2763
|
+
* ```
|
|
2764
|
+
* # ADR-<n>: <Title>
|
|
2765
|
+
*
|
|
2766
|
+
* **Date:** <date>
|
|
2767
|
+
* **Status:** Accepted | Proposed | Superseded | Deprecated
|
|
2768
|
+
* **Deciders:** <who>
|
|
2769
|
+
* ```
|
|
2770
|
+
*
|
|
2771
|
+
* The first directory under `architectureDir` becomes `metadata.domain`
|
|
2772
|
+
* (the "topic"), so projects whose only knowledge substrate is ADRs surface
|
|
2773
|
+
* `architecture/<topic>` as a documented domain rather than reporting empty.
|
|
2774
|
+
*
|
|
2775
|
+
* Soft-fails when the directory is absent (the common case for projects that
|
|
2776
|
+
* do not use the architecture-advisor convention).
|
|
2777
|
+
*/
|
|
2778
|
+
async ingestArchitecture(architectureDir) {
|
|
2779
|
+
const start = Date.now();
|
|
2780
|
+
const errors = [];
|
|
2781
|
+
let files;
|
|
2782
|
+
try {
|
|
2783
|
+
files = await this.findArchitectureAdrFiles(architectureDir);
|
|
2784
|
+
} catch {
|
|
2785
|
+
return emptyResult(Date.now() - start);
|
|
2786
|
+
}
|
|
2787
|
+
let nodesAdded = 0;
|
|
2788
|
+
let edgesAdded = 0;
|
|
2789
|
+
for (const filePath of files) {
|
|
2790
|
+
const delta = await this.processArchitectureAdrFile(filePath, architectureDir, errors);
|
|
2791
|
+
nodesAdded += delta.nodesAdded;
|
|
2792
|
+
edgesAdded += delta.edgesAdded;
|
|
2793
|
+
}
|
|
2794
|
+
return {
|
|
2795
|
+
nodesAdded,
|
|
2796
|
+
nodesUpdated: 0,
|
|
2797
|
+
edgesAdded,
|
|
2798
|
+
edgesUpdated: 0,
|
|
2799
|
+
errors,
|
|
2800
|
+
durationMs: Date.now() - start
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Read, parse, and add a single architecture-advisor ADR. Returns the delta
|
|
2805
|
+
* the caller should fold into the aggregate counts. Errors are appended to
|
|
2806
|
+
* the shared `errors` array rather than thrown, matching the soft-fail
|
|
2807
|
+
* contract of `ingest()`.
|
|
2808
|
+
*/
|
|
2809
|
+
async processArchitectureAdrFile(filePath, architectureDir, errors) {
|
|
2810
|
+
try {
|
|
2811
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
2812
|
+
const parsed = parseArchitectureAdr(raw);
|
|
2813
|
+
if (!parsed) return { nodesAdded: 0, edgesAdded: 0 };
|
|
2814
|
+
const node = buildArchitectureAdrNode(parsed, filePath, architectureDir);
|
|
2815
|
+
this.store.addNode(node);
|
|
2816
|
+
const edgesAdded = this.linkToCode(parsed.body, node.id);
|
|
2817
|
+
return { nodesAdded: 1, edgesAdded };
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2820
|
+
return { nodesAdded: 0, edgesAdded: 0 };
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2623
2823
|
parseFrontmatter(raw) {
|
|
2624
2824
|
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2625
2825
|
if (!match) return null;
|
|
@@ -2661,7 +2861,63 @@ var DecisionIngestor = class {
|
|
|
2661
2861
|
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2662
2862
|
return entries.filter((e) => e.isFile() && e.name.endsWith(".md") && e.name !== "README.md").map((e) => path5.join(dir, e.name));
|
|
2663
2863
|
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Recursively find `ADR-*.md` files under `dir`. Skips default skip dirs
|
|
2866
|
+
* (node_modules etc.) to keep the scan bounded on large repos.
|
|
2867
|
+
*/
|
|
2868
|
+
async findArchitectureAdrFiles(dir) {
|
|
2869
|
+
const results = [];
|
|
2870
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2871
|
+
for (const entry of entries) {
|
|
2872
|
+
const full = path5.join(dir, entry.name);
|
|
2873
|
+
if (entry.isDirectory()) {
|
|
2874
|
+
if (DEFAULT_SKIP_DIRS.has(entry.name)) continue;
|
|
2875
|
+
results.push(...await this.findArchitectureAdrFiles(full));
|
|
2876
|
+
} else if (entry.isFile() && entry.name.endsWith(".md") && /^ADR[-_]?\d/i.test(entry.name)) {
|
|
2877
|
+
results.push(full);
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return results;
|
|
2881
|
+
}
|
|
2664
2882
|
};
|
|
2883
|
+
function buildArchitectureAdrNode(parsed, filePath, architectureDir) {
|
|
2884
|
+
const filename = path5.basename(filePath, ".md");
|
|
2885
|
+
const relFromArch = path5.relative(architectureDir, filePath).replaceAll("\\", "/");
|
|
2886
|
+
const topic = relFromArch.includes("/") ? relFromArch.split("/")[0] : "";
|
|
2887
|
+
const nodeId = topic ? `decision:architecture:${topic}:${filename}` : `decision:architecture:${filename}`;
|
|
2888
|
+
return {
|
|
2889
|
+
id: nodeId,
|
|
2890
|
+
type: "decision",
|
|
2891
|
+
name: parsed.title,
|
|
2892
|
+
path: filePath,
|
|
2893
|
+
content: parsed.body.trim(),
|
|
2894
|
+
metadata: {
|
|
2895
|
+
number: parsed.number,
|
|
2896
|
+
...topic && { domain: topic },
|
|
2897
|
+
source: "architecture",
|
|
2898
|
+
...parsed.date && { date: parsed.date },
|
|
2899
|
+
...parsed.status && { status: parsed.status },
|
|
2900
|
+
...parsed.deciders && { deciders: parsed.deciders }
|
|
2901
|
+
}
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
function parseArchitectureAdr(raw) {
|
|
2905
|
+
const h1 = raw.match(ARCHITECTURE_ADR_H1);
|
|
2906
|
+
if (!h1) return null;
|
|
2907
|
+
const number = h1[1];
|
|
2908
|
+
const title = h1[2].trim();
|
|
2909
|
+
const date = matchField(raw, "Date");
|
|
2910
|
+
const status = matchField(raw, "Status");
|
|
2911
|
+
const deciders = matchField(raw, "Deciders");
|
|
2912
|
+
return {
|
|
2913
|
+
number,
|
|
2914
|
+
title,
|
|
2915
|
+
body: raw,
|
|
2916
|
+
...date && { date },
|
|
2917
|
+
...status && { status },
|
|
2918
|
+
...deciders && { deciders }
|
|
2919
|
+
};
|
|
2920
|
+
}
|
|
2665
2921
|
|
|
2666
2922
|
// src/ingest/RequirementIngestor.ts
|
|
2667
2923
|
var fs5 = __toESM(require("fs/promises"));
|
|
@@ -3011,7 +3267,7 @@ function emptyResult2(diagramType = "unknown") {
|
|
|
3011
3267
|
function detectDiagramType(content) {
|
|
3012
3268
|
for (const line of content.split("\n")) {
|
|
3013
3269
|
const trimmed = line.trim();
|
|
3014
|
-
if (!trimmed) continue;
|
|
3270
|
+
if (!trimmed || trimmed.startsWith("%%")) continue;
|
|
3015
3271
|
if (/^(?:graph|flowchart)\b/i.test(trimmed)) return "flowchart";
|
|
3016
3272
|
if (/^sequenceDiagram\b/.test(trimmed)) return "sequence";
|
|
3017
3273
|
if (/^classDiagram\b/.test(trimmed)) return "class";
|
|
@@ -5783,6 +6039,16 @@ var SNAPSHOT_NODE_TYPES = [
|
|
|
5783
6039
|
"aesthetic_intent",
|
|
5784
6040
|
"image_annotation"
|
|
5785
6041
|
];
|
|
6042
|
+
function emptyIngestResult() {
|
|
6043
|
+
return {
|
|
6044
|
+
nodesAdded: 0,
|
|
6045
|
+
nodesUpdated: 0,
|
|
6046
|
+
edgesAdded: 0,
|
|
6047
|
+
edgesUpdated: 0,
|
|
6048
|
+
errors: [],
|
|
6049
|
+
durationMs: 0
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
5786
6052
|
var KnowledgePipelineRunner = class {
|
|
5787
6053
|
constructor(store) {
|
|
5788
6054
|
this.store = store;
|
|
@@ -5793,8 +6059,10 @@ var KnowledgePipelineRunner = class {
|
|
|
5793
6059
|
async run(options) {
|
|
5794
6060
|
this.inferenceOptions = options.inferenceOptions ?? {};
|
|
5795
6061
|
const remediations = [];
|
|
6062
|
+
const ingestErrors = [];
|
|
5796
6063
|
const preSnapshot = this.buildSnapshot(options.domain);
|
|
5797
6064
|
const extraction = await this.extract(options);
|
|
6065
|
+
ingestErrors.push(...extraction.errors);
|
|
5798
6066
|
const postSnapshot = this.buildSnapshot(options.domain);
|
|
5799
6067
|
let driftResult = this.reconcile(preSnapshot, postSnapshot);
|
|
5800
6068
|
const contradictions = new ContradictionDetector().detect(this.store);
|
|
@@ -5807,7 +6075,8 @@ var KnowledgePipelineRunner = class {
|
|
|
5807
6075
|
options,
|
|
5808
6076
|
driftResult,
|
|
5809
6077
|
gapReport,
|
|
5810
|
-
remediations
|
|
6078
|
+
remediations,
|
|
6079
|
+
ingestErrors
|
|
5811
6080
|
);
|
|
5812
6081
|
iterations = loopResult.iterations;
|
|
5813
6082
|
materialization = loopResult.materialization;
|
|
@@ -5821,7 +6090,8 @@ var KnowledgePipelineRunner = class {
|
|
|
5821
6090
|
return this.buildResult(
|
|
5822
6091
|
driftResult,
|
|
5823
6092
|
iterations,
|
|
5824
|
-
extraction,
|
|
6093
|
+
extraction.counts,
|
|
6094
|
+
ingestErrors,
|
|
5825
6095
|
gapReport,
|
|
5826
6096
|
remediations,
|
|
5827
6097
|
contradictions,
|
|
@@ -5830,7 +6100,7 @@ var KnowledgePipelineRunner = class {
|
|
|
5830
6100
|
);
|
|
5831
6101
|
}
|
|
5832
6102
|
/** Run the remediation convergence loop; returns iteration count and accumulated materialization. */
|
|
5833
|
-
async runRemediationLoop(options, driftResult, gapReport, remediations) {
|
|
6103
|
+
async runRemediationLoop(options, driftResult, gapReport, remediations, ingestErrors) {
|
|
5834
6104
|
const maxIterations = options.maxIterations ?? 5;
|
|
5835
6105
|
let iterations = 1;
|
|
5836
6106
|
let currentDrift = driftResult;
|
|
@@ -5851,7 +6121,8 @@ var KnowledgePipelineRunner = class {
|
|
|
5851
6121
|
}
|
|
5852
6122
|
}
|
|
5853
6123
|
const preSnapshot = this.buildSnapshot(options.domain);
|
|
5854
|
-
await this.extract(options);
|
|
6124
|
+
const reExtract = await this.extract(options);
|
|
6125
|
+
ingestErrors.push(...reExtract.errors);
|
|
5855
6126
|
const postSnapshot = this.buildSnapshot(options.domain);
|
|
5856
6127
|
currentDrift = this.reconcile(preSnapshot, postSnapshot);
|
|
5857
6128
|
currentGapReport = await this.detect(options);
|
|
@@ -5866,13 +6137,14 @@ var KnowledgePipelineRunner = class {
|
|
|
5866
6137
|
};
|
|
5867
6138
|
}
|
|
5868
6139
|
/** Assemble the final pipeline result. */
|
|
5869
|
-
buildResult(driftResult, iterations, extraction, gaps, remediations, contradictions, coverage, materialization) {
|
|
6140
|
+
buildResult(driftResult, iterations, extraction, errors, gaps, remediations, contradictions, coverage, materialization) {
|
|
5870
6141
|
return {
|
|
5871
6142
|
verdict: this.computeVerdict(driftResult),
|
|
5872
6143
|
driftScore: driftResult.driftScore,
|
|
5873
6144
|
iterations,
|
|
5874
6145
|
findings: driftResult.summary,
|
|
5875
6146
|
extraction,
|
|
6147
|
+
errors: Array.from(new Set(errors)),
|
|
5876
6148
|
gaps,
|
|
5877
6149
|
remediations,
|
|
5878
6150
|
contradictions,
|
|
@@ -5903,58 +6175,60 @@ var KnowledgePipelineRunner = class {
|
|
|
5903
6175
|
try {
|
|
5904
6176
|
bkResult = await bkIngestor.ingest(knowledgeDir);
|
|
5905
6177
|
} catch {
|
|
5906
|
-
bkResult =
|
|
5907
|
-
nodesAdded: 0,
|
|
5908
|
-
nodesUpdated: 0,
|
|
5909
|
-
edgesAdded: 0,
|
|
5910
|
-
edgesUpdated: 0,
|
|
5911
|
-
errors: [],
|
|
5912
|
-
durationMs: 0
|
|
5913
|
-
};
|
|
6178
|
+
bkResult = emptyIngestResult();
|
|
5914
6179
|
}
|
|
5915
6180
|
const solutionsDir = path12.join(options.projectDir, "docs", "solutions");
|
|
5916
6181
|
let solutionsResult;
|
|
5917
6182
|
try {
|
|
5918
6183
|
solutionsResult = await bkIngestor.ingestSolutions(solutionsDir);
|
|
5919
6184
|
} catch {
|
|
5920
|
-
solutionsResult =
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
6185
|
+
solutionsResult = emptyIngestResult();
|
|
6186
|
+
}
|
|
6187
|
+
const strategyPath = path12.join(options.projectDir, "STRATEGY.md");
|
|
6188
|
+
let strategyResult;
|
|
6189
|
+
try {
|
|
6190
|
+
strategyResult = await bkIngestor.ingestStrategy(strategyPath);
|
|
6191
|
+
} catch {
|
|
6192
|
+
strategyResult = emptyIngestResult();
|
|
5928
6193
|
}
|
|
5929
6194
|
bkResult = {
|
|
5930
6195
|
...bkResult,
|
|
5931
|
-
nodesAdded: bkResult.nodesAdded + solutionsResult.nodesAdded,
|
|
5932
|
-
errors: [...bkResult.errors, ...solutionsResult.errors]
|
|
6196
|
+
nodesAdded: bkResult.nodesAdded + solutionsResult.nodesAdded + strategyResult.nodesAdded,
|
|
6197
|
+
errors: [...bkResult.errors, ...solutionsResult.errors, ...strategyResult.errors]
|
|
5933
6198
|
};
|
|
5934
6199
|
const decisionsDir = path12.join(options.projectDir, "docs", "knowledge", "decisions");
|
|
6200
|
+
const architectureDir = path12.join(options.projectDir, "docs", "architecture");
|
|
5935
6201
|
const decisionIngestor = new DecisionIngestor(this.store);
|
|
5936
6202
|
let decisionResult;
|
|
5937
6203
|
try {
|
|
5938
6204
|
decisionResult = await decisionIngestor.ingest(decisionsDir);
|
|
5939
6205
|
} catch {
|
|
5940
|
-
decisionResult =
|
|
5941
|
-
nodesAdded: 0,
|
|
5942
|
-
nodesUpdated: 0,
|
|
5943
|
-
edgesAdded: 0,
|
|
5944
|
-
edgesUpdated: 0,
|
|
5945
|
-
errors: [],
|
|
5946
|
-
durationMs: 0
|
|
5947
|
-
};
|
|
6206
|
+
decisionResult = emptyIngestResult();
|
|
5948
6207
|
}
|
|
6208
|
+
let architectureResult;
|
|
6209
|
+
try {
|
|
6210
|
+
architectureResult = await decisionIngestor.ingestArchitecture(architectureDir);
|
|
6211
|
+
} catch {
|
|
6212
|
+
architectureResult = emptyIngestResult();
|
|
6213
|
+
}
|
|
6214
|
+
decisionResult = {
|
|
6215
|
+
...decisionResult,
|
|
6216
|
+
nodesAdded: decisionResult.nodesAdded + architectureResult.nodesAdded,
|
|
6217
|
+
edgesAdded: decisionResult.edgesAdded + architectureResult.edgesAdded,
|
|
6218
|
+
errors: [...decisionResult.errors, ...architectureResult.errors]
|
|
6219
|
+
};
|
|
5949
6220
|
const linker = new KnowledgeLinker(this.store, extractedDir);
|
|
5950
6221
|
const linkResult = await linker.link();
|
|
5951
6222
|
return {
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
6223
|
+
counts: {
|
|
6224
|
+
codeSignals: extractionResult.nodesAdded,
|
|
6225
|
+
diagrams: diagramResult.nodesAdded,
|
|
6226
|
+
linkerFacts: linkResult.factsCreated,
|
|
6227
|
+
businessKnowledge: bkResult.nodesAdded,
|
|
6228
|
+
decisions: decisionResult.nodesAdded,
|
|
6229
|
+
images: imageCount
|
|
6230
|
+
},
|
|
6231
|
+
errors: [...bkResult.errors, ...decisionResult.errors]
|
|
5958
6232
|
};
|
|
5959
6233
|
}
|
|
5960
6234
|
// ── Phase 2: RECONCILE ────────────────────────────────────────────────────
|
|
@@ -9383,6 +9657,16 @@ var DesignIngestor = class {
|
|
|
9383
9657
|
};
|
|
9384
9658
|
|
|
9385
9659
|
// src/constraints/DesignConstraintAdapter.ts
|
|
9660
|
+
var CODE_PREFIX_LABELS = {
|
|
9661
|
+
"ANAT-D": "Component anatomy (definition)",
|
|
9662
|
+
"ANAT-P": "Component anatomy (pattern presence)",
|
|
9663
|
+
"ANAT-U": "Component anatomy (usage)",
|
|
9664
|
+
"CRAFT-C": "Design craft (critique)",
|
|
9665
|
+
"CRAFT-P": "Design craft (polish)",
|
|
9666
|
+
"CRAFT-B": "Design craft (benchmark)",
|
|
9667
|
+
"DESIGN-": "Design constraint (legacy)",
|
|
9668
|
+
"A11Y-": "Accessibility"
|
|
9669
|
+
};
|
|
9386
9670
|
var DesignConstraintAdapter = class {
|
|
9387
9671
|
constructor(store) {
|
|
9388
9672
|
this.store = store;
|
|
@@ -9464,6 +9748,76 @@ var DesignConstraintAdapter = class {
|
|
|
9464
9748
|
return "error";
|
|
9465
9749
|
}
|
|
9466
9750
|
}
|
|
9751
|
+
/**
|
|
9752
|
+
* Record externally-computed craft findings (audit-component-anatomy,
|
|
9753
|
+
* harness-design-craft, etc.) as graph state. Idempotent — re-running on
|
|
9754
|
+
* the same findings produces no duplicate nodes or edges thanks to
|
|
9755
|
+
* GraphStore's keyed merge semantics.
|
|
9756
|
+
*
|
|
9757
|
+
* Each finding becomes:
|
|
9758
|
+
* • a `design_constraint` node keyed by finding code (created lazily
|
|
9759
|
+
* if absent; metadata merged on re-record so the most recent message
|
|
9760
|
+
* / severity wins)
|
|
9761
|
+
* • a `violates_design` edge from the source file (id = file path) to
|
|
9762
|
+
* the constraint node, with per-finding metadata (line, severity,
|
|
9763
|
+
* message, evidence, runId)
|
|
9764
|
+
*
|
|
9765
|
+
* The file node is NOT created here — it's assumed to already exist in
|
|
9766
|
+
* the graph from a prior ingest. If it does not, the edge will still be
|
|
9767
|
+
* created (graph stores edges by key, not by referential integrity) and
|
|
9768
|
+
* a subsequent ingest will populate the file node.
|
|
9769
|
+
*/
|
|
9770
|
+
recordFindings(findings) {
|
|
9771
|
+
let constraintsAdded = 0;
|
|
9772
|
+
let edgesAdded = 0;
|
|
9773
|
+
const seenConstraintIds = /* @__PURE__ */ new Set();
|
|
9774
|
+
for (const finding of findings) {
|
|
9775
|
+
const constraintId = `design_constraint:${finding.code}`;
|
|
9776
|
+
if (!seenConstraintIds.has(finding.code)) {
|
|
9777
|
+
const existing = this.store.getNode(constraintId);
|
|
9778
|
+
if (!existing) constraintsAdded += 1;
|
|
9779
|
+
this.store.addNode({
|
|
9780
|
+
id: constraintId,
|
|
9781
|
+
type: "design_constraint",
|
|
9782
|
+
name: finding.code,
|
|
9783
|
+
metadata: {
|
|
9784
|
+
code: finding.code,
|
|
9785
|
+
label: this.labelForCode(finding.code),
|
|
9786
|
+
mostRecentMessage: finding.message,
|
|
9787
|
+
mostRecentSeverity: finding.severity
|
|
9788
|
+
}
|
|
9789
|
+
});
|
|
9790
|
+
seenConstraintIds.add(finding.code);
|
|
9791
|
+
}
|
|
9792
|
+
const fileId = finding.file;
|
|
9793
|
+
const edgeMetadata = {
|
|
9794
|
+
message: finding.message,
|
|
9795
|
+
severity: finding.severity
|
|
9796
|
+
};
|
|
9797
|
+
if (finding.line !== void 0) edgeMetadata.line = finding.line;
|
|
9798
|
+
if (finding.evidence !== void 0) edgeMetadata.evidence = finding.evidence;
|
|
9799
|
+
if (finding.runId !== void 0) edgeMetadata.runId = finding.runId;
|
|
9800
|
+
const before = this.store.getEdges({
|
|
9801
|
+
from: fileId,
|
|
9802
|
+
to: constraintId,
|
|
9803
|
+
type: "violates_design"
|
|
9804
|
+
});
|
|
9805
|
+
this.store.addEdge({
|
|
9806
|
+
from: fileId,
|
|
9807
|
+
to: constraintId,
|
|
9808
|
+
type: "violates_design",
|
|
9809
|
+
metadata: edgeMetadata
|
|
9810
|
+
});
|
|
9811
|
+
if (before.length === 0) edgesAdded += 1;
|
|
9812
|
+
}
|
|
9813
|
+
return { constraintsAdded, edgesAdded };
|
|
9814
|
+
}
|
|
9815
|
+
labelForCode(code) {
|
|
9816
|
+
for (const prefix of Object.keys(CODE_PREFIX_LABELS)) {
|
|
9817
|
+
if (code.startsWith(prefix)) return CODE_PREFIX_LABELS[prefix];
|
|
9818
|
+
}
|
|
9819
|
+
return "Design constraint";
|
|
9820
|
+
}
|
|
9467
9821
|
};
|
|
9468
9822
|
|
|
9469
9823
|
// src/feedback/GraphFeedbackAdapter.ts
|
|
@@ -9991,7 +10345,7 @@ var ConflictPredictor = class {
|
|
|
9991
10345
|
};
|
|
9992
10346
|
|
|
9993
10347
|
// src/index.ts
|
|
9994
|
-
var VERSION = "0.
|
|
10348
|
+
var VERSION = "0.9.0";
|
|
9995
10349
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9996
10350
|
0 && (module.exports = {
|
|
9997
10351
|
ALL_EXTRACTORS,
|