@harness-engineering/cli 1.26.0 → 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 (266) hide show
  1. package/dist/agents/skills/README.md +1 -0
  2. package/dist/agents/skills/claude-code/cleanup-dead-code/SKILL.md +3 -1
  3. package/dist/agents/skills/claude-code/detect-doc-drift/SKILL.md +3 -1
  4. package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +4 -0
  5. package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +55 -24
  6. package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +49 -10
  7. package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +21 -6
  8. package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -0
  9. package/dist/agents/skills/claude-code/harness-debugging/SKILL.md +13 -1
  10. package/dist/agents/skills/claude-code/harness-dependency-health/SKILL.md +4 -0
  11. package/dist/agents/skills/claude-code/harness-execution/SKILL.md +22 -6
  12. package/dist/agents/skills/claude-code/harness-impact-analysis/SKILL.md +2 -0
  13. package/dist/agents/skills/claude-code/harness-integration/SKILL.md +560 -0
  14. package/dist/agents/skills/claude-code/harness-integration/skill.yaml +65 -0
  15. package/dist/agents/skills/claude-code/harness-integrity/SKILL.md +2 -0
  16. package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +4 -0
  17. package/dist/agents/skills/claude-code/harness-perf/SKILL.md +9 -7
  18. package/dist/agents/skills/claude-code/harness-planning/SKILL.md +102 -11
  19. package/dist/agents/skills/claude-code/harness-refactoring/SKILL.md +13 -4
  20. package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +3 -1
  21. package/dist/agents/skills/claude-code/harness-soundness-review/SKILL.md +4 -0
  22. package/dist/agents/skills/claude-code/harness-tdd/SKILL.md +5 -2
  23. package/dist/agents/skills/claude-code/harness-verification/SKILL.md +4 -0
  24. package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +22 -6
  25. package/dist/agents/skills/claude-code/initialize-harness-project/skill.yaml +2 -1
  26. package/dist/agents/skills/claude-code/initialize-test-suite-project/SKILL.md +415 -0
  27. package/dist/agents/skills/claude-code/initialize-test-suite-project/skill.yaml +35 -0
  28. package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +3 -1
  29. package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +3 -1
  30. package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +4 -0
  31. package/dist/agents/skills/codex/harness-autopilot/SKILL.md +55 -24
  32. package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +49 -10
  33. package/dist/agents/skills/codex/harness-code-review/SKILL.md +21 -6
  34. package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +2 -0
  35. package/dist/agents/skills/codex/harness-debugging/SKILL.md +13 -1
  36. package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +4 -0
  37. package/dist/agents/skills/codex/harness-execution/SKILL.md +22 -6
  38. package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +2 -0
  39. package/dist/agents/skills/codex/harness-integration/SKILL.md +560 -0
  40. package/dist/agents/skills/codex/harness-integration/skill.yaml +65 -0
  41. package/dist/agents/skills/codex/harness-integrity/SKILL.md +2 -0
  42. package/dist/agents/skills/codex/harness-onboarding/SKILL.md +4 -0
  43. package/dist/agents/skills/codex/harness-perf/SKILL.md +9 -7
  44. package/dist/agents/skills/codex/harness-planning/SKILL.md +102 -11
  45. package/dist/agents/skills/codex/harness-refactoring/SKILL.md +13 -4
  46. package/dist/agents/skills/codex/harness-security-scan/SKILL.md +3 -1
  47. package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +4 -0
  48. package/dist/agents/skills/codex/harness-tdd/SKILL.md +5 -2
  49. package/dist/agents/skills/codex/harness-verification/SKILL.md +4 -0
  50. package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +22 -6
  51. package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +2 -1
  52. package/dist/agents/skills/codex/initialize-test-suite-project/SKILL.md +415 -0
  53. package/dist/agents/skills/codex/initialize-test-suite-project/skill.yaml +35 -0
  54. package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +3 -1
  55. package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +3 -1
  56. package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +4 -0
  57. package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +55 -24
  58. package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +49 -10
  59. package/dist/agents/skills/cursor/harness-code-review/SKILL.md +21 -6
  60. package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +2 -0
  61. package/dist/agents/skills/cursor/harness-debugging/SKILL.md +13 -1
  62. package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +4 -0
  63. package/dist/agents/skills/cursor/harness-execution/SKILL.md +22 -6
  64. package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +2 -0
  65. package/dist/agents/skills/cursor/harness-integration/SKILL.md +560 -0
  66. package/dist/agents/skills/cursor/harness-integration/skill.yaml +65 -0
  67. package/dist/agents/skills/cursor/harness-integrity/SKILL.md +2 -0
  68. package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +4 -0
  69. package/dist/agents/skills/cursor/harness-perf/SKILL.md +9 -7
  70. package/dist/agents/skills/cursor/harness-planning/SKILL.md +102 -11
  71. package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +13 -4
  72. package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +3 -1
  73. package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +4 -0
  74. package/dist/agents/skills/cursor/harness-tdd/SKILL.md +5 -2
  75. package/dist/agents/skills/cursor/harness-verification/SKILL.md +4 -0
  76. package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +22 -6
  77. package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +2 -1
  78. package/dist/agents/skills/cursor/initialize-test-suite-project/SKILL.md +415 -0
  79. package/dist/agents/skills/cursor/initialize-test-suite-project/skill.yaml +35 -0
  80. package/dist/agents/skills/gemini-cli/cleanup-dead-code/SKILL.md +3 -1
  81. package/dist/agents/skills/gemini-cli/detect-doc-drift/SKILL.md +3 -1
  82. package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +4 -0
  83. package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +55 -24
  84. package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +49 -10
  85. package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +21 -6
  86. package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -0
  87. package/dist/agents/skills/gemini-cli/harness-debugging/SKILL.md +13 -1
  88. package/dist/agents/skills/gemini-cli/harness-dependency-health/SKILL.md +4 -0
  89. package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +22 -6
  90. package/dist/agents/skills/gemini-cli/harness-impact-analysis/SKILL.md +2 -0
  91. package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +560 -0
  92. package/dist/agents/skills/gemini-cli/harness-integration/skill.yaml +65 -0
  93. package/dist/agents/skills/gemini-cli/harness-integrity/SKILL.md +2 -0
  94. package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +4 -0
  95. package/dist/agents/skills/gemini-cli/harness-perf/SKILL.md +9 -7
  96. package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +102 -11
  97. package/dist/agents/skills/gemini-cli/harness-refactoring/SKILL.md +13 -4
  98. package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +3 -1
  99. package/dist/agents/skills/gemini-cli/harness-soundness-review/SKILL.md +4 -0
  100. package/dist/agents/skills/gemini-cli/harness-tdd/SKILL.md +5 -2
  101. package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +4 -0
  102. package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +22 -6
  103. package/dist/agents/skills/gemini-cli/initialize-harness-project/skill.yaml +2 -1
  104. package/dist/agents/skills/gemini-cli/initialize-test-suite-project/SKILL.md +415 -0
  105. package/dist/agents/skills/gemini-cli/initialize-test-suite-project/skill.yaml +35 -0
  106. package/dist/agents/skills/tests/initialize-test-suite-project.test.ts +152 -0
  107. package/dist/{agents-md-OPOZA5L7.js → agents-md-2RN666DE.js} +3 -3
  108. package/dist/{architecture-PMVMO6SF.js → architecture-ZR33YKJB.js} +4 -4
  109. package/dist/{assess-project-ZW7EKNIT.js → assess-project-LLWWEQEV.js} +1 -1
  110. package/dist/bin/harness-mcp.js +18 -16
  111. package/dist/bin/harness.js +29 -28
  112. package/dist/{business-knowledge-6RHYJOB3.js → business-knowledge-EISPR7FU.js} +1 -1
  113. package/dist/{check-phase-gate-NTLAU3LO.js → check-phase-gate-ZVG56NT2.js} +4 -4
  114. package/dist/chunk-27AJKSQY.js +107 -0
  115. package/dist/{chunk-G6SYTGOH.js → chunk-2FOLD76X.js} +2 -2
  116. package/dist/{chunk-IFHAUFJX.js → chunk-3AJW7VQ3.js} +2 -2
  117. package/dist/{chunk-KXIAHHHP.js → chunk-3XMOWLK3.js} +2 -2
  118. package/dist/{chunk-Z2JORLRT.js → chunk-776JFSPK.js} +1 -1
  119. package/dist/{chunk-4KSKV5MK.js → chunk-A4ETL34H.js} +7 -2
  120. package/dist/{chunk-JWSVLC4I.js → chunk-BO4AJZRE.js} +1 -1
  121. package/dist/{chunk-UBRD5FH4.js → chunk-BZZNYHRY.js} +9 -9
  122. package/dist/{chunk-6SCA4NEZ.js → chunk-C7RPQWBG.js} +1 -1
  123. package/dist/{chunk-NVGQO4VM.js → chunk-CJYNZMOP.js} +8 -8
  124. package/dist/{chunk-7JNZXJKY.js → chunk-EGRQHLDM.js} +5 -5
  125. package/dist/{chunk-EXTWTJRF.js → chunk-EICASBYP.js} +1 -1
  126. package/dist/{chunk-RNAGPTKF.js → chunk-F3Y64VNF.js} +6 -6
  127. package/dist/{chunk-HAOBHL3W.js → chunk-FUS4OOGC.js} +1 -1
  128. package/dist/{chunk-VV2PJT4K.js → chunk-GAIOIINV.js} +1353 -707
  129. package/dist/{chunk-77EZGPSR.js → chunk-IBTHD2UA.js} +139 -78
  130. package/dist/{chunk-RC5ZY3EV.js → chunk-IOW3MW2K.js} +2 -2
  131. package/dist/{chunk-JZ6GORAK.js → chunk-L3Y7L4EG.js} +1 -1
  132. package/dist/{chunk-KFE2X7LG.js → chunk-MGPVA2EM.js} +27 -4
  133. package/dist/{chunk-JBJ4AVE4.js → chunk-Q5DKIL46.js} +3 -3
  134. package/dist/{chunk-NIOB6FTG.js → chunk-QTXW5TIS.js} +1 -1
  135. package/dist/{chunk-ZBQ3ZY75.js → chunk-SA57FVGX.js} +100 -60
  136. package/dist/{chunk-KSFUJT5B.js → chunk-SDDQPMZK.js} +1 -1
  137. package/dist/{chunk-QYSWNPXZ.js → chunk-SDPHFDQS.js} +1 -1
  138. package/dist/{chunk-FI3D6D7U.js → chunk-T7UZOJF4.js} +9 -9
  139. package/dist/{chunk-CAS3TS4Y.js → chunk-TCMVZ3B7.js} +1142 -348
  140. package/dist/{chunk-J3O64D2A.js → chunk-YZH4I4JO.js} +1 -1
  141. package/dist/{ci-workflow-H52EMCL6.js → ci-workflow-JFOL4YWW.js} +3 -3
  142. package/dist/{create-skill-6QWJHQYS.js → create-skill-4T5CBSJ2.js} +2 -2
  143. package/dist/{dist-PGQ32ZI7.js → dist-BZS2XRLG.js} +5 -1
  144. package/dist/{dist-TG63OKA5.js → dist-CPKL5Y2R.js} +2 -2
  145. package/dist/{docs-GP4VUSIB.js → docs-FUTRJQHA.js} +4 -4
  146. package/dist/{engine-KQG5RVXX.js → engine-FB3HCNVT.js} +3 -3
  147. package/dist/{entropy-RXESIYKC.js → entropy-FIEE4YDA.js} +3 -3
  148. package/dist/{feedback-HS7ZYXOU.js → feedback-7EC4T6XG.js} +1 -1
  149. package/dist/{generate-agent-definitions-I4B7CVOB.js → generate-agent-definitions-FOSFADNR.js} +4 -4
  150. package/dist/{graph-loader-TIVI6NQ7.js → graph-loader-QEI7SYOQ.js} +1 -1
  151. package/dist/hooks/adoption-tracker.js +36 -8
  152. package/dist/hooks/sentinel-pre.js +3 -2
  153. package/dist/hooks/telemetry-reporter.js +34 -15
  154. package/dist/index-builder-3AOQ3ZII.js +13 -0
  155. package/dist/index.d.ts +15 -0
  156. package/dist/index.js +31 -30
  157. package/dist/{loader-CWAH6B4H.js → loader-HXUOBTYA.js} +3 -3
  158. package/dist/mcp-ICXMTAD3.js +41 -0
  159. package/dist/{performance-WDW5WS3J.js → performance-TYXBCTR7.js} +4 -4
  160. package/dist/{review-pipeline-QOZGWCTY.js → review-pipeline-B4WPU6BQ.js} +3 -3
  161. package/dist/{runtime-SFEFRPLX.js → runtime-HQQNOARD.js} +3 -3
  162. package/dist/{scan-IJ7EYTF7.js → scan-TX55R6QS.js} +1 -1
  163. package/dist/{security-2K7UUK6T.js → security-25VFLIAO.js} +1 -1
  164. package/dist/templates/orchestrator/harness.orchestrator.md +18 -8
  165. package/dist/{validate-HXM6DXRZ.js → validate-EAVGKZQB.js} +4 -4
  166. package/dist/{validate-cross-check-ULQLEOME.js → validate-cross-check-SPL3H4O5.js} +3 -3
  167. package/package.json +14 -6
  168. package/dist/agents/commands/claude-code/harness/harness.md +0 -36
  169. package/dist/agents/commands/codex/AGENTS.md +0 -39
  170. package/dist/agents/commands/codex/harness/add-harness-component/SKILL.md +0 -204
  171. package/dist/agents/commands/codex/harness/add-harness-component/agents/openai.yaml +0 -3
  172. package/dist/agents/commands/codex/harness/cleanup-dead-code/SKILL.md +0 -257
  173. package/dist/agents/commands/codex/harness/cleanup-dead-code/agents/openai.yaml +0 -3
  174. package/dist/agents/commands/codex/harness/detect-doc-drift/SKILL.md +0 -191
  175. package/dist/agents/commands/codex/harness/detect-doc-drift/agents/openai.yaml +0 -3
  176. package/dist/agents/commands/codex/harness/enforce-architecture/SKILL.md +0 -289
  177. package/dist/agents/commands/codex/harness/enforce-architecture/agents/openai.yaml +0 -3
  178. package/dist/agents/commands/codex/harness/harness-architecture-advisor/SKILL.md +0 -442
  179. package/dist/agents/commands/codex/harness/harness-architecture-advisor/agents/openai.yaml +0 -3
  180. package/dist/agents/commands/codex/harness/harness-autopilot/SKILL.md +0 -929
  181. package/dist/agents/commands/codex/harness/harness-autopilot/agents/openai.yaml +0 -3
  182. package/dist/agents/commands/codex/harness/harness-brainstorming/SKILL.md +0 -418
  183. package/dist/agents/commands/codex/harness/harness-brainstorming/agents/openai.yaml +0 -3
  184. package/dist/agents/commands/codex/harness/harness-code-review/SKILL.md +0 -850
  185. package/dist/agents/commands/codex/harness/harness-code-review/agents/openai.yaml +0 -3
  186. package/dist/agents/commands/codex/harness/harness-codebase-cleanup/SKILL.md +0 -236
  187. package/dist/agents/commands/codex/harness/harness-codebase-cleanup/agents/openai.yaml +0 -3
  188. package/dist/agents/commands/codex/harness/harness-debugging/SKILL.md +0 -378
  189. package/dist/agents/commands/codex/harness/harness-debugging/agents/openai.yaml +0 -3
  190. package/dist/agents/commands/codex/harness/harness-dependency-health/SKILL.md +0 -190
  191. package/dist/agents/commands/codex/harness/harness-dependency-health/agents/openai.yaml +0 -3
  192. package/dist/agents/commands/codex/harness/harness-docs-pipeline/SKILL.md +0 -472
  193. package/dist/agents/commands/codex/harness/harness-docs-pipeline/agents/openai.yaml +0 -3
  194. package/dist/agents/commands/codex/harness/harness-execution/SKILL.md +0 -522
  195. package/dist/agents/commands/codex/harness/harness-execution/agents/openai.yaml +0 -3
  196. package/dist/agents/commands/codex/harness/harness-hotspot-detector/SKILL.md +0 -172
  197. package/dist/agents/commands/codex/harness/harness-hotspot-detector/agents/openai.yaml +0 -3
  198. package/dist/agents/commands/codex/harness/harness-impact-analysis/SKILL.md +0 -195
  199. package/dist/agents/commands/codex/harness/harness-impact-analysis/agents/openai.yaml +0 -3
  200. package/dist/agents/commands/codex/harness/harness-integrity/SKILL.md +0 -178
  201. package/dist/agents/commands/codex/harness/harness-integrity/agents/openai.yaml +0 -3
  202. package/dist/agents/commands/codex/harness/harness-onboarding/SKILL.md +0 -299
  203. package/dist/agents/commands/codex/harness/harness-onboarding/agents/openai.yaml +0 -3
  204. package/dist/agents/commands/codex/harness/harness-perf/SKILL.md +0 -272
  205. package/dist/agents/commands/codex/harness/harness-perf/agents/openai.yaml +0 -3
  206. package/dist/agents/commands/codex/harness/harness-planning/SKILL.md +0 -592
  207. package/dist/agents/commands/codex/harness/harness-planning/agents/openai.yaml +0 -3
  208. package/dist/agents/commands/codex/harness/harness-refactoring/SKILL.md +0 -181
  209. package/dist/agents/commands/codex/harness/harness-refactoring/agents/openai.yaml +0 -3
  210. package/dist/agents/commands/codex/harness/harness-release-readiness/SKILL.md +0 -701
  211. package/dist/agents/commands/codex/harness/harness-release-readiness/agents/openai.yaml +0 -3
  212. package/dist/agents/commands/codex/harness/harness-roadmap/SKILL.md +0 -607
  213. package/dist/agents/commands/codex/harness/harness-roadmap/agents/openai.yaml +0 -3
  214. package/dist/agents/commands/codex/harness/harness-security-scan/SKILL.md +0 -147
  215. package/dist/agents/commands/codex/harness/harness-security-scan/agents/openai.yaml +0 -3
  216. package/dist/agents/commands/codex/harness/harness-skill-authoring/SKILL.md +0 -314
  217. package/dist/agents/commands/codex/harness/harness-skill-authoring/agents/openai.yaml +0 -3
  218. package/dist/agents/commands/codex/harness/harness-soundness-review/SKILL.md +0 -1280
  219. package/dist/agents/commands/codex/harness/harness-soundness-review/agents/openai.yaml +0 -3
  220. package/dist/agents/commands/codex/harness/harness-supply-chain-audit/SKILL.md +0 -255
  221. package/dist/agents/commands/codex/harness/harness-supply-chain-audit/agents/openai.yaml +0 -3
  222. package/dist/agents/commands/codex/harness/harness-tdd/SKILL.md +0 -189
  223. package/dist/agents/commands/codex/harness/harness-tdd/agents/openai.yaml +0 -3
  224. package/dist/agents/commands/codex/harness/harness-test-advisor/SKILL.md +0 -171
  225. package/dist/agents/commands/codex/harness/harness-test-advisor/agents/openai.yaml +0 -3
  226. package/dist/agents/commands/codex/harness/harness-verification/SKILL.md +0 -433
  227. package/dist/agents/commands/codex/harness/harness-verification/agents/openai.yaml +0 -3
  228. package/dist/agents/commands/codex/harness/harness-verify/SKILL.md +0 -170
  229. package/dist/agents/commands/codex/harness/harness-verify/agents/openai.yaml +0 -3
  230. package/dist/agents/commands/codex/harness/initialize-harness-project/SKILL.md +0 -244
  231. package/dist/agents/commands/codex/harness/initialize-harness-project/agents/openai.yaml +0 -3
  232. package/dist/agents/commands/cursor/harness/add-harness-component.mdc +0 -200
  233. package/dist/agents/commands/cursor/harness/cleanup-dead-code.mdc +0 -253
  234. package/dist/agents/commands/cursor/harness/detect-doc-drift.mdc +0 -187
  235. package/dist/agents/commands/cursor/harness/enforce-architecture.mdc +0 -304
  236. package/dist/agents/commands/cursor/harness/harness-architecture-advisor.mdc +0 -457
  237. package/dist/agents/commands/cursor/harness/harness-autopilot.mdc +0 -924
  238. package/dist/agents/commands/cursor/harness/harness-brainstorming.mdc +0 -414
  239. package/dist/agents/commands/cursor/harness/harness-code-review.mdc +0 -865
  240. package/dist/agents/commands/cursor/harness/harness-codebase-cleanup.mdc +0 -232
  241. package/dist/agents/commands/cursor/harness/harness-debugging.mdc +0 -374
  242. package/dist/agents/commands/cursor/harness/harness-dependency-health.mdc +0 -187
  243. package/dist/agents/commands/cursor/harness/harness-docs-pipeline.mdc +0 -468
  244. package/dist/agents/commands/cursor/harness/harness-execution.mdc +0 -518
  245. package/dist/agents/commands/cursor/harness/harness-hotspot-detector.mdc +0 -169
  246. package/dist/agents/commands/cursor/harness/harness-impact-analysis.mdc +0 -192
  247. package/dist/agents/commands/cursor/harness/harness-integrity.mdc +0 -175
  248. package/dist/agents/commands/cursor/harness/harness-onboarding.mdc +0 -296
  249. package/dist/agents/commands/cursor/harness/harness-perf.mdc +0 -268
  250. package/dist/agents/commands/cursor/harness/harness-planning.mdc +0 -587
  251. package/dist/agents/commands/cursor/harness/harness-refactoring.mdc +0 -177
  252. package/dist/agents/commands/cursor/harness/harness-release-readiness.mdc +0 -697
  253. package/dist/agents/commands/cursor/harness/harness-roadmap.mdc +0 -603
  254. package/dist/agents/commands/cursor/harness/harness-security-scan.mdc +0 -162
  255. package/dist/agents/commands/cursor/harness/harness-skill-authoring.mdc +0 -300
  256. package/dist/agents/commands/cursor/harness/harness-soundness-review.mdc +0 -1275
  257. package/dist/agents/commands/cursor/harness/harness-supply-chain-audit.mdc +0 -252
  258. package/dist/agents/commands/cursor/harness/harness-tdd.mdc +0 -185
  259. package/dist/agents/commands/cursor/harness/harness-test-advisor.mdc +0 -168
  260. package/dist/agents/commands/cursor/harness/harness-verification.mdc +0 -429
  261. package/dist/agents/commands/cursor/harness/harness-verify.mdc +0 -167
  262. package/dist/agents/commands/cursor/harness/initialize-harness-project.mdc +0 -240
  263. package/dist/agents/commands/gemini-cli/harness/harness.toml +0 -277
  264. package/dist/hooks/profiles.ts +0 -48
  265. package/dist/mcp-KHK5KYER.js +0 -39
  266. package/dist/{chunk-RRHUCDRD.js → chunk-ZP5E7YET.js} +3 -3
@@ -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)) {
@@ -363,13 +371,280 @@ function detectEarsPattern(text) {
363
371
  if (/^the\s+\w+\s+shall\b/.test(lower)) return "ubiquitous";
364
372
  return void 0;
365
373
  }
366
- function emptyMermaidResult(diagramType = "unknown") {
374
+ function emptyResult2(diagramType = "unknown") {
367
375
  return {
368
376
  entities: [],
369
377
  relationships: [],
370
378
  metadata: { format: "mermaid", diagramType }
371
379
  };
372
380
  }
381
+ function detectDiagramType(content) {
382
+ for (const line of content.split("\n")) {
383
+ const trimmed = line.trim();
384
+ if (!trimmed) continue;
385
+ if (/^(?:graph|flowchart)\b/i.test(trimmed)) return "flowchart";
386
+ if (/^sequenceDiagram\b/.test(trimmed)) return "sequence";
387
+ if (/^classDiagram\b/.test(trimmed)) return "class";
388
+ if (/^erDiagram\b/.test(trimmed)) return "er";
389
+ break;
390
+ }
391
+ return "unknown";
392
+ }
393
+ function isDecisionNode(content, nodeId) {
394
+ const decisionRegex = new RegExp(`${nodeId}\\s*\\{`);
395
+ return decisionRegex.test(content);
396
+ }
397
+ function buildFlowchartEntity(content, match) {
398
+ const id = match[1] ?? "";
399
+ const label = (match[2] ?? "").trim();
400
+ if (!id) return null;
401
+ const decision = isDecisionNode(content, id);
402
+ const entity = {
403
+ id,
404
+ label,
405
+ ...decision ? { type: "decision" } : {}
406
+ };
407
+ return { id, entity };
408
+ }
409
+ function extractFlowchartNodes(content) {
410
+ const entities = /* @__PURE__ */ new Map();
411
+ const nodeRegex = /([A-Za-z0-9_]+)\s*[[({]([^\])}]+)[\])}]/g;
412
+ let match = nodeRegex.exec(content);
413
+ while (match !== null) {
414
+ const parsed = buildFlowchartEntity(content, match);
415
+ if (parsed && !entities.has(parsed.id)) {
416
+ entities.set(parsed.id, parsed.entity);
417
+ }
418
+ match = nodeRegex.exec(content);
419
+ }
420
+ return entities;
421
+ }
422
+ function parseLabeledEdgeMatch(match) {
423
+ const from = match[1] ?? "";
424
+ const label = (match[2] ?? "").trim();
425
+ const to = match[3] ?? "";
426
+ if (!from || !to) return null;
427
+ return { key: `${from}->${to}:${label}`, rel: { from, to, label } };
428
+ }
429
+ function extractLabeledEdges(stripped, edgeKeys) {
430
+ const relationships = [];
431
+ const labeledEdgeRegex = /([A-Za-z0-9_]+)\s*--+>?\|([^|]+)\|\s*([A-Za-z0-9_]+)/g;
432
+ let match = labeledEdgeRegex.exec(stripped);
433
+ while (match !== null) {
434
+ const parsed = parseLabeledEdgeMatch(match);
435
+ if (parsed && !edgeKeys.has(parsed.key)) {
436
+ edgeKeys.add(parsed.key);
437
+ relationships.push(parsed.rel);
438
+ }
439
+ match = labeledEdgeRegex.exec(stripped);
440
+ }
441
+ return relationships;
442
+ }
443
+ function hasLabeledEdgeBetween(labeledEdges, from, to) {
444
+ return labeledEdges.some((r) => r.from === from && r.to === to);
445
+ }
446
+ function extractUnlabeledEdges(stripped, edgeKeys, labeledEdges) {
447
+ const relationships = [];
448
+ const unlabeledEdgeRegex = /([A-Za-z0-9_]+)\s*--+>?\s+([A-Za-z0-9_]+)/g;
449
+ let match = unlabeledEdgeRegex.exec(stripped);
450
+ while (match !== null) {
451
+ const from = match[1] ?? "";
452
+ const to = match[2] ?? "";
453
+ const key = `${from}->${to}`;
454
+ if (from && to && !hasLabeledEdgeBetween(labeledEdges, from, to) && !edgeKeys.has(key)) {
455
+ edgeKeys.add(key);
456
+ relationships.push({ from, to });
457
+ }
458
+ match = unlabeledEdgeRegex.exec(stripped);
459
+ }
460
+ return relationships;
461
+ }
462
+ function parseFlowchart(content, diagramType) {
463
+ const entities = extractFlowchartNodes(content);
464
+ const stripped = content.replace(/[[({][^\])}]*[\])}]/g, "");
465
+ const edgeKeys = /* @__PURE__ */ new Set();
466
+ const labeledEdges = extractLabeledEdges(stripped, edgeKeys);
467
+ const unlabeledEdges = extractUnlabeledEdges(stripped, edgeKeys, labeledEdges);
468
+ return {
469
+ entities: Array.from(entities.values()),
470
+ relationships: [...labeledEdges, ...unlabeledEdges],
471
+ metadata: { format: "mermaid", diagramType }
472
+ };
473
+ }
474
+ function extractParticipants(content) {
475
+ const entities = [];
476
+ const seenParticipants = /* @__PURE__ */ new Set();
477
+ const participantRegex = /participant\s+(\w+)(?:\s+as\s+(.+))?/g;
478
+ let match = participantRegex.exec(content);
479
+ while (match !== null) {
480
+ const id = match[1] ?? "";
481
+ const label = match[2]?.trim() || id;
482
+ if (id && !seenParticipants.has(id)) {
483
+ seenParticipants.add(id);
484
+ entities.push({ id, label });
485
+ }
486
+ match = participantRegex.exec(content);
487
+ }
488
+ return entities;
489
+ }
490
+ function relationshipFromMatch(match) {
491
+ const from = match[1] ?? "";
492
+ const to = match[2] ?? "";
493
+ const label = (match[3] ?? "").trim();
494
+ return from && to ? { from, to, label } : null;
495
+ }
496
+ function collectMessageMatches(content, regex) {
497
+ const results = [];
498
+ let match = regex.exec(content);
499
+ while (match !== null) {
500
+ const rel = relationshipFromMatch(match);
501
+ if (rel) results.push(rel);
502
+ match = regex.exec(content);
503
+ }
504
+ return results;
505
+ }
506
+ function extractMessages(content) {
507
+ const forward = collectMessageMatches(content, /(\w+)\s*->>?\+?\s*(\w+)\s*:\s*(.+)/g);
508
+ const returns = collectMessageMatches(content, /(\w+)\s*-->>?-?\s*(\w+)\s*:\s*(.+)/g);
509
+ return [...forward, ...returns];
510
+ }
511
+ function parseSequence(content, diagramType) {
512
+ return {
513
+ entities: extractParticipants(content),
514
+ relationships: extractMessages(content),
515
+ metadata: { format: "mermaid", diagramType }
516
+ };
517
+ }
518
+ function emptyResult3() {
519
+ return {
520
+ entities: [],
521
+ relationships: [],
522
+ metadata: { format: "d2", diagramType: "architecture" }
523
+ };
524
+ }
525
+ function parseBlockShape(stripped) {
526
+ const match = stripped.match(/^([a-zA-Z0-9_-]+)\s*:\s*(.+?)\s*\{$/);
527
+ if (match) {
528
+ const id = match[1];
529
+ const label = match[2];
530
+ if (id && label) return { id, label };
531
+ }
532
+ return null;
533
+ }
534
+ function parseConnection(stripped) {
535
+ const match = stripped.match(/^([a-zA-Z0-9_.-]+)\s*->\s*([a-zA-Z0-9_.-]+)(?:\s*:\s*(.+?))?$/);
536
+ if (match) {
537
+ const from = match[1] ?? "";
538
+ const to = match[2] ?? "";
539
+ const label = match[3]?.trim();
540
+ if (from && to) return { from, to, ...label ? { label } : {} };
541
+ }
542
+ return null;
543
+ }
544
+ function parseSimpleShape(stripped) {
545
+ const match = stripped.match(/^([a-zA-Z0-9_-]+)\s*:\s*(.+)$/);
546
+ if (match) {
547
+ const id = match[1];
548
+ const label = (match[2] ?? "").trim();
549
+ if (id && label) return { id, label };
550
+ }
551
+ return null;
552
+ }
553
+ function handleBlockOpen(stripped, state) {
554
+ if (state.braceDepth === 0) {
555
+ const shape = parseBlockShape(stripped);
556
+ if (shape) state.entities.set(shape.id, { id: shape.id, label: shape.label });
557
+ }
558
+ state.braceDepth++;
559
+ }
560
+ function processTopLevelLine(stripped, state) {
561
+ const conn = parseConnection(stripped);
562
+ if (conn) {
563
+ state.relationships.push(conn);
564
+ return;
565
+ }
566
+ const shape = parseSimpleShape(stripped);
567
+ if (shape && !state.entities.has(shape.id)) {
568
+ state.entities.set(shape.id, { id: shape.id, label: shape.label });
569
+ }
570
+ }
571
+ function processD2Line(stripped, state) {
572
+ if (!stripped || stripped.startsWith("#")) return;
573
+ if (stripped.endsWith("{")) {
574
+ handleBlockOpen(stripped, state);
575
+ return;
576
+ }
577
+ if (stripped === "}") {
578
+ state.braceDepth = Math.max(0, state.braceDepth - 1);
579
+ return;
580
+ }
581
+ if (state.braceDepth > 0) return;
582
+ processTopLevelLine(stripped, state);
583
+ }
584
+ function emptyResult4() {
585
+ return {
586
+ entities: [],
587
+ relationships: [],
588
+ metadata: { format: "plantuml", diagramType: "unknown" }
589
+ };
590
+ }
591
+ function detectDiagramType2(content) {
592
+ if (/class\s+\w+/.test(content)) return "class";
593
+ if (/\[.+\]/.test(content) || /component\s+/.test(content)) return "component";
594
+ if (/participant\s+/.test(content) || /actor\s+/.test(content)) return "sequence";
595
+ return "unknown";
596
+ }
597
+ function stripWrappers(content) {
598
+ return content.replace(/@startuml\b.*\n?/, "").replace(/@enduml\b.*/, "").trim();
599
+ }
600
+ function extractClasses(body, entities) {
601
+ const classRegex = /class\s+(\w+)/g;
602
+ let match = classRegex.exec(body);
603
+ while (match !== null) {
604
+ const id = match[1] ?? "";
605
+ if (id && !entities.has(id)) {
606
+ entities.set(id, { id, label: id });
607
+ }
608
+ match = classRegex.exec(body);
609
+ }
610
+ }
611
+ function extractComponents(body, entities) {
612
+ const componentRegex = /\[([^\]]+)\]/g;
613
+ let match = componentRegex.exec(body);
614
+ while (match !== null) {
615
+ const label = (match[1] ?? "").trim();
616
+ if (label) {
617
+ const id = label.replace(/\s+/g, "_");
618
+ if (!entities.has(id)) {
619
+ entities.set(id, { id, label });
620
+ }
621
+ }
622
+ match = componentRegex.exec(body);
623
+ }
624
+ }
625
+ function parseRelationshipMatch(match) {
626
+ const from = match[1] ?? "";
627
+ const to = match[2] ?? "";
628
+ if (!from || !to) return null;
629
+ const label = match[3]?.trim();
630
+ return { from, to, ...label ? { label } : {} };
631
+ }
632
+ function collectRelationshipMatches(body, regex) {
633
+ const results = [];
634
+ let match = regex.exec(body);
635
+ while (match !== null) {
636
+ const rel = parseRelationshipMatch(match);
637
+ if (rel) results.push(rel);
638
+ match = regex.exec(body);
639
+ }
640
+ return results;
641
+ }
642
+ function extractRelationships(body) {
643
+ return collectRelationshipMatches(
644
+ body,
645
+ /(\w+)\s*(?:-->|->|<--|<-|\.\.>|--)\s*(\w+)(?:\s*:\s*(.+))?/g
646
+ );
647
+ }
373
648
  function applyReactionBoost(candidate, nodeType, metadata) {
374
649
  if (nodeType !== "conversation" || !metadata.reactions) return;
375
650
  const reactions = metadata.reactions;
@@ -379,11 +654,11 @@ function applyReactionBoost(candidate, nodeType, metadata) {
379
654
  }
380
655
  }
381
656
  function detectLanguage(filePath) {
382
- const name = path9.basename(filePath);
657
+ const name = path10.basename(filePath);
383
658
  for (const skip of SKIP_EXTENSIONS2) {
384
659
  if (name.endsWith(skip)) return void 0;
385
660
  }
386
- const ext = path9.extname(filePath);
661
+ const ext = path10.extname(filePath);
387
662
  return EXT_TO_LANGUAGE[ext];
388
663
  }
389
664
  function createExtractionRunner() {
@@ -421,6 +696,77 @@ function classifyConflict(a, b) {
421
696
  return "temporal_conflict";
422
697
  return "status_divergence";
423
698
  }
699
+ function buildEntry(node, source) {
700
+ return {
701
+ nodeId: node.id,
702
+ source,
703
+ name: node.name,
704
+ content: node.content ?? "",
705
+ lastModified: node.lastModified
706
+ };
707
+ }
708
+ function groupByName(nodes) {
709
+ const byName = /* @__PURE__ */ new Map();
710
+ for (const node of nodes) {
711
+ const key = node.name.toLowerCase().trim();
712
+ const group = byName.get(key) ?? [];
713
+ group.push(node);
714
+ byName.set(key, group);
715
+ }
716
+ return byName;
717
+ }
718
+ function tryAddContradiction(acc, a, b, similarity) {
719
+ const sourceA = a.metadata.source ?? "unknown";
720
+ const sourceB = b.metadata.source ?? "unknown";
721
+ if (sourceA === sourceB) return;
722
+ if ((a.hash ?? a.id) === (b.hash ?? b.id)) return;
723
+ const pairId = [a.id, b.id].sort().join(":");
724
+ if (acc.seen.has(pairId)) return;
725
+ acc.seen.add(pairId);
726
+ const conflictType = classifyConflict(a, b);
727
+ const pairKey = [sourceA, sourceB].sort().join("\u2194");
728
+ acc.sourcePairCounts[pairKey] = (acc.sourcePairCounts[pairKey] ?? 0) + 1;
729
+ acc.contradictions.push({
730
+ id: `contradiction:${a.id}:${b.id}`,
731
+ entityA: buildEntry(a, sourceA),
732
+ entityB: buildEntry(b, sourceB),
733
+ similarity,
734
+ conflictType,
735
+ severity: SEVERITY_MAP[conflictType],
736
+ description: `"${a.name}" has conflicting definitions from ${sourceA} and ${sourceB}`
737
+ });
738
+ }
739
+ function collectExactMatches(byName, acc) {
740
+ for (const [, group] of byName) {
741
+ if (group.length < 2) continue;
742
+ for (let i = 0; i < group.length; i++) {
743
+ for (let j = i + 1; j < group.length; j++) {
744
+ tryAddContradiction(acc, group[i], group[j], 1);
745
+ }
746
+ }
747
+ }
748
+ }
749
+ function collectFuzzyMatches(byName, acc) {
750
+ const keys = [...byName.keys()];
751
+ for (let i = 0; i < keys.length; i++) {
752
+ for (let j = i + 1; j < keys.length; j++) {
753
+ const keyA = keys[i];
754
+ const keyB = keys[j];
755
+ const maxLen = Math.max(keyA.length, keyB.length);
756
+ const minLen = Math.min(keyA.length, keyB.length);
757
+ if (minLen / maxLen < SIMILARITY_THRESHOLD) continue;
758
+ const ratio = levenshteinRatio(keyA, keyB);
759
+ if (ratio < SIMILARITY_THRESHOLD) continue;
760
+ const groupA = byName.get(keyA);
761
+ const groupB = byName.get(keyB);
762
+ for (const a of groupA) {
763
+ for (const b of groupB) {
764
+ tryAddContradiction(acc, a, b, ratio);
765
+ }
766
+ }
767
+ }
768
+ }
769
+ }
424
770
  function toGrade(score) {
425
771
  if (score >= 80) return "A";
426
772
  if (score >= 60) return "B";
@@ -428,6 +774,64 @@ function toGrade(score) {
428
774
  if (score >= 20) return "D";
429
775
  return "F";
430
776
  }
777
+ function groupByDomain(nodes, fallback) {
778
+ const map = /* @__PURE__ */ new Map();
779
+ for (const node of nodes) {
780
+ const domain = node.metadata.domain ?? fallback(node);
781
+ const group = map.get(domain) ?? [];
782
+ group.push(node);
783
+ map.set(domain, group);
784
+ }
785
+ return map;
786
+ }
787
+ function countLinkedEntities(codeEntries, store) {
788
+ const linkedIds = /* @__PURE__ */ new Set();
789
+ for (const codeNode of codeEntries) {
790
+ if (hasKnowledgeEdge(codeNode.id, store)) {
791
+ linkedIds.add(codeNode.id);
792
+ }
793
+ }
794
+ return linkedIds;
795
+ }
796
+ function hasKnowledgeEdge(nodeId, store) {
797
+ for (const edgeType of KNOWLEDGE_EDGE_TYPES) {
798
+ if (store.getEdges({ to: nodeId, type: edgeType }).length > 0) return true;
799
+ }
800
+ return false;
801
+ }
802
+ function computeSourceBreakdown(knEntries) {
803
+ const breakdown = {};
804
+ for (const kn of knEntries) {
805
+ const src = kn.metadata.source ?? "unknown";
806
+ breakdown[src] = (breakdown[src] ?? 0) + 1;
807
+ }
808
+ return breakdown;
809
+ }
810
+ function computeDomainScore(knowledgeEntries, codeEntities, linkedEntities, uniqueSources) {
811
+ const codeCoverageComponent = codeEntities > 0 ? linkedEntities / codeEntities * 60 : 0;
812
+ const knowledgeDepthComponent = Math.min(knowledgeEntries / 10, 1) * 20;
813
+ const sourceDiversityComponent = Math.min(uniqueSources / 3, 1) * 20;
814
+ return Math.round(codeCoverageComponent + knowledgeDepthComponent + sourceDiversityComponent);
815
+ }
816
+ function scoreDomain(domain, knEntries, codeEntries, store) {
817
+ const linkedIds = countLinkedEntities(codeEntries, store);
818
+ const sourceBreakdown = computeSourceBreakdown(knEntries);
819
+ const codeEntities = codeEntries.length;
820
+ const linkedEntities = linkedIds.size;
821
+ const knowledgeEntries = knEntries.length;
822
+ const uniqueSources = Object.keys(sourceBreakdown).length;
823
+ const score = computeDomainScore(knowledgeEntries, codeEntities, linkedEntities, uniqueSources);
824
+ return {
825
+ domain,
826
+ score,
827
+ knowledgeEntries,
828
+ codeEntities,
829
+ linkedEntities,
830
+ unlinkedEntities: codeEntities - linkedEntities,
831
+ sourceBreakdown,
832
+ grade: toGrade(score)
833
+ };
834
+ }
431
835
  function withRetry(client, options) {
432
836
  const maxRetries = options?.maxRetries ?? 3;
433
837
  const baseDelayMs = options?.baseDelayMs ?? 1e3;
@@ -469,7 +873,7 @@ function sanitizeExternalText(text, maxLength = 2e3) {
469
873
  }
470
874
  function linkToCode(store, content, sourceNodeId, edgeType, options) {
471
875
  let edgesCreated = 0;
472
- for (const type of CODE_NODE_TYPES4) {
876
+ for (const type of CODE_NODE_TYPES5) {
473
877
  const nodes = store.findNodes({ type });
474
878
  for (const node of nodes) {
475
879
  if (node.name.length < 3) continue;
@@ -582,7 +986,7 @@ function missingApiKeyResult(envVar, start) {
582
986
  durationMs: Date.now() - start
583
987
  };
584
988
  }
585
- function emptyResult2(errors, start) {
989
+ function emptyResult5(errors, start) {
586
990
  return {
587
991
  nodesAdded: 0,
588
992
  nodesUpdated: 0,
@@ -641,6 +1045,14 @@ function hasConstraintKeyword(text) {
641
1045
  const lower = text.toLowerCase();
642
1046
  return CONSTRAINT_KEYWORDS.some((kw) => lower.includes(kw));
643
1047
  }
1048
+ function buildNodeMetadata(condensed, base) {
1049
+ const metadata = { ...base };
1050
+ if (condensed.method !== "passthrough") {
1051
+ metadata.condensed = condensed.method;
1052
+ metadata.originalLength = condensed.originalLength;
1053
+ }
1054
+ return metadata;
1055
+ }
644
1056
  function buildIngestResult2(nodesAdded, edgesAdded, errors, start) {
645
1057
  return {
646
1058
  nodesAdded,
@@ -651,6 +1063,24 @@ function buildIngestResult2(nodesAdded, edgesAdded, errors, start) {
651
1063
  durationMs: Date.now() - start
652
1064
  };
653
1065
  }
1066
+ function validateFigmaConfig(config) {
1067
+ const apiKeyEnv = config.apiKeyEnv ?? "FIGMA_API_KEY";
1068
+ const apiKey = process.env[apiKeyEnv];
1069
+ if (!apiKey) return { error: `Missing API key: environment variable "${apiKeyEnv}" is not set` };
1070
+ const baseUrlEnv = config.baseUrlEnv ?? "FIGMA_BASE_URL";
1071
+ const baseUrl = process.env[baseUrlEnv] ?? "https://api.figma.com";
1072
+ try {
1073
+ const parsed = new URL(baseUrl);
1074
+ if (parsed.protocol !== "https:" || !parsed.hostname.endsWith("api.figma.com")) {
1075
+ return { error: `Invalid ${baseUrlEnv}: must be an HTTPS URL on api.figma.com` };
1076
+ }
1077
+ } catch {
1078
+ return { error: `Invalid ${baseUrlEnv}: not a valid URL` };
1079
+ }
1080
+ const fileIds = config.fileIds;
1081
+ if (!fileIds || fileIds.length === 0) return { error: "No fileIds provided in connector config" };
1082
+ return { apiKey, baseUrl, fileIds };
1083
+ }
654
1084
  function stripHtml(html) {
655
1085
  return html.replace(/<[^>]*>/g, "");
656
1086
  }
@@ -664,6 +1094,46 @@ function buildIngestResult3(nodesAdded, edgesAdded, errors, start) {
664
1094
  durationMs: Date.now() - start
665
1095
  };
666
1096
  }
1097
+ function validateBaseUrl(baseUrl, baseUrlEnv) {
1098
+ try {
1099
+ const parsed = new URL(baseUrl);
1100
+ if (parsed.protocol !== "https:" || parsed.hostname !== "api.miro.com") {
1101
+ return `Invalid ${baseUrlEnv}: must be an HTTPS URL on api.miro.com`;
1102
+ }
1103
+ } catch {
1104
+ return `Invalid ${baseUrlEnv}: not a valid URL`;
1105
+ }
1106
+ return null;
1107
+ }
1108
+ function resolveConfig(config, start) {
1109
+ const apiKeyEnv = config.apiKeyEnv ?? "MIRO_API_KEY";
1110
+ const apiKey = process.env[apiKeyEnv];
1111
+ if (!apiKey) {
1112
+ return {
1113
+ ok: false,
1114
+ result: buildIngestResult3(
1115
+ 0,
1116
+ 0,
1117
+ [`Missing API key: environment variable "${apiKeyEnv}" is not set`],
1118
+ start
1119
+ )
1120
+ };
1121
+ }
1122
+ const baseUrlEnv = config.baseUrlEnv ?? "MIRO_BASE_URL";
1123
+ const baseUrl = process.env[baseUrlEnv] ?? "https://api.miro.com";
1124
+ const urlError = validateBaseUrl(baseUrl, baseUrlEnv);
1125
+ if (urlError) {
1126
+ return { ok: false, result: buildIngestResult3(0, 0, [urlError], start) };
1127
+ }
1128
+ const boardIds = config.boardIds;
1129
+ if (!boardIds || boardIds.length === 0) {
1130
+ return {
1131
+ ok: false,
1132
+ result: buildIngestResult3(0, 0, ["No boardIds provided in config"], start)
1133
+ };
1134
+ }
1135
+ return { ok: true, apiKey, baseUrl, boardIds, headers: { Authorization: `Bearer ${apiKey}` } };
1136
+ }
667
1137
  function isSkippableWord(cleaned, allConsumed) {
668
1138
  if (allConsumed.has(cleaned)) return true;
669
1139
  const lower = cleaned.toLowerCase();
@@ -891,7 +1361,7 @@ function isDTCGToken(obj) {
891
1361
  }
892
1362
  async function readFileOrNull(filePath) {
893
1363
  try {
894
- return await fs11.readFile(filePath, "utf-8");
1364
+ return await fs13.readFile(filePath, "utf-8");
895
1365
  } catch {
896
1366
  return null;
897
1367
  }
@@ -973,7 +1443,7 @@ function parseAntiPatterns(content) {
973
1443
  }
974
1444
  return patterns;
975
1445
  }
976
- 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, DiagramParser, HEURISTIC_PATTERNS, SCANNABLE_TYPES, KnowledgeLinker, StructuralDriftDetector, KnowledgeStagingAggregator, SKIP_DIRS, EXT_TO_LANGUAGE, SKIP_EXTENSIONS2, EXTRACTOR_EDGE_TYPE, ExtractionRunner, PATTERNS, TestDescriptionExtractor, EnumConstantExtractor, ValidationRuleExtractor, ApiPathExtractor, ALL_EXTRACTORS, ImageAnalysisExtractor, KNOWLEDGE_NODE_TYPES, SIMILARITY_THRESHOLD, 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;
977
1447
  var init_dist = __esm({
978
1448
  "../graph/dist/index.mjs"() {
979
1449
  NODE_TYPES = [
@@ -1000,7 +1470,7 @@ var init_dist = __esm({
1000
1470
  "build",
1001
1471
  "test_result",
1002
1472
  "execution_outcome",
1003
- // Observability (future)
1473
+ // Observability — reserved for future tracing/metrics integration
1004
1474
  "span",
1005
1475
  "metric",
1006
1476
  "log",
@@ -1047,7 +1517,7 @@ var init_dist = __esm({
1047
1517
  "triggered_by",
1048
1518
  "failed_in",
1049
1519
  "outcome_of",
1050
- // Execution relationships (future)
1520
+ // Execution relationships — reserved for future observability integration
1051
1521
  "executed_by",
1052
1522
  "measured_by",
1053
1523
  // Design relationships
@@ -1238,8 +1708,15 @@ var init_dist = __esm({
1238
1708
  await saveGraph(dirPath, allNodes, allEdges);
1239
1709
  }
1240
1710
  async load(dirPath) {
1241
- const data = await loadGraph(dirPath);
1242
- 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;
1243
1720
  this.clear();
1244
1721
  for (const node of data.nodes) {
1245
1722
  this.nodeMap.set(node.id, { ...node });
@@ -1552,9 +2029,10 @@ var init_dist = __esm({
1552
2029
  let edgesAdded = 0;
1553
2030
  const files = await this.findSourceFiles(rootDir);
1554
2031
  const nameToFiles = /* @__PURE__ */ new Map();
2032
+ const contentCache = /* @__PURE__ */ new Map();
1555
2033
  for (const filePath of files) {
1556
2034
  try {
1557
- const result = await this.processFile(filePath, rootDir, nameToFiles);
2035
+ const result = await this.processFile(filePath, rootDir, nameToFiles, contentCache);
1558
2036
  nodesAdded += result.nodesAdded;
1559
2037
  edgesAdded += result.edgesAdded;
1560
2038
  } catch (err) {
@@ -1564,7 +2042,8 @@ var init_dist = __esm({
1564
2042
  for (const filePath of files) {
1565
2043
  try {
1566
2044
  const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
1567
- const content = await fs.readFile(filePath, "utf-8");
2045
+ const content = contentCache.get(filePath);
2046
+ if (content === void 0) continue;
1568
2047
  const callsEdges = this.extractCallsEdgesForFile(relativePath, content, nameToFiles);
1569
2048
  for (const edge of callsEdges) {
1570
2049
  this.store.addEdge(edge);
@@ -1575,7 +2054,8 @@ var init_dist = __esm({
1575
2054
  }
1576
2055
  for (const filePath of files) {
1577
2056
  try {
1578
- const content = await fs.readFile(filePath, "utf-8");
2057
+ const content = contentCache.get(filePath);
2058
+ if (content === void 0) continue;
1579
2059
  edgesAdded += this.extractReqAnnotationsForFile(filePath, content, rootDir);
1580
2060
  } catch {
1581
2061
  }
@@ -1589,11 +2069,12 @@ var init_dist = __esm({
1589
2069
  durationMs: Date.now() - start
1590
2070
  };
1591
2071
  }
1592
- async processFile(filePath, rootDir, nameToFiles) {
2072
+ async processFile(filePath, rootDir, nameToFiles, contentCache) {
1593
2073
  let nodesAdded = 0;
1594
2074
  let edgesAdded = 0;
1595
2075
  const relativePath = path.relative(rootDir, filePath).replace(/\\/g, "/");
1596
2076
  const content = await fs.readFile(filePath, "utf-8");
2077
+ contentCache.set(filePath, content);
1597
2078
  const stat2 = await fs.stat(filePath);
1598
2079
  const fileId = `file:${relativePath}`;
1599
2080
  const fileNode = {
@@ -2631,6 +3112,112 @@ var init_dist = __esm({
2631
3112
  return results;
2632
3113
  }
2633
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
+ };
2634
3221
  REQUIREMENT_SECTIONS = [
2635
3222
  "Observable Truths",
2636
3223
  "Success Criteria",
@@ -2638,7 +3225,7 @@ var init_dist = __esm({
2638
3225
  ];
2639
3226
  SECTION_HEADING_RE = /^#{2,3}\s+(.+)$/;
2640
3227
  NUMBERED_ITEM_RE = /^\s*(\d+)\.\s+(.+)$/;
2641
- CODE_NODE_TYPES3 = ["file", "function", "class", "method", "interface", "variable"];
3228
+ CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
2642
3229
  RequirementIngestor = class {
2643
3230
  constructor(store) {
2644
3231
  this.store = store;
@@ -2656,8 +3243,8 @@ var init_dist = __esm({
2656
3243
  let edgesAdded = 0;
2657
3244
  let featureDirs;
2658
3245
  try {
2659
- const entries = await fs4.readdir(specsDir, { withFileTypes: true });
2660
- 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));
2661
3248
  } catch {
2662
3249
  return emptyResult(Date.now() - start);
2663
3250
  }
@@ -2676,11 +3263,11 @@ var init_dist = __esm({
2676
3263
  };
2677
3264
  }
2678
3265
  async ingestFeatureDir(featureDir, errors) {
2679
- const featureName = path5.basename(featureDir);
2680
- const specPath = path5.join(featureDir, "proposal.md").replaceAll("\\", "/");
3266
+ const featureName = path6.basename(featureDir);
3267
+ const specPath = path6.join(featureDir, "proposal.md").replaceAll("\\", "/");
2681
3268
  let content;
2682
3269
  try {
2683
- content = await fs4.readFile(specPath, "utf-8");
3270
+ content = await fs5.readFile(specPath, "utf-8");
2684
3271
  } catch {
2685
3272
  return { nodesAdded: 0, edgesAdded: 0 };
2686
3273
  }
@@ -2697,7 +3284,7 @@ var init_dist = __esm({
2697
3284
  this.store.addNode({
2698
3285
  id: specNodeId,
2699
3286
  type: "document",
2700
- name: path5.basename(specPath),
3287
+ name: path6.basename(specPath),
2701
3288
  path: specPath,
2702
3289
  metadata: { featureName }
2703
3290
  });
@@ -2812,9 +3399,9 @@ var init_dist = __esm({
2812
3399
  for (const node of fileNodes) {
2813
3400
  if (!node.path) continue;
2814
3401
  const normalizedPath = node.path.replace(/\\/g, "/");
2815
- const isCodeMatch = normalizedPath.includes("packages/") && path5.basename(normalizedPath).includes(featureName);
3402
+ const isCodeMatch = normalizedPath.includes("packages/") && path6.basename(normalizedPath).includes(featureName);
2816
3403
  const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
2817
- path5.basename(normalizedPath).includes(featureName);
3404
+ path6.basename(normalizedPath).includes(featureName);
2818
3405
  if (isCodeMatch && !isTestMatch) {
2819
3406
  this.store.addEdge({
2820
3407
  from: reqId,
@@ -2843,7 +3430,7 @@ var init_dist = __esm({
2843
3430
  */
2844
3431
  linkByKeywordOverlap(reqId, reqText) {
2845
3432
  let count = 0;
2846
- for (const nodeType of CODE_NODE_TYPES3) {
3433
+ for (const nodeType of CODE_NODE_TYPES4) {
2847
3434
  const codeNodes = this.store.findNodes({ type: nodeType });
2848
3435
  for (const node of codeNodes) {
2849
3436
  if (node.name.length < 3) continue;
@@ -2872,269 +3459,63 @@ var init_dist = __esm({
2872
3459
  parse(content, _filePath) {
2873
3460
  const trimmed = content.trim();
2874
3461
  if (!trimmed) {
2875
- return emptyMermaidResult();
3462
+ return emptyResult2();
2876
3463
  }
2877
- const diagramType = this.detectDiagramType(trimmed);
3464
+ const diagramType = detectDiagramType(trimmed);
2878
3465
  switch (diagramType) {
2879
3466
  case "flowchart":
2880
- return this.parseFlowchart(trimmed, diagramType);
3467
+ return parseFlowchart(trimmed, diagramType);
2881
3468
  case "sequence":
2882
- return this.parseSequence(trimmed, diagramType);
3469
+ return parseSequence(trimmed, diagramType);
2883
3470
  default:
2884
- return emptyMermaidResult(diagramType);
3471
+ return emptyResult2(diagramType);
2885
3472
  }
2886
3473
  }
2887
- detectDiagramType(content) {
2888
- const lines = content.split("\n");
2889
- for (const line of lines) {
2890
- const trimmedLine = line.trim();
2891
- if (!trimmedLine) continue;
2892
- if (/^(?:graph|flowchart)\b/i.test(trimmedLine)) return "flowchart";
2893
- if (/^sequenceDiagram\b/.test(trimmedLine)) return "sequence";
2894
- if (/^classDiagram\b/.test(trimmedLine)) return "class";
2895
- if (/^erDiagram\b/.test(trimmedLine)) return "er";
2896
- break;
2897
- }
2898
- return "unknown";
3474
+ };
3475
+ D2Parser = class {
3476
+ canParse(_content, ext) {
3477
+ return ext === ".d2";
2899
3478
  }
2900
- parseFlowchart(content, diagramType) {
2901
- const entities = /* @__PURE__ */ new Map();
2902
- const relationships = [];
2903
- const nodeRegex = /([A-Za-z0-9_]+)\s*[[({]([^\])}]+)[\])}]/g;
2904
- let match;
2905
- match = nodeRegex.exec(content);
2906
- while (match !== null) {
2907
- const id = match[1] ?? "";
2908
- const label = (match[2] ?? "").trim();
2909
- if (id && !entities.has(id)) {
2910
- const isDecision = this.isDecisionNode(content, id);
2911
- entities.set(id, {
2912
- id,
2913
- label,
2914
- ...isDecision ? { type: "decision" } : {}
2915
- });
2916
- }
2917
- match = nodeRegex.exec(content);
2918
- }
2919
- const stripped = content.replace(/[[({][^\])}]*[\])}]/g, "");
2920
- const labeledEdgeRegex = /([A-Za-z0-9_]+)\s*--+>?\|([^|]+)\|\s*([A-Za-z0-9_]+)/g;
2921
- const unlabeledEdgeRegex = /([A-Za-z0-9_]+)\s*--+>?\s+([A-Za-z0-9_]+)/g;
2922
- const edgeKeys = /* @__PURE__ */ new Set();
2923
- match = labeledEdgeRegex.exec(stripped);
2924
- while (match !== null) {
2925
- const from = match[1] ?? "";
2926
- const label = (match[2] ?? "").trim();
2927
- const to = match[3] ?? "";
2928
- if (from && to) {
2929
- const key = `${from}->${to}:${label}`;
2930
- if (!edgeKeys.has(key)) {
2931
- edgeKeys.add(key);
2932
- relationships.push({ from, to, label });
2933
- }
2934
- }
2935
- match = labeledEdgeRegex.exec(stripped);
2936
- }
2937
- match = unlabeledEdgeRegex.exec(stripped);
2938
- while (match !== null) {
2939
- const from = match[1] ?? "";
2940
- const to = match[2] ?? "";
2941
- if (from && to) {
2942
- const hasLabeled = relationships.some((r) => r.from === from && r.to === to);
2943
- if (!hasLabeled) {
2944
- const key = `${from}->${to}`;
2945
- if (!edgeKeys.has(key)) {
2946
- edgeKeys.add(key);
2947
- relationships.push({ from, to });
2948
- }
2949
- }
2950
- }
2951
- match = unlabeledEdgeRegex.exec(stripped);
2952
- }
2953
- return {
2954
- entities: Array.from(entities.values()),
2955
- relationships,
2956
- metadata: { format: "mermaid", diagramType }
3479
+ parse(content, _filePath) {
3480
+ const trimmed = content.trim();
3481
+ if (!trimmed) return emptyResult3();
3482
+ const state = {
3483
+ entities: /* @__PURE__ */ new Map(),
3484
+ relationships: [],
3485
+ braceDepth: 0
2957
3486
  };
2958
- }
2959
- isDecisionNode(content, nodeId) {
2960
- const decisionRegex = new RegExp(`${nodeId}\\s*\\{`);
2961
- return decisionRegex.test(content);
2962
- }
2963
- parseSequence(content, diagramType) {
2964
- const entities = [];
2965
- const relationships = [];
2966
- const seenParticipants = /* @__PURE__ */ new Set();
2967
- const participantRegex = /participant\s+(\w+)(?:\s+as\s+(.+))?/g;
2968
- let match;
2969
- match = participantRegex.exec(content);
2970
- while (match !== null) {
2971
- const id = match[1] ?? "";
2972
- const label = match[2]?.trim() || id;
2973
- if (id && !seenParticipants.has(id)) {
2974
- seenParticipants.add(id);
2975
- entities.push({ id, label });
2976
- }
2977
- match = participantRegex.exec(content);
2978
- }
2979
- const forwardMsgRegex = /(\w+)\s*->>?\+?\s*(\w+)\s*:\s*(.+)/g;
2980
- match = forwardMsgRegex.exec(content);
2981
- while (match !== null) {
2982
- const from = match[1] ?? "";
2983
- const to = match[2] ?? "";
2984
- const label = (match[3] ?? "").trim();
2985
- if (from && to) {
2986
- relationships.push({ from, to, label });
2987
- }
2988
- match = forwardMsgRegex.exec(content);
2989
- }
2990
- const returnMsgRegex = /(\w+)\s*-->>?-?\s*(\w+)\s*:\s*(.+)/g;
2991
- match = returnMsgRegex.exec(content);
2992
- while (match !== null) {
2993
- const from = match[1] ?? "";
2994
- const to = match[2] ?? "";
2995
- const label = (match[3] ?? "").trim();
2996
- if (from && to) {
2997
- relationships.push({ from, to, label });
2998
- }
2999
- match = returnMsgRegex.exec(content);
3487
+ for (const line of trimmed.split("\n")) {
3488
+ processD2Line(line.trim(), state);
3000
3489
  }
3001
3490
  return {
3002
- entities,
3003
- relationships,
3004
- metadata: { format: "mermaid", diagramType }
3491
+ entities: Array.from(state.entities.values()),
3492
+ relationships: state.relationships,
3493
+ metadata: { format: "d2", diagramType: "architecture" }
3005
3494
  };
3006
3495
  }
3007
3496
  };
3008
- D2Parser = class {
3497
+ PlantUmlParser = class {
3009
3498
  canParse(_content, ext) {
3010
- return ext === ".d2";
3499
+ return ext === ".puml" || ext === ".plantuml";
3011
3500
  }
3012
3501
  parse(content, _filePath) {
3013
3502
  const trimmed = content.trim();
3014
- if (!trimmed) {
3015
- return {
3016
- entities: [],
3017
- relationships: [],
3018
- metadata: { format: "d2", diagramType: "architecture" }
3019
- };
3020
- }
3503
+ if (!trimmed) return emptyResult4();
3504
+ const diagramType = detectDiagramType2(trimmed);
3021
3505
  const entities = /* @__PURE__ */ new Map();
3022
- const relationships = [];
3023
- let braceDepth = 0;
3024
- for (const line of trimmed.split("\n")) {
3025
- const stripped = line.trim();
3026
- if (!stripped || stripped.startsWith("#")) continue;
3027
- if (stripped.endsWith("{")) {
3028
- if (braceDepth === 0) {
3029
- const shapeMatch = stripped.match(/^([a-zA-Z0-9_-]+)\s*:\s*(.+?)\s*\{$/);
3030
- if (shapeMatch) {
3031
- const id = shapeMatch[1];
3032
- const label = shapeMatch[2];
3033
- if (id && label) {
3034
- entities.set(id, { id, label });
3035
- }
3036
- }
3037
- }
3038
- braceDepth++;
3039
- continue;
3040
- }
3041
- if (stripped === "}") {
3042
- braceDepth = Math.max(0, braceDepth - 1);
3043
- continue;
3044
- }
3045
- if (braceDepth > 0) continue;
3046
- const connMatch = stripped.match(
3047
- /^([a-zA-Z0-9_.-]+)\s*->\s*([a-zA-Z0-9_.-]+)(?:\s*:\s*(.+?))?$/
3048
- );
3049
- if (connMatch) {
3050
- const from = connMatch[1] ?? "";
3051
- const to = connMatch[2] ?? "";
3052
- const label = connMatch[3]?.trim();
3053
- if (from && to) {
3054
- relationships.push({ from, to, ...label ? { label } : {} });
3055
- }
3056
- continue;
3057
- }
3058
- const simpleMatch = stripped.match(/^([a-zA-Z0-9_-]+)\s*:\s*(.+)$/);
3059
- if (simpleMatch) {
3060
- const id = simpleMatch[1];
3061
- const label = (simpleMatch[2] ?? "").trim();
3062
- if (id && label && !entities.has(id)) {
3063
- entities.set(id, { id, label });
3064
- }
3065
- }
3066
- }
3067
- return {
3068
- entities: Array.from(entities.values()),
3069
- relationships,
3070
- metadata: { format: "d2", diagramType: "architecture" }
3071
- };
3072
- }
3073
- };
3074
- PlantUmlParser = class {
3075
- canParse(_content, ext) {
3076
- return ext === ".puml" || ext === ".plantuml";
3077
- }
3078
- parse(content, _filePath) {
3079
- const trimmed = content.trim();
3080
- if (!trimmed) {
3081
- return {
3082
- entities: [],
3083
- relationships: [],
3084
- metadata: { format: "plantuml", diagramType: "unknown" }
3085
- };
3086
- }
3087
- const diagramType = this.detectDiagramType(trimmed);
3088
- const entities = /* @__PURE__ */ new Map();
3089
- const relationships = [];
3090
- const body = trimmed.replace(/@startuml\b.*\n?/, "").replace(/@enduml\b.*/, "").trim();
3091
- const classRegex = /class\s+(\w+)/g;
3092
- let match;
3093
- match = classRegex.exec(body);
3094
- while (match !== null) {
3095
- const id = match[1] ?? "";
3096
- if (id && !entities.has(id)) {
3097
- entities.set(id, { id, label: id });
3098
- }
3099
- match = classRegex.exec(body);
3100
- }
3101
- const componentRegex1 = /\[([^\]]+)\]/g;
3102
- match = componentRegex1.exec(body);
3103
- while (match !== null) {
3104
- const label = (match[1] ?? "").trim();
3105
- if (label) {
3106
- const id = label.replace(/\s+/g, "_");
3107
- if (!entities.has(id)) {
3108
- entities.set(id, { id, label });
3109
- }
3110
- }
3111
- match = componentRegex1.exec(body);
3112
- }
3113
- const relRegex = /(\w+)\s*(?:-->|->|<--|<-|\.\.>|--)\s*(\w+)(?:\s*:\s*(.+))?/g;
3114
- match = relRegex.exec(body);
3115
- while (match !== null) {
3116
- const from = match[1] ?? "";
3117
- const to = match[2] ?? "";
3118
- const label = match[3]?.trim();
3119
- if (from && to) {
3120
- relationships.push({ from, to, ...label ? { label } : {} });
3121
- }
3122
- match = relRegex.exec(body);
3123
- }
3506
+ const body = stripWrappers(trimmed);
3507
+ extractClasses(body, entities);
3508
+ extractComponents(body, entities);
3509
+ const relationships = extractRelationships(body);
3124
3510
  return {
3125
3511
  entities: Array.from(entities.values()),
3126
3512
  relationships,
3127
3513
  metadata: { format: "plantuml", diagramType }
3128
3514
  };
3129
3515
  }
3130
- detectDiagramType(content) {
3131
- if (/class\s+\w+/.test(content)) return "class";
3132
- if (/\[.+\]/.test(content) || /component\s+/.test(content)) return "component";
3133
- if (/participant\s+/.test(content) || /actor\s+/.test(content)) return "sequence";
3134
- return "unknown";
3135
- }
3136
3516
  };
3137
3517
  DIAGRAM_EXTENSIONS = /* @__PURE__ */ new Set([".mmd", ".mermaid", ".d2", ".puml", ".plantuml"]);
3518
+ SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".harness"]);
3138
3519
  DiagramParser = class {
3139
3520
  constructor(store) {
3140
3521
  this.store = store;
@@ -3146,7 +3527,7 @@ var init_dist = __esm({
3146
3527
  new PlantUmlParser()
3147
3528
  ];
3148
3529
  parse(content, filePath) {
3149
- const ext = path6.extname(filePath).toLowerCase();
3530
+ const ext = path7.extname(filePath).toLowerCase();
3150
3531
  for (const parser of this.parsers) {
3151
3532
  if (parser.canParse(content, ext)) {
3152
3533
  return parser.parse(content, filePath);
@@ -3166,41 +3547,13 @@ var init_dist = __esm({
3166
3547
  const diagramFiles = await this.findDiagramFiles(projectDir);
3167
3548
  for (const filePath of diagramFiles) {
3168
3549
  try {
3169
- const content = await fs5.readFile(filePath, "utf-8");
3170
- const relPath = path6.relative(projectDir, filePath).replaceAll("\\", "/");
3550
+ const content = await fs6.readFile(filePath, "utf-8");
3551
+ const relPath = path7.relative(projectDir, filePath).replaceAll("\\", "/");
3171
3552
  const result = this.parse(content, filePath);
3172
3553
  if (result.entities.length === 0) continue;
3173
3554
  const pathHash = hash(relPath);
3174
- for (const entity of result.entities) {
3175
- const nodeId = `diagram:${pathHash}:${entity.id}`;
3176
- this.store.addNode({
3177
- id: nodeId,
3178
- type: "business_concept",
3179
- name: entity.label,
3180
- path: relPath,
3181
- metadata: {
3182
- source: "diagram",
3183
- format: result.metadata.format,
3184
- diagramType: result.metadata.diagramType,
3185
- confidence: 0.85,
3186
- ...entity.type ? { entityType: entity.type } : {}
3187
- }
3188
- });
3189
- nodesAdded++;
3190
- }
3191
- for (const rel of result.relationships) {
3192
- const fromId = `diagram:${pathHash}:${rel.from}`;
3193
- const toId = `diagram:${pathHash}:${rel.to}`;
3194
- this.store.addEdge({
3195
- from: fromId,
3196
- to: toId,
3197
- type: "references",
3198
- metadata: {
3199
- ...rel.label ? { label: rel.label } : {}
3200
- }
3201
- });
3202
- edgesAdded++;
3203
- }
3555
+ nodesAdded += this.addEntityNodes(result, relPath, pathHash);
3556
+ edgesAdded += this.addRelationshipEdges(result, pathHash);
3204
3557
  } catch (err) {
3205
3558
  errors.push(
3206
3559
  `Failed to parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`
@@ -3216,25 +3569,64 @@ var init_dist = __esm({
3216
3569
  durationMs: Date.now() - start
3217
3570
  };
3218
3571
  }
3572
+ /** Map diagram entities to business_concept graph nodes. */
3573
+ addEntityNodes(result, relPath, pathHash) {
3574
+ let count = 0;
3575
+ for (const entity of result.entities) {
3576
+ const nodeId = `diagram:${pathHash}:${entity.id}`;
3577
+ this.store.addNode({
3578
+ id: nodeId,
3579
+ type: "business_concept",
3580
+ name: entity.label,
3581
+ path: relPath,
3582
+ metadata: {
3583
+ source: "diagram",
3584
+ format: result.metadata.format,
3585
+ diagramType: result.metadata.diagramType,
3586
+ confidence: 0.85,
3587
+ ...entity.type ? { entityType: entity.type } : {}
3588
+ }
3589
+ });
3590
+ count++;
3591
+ }
3592
+ return count;
3593
+ }
3594
+ /** Map diagram relationships to references graph edges. */
3595
+ addRelationshipEdges(result, pathHash) {
3596
+ let count = 0;
3597
+ for (const rel of result.relationships) {
3598
+ const fromId = `diagram:${pathHash}:${rel.from}`;
3599
+ const toId = `diagram:${pathHash}:${rel.to}`;
3600
+ this.store.addEdge({
3601
+ from: fromId,
3602
+ to: toId,
3603
+ type: "references",
3604
+ metadata: {
3605
+ ...rel.label ? { label: rel.label } : {}
3606
+ }
3607
+ });
3608
+ count++;
3609
+ }
3610
+ return count;
3611
+ }
3219
3612
  async findDiagramFiles(dir) {
3220
3613
  const files = [];
3221
- const SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".harness"]);
3222
3614
  const walk = async (currentDir) => {
3223
3615
  let entries;
3224
3616
  try {
3225
- entries = await fs5.readdir(currentDir, { withFileTypes: true });
3617
+ entries = await fs6.readdir(currentDir, { withFileTypes: true });
3226
3618
  } catch {
3227
3619
  return;
3228
3620
  }
3229
3621
  for (const entry of entries) {
3230
3622
  if (entry.isDirectory()) {
3231
- if (!SKIP_DIRS2.has(entry.name)) {
3232
- await walk(path6.join(currentDir, entry.name));
3623
+ if (!SKIP_DIRS.has(entry.name)) {
3624
+ await walk(path7.join(currentDir, entry.name));
3233
3625
  }
3234
3626
  } else if (entry.isFile()) {
3235
- const ext = path6.extname(entry.name).toLowerCase();
3627
+ const ext = path7.extname(entry.name).toLowerCase();
3236
3628
  if (DIAGRAM_EXTENSIONS.has(ext)) {
3237
- files.push(path6.join(currentDir, entry.name));
3629
+ files.push(path7.join(currentDir, entry.name));
3238
3630
  }
3239
3631
  }
3240
3632
  }
@@ -3381,8 +3773,10 @@ var init_dist = __esm({
3381
3773
  if (!duplicate) return false;
3382
3774
  const existingSources = duplicate.metadata.sources ?? [];
3383
3775
  if (!existingSources.includes(candidate.sourceNodeId)) {
3384
- existingSources.push(candidate.sourceNodeId);
3385
- duplicate.metadata.sources = existingSources;
3776
+ this.store.addNode({
3777
+ ...duplicate,
3778
+ metadata: { ...duplicate.metadata, sources: [...existingSources, candidate.sourceNodeId] }
3779
+ });
3386
3780
  }
3387
3781
  return true;
3388
3782
  }
@@ -3416,8 +3810,8 @@ var init_dist = __esm({
3416
3810
  */
3417
3811
  async writeJsonl(candidates) {
3418
3812
  if (!this.outputDir) return;
3419
- await fs6.mkdir(this.outputDir, { recursive: true });
3420
- 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");
3421
3815
  const lines = candidates.map(
3422
3816
  (c) => JSON.stringify({
3423
3817
  id: c.id,
@@ -3430,16 +3824,16 @@ var init_dist = __esm({
3430
3824
  nodeType: c.nodeType
3431
3825
  })
3432
3826
  );
3433
- await fs6.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
3827
+ await fs7.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
3434
3828
  }
3435
3829
  /**
3436
3830
  * Write medium-confidence candidates to staged JSONL for human review.
3437
3831
  */
3438
3832
  async writeStagedJsonl(candidates) {
3439
3833
  if (!this.outputDir || candidates.length === 0) return;
3440
- const stagedDir = path7.join(this.outputDir, "staged");
3441
- await fs6.mkdir(stagedDir, { recursive: true });
3442
- 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");
3443
3837
  const lines = candidates.map(
3444
3838
  (c) => JSON.stringify({
3445
3839
  id: c.id,
@@ -3452,7 +3846,7 @@ var init_dist = __esm({
3452
3846
  nodeType: c.nodeType
3453
3847
  })
3454
3848
  );
3455
- await fs6.writeFile(filePath, lines.join("\n") + "\n");
3849
+ await fs7.writeFile(filePath, lines.join("\n") + "\n");
3456
3850
  }
3457
3851
  /**
3458
3852
  * Apply heuristic patterns to content and return candidate extractions.
@@ -3554,6 +3948,14 @@ var init_dist = __esm({
3554
3948
  return { findings, driftScore, summary };
3555
3949
  }
3556
3950
  };
3951
+ BUSINESS_NODE_TYPES = [
3952
+ "business_concept",
3953
+ "business_rule",
3954
+ "business_process",
3955
+ "business_term",
3956
+ "business_metric",
3957
+ "business_fact"
3958
+ ];
3557
3959
  KnowledgeStagingAggregator = class {
3558
3960
  constructor(projectDir) {
3559
3961
  this.projectDir = projectDir;
@@ -3572,55 +3974,153 @@ var init_dist = __esm({
3572
3974
  if (deduplicated.length === 0) {
3573
3975
  return { staged: 0 };
3574
3976
  }
3575
- const stagedDir = path8.join(this.projectDir, ".harness", "knowledge", "staged");
3576
- await fs7.mkdir(stagedDir, { recursive: true });
3977
+ const stagedDir = path9.join(this.projectDir, ".harness", "knowledge", "staged");
3978
+ await fs8.mkdir(stagedDir, { recursive: true });
3577
3979
  const jsonl = deduplicated.map((entry) => JSON.stringify(entry)).join("\n") + "\n";
3578
- 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");
3579
3981
  return { staged: deduplicated.length };
3580
3982
  }
3581
- async generateGapReport(knowledgeDir) {
3582
- 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();
3583
3993
  let totalEntries = 0;
3584
3994
  try {
3585
- const entries = await fs7.readdir(knowledgeDir, { withFileTypes: true });
3995
+ const entries = await fs8.readdir(knowledgeDir, { withFileTypes: true });
3586
3996
  const domainDirs = entries.filter((e) => e.isDirectory());
3587
3997
  for (const dir of domainDirs) {
3588
- const domainPath = path8.join(knowledgeDir, dir.name);
3589
- const files = await fs7.readdir(domainPath);
3998
+ const domainPath = path9.join(knowledgeDir, dir.name);
3999
+ const files = await fs8.readdir(domainPath);
3590
4000
  const mdFiles = files.filter((f) => f.endsWith(".md"));
3591
4001
  const entryCount = mdFiles.length;
3592
4002
  totalEntries += entryCount;
3593
- 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
+ }
3594
4012
  }
3595
4013
  } catch {
3596
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
+ }
3597
4074
  return {
3598
4075
  domains,
3599
4076
  totalEntries,
4077
+ totalExtracted,
4078
+ totalGaps,
3600
4079
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3601
4080
  };
3602
4081
  }
3603
4082
  async writeGapReport(report) {
3604
- const gapsDir = path8.join(this.projectDir, ".harness", "knowledge");
3605
- 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;
3606
4086
  const lines = [
3607
4087
  "# Knowledge Gaps Report",
3608
4088
  "",
3609
4089
  `Generated: ${report.generatedAt}`,
3610
4090
  "",
3611
4091
  "## Coverage by Domain",
3612
- "",
3613
- "| Domain | Entries |",
3614
- "| ------ | ------- |"
4092
+ ""
3615
4093
  ];
3616
- for (const domain of report.domains) {
3617
- 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}`, "");
3618
4119
  }
3619
- lines.push("", `## Total Entries: ${report.totalEntries}`, "");
3620
- 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");
3621
4121
  }
3622
4122
  };
3623
- SKIP_DIRS = /* @__PURE__ */ new Set([
4123
+ SKIP_DIRS2 = /* @__PURE__ */ new Set([
3624
4124
  "node_modules",
3625
4125
  "dist",
3626
4126
  "target",
@@ -3679,7 +4179,7 @@ var init_dist = __esm({
3679
4179
  let nodesAdded = 0;
3680
4180
  let nodesUpdated = 0;
3681
4181
  let edgesAdded = 0;
3682
- await fs8.mkdir(outputDir, { recursive: true });
4182
+ await fs9.mkdir(outputDir, { recursive: true });
3683
4183
  const files = await this.findSourceFiles(projectDir);
3684
4184
  const recordsByExtractor = /* @__PURE__ */ new Map();
3685
4185
  for (const ext of this.extractors) {
@@ -3688,17 +4188,17 @@ var init_dist = __esm({
3688
4188
  for (const filePath of files) {
3689
4189
  const language = detectLanguage(filePath);
3690
4190
  if (!language) continue;
3691
- const ext = path9.extname(filePath);
4191
+ const ext = path10.extname(filePath);
3692
4192
  let content;
3693
4193
  try {
3694
- content = await fs8.readFile(filePath, "utf-8");
4194
+ content = await fs9.readFile(filePath, "utf-8");
3695
4195
  } catch (err) {
3696
4196
  errors.push(
3697
4197
  `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
3698
4198
  );
3699
4199
  continue;
3700
4200
  }
3701
- const relativePath = path9.relative(projectDir, filePath).replaceAll("\\", "/");
4201
+ const relativePath = path10.relative(projectDir, filePath).replaceAll("\\", "/");
3702
4202
  for (const extractor2 of this.extractors) {
3703
4203
  if (!extractor2.supportedExtensions.includes(ext)) continue;
3704
4204
  try {
@@ -3735,9 +4235,9 @@ var init_dist = __esm({
3735
4235
  }
3736
4236
  /** Write extraction records to JSONL file. */
3737
4237
  async writeJsonl(records, outputDir, extractorName) {
3738
- const filePath = path9.join(outputDir, `${extractorName}.jsonl`);
4238
+ const filePath = path10.join(outputDir, `${extractorName}.jsonl`);
3739
4239
  const lines = records.map((r) => JSON.stringify(r));
3740
- await fs8.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
4240
+ await fs9.writeFile(filePath, lines.join("\n") + (lines.length > 0 ? "\n" : ""));
3741
4241
  }
3742
4242
  /** Create or update a graph node from an extraction record. */
3743
4243
  persistRecord(record, store) {
@@ -3809,13 +4309,13 @@ var init_dist = __esm({
3809
4309
  const results = [];
3810
4310
  let entries;
3811
4311
  try {
3812
- entries = await fs8.readdir(dir, { withFileTypes: true });
4312
+ entries = await fs9.readdir(dir, { withFileTypes: true });
3813
4313
  } catch {
3814
4314
  return results;
3815
4315
  }
3816
4316
  for (const entry of entries) {
3817
- const fullPath = path9.join(dir, entry.name);
3818
- if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
4317
+ const fullPath = path10.join(dir, entry.name);
4318
+ if (entry.isDirectory() && !SKIP_DIRS2.has(entry.name)) {
3819
4319
  results.push(...await this.findSourceFiles(fullPath));
3820
4320
  } else if (entry.isFile() && detectLanguage(fullPath) !== void 0) {
3821
4321
  results.push(fullPath);
@@ -4926,57 +5426,9 @@ var init_dist = __esm({
4926
5426
  const errors = [];
4927
5427
  for (const imagePath of imagePaths) {
4928
5428
  try {
4929
- const response = await this.provider.analyze({
4930
- prompt: "Analyze this image and provide a structured description of its visual contents.",
4931
- systemPrompt: "You are an image analysis assistant. Describe the image contents, detect UI elements, extract visible text, identify design patterns, and note accessibility concerns.",
4932
- responseSchema: {},
4933
- // Schema handled by provider
4934
- maxTokens: 1e3
4935
- });
4936
- const result = response.result;
4937
- const pathHash = hash(imagePath);
4938
- const annotationId = `img:${pathHash}`;
4939
- store.addNode({
4940
- id: annotationId,
4941
- type: "image_annotation",
4942
- name: result.description.slice(0, 200),
4943
- path: imagePath,
4944
- content: result.description,
4945
- hash: hash(result.description),
4946
- metadata: {
4947
- source: "image-analysis",
4948
- detectedElements: result.detectedElements,
4949
- extractedText: result.extractedText,
4950
- designPatterns: result.designPatterns,
4951
- accessibilityNotes: result.accessibilityNotes,
4952
- model: response.model
4953
- }
4954
- });
4955
- nodesAdded++;
4956
- const fileNodes = store.findNodes({ type: "file" });
4957
- const fileNode = fileNodes.find((n) => n.path === imagePath);
4958
- if (fileNode) {
4959
- store.addEdge({ from: annotationId, to: fileNode.id, type: "annotates" });
4960
- edgesAdded++;
4961
- }
4962
- for (const pattern of result.designPatterns) {
4963
- const conceptId = `img:concept:${hash(pattern + imagePath)}`;
4964
- store.addNode({
4965
- id: conceptId,
4966
- type: "business_concept",
4967
- name: pattern,
4968
- content: `Design pattern detected in ${imagePath}: ${pattern}`,
4969
- hash: hash(pattern),
4970
- metadata: {
4971
- source: "image-analysis",
4972
- sourceImage: imagePath,
4973
- domain: "design"
4974
- }
4975
- });
4976
- nodesAdded++;
4977
- store.addEdge({ from: annotationId, to: conceptId, type: "contains" });
4978
- edgesAdded++;
4979
- }
5429
+ const counts = await this.processImage(store, imagePath);
5430
+ nodesAdded += counts.nodes;
5431
+ edgesAdded += counts.edges;
4980
5432
  } catch (err) {
4981
5433
  errors.push(
4982
5434
  `Image analysis failed for ${imagePath}: ${err instanceof Error ? err.message : String(err)}`
@@ -4992,6 +5444,68 @@ var init_dist = __esm({
4992
5444
  durationMs: Date.now() - start
4993
5445
  };
4994
5446
  }
5447
+ /** Analyze a single image and add annotation + concept nodes to the store. */
5448
+ async processImage(store, imagePath) {
5449
+ const response = await this.provider.analyze({
5450
+ prompt: "Analyze this image and provide a structured description of its visual contents.",
5451
+ systemPrompt: "You are an image analysis assistant. Describe the image contents, detect UI elements, extract visible text, identify design patterns, and note accessibility concerns.",
5452
+ responseSchema: {},
5453
+ // Schema handled by provider
5454
+ maxTokens: 1e3
5455
+ });
5456
+ const result = response.result;
5457
+ const annotationId = `img:${hash(imagePath)}`;
5458
+ let nodes = 0;
5459
+ let edges = 0;
5460
+ store.addNode({
5461
+ id: annotationId,
5462
+ type: "image_annotation",
5463
+ name: result.description.slice(0, 200),
5464
+ path: imagePath,
5465
+ content: result.description,
5466
+ hash: hash(result.description),
5467
+ metadata: {
5468
+ source: "image-analysis",
5469
+ detectedElements: result.detectedElements,
5470
+ extractedText: result.extractedText,
5471
+ designPatterns: result.designPatterns,
5472
+ accessibilityNotes: result.accessibilityNotes,
5473
+ model: response.model
5474
+ }
5475
+ });
5476
+ nodes++;
5477
+ edges += this.linkToFileNode(store, annotationId, imagePath);
5478
+ for (const pattern of result.designPatterns) {
5479
+ edges += this.addDesignPatternConcept(store, annotationId, imagePath, pattern);
5480
+ nodes++;
5481
+ }
5482
+ return { nodes, edges };
5483
+ }
5484
+ /** Link annotation to its source file node if it exists. Returns 1 if linked, 0 otherwise. */
5485
+ linkToFileNode(store, annotationId, imagePath) {
5486
+ const fileNode = store.findNodes({ type: "file" }).find((n) => n.path === imagePath);
5487
+ if (!fileNode) return 0;
5488
+ store.addEdge({ from: annotationId, to: fileNode.id, type: "annotates" });
5489
+ return 1;
5490
+ }
5491
+ /** Create a business_concept node for a design pattern and link it. Returns edges added. */
5492
+ addDesignPatternConcept(store, annotationId, imagePath, pattern) {
5493
+ const conceptId = `img:concept:${hash(pattern + imagePath)}`;
5494
+ store.addNode({
5495
+ id: conceptId,
5496
+ type: "business_concept",
5497
+ name: pattern,
5498
+ content: `Design pattern detected in ${imagePath}: ${pattern}`,
5499
+ hash: hash(pattern),
5500
+ metadata: {
5501
+ source: "image-analysis",
5502
+ sourceImage: imagePath,
5503
+ domain: "design"
5504
+ }
5505
+ });
5506
+ store.addEdge({ from: annotationId, to: conceptId, type: "contains" });
5507
+ return 1;
5508
+ }
4995
5509
  };
4996
5510
  KNOWLEDGE_NODE_TYPES = [
4997
5511
  "business_fact",
@@ -5000,89 +5514,35 @@ var init_dist = __esm({
5000
5514
  "business_term",
5001
5515
  "business_concept",
5002
5516
  "business_metric",
5517
+ "decision",
5003
5518
  "design_token",
5004
5519
  "design_constraint",
5005
5520
  "aesthetic_intent",
5006
5521
  "image_annotation"
5007
5522
  ];
5008
5523
  SIMILARITY_THRESHOLD = 0.8;
5524
+ SEVERITY_MAP = {
5525
+ value_mismatch: "critical",
5526
+ definition_conflict: "high",
5527
+ status_divergence: "medium",
5528
+ temporal_conflict: "medium"
5529
+ };
5009
5530
  ContradictionDetector = class {
5010
5531
  detect(store) {
5011
5532
  const nodes = KNOWLEDGE_NODE_TYPES.flatMap((t) => store.findNodes({ type: t }));
5012
- const byName = /* @__PURE__ */ new Map();
5013
- for (const node of nodes) {
5014
- const key = node.name.toLowerCase().trim();
5015
- const group = byName.get(key) ?? [];
5016
- group.push(node);
5017
- byName.set(key, group);
5018
- }
5019
- const contradictions = [];
5020
- const sourcePairCounts = {};
5021
- const seen = /* @__PURE__ */ new Set();
5022
- const addContradiction = (a, b, similarity) => {
5023
- const sourceA = a.metadata.source ?? "unknown";
5024
- const sourceB = b.metadata.source ?? "unknown";
5025
- if (sourceA === sourceB) return;
5026
- const hashA = a.hash ?? a.id;
5027
- const hashB = b.hash ?? b.id;
5028
- if (hashA === hashB) return;
5029
- const pairId = [a.id, b.id].sort().join(":");
5030
- if (seen.has(pairId)) return;
5031
- seen.add(pairId);
5032
- const conflictType = classifyConflict(a, b);
5033
- const severity = conflictType === "value_mismatch" ? "critical" : conflictType === "definition_conflict" ? "high" : "medium";
5034
- const pairKey = [sourceA, sourceB].sort().join("\u2194");
5035
- sourcePairCounts[pairKey] = (sourcePairCounts[pairKey] ?? 0) + 1;
5036
- contradictions.push({
5037
- id: `contradiction:${a.id}:${b.id}`,
5038
- entityA: {
5039
- nodeId: a.id,
5040
- source: sourceA,
5041
- name: a.name,
5042
- content: a.content ?? "",
5043
- lastModified: a.lastModified
5044
- },
5045
- entityB: {
5046
- nodeId: b.id,
5047
- source: sourceB,
5048
- name: b.name,
5049
- content: b.content ?? "",
5050
- lastModified: b.lastModified
5051
- },
5052
- similarity,
5053
- conflictType,
5054
- severity,
5055
- description: `"${a.name}" has conflicting definitions from ${sourceA} and ${sourceB}`
5056
- });
5533
+ const byName = groupByName(nodes);
5534
+ const acc = {
5535
+ contradictions: [],
5536
+ sourcePairCounts: {},
5537
+ seen: /* @__PURE__ */ new Set()
5538
+ };
5539
+ collectExactMatches(byName, acc);
5540
+ collectFuzzyMatches(byName, acc);
5541
+ return {
5542
+ contradictions: acc.contradictions,
5543
+ sourcePairCounts: acc.sourcePairCounts,
5544
+ totalChecked: nodes.length
5057
5545
  };
5058
- for (const [, group] of byName) {
5059
- if (group.length < 2) continue;
5060
- for (let i = 0; i < group.length; i++) {
5061
- for (let j = i + 1; j < group.length; j++) {
5062
- addContradiction(group[i], group[j], 1);
5063
- }
5064
- }
5065
- }
5066
- const keys = [...byName.keys()];
5067
- for (let i = 0; i < keys.length; i++) {
5068
- for (let j = i + 1; j < keys.length; j++) {
5069
- const keyA = keys[i];
5070
- const keyB = keys[j];
5071
- const maxLen = Math.max(keyA.length, keyB.length);
5072
- const minLen = Math.min(keyA.length, keyB.length);
5073
- if (minLen / maxLen < SIMILARITY_THRESHOLD) continue;
5074
- const ratio = levenshteinRatio(keyA, keyB);
5075
- if (ratio < SIMILARITY_THRESHOLD) continue;
5076
- const groupA = byName.get(keyA);
5077
- const groupB = byName.get(keyB);
5078
- for (const a of groupA) {
5079
- for (const b of groupB) {
5080
- addContradiction(a, b, ratio);
5081
- }
5082
- }
5083
- }
5084
- }
5085
- return { contradictions, sourcePairCounts, totalChecked: nodes.length };
5086
5546
  }
5087
5547
  };
5088
5548
  KNOWLEDGE_TYPES = KNOWLEDGE_NODE_TYPES;
@@ -5107,61 +5567,15 @@ var init_dist = __esm({
5107
5567
  CoverageScorer = class {
5108
5568
  score(store) {
5109
5569
  const knowledgeNodes = KNOWLEDGE_TYPES.flatMap((t) => store.findNodes({ type: t }));
5110
- const domainMap = /* @__PURE__ */ new Map();
5111
- for (const node of knowledgeNodes) {
5112
- const domain = node.metadata.domain ?? "unclassified";
5113
- const group = domainMap.get(domain) ?? [];
5114
- group.push(node);
5115
- domainMap.set(domain, group);
5116
- }
5570
+ const domainMap = groupByDomain(knowledgeNodes, () => "unclassified");
5117
5571
  const codeNodes = CODE_TYPES2.flatMap((t) => store.findNodes({ type: t }));
5118
- const codeDomains = /* @__PURE__ */ new Map();
5119
- for (const node of codeNodes) {
5120
- const domain = node.metadata.domain ?? this.domainFromPath(node.path);
5121
- const group = codeDomains.get(domain) ?? [];
5122
- group.push(node);
5123
- codeDomains.set(domain, group);
5124
- }
5572
+ const codeDomains = groupByDomain(codeNodes, (n) => this.domainFromPath(n.path));
5125
5573
  const allDomains = /* @__PURE__ */ new Set([...domainMap.keys(), ...codeDomains.keys()]);
5126
5574
  const domains = [];
5127
5575
  for (const domain of allDomains) {
5128
- const knEntries = domainMap.get(domain) ?? [];
5129
- const codeEntries = codeDomains.get(domain) ?? [];
5130
- const linkedIds = /* @__PURE__ */ new Set();
5131
- for (const codeNode of codeEntries) {
5132
- for (const edgeType of KNOWLEDGE_EDGE_TYPES) {
5133
- const edges = store.getEdges({ to: codeNode.id, type: edgeType });
5134
- if (edges.length > 0) {
5135
- linkedIds.add(codeNode.id);
5136
- break;
5137
- }
5138
- }
5139
- }
5140
- const sourceBreakdown = {};
5141
- for (const kn of knEntries) {
5142
- const src = kn.metadata.source ?? "unknown";
5143
- sourceBreakdown[src] = (sourceBreakdown[src] ?? 0) + 1;
5144
- }
5145
- const codeEntities = codeEntries.length;
5146
- const linkedEntities = linkedIds.size;
5147
- const knowledgeEntries = knEntries.length;
5148
- const uniqueSources = Object.keys(sourceBreakdown).length;
5149
- const codeCoverageComponent = codeEntities > 0 ? linkedEntities / codeEntities * 60 : 0;
5150
- const knowledgeDepthComponent = Math.min(knowledgeEntries / 10, 1) * 20;
5151
- const sourceDiversityComponent = Math.min(uniqueSources / 3, 1) * 20;
5152
- const score = Math.round(
5153
- codeCoverageComponent + knowledgeDepthComponent + sourceDiversityComponent
5576
+ domains.push(
5577
+ scoreDomain(domain, domainMap.get(domain) ?? [], codeDomains.get(domain) ?? [], store)
5154
5578
  );
5155
- domains.push({
5156
- domain,
5157
- score,
5158
- knowledgeEntries,
5159
- codeEntities,
5160
- linkedEntities,
5161
- unlinkedEntities: codeEntities - linkedEntities,
5162
- sourceBreakdown,
5163
- grade: toGrade(score)
5164
- });
5165
5579
  }
5166
5580
  const overallScore = domains.length > 0 ? Math.round(domains.reduce((sum, d) => sum + d.score, 0) / domains.length) : 0;
5167
5581
  return {
@@ -5181,7 +5595,161 @@ var init_dist = __esm({
5181
5595
  return parts[0] ?? "unclassified";
5182
5596
  }
5183
5597
  };
5184
- 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 = [
5185
5753
  "business_concept",
5186
5754
  "business_rule",
5187
5755
  "business_process",
@@ -5190,7 +5758,8 @@ var init_dist = __esm({
5190
5758
  "business_fact"
5191
5759
  ];
5192
5760
  SNAPSHOT_NODE_TYPES = [
5193
- ...BUSINESS_NODE_TYPES,
5761
+ ...BUSINESS_NODE_TYPES2,
5762
+ "decision",
5194
5763
  "design_token",
5195
5764
  "design_constraint",
5196
5765
  "aesthetic_intent",
@@ -5202,51 +5771,98 @@ var init_dist = __esm({
5202
5771
  }
5203
5772
  store;
5204
5773
  async run(options) {
5205
- const maxIterations = options.maxIterations ?? 5;
5206
5774
  const remediations = [];
5207
5775
  const preSnapshot = this.buildSnapshot(options.domain);
5208
5776
  const extraction = await this.extract(options);
5209
5777
  const postSnapshot = this.buildSnapshot(options.domain);
5210
5778
  let driftResult = this.reconcile(preSnapshot, postSnapshot);
5211
- const contradictionDetector = new ContradictionDetector();
5212
- const contradictions = contradictionDetector.detect(this.store);
5779
+ const contradictions = new ContradictionDetector().detect(this.store);
5213
5780
  let gapReport = await this.detect(options);
5214
- const coverageScorer = new CoverageScorer();
5215
- const coverage = coverageScorer.score(this.store);
5781
+ const coverage = new CoverageScorer().score(this.store);
5782
+ let materialization;
5216
5783
  let iterations = 1;
5217
5784
  if (options.fix) {
5218
- let previousFindingCount = driftResult.findings.length;
5219
- while (iterations < maxIterations) {
5220
- if (driftResult.findings.length === 0) break;
5221
- this.remediate(driftResult, remediations, options);
5222
- const rePreSnapshot = this.buildSnapshot(options.domain);
5223
- await this.extract(options);
5224
- const rePostSnapshot = this.buildSnapshot(options.domain);
5225
- driftResult = this.reconcile(rePreSnapshot, rePostSnapshot);
5226
- gapReport = await this.detect(options);
5227
- iterations++;
5228
- const newFindingCount = driftResult.findings.length;
5229
- if (newFindingCount >= previousFindingCount) break;
5230
- previousFindingCount = newFindingCount;
5231
- }
5785
+ const loopResult = await this.runRemediationLoop(
5786
+ options,
5787
+ driftResult,
5788
+ gapReport,
5789
+ remediations
5790
+ );
5791
+ iterations = loopResult.iterations;
5792
+ materialization = loopResult.materialization;
5793
+ }
5794
+ if (options.fix && iterations > 1) {
5795
+ const finalSnapshot = this.buildSnapshot(options.domain);
5796
+ driftResult = this.reconcile(preSnapshot, finalSnapshot);
5797
+ gapReport = await this.detect(options);
5232
5798
  }
5233
5799
  await this.stageNewFindings(driftResult, options);
5800
+ return this.buildResult(
5801
+ driftResult,
5802
+ iterations,
5803
+ extraction,
5804
+ gapReport,
5805
+ remediations,
5806
+ contradictions,
5807
+ coverage,
5808
+ materialization
5809
+ );
5810
+ }
5811
+ /** Run the remediation convergence loop; returns iteration count and accumulated materialization. */
5812
+ async runRemediationLoop(options, driftResult, gapReport, remediations) {
5813
+ const maxIterations = options.maxIterations ?? 5;
5814
+ let iterations = 1;
5815
+ let currentDrift = driftResult;
5816
+ let currentGapReport = gapReport;
5817
+ let previousIssueCount = currentDrift.findings.length + currentGapReport.totalGaps;
5818
+ let accumulatedMaterialization;
5819
+ while (iterations < maxIterations) {
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);
5833
+ await this.extract(options);
5834
+ const postSnapshot = this.buildSnapshot(options.domain);
5835
+ currentDrift = this.reconcile(preSnapshot, postSnapshot);
5836
+ currentGapReport = await this.detect(options);
5837
+ iterations++;
5838
+ const currentIssueCount = currentDrift.findings.length + currentGapReport.totalGaps;
5839
+ if (currentIssueCount >= previousIssueCount) break;
5840
+ previousIssueCount = currentIssueCount;
5841
+ }
5842
+ return {
5843
+ iterations,
5844
+ ...accumulatedMaterialization ? { materialization: accumulatedMaterialization } : {}
5845
+ };
5846
+ }
5847
+ /** Assemble the final pipeline result. */
5848
+ buildResult(driftResult, iterations, extraction, gaps, remediations, contradictions, coverage, materialization) {
5234
5849
  return {
5235
5850
  verdict: this.computeVerdict(driftResult),
5236
5851
  driftScore: driftResult.driftScore,
5237
5852
  iterations,
5238
5853
  findings: driftResult.summary,
5239
5854
  extraction,
5240
- gaps: gapReport,
5855
+ gaps,
5241
5856
  remediations,
5242
5857
  contradictions,
5243
- coverage
5858
+ coverage,
5859
+ ...materialization ? { materialization } : {}
5244
5860
  };
5245
5861
  }
5246
5862
  // ── Phase 1: EXTRACT ──────────────────────────────────────────────────────
5247
5863
  async extract(options) {
5248
- const extractedDir = path10.join(options.projectDir, ".harness", "knowledge", "extracted");
5249
- await fs9.mkdir(extractedDir, { recursive: true });
5864
+ const extractedDir = path12.join(options.projectDir, ".harness", "knowledge", "extracted");
5865
+ await fs11.mkdir(extractedDir, { recursive: true });
5250
5866
  const runner = createExtractionRunner();
5251
5867
  const extractionResult = await runner.run(options.projectDir, this.store, extractedDir);
5252
5868
  const diagramParser = new DiagramParser(this.store);
@@ -5260,7 +5876,7 @@ var init_dist = __esm({
5260
5876
  const imageResult = await imageExtractor.analyze(this.store, imagePaths);
5261
5877
  imageCount = imageResult.nodesAdded;
5262
5878
  }
5263
- const knowledgeDir = path10.join(options.projectDir, "docs", "knowledge");
5879
+ const knowledgeDir = path12.join(options.projectDir, "docs", "knowledge");
5264
5880
  const bkIngestor = new BusinessKnowledgeIngestor(this.store);
5265
5881
  let bkResult;
5266
5882
  try {
@@ -5275,6 +5891,21 @@ var init_dist = __esm({
5275
5891
  durationMs: 0
5276
5892
  };
5277
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
+ }
5278
5909
  const linker = new KnowledgeLinker(this.store, extractedDir);
5279
5910
  const linkResult = await linker.link();
5280
5911
  return {
@@ -5282,6 +5913,7 @@ var init_dist = __esm({
5282
5913
  diagrams: diagramResult.nodesAdded,
5283
5914
  linkerFacts: linkResult.factsCreated,
5284
5915
  businessKnowledge: bkResult.nodesAdded,
5916
+ decisions: decisionResult.nodesAdded,
5285
5917
  images: imageCount
5286
5918
  };
5287
5919
  }
@@ -5308,14 +5940,14 @@ var init_dist = __esm({
5308
5940
  }
5309
5941
  // ── Phase 3: DETECT ───────────────────────────────────────────────────────
5310
5942
  async detect(options) {
5311
- const knowledgeDir = path10.join(options.projectDir, "docs", "knowledge");
5943
+ const knowledgeDir = path12.join(options.projectDir, "docs", "knowledge");
5312
5944
  const aggregator = new KnowledgeStagingAggregator(options.projectDir);
5313
- const gapReport = await aggregator.generateGapReport(knowledgeDir);
5945
+ const gapReport = await aggregator.generateGapReport(knowledgeDir, this.store);
5314
5946
  await aggregator.writeGapReport(gapReport);
5315
5947
  return gapReport;
5316
5948
  }
5317
5949
  // ── Phase 4: REMEDIATE ────────────────────────────────────────────────────
5318
- remediate(driftResult, remediations, options) {
5950
+ async remediate(driftResult, remediations, options, gapReport) {
5319
5951
  for (const finding of driftResult.findings) {
5320
5952
  switch (finding.classification) {
5321
5953
  case "stale":
@@ -5333,6 +5965,22 @@ var init_dist = __esm({
5333
5965
  break;
5334
5966
  }
5335
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;
5336
5984
  }
5337
5985
  async stageNewFindings(driftResult, options) {
5338
5986
  const newFindings = driftResult.findings.filter((f) => f.classification === "new");
@@ -5365,7 +6013,7 @@ var init_dist = __esm({
5365
6013
  return "fail";
5366
6014
  }
5367
6015
  };
5368
- CODE_NODE_TYPES4 = ["file", "function", "class", "method", "interface", "variable"];
6016
+ CODE_NODE_TYPES5 = ["file", "function", "class", "method", "interface", "variable"];
5369
6017
  RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
5370
6018
  SANITIZE_RULES = [
5371
6019
  // Strip XML/HTML-like instruction tags that could be interpreted as system prompts
@@ -5392,7 +6040,7 @@ var init_dist = __esm({
5392
6040
  SyncManager = class {
5393
6041
  constructor(store, graphDir) {
5394
6042
  this.store = store;
5395
- this.metadataPath = path11.join(graphDir, "sync-metadata.json");
6043
+ this.metadataPath = path13.join(graphDir, "sync-metadata.json");
5396
6044
  }
5397
6045
  store;
5398
6046
  registrations = /* @__PURE__ */ new Map();
@@ -5457,15 +6105,15 @@ var init_dist = __esm({
5457
6105
  }
5458
6106
  async loadMetadata() {
5459
6107
  try {
5460
- const raw = await fs10.readFile(this.metadataPath, "utf-8");
6108
+ const raw = await fs12.readFile(this.metadataPath, "utf-8");
5461
6109
  return JSON.parse(raw);
5462
6110
  } catch {
5463
6111
  return { connectors: {} };
5464
6112
  }
5465
6113
  }
5466
6114
  async saveMetadata(metadata) {
5467
- await fs10.mkdir(path11.dirname(this.metadataPath), { recursive: true });
5468
- 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");
5469
6117
  }
5470
6118
  };
5471
6119
  SUMMARIZE_PROMPT = `Summarize the following content to fit within the specified length.
@@ -5503,6 +6151,14 @@ Content to summarize:
5503
6151
  start
5504
6152
  );
5505
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
+ }
5506
6162
  const jql = buildJql(config);
5507
6163
  const headers = { Authorization: `Basic ${apiKey}`, "Content-Type": "application/json" };
5508
6164
  try {
@@ -5756,6 +6412,14 @@ ${replyLines.join("\n")}`;
5756
6412
  }
5757
6413
  const baseUrlEnv = config.baseUrlEnv ?? "CONFLUENCE_BASE_URL";
5758
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
+ }
5759
6423
  const spaceKey = config.spaceKey ?? "";
5760
6424
  const counts = await this.fetchAllPagesHandled(
5761
6425
  store,
@@ -5860,7 +6524,7 @@ ${replyLines.join("\n")}`;
5860
6524
  const apiKeyEnv = config.apiKeyEnv ?? "GITHUB_TOKEN";
5861
6525
  const apiKey = process.env[apiKeyEnv];
5862
6526
  if (!apiKey) {
5863
- return emptyResult2(
6527
+ return emptyResult5(
5864
6528
  [`Missing API key: environment variable "${apiKeyEnv}" is not set`],
5865
6529
  start
5866
6530
  );
@@ -5927,35 +6591,9 @@ ${replyLines.join("\n")}`;
5927
6591
  }
5928
6592
  async ingest(store, config) {
5929
6593
  const start = Date.now();
5930
- const apiKeyEnv = config.apiKeyEnv ?? "FIGMA_API_KEY";
5931
- const apiKey = process.env[apiKeyEnv];
5932
- if (!apiKey) {
5933
- return buildIngestResult2(
5934
- 0,
5935
- 0,
5936
- [`Missing API key: environment variable "${apiKeyEnv}" is not set`],
5937
- start
5938
- );
5939
- }
5940
- const baseUrlEnv = config.baseUrlEnv ?? "FIGMA_BASE_URL";
5941
- const baseUrl = process.env[baseUrlEnv] ?? "https://api.figma.com";
5942
- try {
5943
- const parsed = new URL(baseUrl);
5944
- if (parsed.protocol !== "https:" || !parsed.hostname.endsWith("api.figma.com")) {
5945
- return buildIngestResult2(
5946
- 0,
5947
- 0,
5948
- [`Invalid ${baseUrlEnv}: must be an HTTPS URL on api.figma.com`],
5949
- start
5950
- );
5951
- }
5952
- } catch {
5953
- return buildIngestResult2(0, 0, [`Invalid ${baseUrlEnv}: not a valid URL`], start);
5954
- }
5955
- const fileIds = config.fileIds;
5956
- if (!fileIds || fileIds.length === 0) {
5957
- return buildIngestResult2(0, 0, ["No fileIds provided in connector config"], start);
5958
- }
6594
+ const validated = validateFigmaConfig(config);
6595
+ if ("error" in validated) return buildIngestResult2(0, 0, [validated.error], start);
6596
+ const { apiKey, baseUrl, fileIds } = validated;
5959
6597
  const headers = { "X-FIGMA-TOKEN": apiKey };
5960
6598
  const maxLen = config.maxContentLength ?? 4e3;
5961
6599
  let nodesAdded = 0;
@@ -5975,6 +6613,17 @@ ${replyLines.join("\n")}`;
5975
6613
  return buildIngestResult2(nodesAdded, edgesAdded, errors, start);
5976
6614
  }
5977
6615
  async processFile(store, baseUrl, fileId, headers, maxLen) {
6616
+ let nodesAdded = 0;
6617
+ let edgesAdded = 0;
6618
+ const styleCounts = await this.ingestStyles(store, baseUrl, fileId, headers, maxLen);
6619
+ nodesAdded += styleCounts.nodesAdded;
6620
+ edgesAdded += styleCounts.edgesAdded;
6621
+ const componentCounts = await this.ingestComponents(store, baseUrl, fileId, headers, maxLen);
6622
+ nodesAdded += componentCounts.nodesAdded;
6623
+ edgesAdded += componentCounts.edgesAdded;
6624
+ return { nodesAdded, edgesAdded };
6625
+ }
6626
+ async ingestStyles(store, baseUrl, fileId, headers, maxLen) {
5978
6627
  let nodesAdded = 0;
5979
6628
  let edgesAdded = 0;
5980
6629
  const stylesUrl = `${baseUrl}/v1/files/${fileId}/styles`;
@@ -5982,90 +6631,112 @@ ${replyLines.join("\n")}`;
5982
6631
  if (!stylesResponse.ok) throw new Error(`Styles request failed for file ${fileId}`);
5983
6632
  const stylesData = await stylesResponse.json();
5984
6633
  for (const style of stylesData.meta.styles) {
5985
- const nodeId = `figma:token:${style.key}`;
5986
- const condensed = await condenseContent(sanitizeExternalText(style.description || "", 2e3), {
5987
- maxLength: maxLen
5988
- });
5989
- const metadata = {
5990
- source: "figma",
5991
- key: style.key,
5992
- styleType: style.style_type,
5993
- fileId
5994
- };
5995
- if (condensed.method !== "passthrough") {
5996
- metadata.condensed = condensed.method;
5997
- metadata.originalLength = condensed.originalLength;
5998
- }
5999
- store.addNode({
6000
- id: nodeId,
6001
- type: "design_token",
6002
- name: sanitizeExternalText(style.name, 500),
6003
- content: condensed.content,
6004
- metadata
6005
- });
6006
- nodesAdded++;
6007
- const searchText = sanitizeExternalText([style.name, style.description].join(" "));
6008
- edgesAdded += linkToCode(store, searchText, nodeId, "references");
6634
+ const counts = await this.processStyle(store, style, fileId, maxLen);
6635
+ nodesAdded += counts.nodesAdded;
6636
+ edgesAdded += counts.edgesAdded;
6009
6637
  }
6638
+ return { nodesAdded, edgesAdded };
6639
+ }
6640
+ async processStyle(store, style, fileId, maxLen) {
6641
+ const nodeId = `figma:token:${style.key}`;
6642
+ const condensed = await condenseContent(sanitizeExternalText(style.description || "", 2e3), {
6643
+ maxLength: maxLen
6644
+ });
6645
+ const metadata = buildNodeMetadata(condensed, {
6646
+ source: "figma",
6647
+ key: style.key,
6648
+ styleType: style.style_type,
6649
+ fileId
6650
+ });
6651
+ store.addNode({
6652
+ id: nodeId,
6653
+ type: "design_token",
6654
+ name: sanitizeExternalText(style.name, 500),
6655
+ content: condensed.content,
6656
+ metadata
6657
+ });
6658
+ const searchText = sanitizeExternalText([style.name, style.description].join(" "));
6659
+ const edgesAdded = linkToCode(store, searchText, nodeId, "references");
6660
+ return { nodesAdded: 1, edgesAdded };
6661
+ }
6662
+ async ingestComponents(store, baseUrl, fileId, headers, maxLen) {
6663
+ let nodesAdded = 0;
6664
+ let edgesAdded = 0;
6010
6665
  const componentsUrl = `${baseUrl}/v1/files/${fileId}/components`;
6011
6666
  const componentsResponse = await this.httpClient(componentsUrl, { headers });
6012
6667
  if (!componentsResponse.ok) throw new Error(`Components request failed for file ${fileId}`);
6013
6668
  const componentsData = await componentsResponse.json();
6014
6669
  for (const component of componentsData.meta.components) {
6015
- const description = component.description || "";
6016
- if (description) {
6017
- const intentId = `figma:intent:${component.key}`;
6018
- const condensed = await condenseContent(sanitizeExternalText(description, 2e3), {
6019
- maxLength: maxLen
6020
- });
6021
- const metadata = {
6022
- source: "figma",
6023
- key: component.key,
6024
- fileId
6025
- };
6026
- if (condensed.method !== "passthrough") {
6027
- metadata.condensed = condensed.method;
6028
- metadata.originalLength = condensed.originalLength;
6029
- }
6030
- store.addNode({
6031
- id: intentId,
6032
- type: "aesthetic_intent",
6033
- name: sanitizeExternalText(component.name, 500),
6034
- content: condensed.content,
6035
- metadata
6036
- });
6037
- nodesAdded++;
6038
- const searchText = sanitizeExternalText([component.name, description].join(" "));
6039
- edgesAdded += linkToCode(store, searchText, intentId, "references");
6040
- }
6041
- if (description && hasConstraintKeyword(description)) {
6042
- const constraintId = `figma:constraint:${component.key}`;
6043
- const condensed = await condenseContent(sanitizeExternalText(description, 2e3), {
6044
- maxLength: maxLen
6045
- });
6046
- const metadata = {
6047
- source: "figma",
6048
- key: component.key,
6049
- fileId
6050
- };
6051
- if (condensed.method !== "passthrough") {
6052
- metadata.condensed = condensed.method;
6053
- metadata.originalLength = condensed.originalLength;
6054
- }
6055
- store.addNode({
6056
- id: constraintId,
6057
- type: "design_constraint",
6058
- name: sanitizeExternalText(component.name, 500),
6059
- content: condensed.content,
6060
- metadata
6061
- });
6062
- nodesAdded++;
6063
- const searchText = sanitizeExternalText([component.name, description].join(" "));
6064
- edgesAdded += linkToCode(store, searchText, constraintId, "references");
6065
- }
6670
+ const counts = await this.processComponent(store, component, fileId, maxLen);
6671
+ nodesAdded += counts.nodesAdded;
6672
+ edgesAdded += counts.edgesAdded;
6673
+ }
6674
+ return { nodesAdded, edgesAdded };
6675
+ }
6676
+ async processComponent(store, component, fileId, maxLen) {
6677
+ let nodesAdded = 0;
6678
+ let edgesAdded = 0;
6679
+ const description = component.description || "";
6680
+ if (description) {
6681
+ const counts = await this.addComponentIntent(store, component, description, fileId, maxLen);
6682
+ nodesAdded += counts.nodesAdded;
6683
+ edgesAdded += counts.edgesAdded;
6684
+ }
6685
+ if (description && hasConstraintKeyword(description)) {
6686
+ const counts = await this.addComponentConstraint(
6687
+ store,
6688
+ component,
6689
+ description,
6690
+ fileId,
6691
+ maxLen
6692
+ );
6693
+ nodesAdded += counts.nodesAdded;
6694
+ edgesAdded += counts.edgesAdded;
6066
6695
  }
6067
6696
  return { nodesAdded, edgesAdded };
6068
6697
  }
6698
+ async addComponentIntent(store, component, description, fileId, maxLen) {
6699
+ const intentId = `figma:intent:${component.key}`;
6700
+ const condensed = await condenseContent(sanitizeExternalText(description, 2e3), {
6701
+ maxLength: maxLen
6702
+ });
6703
+ const metadata = buildNodeMetadata(condensed, {
6704
+ source: "figma",
6705
+ key: component.key,
6706
+ fileId
6707
+ });
6708
+ store.addNode({
6709
+ id: intentId,
6710
+ type: "aesthetic_intent",
6711
+ name: sanitizeExternalText(component.name, 500),
6712
+ content: condensed.content,
6713
+ metadata
6714
+ });
6715
+ const searchText = sanitizeExternalText([component.name, description].join(" "));
6716
+ const edgesAdded = linkToCode(store, searchText, intentId, "references");
6717
+ return { nodesAdded: 1, edgesAdded };
6718
+ }
6719
+ async addComponentConstraint(store, component, description, fileId, maxLen) {
6720
+ const constraintId = `figma:constraint:${component.key}`;
6721
+ const condensed = await condenseContent(sanitizeExternalText(description, 2e3), {
6722
+ maxLength: maxLen
6723
+ });
6724
+ const metadata = buildNodeMetadata(condensed, {
6725
+ source: "figma",
6726
+ key: component.key,
6727
+ fileId
6728
+ });
6729
+ store.addNode({
6730
+ id: constraintId,
6731
+ type: "design_constraint",
6732
+ name: sanitizeExternalText(component.name, 500),
6733
+ content: condensed.content,
6734
+ metadata
6735
+ });
6736
+ const searchText = sanitizeExternalText([component.name, description].join(" "));
6737
+ const edgesAdded = linkToCode(store, searchText, constraintId, "references");
6738
+ return { nodesAdded: 1, edgesAdded };
6739
+ }
6069
6740
  };
6070
6741
  MIN_CONTENT_LENGTH = 10;
6071
6742
  CONCEPT_ITEM_TYPES = /* @__PURE__ */ new Set(["sticky_note", "text"]);
@@ -6078,36 +6749,9 @@ ${replyLines.join("\n")}`;
6078
6749
  }
6079
6750
  async ingest(store, config) {
6080
6751
  const start = Date.now();
6081
- const apiKeyEnv = config.apiKeyEnv ?? "MIRO_API_KEY";
6082
- const apiKey = process.env[apiKeyEnv];
6083
- if (!apiKey) {
6084
- return buildIngestResult3(
6085
- 0,
6086
- 0,
6087
- [`Missing API key: environment variable "${apiKeyEnv}" is not set`],
6088
- start
6089
- );
6090
- }
6091
- const baseUrlEnv = config.baseUrlEnv ?? "MIRO_BASE_URL";
6092
- const baseUrl = process.env[baseUrlEnv] ?? "https://api.miro.com";
6093
- try {
6094
- const parsed = new URL(baseUrl);
6095
- if (parsed.protocol !== "https:" || parsed.hostname !== "api.miro.com") {
6096
- return buildIngestResult3(
6097
- 0,
6098
- 0,
6099
- [`Invalid ${baseUrlEnv}: must be an HTTPS URL on api.miro.com`],
6100
- start
6101
- );
6102
- }
6103
- } catch {
6104
- return buildIngestResult3(0, 0, [`Invalid ${baseUrlEnv}: not a valid URL`], start);
6105
- }
6106
- const boardIds = config.boardIds;
6107
- if (!boardIds || boardIds.length === 0) {
6108
- return buildIngestResult3(0, 0, ["No boardIds provided in config"], start);
6109
- }
6110
- const headers = { Authorization: `Bearer ${apiKey}` };
6752
+ const resolved = resolveConfig(config, start);
6753
+ if (!resolved.ok) return resolved.result;
6754
+ const { baseUrl, boardIds, headers } = resolved;
6111
6755
  let totalNodesAdded = 0;
6112
6756
  let totalEdgesAdded = 0;
6113
6757
  const errors = [];
@@ -6307,7 +6951,7 @@ ${replyLines.join("\n")}`;
6307
6951
  return 0;
6308
6952
  }
6309
6953
  };
6310
- CODE_NODE_TYPES5 = ["file", "function", "class", "method", "interface", "variable"];
6954
+ CODE_NODE_TYPES6 = ["file", "function", "class", "method", "interface", "variable"];
6311
6955
  GraphEntropyAdapter = class {
6312
6956
  constructor(store) {
6313
6957
  this.store = store;
@@ -6387,7 +7031,7 @@ ${replyLines.join("\n")}`;
6387
7031
  }
6388
7032
  findEntryPoints() {
6389
7033
  const entryPoints = [];
6390
- for (const nodeType of CODE_NODE_TYPES5) {
7034
+ for (const nodeType of CODE_NODE_TYPES6) {
6391
7035
  const nodes = this.store.findNodes({ type: nodeType });
6392
7036
  for (const node of nodes) {
6393
7037
  const isIndexFile = nodeType === "file" && node.name === "index.ts";
@@ -6423,7 +7067,7 @@ ${replyLines.join("\n")}`;
6423
7067
  }
6424
7068
  collectUnreachableNodes(visited) {
6425
7069
  const unreachableNodes = [];
6426
- for (const nodeType of CODE_NODE_TYPES5) {
7070
+ for (const nodeType of CODE_NODE_TYPES6) {
6427
7071
  const nodes = this.store.findNodes({ type: nodeType });
6428
7072
  for (const node of nodes) {
6429
7073
  if (!visited.has(node.id)) {
@@ -7240,9 +7884,9 @@ ${replyLines.join("\n")}`;
7240
7884
  extractPaths(trimmed, add) {
7241
7885
  const consumed = /* @__PURE__ */ new Set();
7242
7886
  for (const match of trimmed.matchAll(FILE_PATH_RE)) {
7243
- const path13 = match[0];
7244
- add(path13);
7245
- consumed.add(path13);
7887
+ const path15 = match[0];
7888
+ add(path15);
7889
+ consumed.add(path15);
7246
7890
  }
7247
7891
  return consumed;
7248
7892
  }
@@ -7312,8 +7956,8 @@ ${replyLines.join("\n")}`;
7312
7956
  if (isPathLike && node.path.includes(raw)) {
7313
7957
  return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
7314
7958
  }
7315
- const basename7 = node.path.split("/").pop() ?? "";
7316
- if (basename7.includes(raw)) {
7959
+ const basename10 = node.path.split("/").pop() ?? "";
7960
+ if (basename10.includes(raw)) {
7317
7961
  return { raw, nodeId: node.id, node, confidence: 0.6, method: "path" };
7318
7962
  }
7319
7963
  if (raw.length >= 4 && node.path.includes(raw)) {
@@ -7390,13 +8034,13 @@ ${replyLines.join("\n")}`;
7390
8034
  const context = Array.isArray(d?.context) ? d.context : [];
7391
8035
  const firstEntity = entities[0];
7392
8036
  const nodeType = firstEntity?.node.type ?? "node";
7393
- const path13 = firstEntity?.node.path ?? "unknown";
8037
+ const path15 = firstEntity?.node.path ?? "unknown";
7394
8038
  let neighborCount = 0;
7395
8039
  const firstContext = context[0];
7396
8040
  if (firstContext && Array.isArray(firstContext.nodes)) {
7397
8041
  neighborCount = firstContext.nodes.length;
7398
8042
  }
7399
- return `**${entityName}** is a ${nodeType} at \`${path13}\`. Connected to ${neighborCount} nodes.`;
8043
+ return `**${entityName}** is a ${nodeType} at \`${path15}\`. Connected to ${neighborCount} nodes.`;
7400
8044
  }
7401
8045
  formatAnomaly(data) {
7402
8046
  const d = data;
@@ -7666,7 +8310,7 @@ ${replyLines.join("\n")}`;
7666
8310
  debug: ["failure", "learning", "function", "method"],
7667
8311
  plan: ["adr", "document", "module", "layer"]
7668
8312
  };
7669
- CODE_NODE_TYPES6 = /* @__PURE__ */ new Set([
8313
+ CODE_NODE_TYPES7 = /* @__PURE__ */ new Set([
7670
8314
  "file",
7671
8315
  "function",
7672
8316
  "class",
@@ -7876,7 +8520,7 @@ ${replyLines.join("\n")}`;
7876
8520
  */
7877
8521
  checkCoverage() {
7878
8522
  const codeNodes = [];
7879
- for (const type of CODE_NODE_TYPES6) {
8523
+ for (const type of CODE_NODE_TYPES7) {
7880
8524
  codeNodes.push(...this.store.findNodes({ type }));
7881
8525
  }
7882
8526
  const documented = [];
@@ -8021,8 +8665,8 @@ ${replyLines.join("\n")}`;
8021
8665
  async ingestAll(designDir) {
8022
8666
  const start = Date.now();
8023
8667
  const [tokensResult, intentResult] = await Promise.all([
8024
- this.ingestTokens(path12.join(designDir, "tokens.json")),
8025
- this.ingestDesignIntent(path12.join(designDir, "DESIGN.md"))
8668
+ this.ingestTokens(path14.join(designDir, "tokens.json")),
8669
+ this.ingestDesignIntent(path14.join(designDir, "DESIGN.md"))
8026
8670
  ]);
8027
8671
  const merged = mergeResults(tokensResult, intentResult);
8028
8672
  return { ...merged, durationMs: Date.now() - start };
@@ -8252,10 +8896,10 @@ ${replyLines.join("\n")}`;
8252
8896
  includeTypes: ["file"]
8253
8897
  });
8254
8898
  for (const n of queryResult.nodes) {
8255
- const path13 = n.path ?? n.id.replace(/^file:/, "");
8256
- if (!fileSet.has(path13)) {
8257
- if (!result.has(path13)) {
8258
- 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);
8259
8903
  }
8260
8904
  }
8261
8905
  }
@@ -8628,7 +9272,7 @@ ${replyLines.join("\n")}`;
8628
9272
  return parts.join(" ");
8629
9273
  }
8630
9274
  };
8631
- VERSION = "0.4.3";
9275
+ VERSION = "0.6.0";
8632
9276
  }
8633
9277
  });
8634
9278
 
@@ -8655,6 +9299,7 @@ export {
8655
9299
  TopologicalLinker,
8656
9300
  KnowledgeIngestor,
8657
9301
  BusinessKnowledgeIngestor,
9302
+ DecisionIngestor,
8658
9303
  RequirementIngestor,
8659
9304
  MermaidParser,
8660
9305
  D2Parser,
@@ -8673,6 +9318,7 @@ export {
8673
9318
  ImageAnalysisExtractor,
8674
9319
  ContradictionDetector,
8675
9320
  CoverageScorer,
9321
+ KnowledgeDocMaterializer,
8676
9322
  KnowledgePipelineRunner,
8677
9323
  linkToCode,
8678
9324
  SyncManager,