@harness-engineering/cli 1.26.1 → 1.27.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.
Files changed (149) hide show
  1. package/dist/agents/skills/claude-code/cleanup-dead-code/SKILL.md +3 -1
  2. package/dist/agents/skills/claude-code/detect-doc-drift/SKILL.md +3 -1
  3. package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +4 -0
  4. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +55 -24
  5. package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +16 -5
  6. package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +21 -6
  7. package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -0
  8. package/dist/agents/skills/claude-code/harness-debugging/SKILL.md +13 -1
  9. package/dist/agents/skills/claude-code/harness-dependency-health/SKILL.md +4 -0
  10. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +16 -6
  11. package/dist/agents/skills/claude-code/harness-impact-analysis/SKILL.md +2 -0
  12. package/dist/agents/skills/claude-code/harness-integration/SKILL.md +560 -0
  13. package/dist/agents/skills/claude-code/harness-integration/skill.yaml +65 -0
  14. package/dist/agents/skills/claude-code/harness-integrity/SKILL.md +2 -0
  15. package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +4 -0
  16. package/dist/agents/skills/claude-code/harness-perf/SKILL.md +9 -7
  17. package/dist/agents/skills/claude-code/harness-planning/SKILL.md +77 -10
  18. package/dist/agents/skills/claude-code/harness-refactoring/SKILL.md +13 -4
  19. package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +3 -1
  20. package/dist/agents/skills/claude-code/harness-soundness-review/SKILL.md +4 -0
  21. package/dist/agents/skills/claude-code/harness-tdd/SKILL.md +5 -2
  22. package/dist/agents/skills/claude-code/harness-verification/SKILL.md +4 -0
  23. package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +3 -1
  24. package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +3 -1
  25. package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +4 -0
  26. package/dist/agents/skills/codex/harness-autopilot/SKILL.md +55 -24
  27. package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +16 -5
  28. package/dist/agents/skills/codex/harness-code-review/SKILL.md +21 -6
  29. package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +2 -0
  30. package/dist/agents/skills/codex/harness-debugging/SKILL.md +13 -1
  31. package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +4 -0
  32. package/dist/agents/skills/codex/harness-execution/SKILL.md +16 -6
  33. package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +2 -0
  34. package/dist/agents/skills/codex/harness-integration/SKILL.md +560 -0
  35. package/dist/agents/skills/codex/harness-integration/skill.yaml +65 -0
  36. package/dist/agents/skills/codex/harness-integrity/SKILL.md +2 -0
  37. package/dist/agents/skills/codex/harness-onboarding/SKILL.md +4 -0
  38. package/dist/agents/skills/codex/harness-perf/SKILL.md +9 -7
  39. package/dist/agents/skills/codex/harness-planning/SKILL.md +77 -10
  40. package/dist/agents/skills/codex/harness-refactoring/SKILL.md +13 -4
  41. package/dist/agents/skills/codex/harness-security-scan/SKILL.md +3 -1
  42. package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +4 -0
  43. package/dist/agents/skills/codex/harness-tdd/SKILL.md +5 -2
  44. package/dist/agents/skills/codex/harness-verification/SKILL.md +4 -0
  45. package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +3 -1
  46. package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +3 -1
  47. package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +4 -0
  48. package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +55 -24
  49. package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +16 -5
  50. package/dist/agents/skills/cursor/harness-code-review/SKILL.md +21 -6
  51. package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +2 -0
  52. package/dist/agents/skills/cursor/harness-debugging/SKILL.md +13 -1
  53. package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +4 -0
  54. package/dist/agents/skills/cursor/harness-execution/SKILL.md +16 -6
  55. package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +2 -0
  56. package/dist/agents/skills/cursor/harness-integration/SKILL.md +560 -0
  57. package/dist/agents/skills/cursor/harness-integration/skill.yaml +65 -0
  58. package/dist/agents/skills/cursor/harness-integrity/SKILL.md +2 -0
  59. package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +4 -0
  60. package/dist/agents/skills/cursor/harness-perf/SKILL.md +9 -7
  61. package/dist/agents/skills/cursor/harness-planning/SKILL.md +77 -10
  62. package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +13 -4
  63. package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +3 -1
  64. package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +4 -0
  65. package/dist/agents/skills/cursor/harness-tdd/SKILL.md +5 -2
  66. package/dist/agents/skills/cursor/harness-verification/SKILL.md +4 -0
  67. package/dist/agents/skills/gemini-cli/cleanup-dead-code/SKILL.md +3 -1
  68. package/dist/agents/skills/gemini-cli/detect-doc-drift/SKILL.md +3 -1
  69. package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +4 -0
  70. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +55 -24
  71. package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +16 -5
  72. package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +21 -6
  73. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -0
  74. package/dist/agents/skills/gemini-cli/harness-debugging/SKILL.md +13 -1
  75. package/dist/agents/skills/gemini-cli/harness-dependency-health/SKILL.md +4 -0
  76. package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +16 -6
  77. package/dist/agents/skills/gemini-cli/harness-impact-analysis/SKILL.md +2 -0
  78. package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +560 -0
  79. package/dist/agents/skills/gemini-cli/harness-integration/skill.yaml +65 -0
  80. package/dist/agents/skills/gemini-cli/harness-integrity/SKILL.md +2 -0
  81. package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +4 -0
  82. package/dist/agents/skills/gemini-cli/harness-perf/SKILL.md +9 -7
  83. package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +77 -10
  84. package/dist/agents/skills/gemini-cli/harness-refactoring/SKILL.md +13 -4
  85. package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +3 -1
  86. package/dist/agents/skills/gemini-cli/harness-soundness-review/SKILL.md +4 -0
  87. package/dist/agents/skills/gemini-cli/harness-tdd/SKILL.md +5 -2
  88. package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +4 -0
  89. package/dist/{agents-md-VI3HLR7E.js → agents-md-2RN666DE.js} +3 -3
  90. package/dist/{architecture-ZDDKEVID.js → architecture-ZR33YKJB.js} +4 -4
  91. package/dist/{assess-project-Y6W7YDUA.js → assess-project-LLWWEQEV.js} +1 -1
  92. package/dist/bin/harness-mcp.js +17 -16
  93. package/dist/bin/harness.js +27 -26
  94. package/dist/{business-knowledge-6RHYJOB3.js → business-knowledge-EISPR7FU.js} +1 -1
  95. package/dist/{check-phase-gate-CXR3VM22.js → check-phase-gate-ZVG56NT2.js} +4 -4
  96. package/dist/chunk-27AJKSQY.js +107 -0
  97. package/dist/{chunk-DBIX3X4H.js → chunk-2FOLD76X.js} +2 -2
  98. package/dist/{chunk-3AMQYWGT.js → chunk-3AJW7VQ3.js} +2 -2
  99. package/dist/{chunk-FUND5WJR.js → chunk-3XMOWLK3.js} +2 -2
  100. package/dist/{chunk-OLW7BJ5N.js → chunk-776JFSPK.js} +1 -1
  101. package/dist/{chunk-VKS3F46M.js → chunk-A4ETL34H.js} +7 -2
  102. package/dist/{chunk-JV4Y2U5F.js → chunk-BO4AJZRE.js} +1 -1
  103. package/dist/{chunk-WZ7UHPT3.js → chunk-BZZNYHRY.js} +9 -9
  104. package/dist/{chunk-ZN6ROTYP.js → chunk-C7RPQWBG.js} +1 -1
  105. package/dist/{chunk-RAVD7QLY.js → chunk-CJYNZMOP.js} +8 -8
  106. package/dist/{chunk-PNNFCVW4.js → chunk-EGRQHLDM.js} +5 -5
  107. package/dist/{chunk-N6B6RKQQ.js → chunk-EICASBYP.js} +1 -1
  108. package/dist/{chunk-K56DTX7G.js → chunk-F3Y64VNF.js} +6 -6
  109. package/dist/{chunk-ORNEK65Y.js → chunk-FUS4OOGC.js} +1 -1
  110. package/dist/{chunk-2FPUPRCY.js → chunk-GAIOIINV.js} +594 -127
  111. package/dist/{chunk-YC4EYIER.js → chunk-IBTHD2UA.js} +132 -77
  112. package/dist/{chunk-RC5ZY3EV.js → chunk-IOW3MW2K.js} +2 -2
  113. package/dist/{chunk-B66LRB5M.js → chunk-L3Y7L4EG.js} +1 -1
  114. package/dist/{chunk-BK4DJIIY.js → chunk-MGPVA2EM.js} +27 -4
  115. package/dist/{chunk-NY7DTZ6K.js → chunk-Q5DKIL46.js} +3 -3
  116. package/dist/{chunk-Q3ZLVMQM.js → chunk-QTXW5TIS.js} +1 -1
  117. package/dist/{chunk-AMRC4FU2.js → chunk-SA57FVGX.js} +92 -55
  118. package/dist/{chunk-TY2ESLVL.js → chunk-SDDQPMZK.js} +1 -1
  119. package/dist/{chunk-7GY6WNEI.js → chunk-SDPHFDQS.js} +1 -1
  120. package/dist/{chunk-56A6OXVA.js → chunk-T7UZOJF4.js} +9 -9
  121. package/dist/{chunk-2BPPS6YQ.js → chunk-TCMVZ3B7.js} +457 -362
  122. package/dist/{chunk-TSVYBDDE.js → chunk-YZH4I4JO.js} +1 -1
  123. package/dist/{ci-workflow-EW37KVPS.js → ci-workflow-JFOL4YWW.js} +3 -3
  124. package/dist/{dist-NCNAXUFD.js → dist-BZS2XRLG.js} +5 -1
  125. package/dist/{dist-CJDGASPP.js → dist-CPKL5Y2R.js} +2 -2
  126. package/dist/{docs-4XI2FQW2.js → docs-FUTRJQHA.js} +4 -4
  127. package/dist/{engine-4L4CDTHU.js → engine-FB3HCNVT.js} +3 -3
  128. package/dist/{entropy-PBYSIQYS.js → entropy-FIEE4YDA.js} +3 -3
  129. package/dist/{feedback-OTIFW5MK.js → feedback-7EC4T6XG.js} +1 -1
  130. package/dist/{generate-agent-definitions-CZGUXIIN.js → generate-agent-definitions-FOSFADNR.js} +4 -4
  131. package/dist/{graph-loader-RDXSEBD7.js → graph-loader-QEI7SYOQ.js} +1 -1
  132. package/dist/hooks/adoption-tracker.js +36 -8
  133. package/dist/hooks/sentinel-pre.js +3 -2
  134. package/dist/hooks/telemetry-reporter.js +34 -15
  135. package/dist/index-builder-3AOQ3ZII.js +13 -0
  136. package/dist/index.d.ts +15 -0
  137. package/dist/index.js +29 -28
  138. package/dist/{loader-UZOXCWFC.js → loader-HXUOBTYA.js} +3 -3
  139. package/dist/{mcp-GWVVV6LH.js → mcp-ICXMTAD3.js} +17 -16
  140. package/dist/{performance-5UQPP3XX.js → performance-TYXBCTR7.js} +4 -4
  141. package/dist/{review-pipeline-65V4EO6O.js → review-pipeline-B4WPU6BQ.js} +3 -3
  142. package/dist/{runtime-PXX7HNB3.js → runtime-HQQNOARD.js} +3 -3
  143. package/dist/{scan-WM7XDUF7.js → scan-TX55R6QS.js} +1 -1
  144. package/dist/{security-CAYDRZIZ.js → security-25VFLIAO.js} +1 -1
  145. package/dist/templates/orchestrator/harness.orchestrator.md +18 -8
  146. package/dist/{validate-CWGXR7QM.js → validate-EAVGKZQB.js} +4 -4
  147. package/dist/{validate-cross-check-BKCNLZYG.js → validate-cross-check-SPL3H4O5.js} +3 -3
  148. package/package.json +13 -5
  149. 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 fs11 from "fs/promises";
37
- import * as path12 from "path";
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 null;
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 null;
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 = path9.basename(filePath);
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 = path9.extname(filePath);
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 CODE_NODE_TYPES4) {
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 fs11.readFile(filePath, "utf-8");
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, CODE_NODE_TYPES3, RequirementIngestor, MermaidParser, D2Parser, PlantUmlParser, DIAGRAM_EXTENSIONS, SKIP_DIRS, DiagramParser, HEURISTIC_PATTERNS, SCANNABLE_TYPES, KnowledgeLinker, StructuralDriftDetector, 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, BUSINESS_NODE_TYPES, SNAPSHOT_NODE_TYPES, KnowledgePipelineRunner, CODE_NODE_TYPES4, 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_TYPES5, 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_TYPES6, Assembler, GraphConstraintAdapter, DesignIngestor, DesignConstraintAdapter, GraphFeedbackAdapter, DEFAULT_EDGE_TYPES, TaskIndependenceAnalyzer, ConflictPredictor, VERSION;
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 (future)
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 (future)
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 data = await loadGraph(dirPath);
1704
- if (!data) return false;
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 = await fs.readFile(filePath, "utf-8");
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 = await fs.readFile(filePath, "utf-8");
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
- CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
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 fs4.readdir(specsDir, { withFileTypes: true });
3122
- featureDirs = entries.filter((e) => e.isDirectory()).map((e) => path5.join(specsDir, e.name));
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 = path5.basename(featureDir);
3142
- const specPath = path5.join(featureDir, "proposal.md").replaceAll("\\", "/");
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 fs4.readFile(specPath, "utf-8");
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: path5.basename(specPath),
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/") && path5.basename(normalizedPath).includes(featureName);
3402
+ const isCodeMatch = normalizedPath.includes("packages/") && path6.basename(normalizedPath).includes(featureName);
3278
3403
  const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
3279
- path5.basename(normalizedPath).includes(featureName);
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 CODE_NODE_TYPES3) {
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 = path6.extname(filePath).toLowerCase();
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 fs5.readFile(filePath, "utf-8");
3426
- const relPath = path6.relative(projectDir, filePath).replaceAll("\\", "/");
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 fs5.readdir(currentDir, { withFileTypes: true });
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(path6.join(currentDir, entry.name));
3624
+ await walk(path7.join(currentDir, entry.name));
3500
3625
  }
3501
3626
  } else if (entry.isFile()) {
3502
- const ext = path6.extname(entry.name).toLowerCase();
3627
+ const ext = path7.extname(entry.name).toLowerCase();
3503
3628
  if (DIAGRAM_EXTENSIONS.has(ext)) {
3504
- files.push(path6.join(currentDir, entry.name));
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
- existingSources.push(candidate.sourceNodeId);
3652
- duplicate.metadata.sources = existingSources;
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 fs6.mkdir(this.outputDir, { recursive: true });
3687
- const filePath = path7.join(this.outputDir, "linker.jsonl");
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 fs6.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
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 = path7.join(this.outputDir, "staged");
3708
- await fs6.mkdir(stagedDir, { recursive: true });
3709
- const filePath = path7.join(stagedDir, "linker-staged.jsonl");
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 fs6.writeFile(filePath, lines.join("\n") + "\n");
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 = path8.join(this.projectDir, ".harness", "knowledge", "staged");
3843
- await fs7.mkdir(stagedDir, { recursive: true });
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 fs7.writeFile(path8.join(stagedDir, "pipeline-staged.jsonl"), jsonl, "utf-8");
3980
+ await fs8.writeFile(path9.join(stagedDir, "pipeline-staged.jsonl"), jsonl, "utf-8");
3846
3981
  return { staged: deduplicated.length };
3847
3982
  }
3848
- async generateGapReport(knowledgeDir) {
3849
- const domains = [];
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 fs7.readdir(knowledgeDir, { withFileTypes: true });
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 = path8.join(knowledgeDir, dir.name);
3856
- const files = await fs7.readdir(domainPath);
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
- domains.push({ domain: dir.name, entryCount });
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 = path8.join(this.projectDir, ".harness", "knowledge");
3872
- await fs7.mkdir(gapsDir, { recursive: true });
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
- for (const domain of report.domains) {
3884
- lines.push(`| ${domain.domain} | ${domain.entryCount} |`);
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
- lines.push("", `## Total Entries: ${report.totalEntries}`, "");
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 fs8.mkdir(outputDir, { recursive: true });
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 = path9.extname(filePath);
4191
+ const ext = path10.extname(filePath);
3959
4192
  let content;
3960
4193
  try {
3961
- content = await fs8.readFile(filePath, "utf-8");
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 = path9.relative(projectDir, filePath).replaceAll("\\", "/");
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 = path9.join(outputDir, `${extractorName}.jsonl`);
4238
+ const filePath = path10.join(outputDir, `${extractorName}.jsonl`);
4006
4239
  const lines = records.map((r) => JSON.stringify(r));
4007
- await fs8.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
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 fs8.readdir(dir, { withFileTypes: true });
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 = path9.join(dir, entry.name);
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
- BUSINESS_NODE_TYPES = [
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
- ...BUSINESS_NODE_TYPES,
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
- let driftResult = this.runReconciliation(options);
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
- const iterations = options.fix ? await this.runRemediationLoop(options, driftResult, gapReport, remediations) : 1;
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
- driftResult = this.runReconciliation(options);
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 phases 1-2 (extract snapshot, reconcile) and return drift result. */
5408
- runReconciliation(options) {
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 previousFindingCount = currentDrift.findings.length;
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
- currentDrift = this.runReconciliation(options);
5424
- await this.detect(options);
5834
+ const postSnapshot = this.buildSnapshot(options.domain);
5835
+ currentDrift = this.reconcile(preSnapshot, postSnapshot);
5836
+ currentGapReport = await this.detect(options);
5425
5837
  iterations++;
5426
- if (currentDrift.findings.length >= previousFindingCount) break;
5427
- previousFindingCount = currentDrift.findings.length;
5838
+ const currentIssueCount = currentDrift.findings.length + currentGapReport.totalGaps;
5839
+ if (currentIssueCount >= previousIssueCount) break;
5840
+ previousIssueCount = currentIssueCount;
5428
5841
  }
5429
- return iterations;
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 = path10.join(options.projectDir, ".harness", "knowledge", "extracted");
5448
- await fs9.mkdir(extractedDir, { recursive: true });
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 = path10.join(options.projectDir, "docs", "knowledge");
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 = path10.join(options.projectDir, "docs", "knowledge");
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
- CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
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 = path11.join(graphDir, "sync-metadata.json");
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 fs10.readFile(this.metadataPath, "utf-8");
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 fs10.mkdir(path11.dirname(this.metadataPath), { recursive: true });
5667
- await fs10.writeFile(this.metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
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
- CODE_NODE_TYPES5 = ["file", "function", "class", "method", "interface", "variable"];
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 CODE_NODE_TYPES5) {
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 CODE_NODE_TYPES5) {
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 path13 = match[0];
7423
- add(path13);
7424
- consumed.add(path13);
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 basename7 = node.path.split("/").pop() ?? "";
7495
- if (basename7.includes(raw)) {
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 path13 = firstEntity?.node.path ?? "unknown";
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 \`${path13}\`. Connected to ${neighborCount} nodes.`;
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
- CODE_NODE_TYPES6 = /* @__PURE__ */ new Set([
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 CODE_NODE_TYPES6) {
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(path12.join(designDir, "tokens.json")),
8204
- this.ingestDesignIntent(path12.join(designDir, "DESIGN.md"))
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 path13 = n.path ?? n.id.replace(/^file:/, "");
8435
- if (!fileSet.has(path13)) {
8436
- if (!result.has(path13)) {
8437
- result.set(path13, file);
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.4.3";
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,