@harness-engineering/cli 1.26.1 → 1.27.1
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/agents/skills/claude-code/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/claude-code/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +16 -5
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/claude-code/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +16 -6
- package/dist/agents/skills/claude-code/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/claude-code/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/claude-code/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +77 -10
- package/dist/agents/skills/claude-code/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/claude-code/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +16 -5
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-execution/SKILL.md +16 -6
- package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/codex/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/codex/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/codex/harness-planning/SKILL.md +77 -10
- package/dist/agents/skills/codex/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/codex/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/codex/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +16 -5
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +16 -6
- package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/cursor/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/cursor/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +77 -10
- package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/gemini-cli/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +16 -5
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/gemini-cli/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +16 -6
- package/dist/agents/skills/gemini-cli/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/gemini-cli/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/gemini-cli/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +77 -10
- package/dist/agents/skills/gemini-cli/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/gemini-cli/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +4 -0
- package/dist/{agents-md-VI3HLR7E.js → agents-md-2RN666DE.js} +3 -3
- package/dist/{architecture-ZDDKEVID.js → architecture-ZR33YKJB.js} +4 -4
- package/dist/{assess-project-Y6W7YDUA.js → assess-project-LLWWEQEV.js} +1 -1
- package/dist/bin/harness-mcp.js +17 -16
- package/dist/bin/harness.js +27 -26
- package/dist/{business-knowledge-6RHYJOB3.js → business-knowledge-EISPR7FU.js} +1 -1
- package/dist/{check-phase-gate-CXR3VM22.js → check-phase-gate-ZVG56NT2.js} +4 -4
- package/dist/chunk-27AJKSQY.js +107 -0
- package/dist/{chunk-DBIX3X4H.js → chunk-2FOLD76X.js} +2 -2
- package/dist/{chunk-3AMQYWGT.js → chunk-3AJW7VQ3.js} +2 -2
- package/dist/{chunk-FUND5WJR.js → chunk-3XMOWLK3.js} +2 -2
- package/dist/{chunk-OLW7BJ5N.js → chunk-776JFSPK.js} +1 -1
- package/dist/{chunk-VKS3F46M.js → chunk-A4ETL34H.js} +7 -2
- package/dist/{chunk-JV4Y2U5F.js → chunk-BO4AJZRE.js} +1 -1
- package/dist/{chunk-WZ7UHPT3.js → chunk-BZZNYHRY.js} +9 -9
- package/dist/{chunk-ZN6ROTYP.js → chunk-C7RPQWBG.js} +1 -1
- package/dist/{chunk-RAVD7QLY.js → chunk-CJYNZMOP.js} +8 -8
- package/dist/{chunk-PNNFCVW4.js → chunk-EGRQHLDM.js} +5 -5
- package/dist/{chunk-2BPPS6YQ.js → chunk-EHTUWULH.js} +458 -363
- package/dist/{chunk-N6B6RKQQ.js → chunk-EICASBYP.js} +1 -1
- package/dist/{chunk-K56DTX7G.js → chunk-F3Y64VNF.js} +6 -6
- package/dist/{chunk-ORNEK65Y.js → chunk-FUS4OOGC.js} +1 -1
- package/dist/{chunk-2FPUPRCY.js → chunk-GAIOIINV.js} +594 -127
- package/dist/{chunk-YC4EYIER.js → chunk-IBTHD2UA.js} +132 -77
- package/dist/{chunk-RC5ZY3EV.js → chunk-IOW3MW2K.js} +2 -2
- package/dist/{chunk-B66LRB5M.js → chunk-L3Y7L4EG.js} +1 -1
- package/dist/{chunk-BK4DJIIY.js → chunk-MGPVA2EM.js} +27 -4
- package/dist/{chunk-NY7DTZ6K.js → chunk-Q5DKIL46.js} +3 -3
- package/dist/{chunk-TY2ESLVL.js → chunk-SDDQPMZK.js} +1 -1
- package/dist/{chunk-7GY6WNEI.js → chunk-SDPHFDQS.js} +1 -1
- package/dist/{chunk-56A6OXVA.js → chunk-T7UZOJF4.js} +9 -9
- package/dist/{chunk-AMRC4FU2.js → chunk-VCC47EXU.js} +109 -57
- package/dist/{chunk-Q3ZLVMQM.js → chunk-XKU37HGH.js} +10 -1
- package/dist/{chunk-TSVYBDDE.js → chunk-YZH4I4JO.js} +1 -1
- package/dist/{ci-workflow-EW37KVPS.js → ci-workflow-JFOL4YWW.js} +3 -3
- package/dist/{dist-NCNAXUFD.js → dist-BZS2XRLG.js} +5 -1
- package/dist/{dist-CJDGASPP.js → dist-CPKL5Y2R.js} +2 -2
- package/dist/{docs-4XI2FQW2.js → docs-FUTRJQHA.js} +4 -4
- package/dist/{engine-4L4CDTHU.js → engine-FB3HCNVT.js} +3 -3
- package/dist/{entropy-PBYSIQYS.js → entropy-FIEE4YDA.js} +3 -3
- package/dist/{feedback-OTIFW5MK.js → feedback-7EC4T6XG.js} +1 -1
- package/dist/{generate-agent-definitions-CZGUXIIN.js → generate-agent-definitions-FOSFADNR.js} +4 -4
- package/dist/{graph-loader-RDXSEBD7.js → graph-loader-QEI7SYOQ.js} +1 -1
- package/dist/hooks/adoption-tracker.js +36 -8
- package/dist/hooks/sentinel-pre.js +3 -2
- package/dist/hooks/telemetry-reporter.js +34 -15
- package/dist/index-builder-3AOQ3ZII.js +13 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +29 -28
- package/dist/{loader-UZOXCWFC.js → loader-HXUOBTYA.js} +3 -3
- package/dist/{mcp-GWVVV6LH.js → mcp-NUG4BMKJ.js} +17 -16
- package/dist/{performance-5UQPP3XX.js → performance-TYXBCTR7.js} +4 -4
- package/dist/{review-pipeline-65V4EO6O.js → review-pipeline-B4WPU6BQ.js} +3 -3
- package/dist/{runtime-PXX7HNB3.js → runtime-HQQNOARD.js} +3 -3
- package/dist/{scan-WM7XDUF7.js → scan-M4EGDAQ2.js} +1 -1
- package/dist/{security-CAYDRZIZ.js → security-25VFLIAO.js} +1 -1
- package/dist/templates/orchestrator/harness.orchestrator.md +18 -8
- package/dist/{validate-CWGXR7QM.js → validate-EAVGKZQB.js} +4 -4
- package/dist/{validate-cross-check-BKCNLZYG.js → validate-cross-check-SPL3H4O5.js} +3 -3
- package/package.json +13 -5
- package/dist/hooks/profiles.ts +0 -48
|
@@ -19,22 +19,26 @@ import * as fs3 from "fs/promises";
|
|
|
19
19
|
import * as path4 from "path";
|
|
20
20
|
import * as fs4 from "fs/promises";
|
|
21
21
|
import * as path5 from "path";
|
|
22
|
-
import * as fs9 from "fs/promises";
|
|
23
|
-
import * as path10 from "path";
|
|
24
22
|
import * as fs5 from "fs/promises";
|
|
25
23
|
import * as path6 from "path";
|
|
24
|
+
import * as fs11 from "fs/promises";
|
|
25
|
+
import * as path12 from "path";
|
|
26
26
|
import * as fs6 from "fs/promises";
|
|
27
27
|
import * as path7 from "path";
|
|
28
28
|
import * as fs7 from "fs/promises";
|
|
29
29
|
import * as path8 from "path";
|
|
30
30
|
import * as fs8 from "fs/promises";
|
|
31
31
|
import * as path9 from "path";
|
|
32
|
+
import * as fs9 from "fs/promises";
|
|
33
|
+
import * as path10 from "path";
|
|
32
34
|
import * as fs10 from "fs/promises";
|
|
33
35
|
import * as path11 from "path";
|
|
36
|
+
import * as fs12 from "fs/promises";
|
|
37
|
+
import * as path13 from "path";
|
|
34
38
|
import { minimatch } from "minimatch";
|
|
35
39
|
import { relative as relative5 } from "path";
|
|
36
|
-
import * as
|
|
37
|
-
import * as
|
|
40
|
+
import * as fs13 from "fs/promises";
|
|
41
|
+
import * as path14 from "path";
|
|
38
42
|
function streamGraphJson(filePath, nodes, edges) {
|
|
39
43
|
return new Promise((resolve, reject) => {
|
|
40
44
|
const stream = createWriteStream(filePath, { encoding: "utf-8" });
|
|
@@ -50,7 +54,7 @@ function streamGraphJson(filePath, nodes, edges) {
|
|
|
50
54
|
stream.write(JSON.stringify(edges[i]));
|
|
51
55
|
}
|
|
52
56
|
stream.write("]}");
|
|
53
|
-
stream.end(resolve);
|
|
57
|
+
stream.end(() => resolve());
|
|
54
58
|
});
|
|
55
59
|
}
|
|
56
60
|
async function saveGraph(dirPath, nodes, edges) {
|
|
@@ -73,15 +77,19 @@ async function loadGraph(dirPath) {
|
|
|
73
77
|
await access(metaPath);
|
|
74
78
|
await access(graphPath);
|
|
75
79
|
} catch {
|
|
76
|
-
return
|
|
80
|
+
return { status: "not_found" };
|
|
77
81
|
}
|
|
78
82
|
const metaContent = await readFile(metaPath, "utf-8");
|
|
79
83
|
const metadata = JSON.parse(metaContent);
|
|
80
84
|
if (metadata.schemaVersion !== CURRENT_SCHEMA_VERSION) {
|
|
81
|
-
return
|
|
85
|
+
return {
|
|
86
|
+
status: "schema_mismatch",
|
|
87
|
+
found: metadata.schemaVersion,
|
|
88
|
+
expected: CURRENT_SCHEMA_VERSION
|
|
89
|
+
};
|
|
82
90
|
}
|
|
83
91
|
const graphContent = await readFile(graphPath, "utf-8");
|
|
84
|
-
return JSON.parse(graphContent);
|
|
92
|
+
return { status: "loaded", graph: JSON.parse(graphContent) };
|
|
85
93
|
}
|
|
86
94
|
function safeMerge(target, source) {
|
|
87
95
|
for (const key of Object.keys(source)) {
|
|
@@ -646,11 +654,11 @@ function applyReactionBoost(candidate, nodeType, metadata) {
|
|
|
646
654
|
}
|
|
647
655
|
}
|
|
648
656
|
function detectLanguage(filePath) {
|
|
649
|
-
const name =
|
|
657
|
+
const name = path10.basename(filePath);
|
|
650
658
|
for (const skip of SKIP_EXTENSIONS2) {
|
|
651
659
|
if (name.endsWith(skip)) return void 0;
|
|
652
660
|
}
|
|
653
|
-
const ext =
|
|
661
|
+
const ext = path10.extname(filePath);
|
|
654
662
|
return EXT_TO_LANGUAGE[ext];
|
|
655
663
|
}
|
|
656
664
|
function createExtractionRunner() {
|
|
@@ -865,7 +873,7 @@ function sanitizeExternalText(text, maxLength = 2e3) {
|
|
|
865
873
|
}
|
|
866
874
|
function linkToCode(store, content, sourceNodeId, edgeType, options) {
|
|
867
875
|
let edgesCreated = 0;
|
|
868
|
-
for (const type of
|
|
876
|
+
for (const type of CODE_NODE_TYPES5) {
|
|
869
877
|
const nodes = store.findNodes({ type });
|
|
870
878
|
for (const node of nodes) {
|
|
871
879
|
if (node.name.length < 3) continue;
|
|
@@ -1353,7 +1361,7 @@ function isDTCGToken(obj) {
|
|
|
1353
1361
|
}
|
|
1354
1362
|
async function readFileOrNull(filePath) {
|
|
1355
1363
|
try {
|
|
1356
|
-
return await
|
|
1364
|
+
return await fs13.readFile(filePath, "utf-8");
|
|
1357
1365
|
} catch {
|
|
1358
1366
|
return null;
|
|
1359
1367
|
}
|
|
@@ -1435,7 +1443,7 @@ function parseAntiPatterns(content) {
|
|
|
1435
1443
|
}
|
|
1436
1444
|
return patterns;
|
|
1437
1445
|
}
|
|
1438
|
-
var NODE_TYPES, EDGE_TYPES, OBSERVABILITY_TYPES, CURRENT_SCHEMA_VERSION, NODE_STABILITY, GraphNodeSchema, GraphEdgeSchema, POISONED_KEYS, GraphStore, VectorStore, DEFAULT_TTL_MS, PackedSummaryCache, ContextQL, TEST_TYPES, DOC_TYPES, CODE_TYPES, SKIP_METHOD_NAMES, FUNCTION_PATTERNS, CLASS_PATTERNS, INTERFACE_PATTERNS, METHOD_PATTERNS, GO_METHOD_PATTERN, VARIABLE_PATTERNS, SUPPORTED_EXTENSIONS, SKIP_EXTENSIONS, CodeIngestor, execFileAsync, GitIngestor, TopologicalLinker, CODE_NODE_TYPES, KnowledgeIngestor, BUSINESS_KNOWLEDGE_TYPES, GOVERNS_SOURCE_TYPES, CODE_NODE_TYPES2, MEASURABLE_TYPES, BusinessKnowledgeIngestor, REQUIREMENT_SECTIONS, SECTION_HEADING_RE, NUMBERED_ITEM_RE,
|
|
1446
|
+
var NODE_TYPES, EDGE_TYPES, OBSERVABILITY_TYPES, CURRENT_SCHEMA_VERSION, NODE_STABILITY, GraphNodeSchema, GraphEdgeSchema, POISONED_KEYS, GraphStore, VectorStore, DEFAULT_TTL_MS, PackedSummaryCache, ContextQL, TEST_TYPES, DOC_TYPES, CODE_TYPES, SKIP_METHOD_NAMES, FUNCTION_PATTERNS, CLASS_PATTERNS, INTERFACE_PATTERNS, METHOD_PATTERNS, GO_METHOD_PATTERN, VARIABLE_PATTERNS, SUPPORTED_EXTENSIONS, SKIP_EXTENSIONS, CodeIngestor, execFileAsync, GitIngestor, TopologicalLinker, CODE_NODE_TYPES, KnowledgeIngestor, BUSINESS_KNOWLEDGE_TYPES, GOVERNS_SOURCE_TYPES, CODE_NODE_TYPES2, MEASURABLE_TYPES, BusinessKnowledgeIngestor, CODE_NODE_TYPES3, DecisionIngestor, REQUIREMENT_SECTIONS, SECTION_HEADING_RE, NUMBERED_ITEM_RE, CODE_NODE_TYPES4, RequirementIngestor, MermaidParser, D2Parser, PlantUmlParser, DIAGRAM_EXTENSIONS, SKIP_DIRS, DiagramParser, HEURISTIC_PATTERNS, SCANNABLE_TYPES, KnowledgeLinker, StructuralDriftDetector, BUSINESS_NODE_TYPES, KnowledgeStagingAggregator, SKIP_DIRS2, EXT_TO_LANGUAGE, SKIP_EXTENSIONS2, EXTRACTOR_EDGE_TYPE, ExtractionRunner, PATTERNS, TestDescriptionExtractor, EnumConstantExtractor, ValidationRuleExtractor, ApiPathExtractor, ALL_EXTRACTORS, ImageAnalysisExtractor, KNOWLEDGE_NODE_TYPES, SIMILARITY_THRESHOLD, SEVERITY_MAP, ContradictionDetector, KNOWLEDGE_TYPES, CODE_TYPES2, KNOWLEDGE_EDGE_TYPES, CoverageScorer, VALID_BUSINESS_TYPES, DEFAULT_MAX_DOCS, MAX_COLLISION_SUFFIX, KnowledgeDocMaterializer, BUSINESS_NODE_TYPES2, SNAPSHOT_NODE_TYPES, KnowledgePipelineRunner, CODE_NODE_TYPES5, RETRYABLE_STATUSES, SANITIZE_RULES, SyncManager, SUMMARIZE_PROMPT, JiraConnector, SlackConnector, ConfluenceConnector, CIConnector, CONSTRAINT_KEYWORDS, FigmaConnector, MIN_CONTENT_LENGTH, CONCEPT_ITEM_TYPES, MiroConnector, STOP_WORDS, FusionLayer, CODE_NODE_TYPES6, GraphEntropyAdapter, GraphComplexityAdapter, GraphCouplingAdapter, DEFAULT_THRESHOLD, DEFAULT_METRICS, RECOGNIZED_METRICS, GraphAnomalyAdapter, INTENTS, SIGNAL_WEIGHTS, INTENT_SIGNALS, IntentClassifier, INTENT_KEYWORDS, STOP_WORDS2, PASCAL_OR_CAMEL_RE, FILE_PATH_RE, QUOTED_RE, EntityExtractor, EntityResolver, ResponseFormatter, CompositeProbabilityStrategy, DEFAULT_PROBABILITY_FLOOR, DEFAULT_MAX_DEPTH, CascadeSimulator, ENTITY_REQUIRED_INTENTS, classifier, extractor, formatter, PHASE_NODE_TYPES, CODE_NODE_TYPES7, Assembler, GraphConstraintAdapter, DesignIngestor, DesignConstraintAdapter, GraphFeedbackAdapter, DEFAULT_EDGE_TYPES, TaskIndependenceAnalyzer, ConflictPredictor, VERSION;
|
|
1439
1447
|
var init_dist = __esm({
|
|
1440
1448
|
"../graph/dist/index.mjs"() {
|
|
1441
1449
|
NODE_TYPES = [
|
|
@@ -1462,7 +1470,7 @@ var init_dist = __esm({
|
|
|
1462
1470
|
"build",
|
|
1463
1471
|
"test_result",
|
|
1464
1472
|
"execution_outcome",
|
|
1465
|
-
// Observability
|
|
1473
|
+
// Observability — reserved for future tracing/metrics integration
|
|
1466
1474
|
"span",
|
|
1467
1475
|
"metric",
|
|
1468
1476
|
"log",
|
|
@@ -1509,7 +1517,7 @@ var init_dist = __esm({
|
|
|
1509
1517
|
"triggered_by",
|
|
1510
1518
|
"failed_in",
|
|
1511
1519
|
"outcome_of",
|
|
1512
|
-
// Execution relationships
|
|
1520
|
+
// Execution relationships — reserved for future observability integration
|
|
1513
1521
|
"executed_by",
|
|
1514
1522
|
"measured_by",
|
|
1515
1523
|
// Design relationships
|
|
@@ -1700,8 +1708,15 @@ var init_dist = __esm({
|
|
|
1700
1708
|
await saveGraph(dirPath, allNodes, allEdges);
|
|
1701
1709
|
}
|
|
1702
1710
|
async load(dirPath) {
|
|
1703
|
-
const
|
|
1704
|
-
if (
|
|
1711
|
+
const result = await loadGraph(dirPath);
|
|
1712
|
+
if (result.status === "not_found") return false;
|
|
1713
|
+
if (result.status === "schema_mismatch") {
|
|
1714
|
+
console.warn(
|
|
1715
|
+
`[graph] Schema version mismatch: graph was saved with v${result.found} but current is v${result.expected}. Graph will be rebuilt from scratch. Run \`harness graph scan\` to repopulate.`
|
|
1716
|
+
);
|
|
1717
|
+
return false;
|
|
1718
|
+
}
|
|
1719
|
+
const data = result.graph;
|
|
1705
1720
|
this.clear();
|
|
1706
1721
|
for (const node of data.nodes) {
|
|
1707
1722
|
this.nodeMap.set(node.id, { ...node });
|
|
@@ -2014,9 +2029,10 @@ var init_dist = __esm({
|
|
|
2014
2029
|
let edgesAdded = 0;
|
|
2015
2030
|
const files = await this.findSourceFiles(rootDir);
|
|
2016
2031
|
const nameToFiles = /* @__PURE__ */ new Map();
|
|
2032
|
+
const contentCache = /* @__PURE__ */ new Map();
|
|
2017
2033
|
for (const filePath of files) {
|
|
2018
2034
|
try {
|
|
2019
|
-
const result = await this.processFile(filePath, rootDir, nameToFiles);
|
|
2035
|
+
const result = await this.processFile(filePath, rootDir, nameToFiles, contentCache);
|
|
2020
2036
|
nodesAdded += result.nodesAdded;
|
|
2021
2037
|
edgesAdded += result.edgesAdded;
|
|
2022
2038
|
} catch (err) {
|
|
@@ -2026,7 +2042,8 @@ var init_dist = __esm({
|
|
|
2026
2042
|
for (const filePath of files) {
|
|
2027
2043
|
try {
|
|
2028
2044
|
const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2029
|
-
const content =
|
|
2045
|
+
const content = contentCache.get(filePath);
|
|
2046
|
+
if (content === void 0) continue;
|
|
2030
2047
|
const callsEdges = this.extractCallsEdgesForFile(relativePath, content, nameToFiles);
|
|
2031
2048
|
for (const edge of callsEdges) {
|
|
2032
2049
|
this.store.addEdge(edge);
|
|
@@ -2037,7 +2054,8 @@ var init_dist = __esm({
|
|
|
2037
2054
|
}
|
|
2038
2055
|
for (const filePath of files) {
|
|
2039
2056
|
try {
|
|
2040
|
-
const content =
|
|
2057
|
+
const content = contentCache.get(filePath);
|
|
2058
|
+
if (content === void 0) continue;
|
|
2041
2059
|
edgesAdded += this.extractReqAnnotationsForFile(filePath, content, rootDir);
|
|
2042
2060
|
} catch {
|
|
2043
2061
|
}
|
|
@@ -2051,11 +2069,12 @@ var init_dist = __esm({
|
|
|
2051
2069
|
durationMs: Date.now() - start
|
|
2052
2070
|
};
|
|
2053
2071
|
}
|
|
2054
|
-
async processFile(filePath, rootDir, nameToFiles) {
|
|
2072
|
+
async processFile(filePath, rootDir, nameToFiles, contentCache) {
|
|
2055
2073
|
let nodesAdded = 0;
|
|
2056
2074
|
let edgesAdded = 0;
|
|
2057
2075
|
const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
|
|
2058
2076
|
const content = await fs.readFile(filePath, "utf-8");
|
|
2077
|
+
contentCache.set(filePath, content);
|
|
2059
2078
|
const stat2 = await fs.stat(filePath);
|
|
2060
2079
|
const fileId = `file:${relativePath}`;
|
|
2061
2080
|
const fileNode = {
|
|
@@ -3093,6 +3112,112 @@ var init_dist = __esm({
|
|
|
3093
3112
|
return results;
|
|
3094
3113
|
}
|
|
3095
3114
|
};
|
|
3115
|
+
CODE_NODE_TYPES3 = [
|
|
3116
|
+
"file",
|
|
3117
|
+
"function",
|
|
3118
|
+
"class",
|
|
3119
|
+
"method",
|
|
3120
|
+
"interface",
|
|
3121
|
+
"variable"
|
|
3122
|
+
];
|
|
3123
|
+
DecisionIngestor = class {
|
|
3124
|
+
constructor(store) {
|
|
3125
|
+
this.store = store;
|
|
3126
|
+
}
|
|
3127
|
+
store;
|
|
3128
|
+
async ingest(decisionsDir) {
|
|
3129
|
+
const start = Date.now();
|
|
3130
|
+
const errors = [];
|
|
3131
|
+
let files;
|
|
3132
|
+
try {
|
|
3133
|
+
files = await this.findDecisionFiles(decisionsDir);
|
|
3134
|
+
} catch {
|
|
3135
|
+
return emptyResult(Date.now() - start);
|
|
3136
|
+
}
|
|
3137
|
+
let nodesAdded = 0;
|
|
3138
|
+
let edgesAdded = 0;
|
|
3139
|
+
for (const filePath of files) {
|
|
3140
|
+
try {
|
|
3141
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
3142
|
+
const parsed = this.parseFrontmatter(raw);
|
|
3143
|
+
if (!parsed) continue;
|
|
3144
|
+
const { frontmatter, body } = parsed;
|
|
3145
|
+
if (!frontmatter.number || !frontmatter.title) continue;
|
|
3146
|
+
const filename = path5.basename(filePath, ".md");
|
|
3147
|
+
const nodeId = `decision:${filename}`;
|
|
3148
|
+
const node = {
|
|
3149
|
+
id: nodeId,
|
|
3150
|
+
type: "decision",
|
|
3151
|
+
name: frontmatter.title,
|
|
3152
|
+
path: filePath,
|
|
3153
|
+
content: body.trim(),
|
|
3154
|
+
metadata: {
|
|
3155
|
+
number: frontmatter.number,
|
|
3156
|
+
...frontmatter.date && { date: frontmatter.date },
|
|
3157
|
+
...frontmatter.status && { status: frontmatter.status },
|
|
3158
|
+
...frontmatter.tier && { tier: frontmatter.tier },
|
|
3159
|
+
...frontmatter.source && { source: frontmatter.source },
|
|
3160
|
+
...frontmatter.supersedes && { supersedes: frontmatter.supersedes }
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
this.store.addNode(node);
|
|
3164
|
+
nodesAdded++;
|
|
3165
|
+
edgesAdded += this.linkToCode(body, nodeId);
|
|
3166
|
+
} catch (err) {
|
|
3167
|
+
errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
return {
|
|
3171
|
+
nodesAdded,
|
|
3172
|
+
nodesUpdated: 0,
|
|
3173
|
+
edgesAdded,
|
|
3174
|
+
edgesUpdated: 0,
|
|
3175
|
+
errors,
|
|
3176
|
+
durationMs: Date.now() - start
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
parseFrontmatter(raw) {
|
|
3180
|
+
const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
3181
|
+
if (!match) return null;
|
|
3182
|
+
const yamlBlock = match[1];
|
|
3183
|
+
const body = match[2];
|
|
3184
|
+
const frontmatter = {};
|
|
3185
|
+
for (const line of yamlBlock.split("\n")) {
|
|
3186
|
+
const kvMatch = line.match(/^(\w+):\s*(.+)$/);
|
|
3187
|
+
if (!kvMatch) continue;
|
|
3188
|
+
frontmatter[kvMatch[1]] = kvMatch[2].trim();
|
|
3189
|
+
}
|
|
3190
|
+
if (!frontmatter.number || !frontmatter.title) return null;
|
|
3191
|
+
return {
|
|
3192
|
+
frontmatter,
|
|
3193
|
+
body
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
linkToCode(content, sourceNodeId) {
|
|
3197
|
+
let count = 0;
|
|
3198
|
+
for (const nodeType of CODE_NODE_TYPES3) {
|
|
3199
|
+
const codeNodes = this.store.findNodes({ type: nodeType });
|
|
3200
|
+
for (const node of codeNodes) {
|
|
3201
|
+
if (node.name.length < 3) continue;
|
|
3202
|
+
const escaped = node.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3203
|
+
const namePattern = new RegExp(`\\b${escaped}\\b`, "i");
|
|
3204
|
+
if (namePattern.test(content)) {
|
|
3205
|
+
this.store.addEdge({
|
|
3206
|
+
from: sourceNodeId,
|
|
3207
|
+
to: node.id,
|
|
3208
|
+
type: "decided"
|
|
3209
|
+
});
|
|
3210
|
+
count++;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
return count;
|
|
3215
|
+
}
|
|
3216
|
+
async findDecisionFiles(dir) {
|
|
3217
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
3218
|
+
return entries.filter((e) => e.isFile() && e.name.endsWith(".md") && e.name !== "README.md").map((e) => path5.join(dir, e.name));
|
|
3219
|
+
}
|
|
3220
|
+
};
|
|
3096
3221
|
REQUIREMENT_SECTIONS = [
|
|
3097
3222
|
"Observable Truths",
|
|
3098
3223
|
"Success Criteria",
|
|
@@ -3100,7 +3225,7 @@ var init_dist = __esm({
|
|
|
3100
3225
|
];
|
|
3101
3226
|
SECTION_HEADING_RE = /^#{2,3}\s+(.+)$/;
|
|
3102
3227
|
NUMBERED_ITEM_RE = /^\s*(\d+)\.\s+(.+)$/;
|
|
3103
|
-
|
|
3228
|
+
CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
|
|
3104
3229
|
RequirementIngestor = class {
|
|
3105
3230
|
constructor(store) {
|
|
3106
3231
|
this.store = store;
|
|
@@ -3118,8 +3243,8 @@ var init_dist = __esm({
|
|
|
3118
3243
|
let edgesAdded = 0;
|
|
3119
3244
|
let featureDirs;
|
|
3120
3245
|
try {
|
|
3121
|
-
const entries = await
|
|
3122
|
-
featureDirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
3246
|
+
const entries = await fs5.readdir(specsDir, { withFileTypes: true });
|
|
3247
|
+
featureDirs = entries.filter((e) => e.isDirectory()).map((e) => path6.join(specsDir, e.name));
|
|
3123
3248
|
} catch {
|
|
3124
3249
|
return emptyResult(Date.now() - start);
|
|
3125
3250
|
}
|
|
@@ -3138,11 +3263,11 @@ var init_dist = __esm({
|
|
|
3138
3263
|
};
|
|
3139
3264
|
}
|
|
3140
3265
|
async ingestFeatureDir(featureDir, errors) {
|
|
3141
|
-
const featureName =
|
|
3142
|
-
const specPath =
|
|
3266
|
+
const featureName = path6.basename(featureDir);
|
|
3267
|
+
const specPath = path6.join(featureDir, "proposal.md").replaceAll("\\", "/");
|
|
3143
3268
|
let content;
|
|
3144
3269
|
try {
|
|
3145
|
-
content = await
|
|
3270
|
+
content = await fs5.readFile(specPath, "utf-8");
|
|
3146
3271
|
} catch {
|
|
3147
3272
|
return { nodesAdded: 0, edgesAdded: 0 };
|
|
3148
3273
|
}
|
|
@@ -3159,7 +3284,7 @@ var init_dist = __esm({
|
|
|
3159
3284
|
this.store.addNode({
|
|
3160
3285
|
id: specNodeId,
|
|
3161
3286
|
type: "document",
|
|
3162
|
-
name:
|
|
3287
|
+
name: path6.basename(specPath),
|
|
3163
3288
|
path: specPath,
|
|
3164
3289
|
metadata: { featureName }
|
|
3165
3290
|
});
|
|
@@ -3274,9 +3399,9 @@ var init_dist = __esm({
|
|
|
3274
3399
|
for (const node of fileNodes) {
|
|
3275
3400
|
if (!node.path) continue;
|
|
3276
3401
|
const normalizedPath = node.path.replace(/\\/g, "/");
|
|
3277
|
-
const isCodeMatch = normalizedPath.includes("packages/") &&
|
|
3402
|
+
const isCodeMatch = normalizedPath.includes("packages/") && path6.basename(normalizedPath).includes(featureName);
|
|
3278
3403
|
const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
|
|
3279
|
-
|
|
3404
|
+
path6.basename(normalizedPath).includes(featureName);
|
|
3280
3405
|
if (isCodeMatch && !isTestMatch) {
|
|
3281
3406
|
this.store.addEdge({
|
|
3282
3407
|
from: reqId,
|
|
@@ -3305,7 +3430,7 @@ var init_dist = __esm({
|
|
|
3305
3430
|
*/
|
|
3306
3431
|
linkByKeywordOverlap(reqId, reqText) {
|
|
3307
3432
|
let count = 0;
|
|
3308
|
-
for (const nodeType of
|
|
3433
|
+
for (const nodeType of CODE_NODE_TYPES4) {
|
|
3309
3434
|
const codeNodes = this.store.findNodes({ type: nodeType });
|
|
3310
3435
|
for (const node of codeNodes) {
|
|
3311
3436
|
if (node.name.length < 3) continue;
|
|
@@ -3402,7 +3527,7 @@ var init_dist = __esm({
|
|
|
3402
3527
|
new PlantUmlParser()
|
|
3403
3528
|
];
|
|
3404
3529
|
parse(content, filePath) {
|
|
3405
|
-
const ext =
|
|
3530
|
+
const ext = path7.extname(filePath).toLowerCase();
|
|
3406
3531
|
for (const parser of this.parsers) {
|
|
3407
3532
|
if (parser.canParse(content, ext)) {
|
|
3408
3533
|
return parser.parse(content, filePath);
|
|
@@ -3422,8 +3547,8 @@ var init_dist = __esm({
|
|
|
3422
3547
|
const diagramFiles = await this.findDiagramFiles(projectDir);
|
|
3423
3548
|
for (const filePath of diagramFiles) {
|
|
3424
3549
|
try {
|
|
3425
|
-
const content = await
|
|
3426
|
-
const relPath =
|
|
3550
|
+
const content = await fs6.readFile(filePath, "utf-8");
|
|
3551
|
+
const relPath = path7.relative(projectDir, filePath).replaceAll("\\", "/");
|
|
3427
3552
|
const result = this.parse(content, filePath);
|
|
3428
3553
|
if (result.entities.length === 0) continue;
|
|
3429
3554
|
const pathHash = hash(relPath);
|
|
@@ -3489,19 +3614,19 @@ var init_dist = __esm({
|
|
|
3489
3614
|
const walk = async (currentDir) => {
|
|
3490
3615
|
let entries;
|
|
3491
3616
|
try {
|
|
3492
|
-
entries = await
|
|
3617
|
+
entries = await fs6.readdir(currentDir, { withFileTypes: true });
|
|
3493
3618
|
} catch {
|
|
3494
3619
|
return;
|
|
3495
3620
|
}
|
|
3496
3621
|
for (const entry of entries) {
|
|
3497
3622
|
if (entry.isDirectory()) {
|
|
3498
3623
|
if (!SKIP_DIRS.has(entry.name)) {
|
|
3499
|
-
await walk(
|
|
3624
|
+
await walk(path7.join(currentDir, entry.name));
|
|
3500
3625
|
}
|
|
3501
3626
|
} else if (entry.isFile()) {
|
|
3502
|
-
const ext =
|
|
3627
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
3503
3628
|
if (DIAGRAM_EXTENSIONS.has(ext)) {
|
|
3504
|
-
files.push(
|
|
3629
|
+
files.push(path7.join(currentDir, entry.name));
|
|
3505
3630
|
}
|
|
3506
3631
|
}
|
|
3507
3632
|
}
|
|
@@ -3648,8 +3773,10 @@ var init_dist = __esm({
|
|
|
3648
3773
|
if (!duplicate) return false;
|
|
3649
3774
|
const existingSources = duplicate.metadata.sources ?? [];
|
|
3650
3775
|
if (!existingSources.includes(candidate.sourceNodeId)) {
|
|
3651
|
-
|
|
3652
|
-
|
|
3776
|
+
this.store.addNode({
|
|
3777
|
+
...duplicate,
|
|
3778
|
+
metadata: { ...duplicate.metadata, sources: [...existingSources, candidate.sourceNodeId] }
|
|
3779
|
+
});
|
|
3653
3780
|
}
|
|
3654
3781
|
return true;
|
|
3655
3782
|
}
|
|
@@ -3683,8 +3810,8 @@ var init_dist = __esm({
|
|
|
3683
3810
|
*/
|
|
3684
3811
|
async writeJsonl(candidates) {
|
|
3685
3812
|
if (!this.outputDir) return;
|
|
3686
|
-
await
|
|
3687
|
-
const filePath =
|
|
3813
|
+
await fs7.mkdir(this.outputDir, { recursive: true });
|
|
3814
|
+
const filePath = path8.join(this.outputDir, "linker.jsonl");
|
|
3688
3815
|
const lines = candidates.map(
|
|
3689
3816
|
(c) => JSON.stringify({
|
|
3690
3817
|
id: c.id,
|
|
@@ -3697,16 +3824,16 @@ var init_dist = __esm({
|
|
|
3697
3824
|
nodeType: c.nodeType
|
|
3698
3825
|
})
|
|
3699
3826
|
);
|
|
3700
|
-
await
|
|
3827
|
+
await fs7.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
|
|
3701
3828
|
}
|
|
3702
3829
|
/**
|
|
3703
3830
|
* Write medium-confidence candidates to staged JSONL for human review.
|
|
3704
3831
|
*/
|
|
3705
3832
|
async writeStagedJsonl(candidates) {
|
|
3706
3833
|
if (!this.outputDir || candidates.length === 0) return;
|
|
3707
|
-
const stagedDir =
|
|
3708
|
-
await
|
|
3709
|
-
const filePath =
|
|
3834
|
+
const stagedDir = path8.join(this.outputDir, "staged");
|
|
3835
|
+
await fs7.mkdir(stagedDir, { recursive: true });
|
|
3836
|
+
const filePath = path8.join(stagedDir, "linker-staged.jsonl");
|
|
3710
3837
|
const lines = candidates.map(
|
|
3711
3838
|
(c) => JSON.stringify({
|
|
3712
3839
|
id: c.id,
|
|
@@ -3719,7 +3846,7 @@ var init_dist = __esm({
|
|
|
3719
3846
|
nodeType: c.nodeType
|
|
3720
3847
|
})
|
|
3721
3848
|
);
|
|
3722
|
-
await
|
|
3849
|
+
await fs7.writeFile(filePath, lines.join("\n") + "\n");
|
|
3723
3850
|
}
|
|
3724
3851
|
/**
|
|
3725
3852
|
* Apply heuristic patterns to content and return candidate extractions.
|
|
@@ -3821,6 +3948,14 @@ var init_dist = __esm({
|
|
|
3821
3948
|
return { findings, driftScore, summary };
|
|
3822
3949
|
}
|
|
3823
3950
|
};
|
|
3951
|
+
BUSINESS_NODE_TYPES = [
|
|
3952
|
+
"business_concept",
|
|
3953
|
+
"business_rule",
|
|
3954
|
+
"business_process",
|
|
3955
|
+
"business_term",
|
|
3956
|
+
"business_metric",
|
|
3957
|
+
"business_fact"
|
|
3958
|
+
];
|
|
3824
3959
|
KnowledgeStagingAggregator = class {
|
|
3825
3960
|
constructor(projectDir) {
|
|
3826
3961
|
this.projectDir = projectDir;
|
|
@@ -3839,52 +3974,150 @@ var init_dist = __esm({
|
|
|
3839
3974
|
if (deduplicated.length === 0) {
|
|
3840
3975
|
return { staged: 0 };
|
|
3841
3976
|
}
|
|
3842
|
-
const stagedDir =
|
|
3843
|
-
await
|
|
3977
|
+
const stagedDir = path9.join(this.projectDir, ".harness", "knowledge", "staged");
|
|
3978
|
+
await fs8.mkdir(stagedDir, { recursive: true });
|
|
3844
3979
|
const jsonl = deduplicated.map((entry) => JSON.stringify(entry)).join("\n") + "\n";
|
|
3845
|
-
await
|
|
3980
|
+
await fs8.writeFile(path9.join(stagedDir, "pipeline-staged.jsonl"), jsonl, "utf-8");
|
|
3846
3981
|
return { staged: deduplicated.length };
|
|
3847
3982
|
}
|
|
3848
|
-
async
|
|
3849
|
-
const
|
|
3983
|
+
async extractDocName(filePath) {
|
|
3984
|
+
const raw = await fs8.readFile(filePath, "utf-8");
|
|
3985
|
+
const fmMatch = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
3986
|
+
const body = fmMatch ? fmMatch[1] : raw;
|
|
3987
|
+
const titleMatch = body.match(/^#\s+(.+)$/m);
|
|
3988
|
+
return titleMatch ? titleMatch[1].trim() : path9.basename(filePath, ".md");
|
|
3989
|
+
}
|
|
3990
|
+
async generateGapReport(knowledgeDir, store) {
|
|
3991
|
+
const domainDocNames = /* @__PURE__ */ new Map();
|
|
3992
|
+
const domainEntryCounts = /* @__PURE__ */ new Map();
|
|
3850
3993
|
let totalEntries = 0;
|
|
3851
3994
|
try {
|
|
3852
|
-
const entries = await
|
|
3995
|
+
const entries = await fs8.readdir(knowledgeDir, { withFileTypes: true });
|
|
3853
3996
|
const domainDirs = entries.filter((e) => e.isDirectory());
|
|
3854
3997
|
for (const dir of domainDirs) {
|
|
3855
|
-
const domainPath =
|
|
3856
|
-
const files = await
|
|
3998
|
+
const domainPath = path9.join(knowledgeDir, dir.name);
|
|
3999
|
+
const files = await fs8.readdir(domainPath);
|
|
3857
4000
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
3858
4001
|
const entryCount = mdFiles.length;
|
|
3859
4002
|
totalEntries += entryCount;
|
|
3860
|
-
|
|
4003
|
+
domainEntryCounts.set(dir.name, entryCount);
|
|
4004
|
+
if (store) {
|
|
4005
|
+
const names = [];
|
|
4006
|
+
for (const file of mdFiles) {
|
|
4007
|
+
const name = await this.extractDocName(path9.join(domainPath, file));
|
|
4008
|
+
names.push(name.toLowerCase().trim());
|
|
4009
|
+
}
|
|
4010
|
+
domainDocNames.set(dir.name, names);
|
|
4011
|
+
}
|
|
3861
4012
|
}
|
|
3862
4013
|
} catch {
|
|
3863
4014
|
}
|
|
4015
|
+
if (!store) {
|
|
4016
|
+
const domains2 = [];
|
|
4017
|
+
for (const [domain, entryCount] of domainEntryCounts) {
|
|
4018
|
+
domains2.push({ domain, entryCount, extractedCount: 0, gapCount: 0, gapEntries: [] });
|
|
4019
|
+
}
|
|
4020
|
+
return {
|
|
4021
|
+
domains: domains2,
|
|
4022
|
+
totalEntries,
|
|
4023
|
+
totalExtracted: 0,
|
|
4024
|
+
totalGaps: 0,
|
|
4025
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4026
|
+
};
|
|
4027
|
+
}
|
|
4028
|
+
const extractedByDomain = /* @__PURE__ */ new Map();
|
|
4029
|
+
for (const nodeType of BUSINESS_NODE_TYPES) {
|
|
4030
|
+
const nodes = store.findNodes({ type: nodeType });
|
|
4031
|
+
for (const node of nodes) {
|
|
4032
|
+
const domain = node.metadata?.domain ?? "unknown";
|
|
4033
|
+
const list = extractedByDomain.get(domain) ?? [];
|
|
4034
|
+
list.push(node);
|
|
4035
|
+
extractedByDomain.set(domain, list);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
for (const [domain, nodes] of extractedByDomain) {
|
|
4039
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4040
|
+
const deduped = nodes.filter((n) => {
|
|
4041
|
+
const key = n.name.toLowerCase().trim();
|
|
4042
|
+
if (seen.has(key)) return false;
|
|
4043
|
+
seen.add(key);
|
|
4044
|
+
return true;
|
|
4045
|
+
});
|
|
4046
|
+
extractedByDomain.set(domain, deduped);
|
|
4047
|
+
}
|
|
4048
|
+
const allDomains = /* @__PURE__ */ new Set([...domainEntryCounts.keys(), ...extractedByDomain.keys()]);
|
|
4049
|
+
const domains = [];
|
|
4050
|
+
let totalExtracted = 0;
|
|
4051
|
+
let totalGaps = 0;
|
|
4052
|
+
for (const domain of allDomains) {
|
|
4053
|
+
const entryCount = domainEntryCounts.get(domain) ?? 0;
|
|
4054
|
+
const extractedNodes = extractedByDomain.get(domain) ?? [];
|
|
4055
|
+
const extractedCount = extractedNodes.length;
|
|
4056
|
+
totalExtracted += extractedCount;
|
|
4057
|
+
const docNames = domainDocNames.get(domain) ?? [];
|
|
4058
|
+
const gapEntries = [];
|
|
4059
|
+
for (const node of extractedNodes) {
|
|
4060
|
+
const normalizedName = node.name.toLowerCase().trim();
|
|
4061
|
+
if (!docNames.includes(normalizedName)) {
|
|
4062
|
+
gapEntries.push({
|
|
4063
|
+
nodeId: node.id,
|
|
4064
|
+
name: node.name,
|
|
4065
|
+
nodeType: node.type,
|
|
4066
|
+
source: node.metadata?.source ?? "unknown",
|
|
4067
|
+
hasContent: Boolean(node.content && node.content.trim().length >= 10)
|
|
4068
|
+
});
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
totalGaps += gapEntries.length;
|
|
4072
|
+
domains.push({ domain, entryCount, extractedCount, gapCount: gapEntries.length, gapEntries });
|
|
4073
|
+
}
|
|
3864
4074
|
return {
|
|
3865
4075
|
domains,
|
|
3866
4076
|
totalEntries,
|
|
4077
|
+
totalExtracted,
|
|
4078
|
+
totalGaps,
|
|
3867
4079
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3868
4080
|
};
|
|
3869
4081
|
}
|
|
3870
4082
|
async writeGapReport(report) {
|
|
3871
|
-
const gapsDir =
|
|
3872
|
-
await
|
|
4083
|
+
const gapsDir = path9.join(this.projectDir, ".harness", "knowledge");
|
|
4084
|
+
await fs8.mkdir(gapsDir, { recursive: true });
|
|
4085
|
+
const hasDifferential = report.totalExtracted > 0;
|
|
3873
4086
|
const lines = [
|
|
3874
4087
|
"# Knowledge Gaps Report",
|
|
3875
4088
|
"",
|
|
3876
4089
|
`Generated: ${report.generatedAt}`,
|
|
3877
4090
|
"",
|
|
3878
4091
|
"## Coverage by Domain",
|
|
3879
|
-
""
|
|
3880
|
-
"| Domain | Entries |",
|
|
3881
|
-
"| ------ | ------- |"
|
|
4092
|
+
""
|
|
3882
4093
|
];
|
|
3883
|
-
|
|
3884
|
-
lines.push(
|
|
4094
|
+
if (hasDifferential) {
|
|
4095
|
+
lines.push(
|
|
4096
|
+
"| Domain | Documented | Extracted | Gaps |",
|
|
4097
|
+
"| ------ | ---------- | --------- | ---- |"
|
|
4098
|
+
);
|
|
4099
|
+
for (const domain of report.domains) {
|
|
4100
|
+
lines.push(
|
|
4101
|
+
`| ${domain.domain} | ${domain.entryCount} | ${domain.extractedCount} | ${domain.gapCount} |`
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
lines.push(
|
|
4105
|
+
"",
|
|
4106
|
+
`## Summary`,
|
|
4107
|
+
"",
|
|
4108
|
+
`- **Total Documented:** ${report.totalEntries}`,
|
|
4109
|
+
`- **Total Extracted:** ${report.totalExtracted}`,
|
|
4110
|
+
`- **Total Gaps:** ${report.totalGaps}`,
|
|
4111
|
+
""
|
|
4112
|
+
);
|
|
4113
|
+
} else {
|
|
4114
|
+
lines.push("| Domain | Entries |", "| ------ | ------- |");
|
|
4115
|
+
for (const domain of report.domains) {
|
|
4116
|
+
lines.push(`| ${domain.domain} | ${domain.entryCount} |`);
|
|
4117
|
+
}
|
|
4118
|
+
lines.push("", `## Total Entries: ${report.totalEntries}`, "");
|
|
3885
4119
|
}
|
|
3886
|
-
|
|
3887
|
-
await fs7.writeFile(path8.join(gapsDir, "gaps.md"), lines.join("\n"), "utf-8");
|
|
4120
|
+
await fs8.writeFile(path9.join(gapsDir, "gaps.md"), lines.join("\n"), "utf-8");
|
|
3888
4121
|
}
|
|
3889
4122
|
};
|
|
3890
4123
|
SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
@@ -3946,7 +4179,7 @@ var init_dist = __esm({
|
|
|
3946
4179
|
let nodesAdded = 0;
|
|
3947
4180
|
let nodesUpdated = 0;
|
|
3948
4181
|
let edgesAdded = 0;
|
|
3949
|
-
await
|
|
4182
|
+
await fs9.mkdir(outputDir, { recursive: true });
|
|
3950
4183
|
const files = await this.findSourceFiles(projectDir);
|
|
3951
4184
|
const recordsByExtractor = /* @__PURE__ */ new Map();
|
|
3952
4185
|
for (const ext of this.extractors) {
|
|
@@ -3955,17 +4188,17 @@ var init_dist = __esm({
|
|
|
3955
4188
|
for (const filePath of files) {
|
|
3956
4189
|
const language = detectLanguage(filePath);
|
|
3957
4190
|
if (!language) continue;
|
|
3958
|
-
const ext =
|
|
4191
|
+
const ext = path10.extname(filePath);
|
|
3959
4192
|
let content;
|
|
3960
4193
|
try {
|
|
3961
|
-
content = await
|
|
4194
|
+
content = await fs9.readFile(filePath, "utf-8");
|
|
3962
4195
|
} catch (err) {
|
|
3963
4196
|
errors.push(
|
|
3964
4197
|
`Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
3965
4198
|
);
|
|
3966
4199
|
continue;
|
|
3967
4200
|
}
|
|
3968
|
-
const relativePath =
|
|
4201
|
+
const relativePath = path10.relative(projectDir, filePath).replaceAll("\\", "/");
|
|
3969
4202
|
for (const extractor2 of this.extractors) {
|
|
3970
4203
|
if (!extractor2.supportedExtensions.includes(ext)) continue;
|
|
3971
4204
|
try {
|
|
@@ -4002,9 +4235,9 @@ var init_dist = __esm({
|
|
|
4002
4235
|
}
|
|
4003
4236
|
/** Write extraction records to JSONL file. */
|
|
4004
4237
|
async writeJsonl(records, outputDir, extractorName) {
|
|
4005
|
-
const filePath =
|
|
4238
|
+
const filePath = path10.join(outputDir, `${extractorName}.jsonl`);
|
|
4006
4239
|
const lines = records.map((r) => JSON.stringify(r));
|
|
4007
|
-
await
|
|
4240
|
+
await fs9.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
|
|
4008
4241
|
}
|
|
4009
4242
|
/** Create or update a graph node from an extraction record. */
|
|
4010
4243
|
persistRecord(record, store) {
|
|
@@ -4076,12 +4309,12 @@ var init_dist = __esm({
|
|
|
4076
4309
|
const results = [];
|
|
4077
4310
|
let entries;
|
|
4078
4311
|
try {
|
|
4079
|
-
entries = await
|
|
4312
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
4080
4313
|
} catch {
|
|
4081
4314
|
return results;
|
|
4082
4315
|
}
|
|
4083
4316
|
for (const entry of entries) {
|
|
4084
|
-
const fullPath =
|
|
4317
|
+
const fullPath = path10.join(dir, entry.name);
|
|
4085
4318
|
if (entry.isDirectory() && !SKIP_DIRS2.has(entry.name)) {
|
|
4086
4319
|
results.push(...await this.findSourceFiles(fullPath));
|
|
4087
4320
|
} else if (entry.isFile() && detectLanguage(fullPath) !== void 0) {
|
|
@@ -5281,6 +5514,7 @@ var init_dist = __esm({
|
|
|
5281
5514
|
"business_term",
|
|
5282
5515
|
"business_concept",
|
|
5283
5516
|
"business_metric",
|
|
5517
|
+
"decision",
|
|
5284
5518
|
"design_token",
|
|
5285
5519
|
"design_constraint",
|
|
5286
5520
|
"aesthetic_intent",
|
|
@@ -5361,7 +5595,161 @@ var init_dist = __esm({
|
|
|
5361
5595
|
return parts[0] ?? "unclassified";
|
|
5362
5596
|
}
|
|
5363
5597
|
};
|
|
5364
|
-
|
|
5598
|
+
VALID_BUSINESS_TYPES = /* @__PURE__ */ new Set([
|
|
5599
|
+
"business_rule",
|
|
5600
|
+
"business_process",
|
|
5601
|
+
"business_concept",
|
|
5602
|
+
"business_term",
|
|
5603
|
+
"business_metric"
|
|
5604
|
+
]);
|
|
5605
|
+
DEFAULT_MAX_DOCS = 50;
|
|
5606
|
+
MAX_COLLISION_SUFFIX = 10;
|
|
5607
|
+
KnowledgeDocMaterializer = class {
|
|
5608
|
+
constructor(store) {
|
|
5609
|
+
this.store = store;
|
|
5610
|
+
}
|
|
5611
|
+
store;
|
|
5612
|
+
async materialize(gapEntries, options) {
|
|
5613
|
+
const maxDocs = options.maxDocs ?? DEFAULT_MAX_DOCS;
|
|
5614
|
+
const created = [];
|
|
5615
|
+
const skipped = [];
|
|
5616
|
+
const createdNames = /* @__PURE__ */ new Set();
|
|
5617
|
+
for (const entry of gapEntries) {
|
|
5618
|
+
const resolved = await this.resolveEntry(
|
|
5619
|
+
entry,
|
|
5620
|
+
created.length,
|
|
5621
|
+
maxDocs,
|
|
5622
|
+
createdNames,
|
|
5623
|
+
options
|
|
5624
|
+
);
|
|
5625
|
+
if ("reason" in resolved) {
|
|
5626
|
+
skipped.push(resolved);
|
|
5627
|
+
continue;
|
|
5628
|
+
}
|
|
5629
|
+
const { node, domain, domainDir, nameKey } = resolved;
|
|
5630
|
+
await fs10.mkdir(domainDir, { recursive: true });
|
|
5631
|
+
const basename10 = this.generateFilename(entry.name);
|
|
5632
|
+
const filename = await this.resolveCollision(domainDir, basename10);
|
|
5633
|
+
const content = this.formatDoc(node, domain);
|
|
5634
|
+
const filePath = ["docs", "knowledge", domain, filename].join("/");
|
|
5635
|
+
await fs10.writeFile(path11.join(options.projectDir, filePath), content, "utf-8");
|
|
5636
|
+
createdNames.add(nameKey);
|
|
5637
|
+
created.push({ filePath, nodeId: entry.nodeId, domain, name: entry.name });
|
|
5638
|
+
}
|
|
5639
|
+
return { created, skipped };
|
|
5640
|
+
}
|
|
5641
|
+
async resolveEntry(entry, createdCount, maxDocs, createdNames, options) {
|
|
5642
|
+
if (!entry.hasContent) {
|
|
5643
|
+
return { nodeId: entry.nodeId, name: entry.name, reason: "no_content" };
|
|
5644
|
+
}
|
|
5645
|
+
const node = this.store.getNode(entry.nodeId);
|
|
5646
|
+
if (!node || !node.content || node.content.trim().length < 10) {
|
|
5647
|
+
return { nodeId: entry.nodeId, name: entry.name, reason: "no_content" };
|
|
5648
|
+
}
|
|
5649
|
+
const domain = this.inferDomain(node);
|
|
5650
|
+
if (!domain || /[/\\]|\.\.|\0/.test(domain)) {
|
|
5651
|
+
return { nodeId: entry.nodeId, name: entry.name, reason: "no_domain" };
|
|
5652
|
+
}
|
|
5653
|
+
if (createdCount >= maxDocs) {
|
|
5654
|
+
return { nodeId: entry.nodeId, name: entry.name, reason: "cap_reached" };
|
|
5655
|
+
}
|
|
5656
|
+
const domainDir = path11.join(options.projectDir, "docs", "knowledge", domain);
|
|
5657
|
+
const nameKey = `${domain}:${entry.name.toLowerCase().trim()}`;
|
|
5658
|
+
if (!createdNames.has(nameKey) && await this.hasExistingDoc(domainDir, entry.name)) {
|
|
5659
|
+
return { nodeId: entry.nodeId, name: entry.name, reason: "already_documented" };
|
|
5660
|
+
}
|
|
5661
|
+
if (options.dryRun) {
|
|
5662
|
+
return { nodeId: entry.nodeId, name: entry.name, reason: "dry_run" };
|
|
5663
|
+
}
|
|
5664
|
+
return { node, domain, domainDir, nameKey };
|
|
5665
|
+
}
|
|
5666
|
+
inferDomain(node) {
|
|
5667
|
+
if (node.metadata?.domain && typeof node.metadata.domain === "string") {
|
|
5668
|
+
return node.metadata.domain;
|
|
5669
|
+
}
|
|
5670
|
+
if (node.path) {
|
|
5671
|
+
const pkgMatch = node.path.match(/^packages\/([^/]+)/);
|
|
5672
|
+
if (pkgMatch) return pkgMatch[1];
|
|
5673
|
+
const srcMatch = node.path.match(/^src\/([^/]+)/);
|
|
5674
|
+
if (srcMatch) return srcMatch[1];
|
|
5675
|
+
}
|
|
5676
|
+
if (node.metadata?.source === "knowledge-linker" || node.metadata?.source === "connector") {
|
|
5677
|
+
const connector = node.metadata.connectorName;
|
|
5678
|
+
if (typeof connector === "string") return connector;
|
|
5679
|
+
return "general";
|
|
5680
|
+
}
|
|
5681
|
+
return null;
|
|
5682
|
+
}
|
|
5683
|
+
generateFilename(name) {
|
|
5684
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
5685
|
+
return `${slug}.md`;
|
|
5686
|
+
}
|
|
5687
|
+
async resolveCollision(dir, basename10) {
|
|
5688
|
+
const ext = path11.extname(basename10);
|
|
5689
|
+
const stem = path11.basename(basename10, ext);
|
|
5690
|
+
try {
|
|
5691
|
+
await fs10.access(path11.join(dir, basename10));
|
|
5692
|
+
} catch {
|
|
5693
|
+
return basename10;
|
|
5694
|
+
}
|
|
5695
|
+
for (let i = 2; i <= MAX_COLLISION_SUFFIX; i++) {
|
|
5696
|
+
const candidate = `${stem}-${i}${ext}`;
|
|
5697
|
+
try {
|
|
5698
|
+
await fs10.access(path11.join(dir, candidate));
|
|
5699
|
+
} catch {
|
|
5700
|
+
return candidate;
|
|
5701
|
+
}
|
|
5702
|
+
}
|
|
5703
|
+
throw new Error(
|
|
5704
|
+
`Cannot resolve filename collision for "${basename10}" after ${MAX_COLLISION_SUFFIX} attempts`
|
|
5705
|
+
);
|
|
5706
|
+
}
|
|
5707
|
+
formatDoc(node, domain) {
|
|
5708
|
+
const mappedType = this.mapNodeType(node);
|
|
5709
|
+
const sanitize = (s) => s.replace(/[\n\r]/g, " ").replace(/:/g, "-");
|
|
5710
|
+
const lines = ["---", `type: ${sanitize(mappedType)}`, `domain: ${sanitize(domain)}`];
|
|
5711
|
+
const tags = node.metadata?.tags;
|
|
5712
|
+
if (Array.isArray(tags) && tags.length > 0) {
|
|
5713
|
+
lines.push(`tags: [${tags.map((t) => sanitize(String(t))).join(", ")}]`);
|
|
5714
|
+
}
|
|
5715
|
+
const related = node.metadata?.related;
|
|
5716
|
+
if (Array.isArray(related) && related.length > 0) {
|
|
5717
|
+
lines.push(`related: [${related.map((r) => sanitize(String(r))).join(", ")}]`);
|
|
5718
|
+
}
|
|
5719
|
+
const title = (node.name ?? "").replace(/[\n\r]/g, " ");
|
|
5720
|
+
lines.push("---", "", `# ${title}`, "", node.content ?? "", "");
|
|
5721
|
+
return lines.join("\n");
|
|
5722
|
+
}
|
|
5723
|
+
/** Check if a doc with a matching title already exists in the domain directory. */
|
|
5724
|
+
async hasExistingDoc(domainDir, name) {
|
|
5725
|
+
const normalizedName = name.toLowerCase().trim();
|
|
5726
|
+
try {
|
|
5727
|
+
const files = await fs10.readdir(domainDir);
|
|
5728
|
+
for (const file of files) {
|
|
5729
|
+
if (!file.endsWith(".md")) continue;
|
|
5730
|
+
const raw = await fs10.readFile(path11.join(domainDir, file), "utf-8");
|
|
5731
|
+
const fmMatch = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
5732
|
+
const body = fmMatch ? fmMatch[1] : raw;
|
|
5733
|
+
const titleMatch = body.match(/^#\s+(.+)$/m);
|
|
5734
|
+
if (titleMatch && titleMatch[1].trim().toLowerCase() === normalizedName) {
|
|
5735
|
+
return true;
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
} catch {
|
|
5739
|
+
}
|
|
5740
|
+
return false;
|
|
5741
|
+
}
|
|
5742
|
+
mapNodeType(node) {
|
|
5743
|
+
if (VALID_BUSINESS_TYPES.has(node.type)) {
|
|
5744
|
+
return node.type;
|
|
5745
|
+
}
|
|
5746
|
+
if (node.type === "business_fact") {
|
|
5747
|
+
return "business_rule";
|
|
5748
|
+
}
|
|
5749
|
+
return "business_concept";
|
|
5750
|
+
}
|
|
5751
|
+
};
|
|
5752
|
+
BUSINESS_NODE_TYPES2 = [
|
|
5365
5753
|
"business_concept",
|
|
5366
5754
|
"business_rule",
|
|
5367
5755
|
"business_process",
|
|
@@ -5370,7 +5758,8 @@ var init_dist = __esm({
|
|
|
5370
5758
|
"business_fact"
|
|
5371
5759
|
];
|
|
5372
5760
|
SNAPSHOT_NODE_TYPES = [
|
|
5373
|
-
...
|
|
5761
|
+
...BUSINESS_NODE_TYPES2,
|
|
5762
|
+
"decision",
|
|
5374
5763
|
"design_token",
|
|
5375
5764
|
"design_constraint",
|
|
5376
5765
|
"aesthetic_intent",
|
|
@@ -5383,14 +5772,28 @@ var init_dist = __esm({
|
|
|
5383
5772
|
store;
|
|
5384
5773
|
async run(options) {
|
|
5385
5774
|
const remediations = [];
|
|
5775
|
+
const preSnapshot = this.buildSnapshot(options.domain);
|
|
5386
5776
|
const extraction = await this.extract(options);
|
|
5387
|
-
|
|
5777
|
+
const postSnapshot = this.buildSnapshot(options.domain);
|
|
5778
|
+
let driftResult = this.reconcile(preSnapshot, postSnapshot);
|
|
5388
5779
|
const contradictions = new ContradictionDetector().detect(this.store);
|
|
5389
5780
|
let gapReport = await this.detect(options);
|
|
5390
5781
|
const coverage = new CoverageScorer().score(this.store);
|
|
5391
|
-
|
|
5782
|
+
let materialization;
|
|
5783
|
+
let iterations = 1;
|
|
5784
|
+
if (options.fix) {
|
|
5785
|
+
const loopResult = await this.runRemediationLoop(
|
|
5786
|
+
options,
|
|
5787
|
+
driftResult,
|
|
5788
|
+
gapReport,
|
|
5789
|
+
remediations
|
|
5790
|
+
);
|
|
5791
|
+
iterations = loopResult.iterations;
|
|
5792
|
+
materialization = loopResult.materialization;
|
|
5793
|
+
}
|
|
5392
5794
|
if (options.fix && iterations > 1) {
|
|
5393
|
-
|
|
5795
|
+
const finalSnapshot = this.buildSnapshot(options.domain);
|
|
5796
|
+
driftResult = this.reconcile(preSnapshot, finalSnapshot);
|
|
5394
5797
|
gapReport = await this.detect(options);
|
|
5395
5798
|
}
|
|
5396
5799
|
await this.stageNewFindings(driftResult, options);
|
|
@@ -5401,35 +5804,48 @@ var init_dist = __esm({
|
|
|
5401
5804
|
gapReport,
|
|
5402
5805
|
remediations,
|
|
5403
5806
|
contradictions,
|
|
5404
|
-
coverage
|
|
5807
|
+
coverage,
|
|
5808
|
+
materialization
|
|
5405
5809
|
);
|
|
5406
5810
|
}
|
|
5407
|
-
/** Run
|
|
5408
|
-
|
|
5409
|
-
const preSnapshot = this.buildSnapshot(options.domain);
|
|
5410
|
-
const postSnapshot = this.buildSnapshot(options.domain);
|
|
5411
|
-
return this.reconcile(preSnapshot, postSnapshot);
|
|
5412
|
-
}
|
|
5413
|
-
/** Run the remediation convergence loop; returns total iteration count. */
|
|
5414
|
-
async runRemediationLoop(options, driftResult, _gapReport, remediations) {
|
|
5811
|
+
/** Run the remediation convergence loop; returns iteration count and accumulated materialization. */
|
|
5812
|
+
async runRemediationLoop(options, driftResult, gapReport, remediations) {
|
|
5415
5813
|
const maxIterations = options.maxIterations ?? 5;
|
|
5416
5814
|
let iterations = 1;
|
|
5417
5815
|
let currentDrift = driftResult;
|
|
5418
|
-
let
|
|
5816
|
+
let currentGapReport = gapReport;
|
|
5817
|
+
let previousIssueCount = currentDrift.findings.length + currentGapReport.totalGaps;
|
|
5818
|
+
let accumulatedMaterialization;
|
|
5419
5819
|
while (iterations < maxIterations) {
|
|
5420
|
-
if (currentDrift.findings.length === 0) break;
|
|
5421
|
-
this.remediate(currentDrift, remediations, options);
|
|
5820
|
+
if (currentDrift.findings.length === 0 && currentGapReport.totalGaps === 0) break;
|
|
5821
|
+
const matResult = await this.remediate(currentDrift, remediations, options, currentGapReport);
|
|
5822
|
+
if (matResult) {
|
|
5823
|
+
if (!accumulatedMaterialization) {
|
|
5824
|
+
accumulatedMaterialization = matResult;
|
|
5825
|
+
} else {
|
|
5826
|
+
accumulatedMaterialization = {
|
|
5827
|
+
created: [...accumulatedMaterialization.created, ...matResult.created],
|
|
5828
|
+
skipped: [...accumulatedMaterialization.skipped, ...matResult.skipped]
|
|
5829
|
+
};
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
const preSnapshot = this.buildSnapshot(options.domain);
|
|
5422
5833
|
await this.extract(options);
|
|
5423
|
-
|
|
5424
|
-
|
|
5834
|
+
const postSnapshot = this.buildSnapshot(options.domain);
|
|
5835
|
+
currentDrift = this.reconcile(preSnapshot, postSnapshot);
|
|
5836
|
+
currentGapReport = await this.detect(options);
|
|
5425
5837
|
iterations++;
|
|
5426
|
-
|
|
5427
|
-
|
|
5838
|
+
const currentIssueCount = currentDrift.findings.length + currentGapReport.totalGaps;
|
|
5839
|
+
if (currentIssueCount >= previousIssueCount) break;
|
|
5840
|
+
previousIssueCount = currentIssueCount;
|
|
5428
5841
|
}
|
|
5429
|
-
return
|
|
5842
|
+
return {
|
|
5843
|
+
iterations,
|
|
5844
|
+
...accumulatedMaterialization ? { materialization: accumulatedMaterialization } : {}
|
|
5845
|
+
};
|
|
5430
5846
|
}
|
|
5431
5847
|
/** Assemble the final pipeline result. */
|
|
5432
|
-
buildResult(driftResult, iterations, extraction, gaps, remediations, contradictions, coverage) {
|
|
5848
|
+
buildResult(driftResult, iterations, extraction, gaps, remediations, contradictions, coverage, materialization) {
|
|
5433
5849
|
return {
|
|
5434
5850
|
verdict: this.computeVerdict(driftResult),
|
|
5435
5851
|
driftScore: driftResult.driftScore,
|
|
@@ -5439,13 +5855,14 @@ var init_dist = __esm({
|
|
|
5439
5855
|
gaps,
|
|
5440
5856
|
remediations,
|
|
5441
5857
|
contradictions,
|
|
5442
|
-
coverage
|
|
5858
|
+
coverage,
|
|
5859
|
+
...materialization ? { materialization } : {}
|
|
5443
5860
|
};
|
|
5444
5861
|
}
|
|
5445
5862
|
// ── Phase 1: EXTRACT ──────────────────────────────────────────────────────
|
|
5446
5863
|
async extract(options) {
|
|
5447
|
-
const extractedDir =
|
|
5448
|
-
await
|
|
5864
|
+
const extractedDir = path12.join(options.projectDir, ".harness", "knowledge", "extracted");
|
|
5865
|
+
await fs11.mkdir(extractedDir, { recursive: true });
|
|
5449
5866
|
const runner = createExtractionRunner();
|
|
5450
5867
|
const extractionResult = await runner.run(options.projectDir, this.store, extractedDir);
|
|
5451
5868
|
const diagramParser = new DiagramParser(this.store);
|
|
@@ -5459,7 +5876,7 @@ var init_dist = __esm({
|
|
|
5459
5876
|
const imageResult = await imageExtractor.analyze(this.store, imagePaths);
|
|
5460
5877
|
imageCount = imageResult.nodesAdded;
|
|
5461
5878
|
}
|
|
5462
|
-
const knowledgeDir =
|
|
5879
|
+
const knowledgeDir = path12.join(options.projectDir, "docs", "knowledge");
|
|
5463
5880
|
const bkIngestor = new BusinessKnowledgeIngestor(this.store);
|
|
5464
5881
|
let bkResult;
|
|
5465
5882
|
try {
|
|
@@ -5474,6 +5891,21 @@ var init_dist = __esm({
|
|
|
5474
5891
|
durationMs: 0
|
|
5475
5892
|
};
|
|
5476
5893
|
}
|
|
5894
|
+
const decisionsDir = path12.join(options.projectDir, "docs", "knowledge", "decisions");
|
|
5895
|
+
const decisionIngestor = new DecisionIngestor(this.store);
|
|
5896
|
+
let decisionResult;
|
|
5897
|
+
try {
|
|
5898
|
+
decisionResult = await decisionIngestor.ingest(decisionsDir);
|
|
5899
|
+
} catch {
|
|
5900
|
+
decisionResult = {
|
|
5901
|
+
nodesAdded: 0,
|
|
5902
|
+
nodesUpdated: 0,
|
|
5903
|
+
edgesAdded: 0,
|
|
5904
|
+
edgesUpdated: 0,
|
|
5905
|
+
errors: [],
|
|
5906
|
+
durationMs: 0
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5477
5909
|
const linker = new KnowledgeLinker(this.store, extractedDir);
|
|
5478
5910
|
const linkResult = await linker.link();
|
|
5479
5911
|
return {
|
|
@@ -5481,6 +5913,7 @@ var init_dist = __esm({
|
|
|
5481
5913
|
diagrams: diagramResult.nodesAdded,
|
|
5482
5914
|
linkerFacts: linkResult.factsCreated,
|
|
5483
5915
|
businessKnowledge: bkResult.nodesAdded,
|
|
5916
|
+
decisions: decisionResult.nodesAdded,
|
|
5484
5917
|
images: imageCount
|
|
5485
5918
|
};
|
|
5486
5919
|
}
|
|
@@ -5507,14 +5940,14 @@ var init_dist = __esm({
|
|
|
5507
5940
|
}
|
|
5508
5941
|
// ── Phase 3: DETECT ───────────────────────────────────────────────────────
|
|
5509
5942
|
async detect(options) {
|
|
5510
|
-
const knowledgeDir =
|
|
5943
|
+
const knowledgeDir = path12.join(options.projectDir, "docs", "knowledge");
|
|
5511
5944
|
const aggregator = new KnowledgeStagingAggregator(options.projectDir);
|
|
5512
|
-
const gapReport = await aggregator.generateGapReport(knowledgeDir);
|
|
5945
|
+
const gapReport = await aggregator.generateGapReport(knowledgeDir, this.store);
|
|
5513
5946
|
await aggregator.writeGapReport(gapReport);
|
|
5514
5947
|
return gapReport;
|
|
5515
5948
|
}
|
|
5516
5949
|
// ── Phase 4: REMEDIATE ────────────────────────────────────────────────────
|
|
5517
|
-
remediate(driftResult, remediations, options) {
|
|
5950
|
+
async remediate(driftResult, remediations, options, gapReport) {
|
|
5518
5951
|
for (const finding of driftResult.findings) {
|
|
5519
5952
|
switch (finding.classification) {
|
|
5520
5953
|
case "stale":
|
|
@@ -5532,6 +5965,22 @@ var init_dist = __esm({
|
|
|
5532
5965
|
break;
|
|
5533
5966
|
}
|
|
5534
5967
|
}
|
|
5968
|
+
if (!options.ci) {
|
|
5969
|
+
const allGapEntries = gapReport.domains.flatMap((d) => d.gapEntries);
|
|
5970
|
+
const materializable = allGapEntries.filter((e) => e.hasContent);
|
|
5971
|
+
if (materializable.length > 0) {
|
|
5972
|
+
const materializer = new KnowledgeDocMaterializer(this.store);
|
|
5973
|
+
const matResult = await materializer.materialize(materializable, {
|
|
5974
|
+
projectDir: options.projectDir,
|
|
5975
|
+
dryRun: false
|
|
5976
|
+
});
|
|
5977
|
+
for (const doc of matResult.created) {
|
|
5978
|
+
remediations.push(`created doc: ${doc.filePath}`);
|
|
5979
|
+
}
|
|
5980
|
+
return matResult;
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
return void 0;
|
|
5535
5984
|
}
|
|
5536
5985
|
async stageNewFindings(driftResult, options) {
|
|
5537
5986
|
const newFindings = driftResult.findings.filter((f) => f.classification === "new");
|
|
@@ -5564,7 +6013,7 @@ var init_dist = __esm({
|
|
|
5564
6013
|
return "fail";
|
|
5565
6014
|
}
|
|
5566
6015
|
};
|
|
5567
|
-
|
|
6016
|
+
CODE_NODE_TYPES5 = ["file", "function", "class", "method", "interface", "variable"];
|
|
5568
6017
|
RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
5569
6018
|
SANITIZE_RULES = [
|
|
5570
6019
|
// Strip XML/HTML-like instruction tags that could be interpreted as system prompts
|
|
@@ -5591,7 +6040,7 @@ var init_dist = __esm({
|
|
|
5591
6040
|
SyncManager = class {
|
|
5592
6041
|
constructor(store, graphDir) {
|
|
5593
6042
|
this.store = store;
|
|
5594
|
-
this.metadataPath =
|
|
6043
|
+
this.metadataPath = path13.join(graphDir, "sync-metadata.json");
|
|
5595
6044
|
}
|
|
5596
6045
|
store;
|
|
5597
6046
|
registrations = /* @__PURE__ */ new Map();
|
|
@@ -5656,15 +6105,15 @@ var init_dist = __esm({
|
|
|
5656
6105
|
}
|
|
5657
6106
|
async loadMetadata() {
|
|
5658
6107
|
try {
|
|
5659
|
-
const raw = await
|
|
6108
|
+
const raw = await fs12.readFile(this.metadataPath, "utf-8");
|
|
5660
6109
|
return JSON.parse(raw);
|
|
5661
6110
|
} catch {
|
|
5662
6111
|
return { connectors: {} };
|
|
5663
6112
|
}
|
|
5664
6113
|
}
|
|
5665
6114
|
async saveMetadata(metadata) {
|
|
5666
|
-
await
|
|
5667
|
-
await
|
|
6115
|
+
await fs12.mkdir(path13.dirname(this.metadataPath), { recursive: true });
|
|
6116
|
+
await fs12.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
5668
6117
|
}
|
|
5669
6118
|
};
|
|
5670
6119
|
SUMMARIZE_PROMPT = `Summarize the following content to fit within the specified length.
|
|
@@ -5702,6 +6151,14 @@ Content to summarize:
|
|
|
5702
6151
|
start
|
|
5703
6152
|
);
|
|
5704
6153
|
}
|
|
6154
|
+
try {
|
|
6155
|
+
const parsed = new URL(baseUrl);
|
|
6156
|
+
if (parsed.protocol !== "https:" && parsed.hostname !== "localhost") {
|
|
6157
|
+
return buildIngestResult(0, 0, [`${baseUrlEnv} must use HTTPS`], start);
|
|
6158
|
+
}
|
|
6159
|
+
} catch {
|
|
6160
|
+
return buildIngestResult(0, 0, [`${baseUrlEnv} is not a valid URL`], start);
|
|
6161
|
+
}
|
|
5705
6162
|
const jql = buildJql(config);
|
|
5706
6163
|
const headers = { Authorization: `Basic ${apiKey}`, "Content-Type": "application/json" };
|
|
5707
6164
|
try {
|
|
@@ -5955,6 +6412,14 @@ ${replyLines.join("\n")}`;
|
|
|
5955
6412
|
}
|
|
5956
6413
|
const baseUrlEnv = config.baseUrlEnv ?? "CONFLUENCE_BASE_URL";
|
|
5957
6414
|
const baseUrl = process.env[baseUrlEnv] ?? "";
|
|
6415
|
+
try {
|
|
6416
|
+
const parsed = new URL(baseUrl);
|
|
6417
|
+
if (parsed.protocol !== "https:" && parsed.hostname !== "localhost") {
|
|
6418
|
+
return missingApiKeyResult(`${baseUrlEnv} must use HTTPS`, start);
|
|
6419
|
+
}
|
|
6420
|
+
} catch {
|
|
6421
|
+
return missingApiKeyResult(`${baseUrlEnv} is not a valid URL`, start);
|
|
6422
|
+
}
|
|
5958
6423
|
const spaceKey = config.spaceKey ?? "";
|
|
5959
6424
|
const counts = await this.fetchAllPagesHandled(
|
|
5960
6425
|
store,
|
|
@@ -6486,7 +6951,7 @@ ${replyLines.join("\n")}`;
|
|
|
6486
6951
|
return 0;
|
|
6487
6952
|
}
|
|
6488
6953
|
};
|
|
6489
|
-
|
|
6954
|
+
CODE_NODE_TYPES6 = ["file", "function", "class", "method", "interface", "variable"];
|
|
6490
6955
|
GraphEntropyAdapter = class {
|
|
6491
6956
|
constructor(store) {
|
|
6492
6957
|
this.store = store;
|
|
@@ -6566,7 +7031,7 @@ ${replyLines.join("\n")}`;
|
|
|
6566
7031
|
}
|
|
6567
7032
|
findEntryPoints() {
|
|
6568
7033
|
const entryPoints = [];
|
|
6569
|
-
for (const nodeType of
|
|
7034
|
+
for (const nodeType of CODE_NODE_TYPES6) {
|
|
6570
7035
|
const nodes = this.store.findNodes({ type: nodeType });
|
|
6571
7036
|
for (const node of nodes) {
|
|
6572
7037
|
const isIndexFile = nodeType === "file" && node.name === "index.ts";
|
|
@@ -6602,7 +7067,7 @@ ${replyLines.join("\n")}`;
|
|
|
6602
7067
|
}
|
|
6603
7068
|
collectUnreachableNodes(visited) {
|
|
6604
7069
|
const unreachableNodes = [];
|
|
6605
|
-
for (const nodeType of
|
|
7070
|
+
for (const nodeType of CODE_NODE_TYPES6) {
|
|
6606
7071
|
const nodes = this.store.findNodes({ type: nodeType });
|
|
6607
7072
|
for (const node of nodes) {
|
|
6608
7073
|
if (!visited.has(node.id)) {
|
|
@@ -7419,9 +7884,9 @@ ${replyLines.join("\n")}`;
|
|
|
7419
7884
|
extractPaths(trimmed, add) {
|
|
7420
7885
|
const consumed = /* @__PURE__ */ new Set();
|
|
7421
7886
|
for (const match of trimmed.matchAll(FILE_PATH_RE)) {
|
|
7422
|
-
const
|
|
7423
|
-
add(
|
|
7424
|
-
consumed.add(
|
|
7887
|
+
const path15 = match[0];
|
|
7888
|
+
add(path15);
|
|
7889
|
+
consumed.add(path15);
|
|
7425
7890
|
}
|
|
7426
7891
|
return consumed;
|
|
7427
7892
|
}
|
|
@@ -7491,8 +7956,8 @@ ${replyLines.join("\n")}`;
|
|
|
7491
7956
|
if (isPathLike && node.path.includes(raw)) {
|
|
7492
7957
|
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
7493
7958
|
}
|
|
7494
|
-
const
|
|
7495
|
-
if (
|
|
7959
|
+
const basename10 = node.path.split("/").pop() ?? "";
|
|
7960
|
+
if (basename10.includes(raw)) {
|
|
7496
7961
|
return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
|
|
7497
7962
|
}
|
|
7498
7963
|
if (raw.length >= 4 && node.path.includes(raw)) {
|
|
@@ -7569,13 +8034,13 @@ ${replyLines.join("\n")}`;
|
|
|
7569
8034
|
const context = Array.isArray(d?.context) ? d.context : [];
|
|
7570
8035
|
const firstEntity = entities[0];
|
|
7571
8036
|
const nodeType = firstEntity?.node.type ?? "node";
|
|
7572
|
-
const
|
|
8037
|
+
const path15 = firstEntity?.node.path ?? "unknown";
|
|
7573
8038
|
let neighborCount = 0;
|
|
7574
8039
|
const firstContext = context[0];
|
|
7575
8040
|
if (firstContext && Array.isArray(firstContext.nodes)) {
|
|
7576
8041
|
neighborCount = firstContext.nodes.length;
|
|
7577
8042
|
}
|
|
7578
|
-
return `**${entityName}** is a ${nodeType} at \`${
|
|
8043
|
+
return `**${entityName}** is a ${nodeType} at \`${path15}\`. Connected to ${neighborCount} nodes.`;
|
|
7579
8044
|
}
|
|
7580
8045
|
formatAnomaly(data) {
|
|
7581
8046
|
const d = data;
|
|
@@ -7845,7 +8310,7 @@ ${replyLines.join("\n")}`;
|
|
|
7845
8310
|
debug: ["failure", "learning", "function", "method"],
|
|
7846
8311
|
plan: ["adr", "document", "module", "layer"]
|
|
7847
8312
|
};
|
|
7848
|
-
|
|
8313
|
+
CODE_NODE_TYPES7 = /* @__PURE__ */ new Set([
|
|
7849
8314
|
"file",
|
|
7850
8315
|
"function",
|
|
7851
8316
|
"class",
|
|
@@ -8055,7 +8520,7 @@ ${replyLines.join("\n")}`;
|
|
|
8055
8520
|
*/
|
|
8056
8521
|
checkCoverage() {
|
|
8057
8522
|
const codeNodes = [];
|
|
8058
|
-
for (const type of
|
|
8523
|
+
for (const type of CODE_NODE_TYPES7) {
|
|
8059
8524
|
codeNodes.push(...this.store.findNodes({ type }));
|
|
8060
8525
|
}
|
|
8061
8526
|
const documented = [];
|
|
@@ -8200,8 +8665,8 @@ ${replyLines.join("\n")}`;
|
|
|
8200
8665
|
async ingestAll(designDir) {
|
|
8201
8666
|
const start = Date.now();
|
|
8202
8667
|
const [tokensResult, intentResult] = await Promise.all([
|
|
8203
|
-
this.ingestTokens(
|
|
8204
|
-
this.ingestDesignIntent(
|
|
8668
|
+
this.ingestTokens(path14.join(designDir, "tokens.json")),
|
|
8669
|
+
this.ingestDesignIntent(path14.join(designDir, "DESIGN.md"))
|
|
8205
8670
|
]);
|
|
8206
8671
|
const merged = mergeResults(tokensResult, intentResult);
|
|
8207
8672
|
return { ...merged, durationMs: Date.now() - start };
|
|
@@ -8431,10 +8896,10 @@ ${replyLines.join("\n")}`;
|
|
|
8431
8896
|
includeTypes: ["file"]
|
|
8432
8897
|
});
|
|
8433
8898
|
for (const n of queryResult.nodes) {
|
|
8434
|
-
const
|
|
8435
|
-
if (!fileSet.has(
|
|
8436
|
-
if (!result.has(
|
|
8437
|
-
result.set(
|
|
8899
|
+
const path15 = n.path ?? n.id.replace(/^file:/, "");
|
|
8900
|
+
if (!fileSet.has(path15)) {
|
|
8901
|
+
if (!result.has(path15)) {
|
|
8902
|
+
result.set(path15, file);
|
|
8438
8903
|
}
|
|
8439
8904
|
}
|
|
8440
8905
|
}
|
|
@@ -8807,7 +9272,7 @@ ${replyLines.join("\n")}`;
|
|
|
8807
9272
|
return parts.join(" ");
|
|
8808
9273
|
}
|
|
8809
9274
|
};
|
|
8810
|
-
VERSION = "0.
|
|
9275
|
+
VERSION = "0.6.0";
|
|
8811
9276
|
}
|
|
8812
9277
|
});
|
|
8813
9278
|
|
|
@@ -8834,6 +9299,7 @@ export {
|
|
|
8834
9299
|
TopologicalLinker,
|
|
8835
9300
|
KnowledgeIngestor,
|
|
8836
9301
|
BusinessKnowledgeIngestor,
|
|
9302
|
+
DecisionIngestor,
|
|
8837
9303
|
RequirementIngestor,
|
|
8838
9304
|
MermaidParser,
|
|
8839
9305
|
D2Parser,
|
|
@@ -8852,6 +9318,7 @@ export {
|
|
|
8852
9318
|
ImageAnalysisExtractor,
|
|
8853
9319
|
ContradictionDetector,
|
|
8854
9320
|
CoverageScorer,
|
|
9321
|
+
KnowledgeDocMaterializer,
|
|
8855
9322
|
KnowledgePipelineRunner,
|
|
8856
9323
|
linkToCode,
|
|
8857
9324
|
SyncManager,
|