@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.
- package/dist/agents/skills/README.md +1 -0
- package/dist/agents/skills/claude-code/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/claude-code/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/claude-code/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +49 -10
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/claude-code/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/claude-code/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +22 -6
- package/dist/agents/skills/claude-code/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/claude-code/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/claude-code/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +102 -11
- package/dist/agents/skills/claude-code/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/claude-code/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/claude-code/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +22 -6
- package/dist/agents/skills/claude-code/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/claude-code/initialize-test-suite-project/SKILL.md +415 -0
- package/dist/agents/skills/claude-code/initialize-test-suite-project/skill.yaml +35 -0
- package/dist/agents/skills/codex/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/codex/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +49 -10
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/codex/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/codex/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-execution/SKILL.md +22 -6
- package/dist/agents/skills/codex/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/codex/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/codex/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/codex/harness-planning/SKILL.md +102 -11
- package/dist/agents/skills/codex/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/codex/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/codex/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/codex/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/codex/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +22 -6
- package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/codex/initialize-test-suite-project/SKILL.md +415 -0
- package/dist/agents/skills/codex/initialize-test-suite-project/skill.yaml +35 -0
- package/dist/agents/skills/cursor/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/cursor/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +49 -10
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/cursor/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/cursor/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +22 -6
- package/dist/agents/skills/cursor/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/cursor/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/cursor/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +102 -11
- package/dist/agents/skills/cursor/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/cursor/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/cursor/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/cursor/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +22 -6
- package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/cursor/initialize-test-suite-project/SKILL.md +415 -0
- package/dist/agents/skills/cursor/initialize-test-suite-project/skill.yaml +35 -0
- package/dist/agents/skills/gemini-cli/cleanup-dead-code/SKILL.md +3 -1
- package/dist/agents/skills/gemini-cli/detect-doc-drift/SKILL.md +3 -1
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +55 -24
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +49 -10
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +21 -6
- package/dist/agents/skills/gemini-cli/harness-codebase-cleanup/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-debugging/SKILL.md +13 -1
- package/dist/agents/skills/gemini-cli/harness-dependency-health/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +22 -6
- package/dist/agents/skills/gemini-cli/harness-impact-analysis/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +560 -0
- package/dist/agents/skills/gemini-cli/harness-integration/skill.yaml +65 -0
- package/dist/agents/skills/gemini-cli/harness-integrity/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-perf/SKILL.md +9 -7
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +102 -11
- package/dist/agents/skills/gemini-cli/harness-refactoring/SKILL.md +13 -4
- package/dist/agents/skills/gemini-cli/harness-security-scan/SKILL.md +3 -1
- package/dist/agents/skills/gemini-cli/harness-soundness-review/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/harness-tdd/SKILL.md +5 -2
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +4 -0
- package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +22 -6
- package/dist/agents/skills/gemini-cli/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/gemini-cli/initialize-test-suite-project/SKILL.md +415 -0
- package/dist/agents/skills/gemini-cli/initialize-test-suite-project/skill.yaml +35 -0
- package/dist/agents/skills/tests/initialize-test-suite-project.test.ts +152 -0
- package/dist/{agents-md-OPOZA5L7.js → agents-md-2RN666DE.js} +3 -3
- package/dist/{architecture-PMVMO6SF.js → architecture-ZR33YKJB.js} +4 -4
- package/dist/{assess-project-ZW7EKNIT.js → assess-project-LLWWEQEV.js} +1 -1
- package/dist/bin/harness-mcp.js +18 -16
- package/dist/bin/harness.js +29 -28
- package/dist/{business-knowledge-6RHYJOB3.js → business-knowledge-EISPR7FU.js} +1 -1
- package/dist/{check-phase-gate-NTLAU3LO.js → check-phase-gate-ZVG56NT2.js} +4 -4
- package/dist/chunk-27AJKSQY.js +107 -0
- package/dist/{chunk-G6SYTGOH.js → chunk-2FOLD76X.js} +2 -2
- package/dist/{chunk-IFHAUFJX.js → chunk-3AJW7VQ3.js} +2 -2
- package/dist/{chunk-KXIAHHHP.js → chunk-3XMOWLK3.js} +2 -2
- package/dist/{chunk-Z2JORLRT.js → chunk-776JFSPK.js} +1 -1
- package/dist/{chunk-4KSKV5MK.js → chunk-A4ETL34H.js} +7 -2
- package/dist/{chunk-JWSVLC4I.js → chunk-BO4AJZRE.js} +1 -1
- package/dist/{chunk-UBRD5FH4.js → chunk-BZZNYHRY.js} +9 -9
- package/dist/{chunk-6SCA4NEZ.js → chunk-C7RPQWBG.js} +1 -1
- package/dist/{chunk-NVGQO4VM.js → chunk-CJYNZMOP.js} +8 -8
- package/dist/{chunk-7JNZXJKY.js → chunk-EGRQHLDM.js} +5 -5
- package/dist/{chunk-EXTWTJRF.js → chunk-EICASBYP.js} +1 -1
- package/dist/{chunk-RNAGPTKF.js → chunk-F3Y64VNF.js} +6 -6
- package/dist/{chunk-HAOBHL3W.js → chunk-FUS4OOGC.js} +1 -1
- package/dist/{chunk-VV2PJT4K.js → chunk-GAIOIINV.js} +1353 -707
- package/dist/{chunk-77EZGPSR.js → chunk-IBTHD2UA.js} +139 -78
- package/dist/{chunk-RC5ZY3EV.js → chunk-IOW3MW2K.js} +2 -2
- package/dist/{chunk-JZ6GORAK.js → chunk-L3Y7L4EG.js} +1 -1
- package/dist/{chunk-KFE2X7LG.js → chunk-MGPVA2EM.js} +27 -4
- package/dist/{chunk-JBJ4AVE4.js → chunk-Q5DKIL46.js} +3 -3
- package/dist/{chunk-NIOB6FTG.js → chunk-QTXW5TIS.js} +1 -1
- package/dist/{chunk-ZBQ3ZY75.js → chunk-SA57FVGX.js} +100 -60
- package/dist/{chunk-KSFUJT5B.js → chunk-SDDQPMZK.js} +1 -1
- package/dist/{chunk-QYSWNPXZ.js → chunk-SDPHFDQS.js} +1 -1
- package/dist/{chunk-FI3D6D7U.js → chunk-T7UZOJF4.js} +9 -9
- package/dist/{chunk-CAS3TS4Y.js → chunk-TCMVZ3B7.js} +1142 -348
- package/dist/{chunk-J3O64D2A.js → chunk-YZH4I4JO.js} +1 -1
- package/dist/{ci-workflow-H52EMCL6.js → ci-workflow-JFOL4YWW.js} +3 -3
- package/dist/{create-skill-6QWJHQYS.js → create-skill-4T5CBSJ2.js} +2 -2
- package/dist/{dist-PGQ32ZI7.js → dist-BZS2XRLG.js} +5 -1
- package/dist/{dist-TG63OKA5.js → dist-CPKL5Y2R.js} +2 -2
- package/dist/{docs-GP4VUSIB.js → docs-FUTRJQHA.js} +4 -4
- package/dist/{engine-KQG5RVXX.js → engine-FB3HCNVT.js} +3 -3
- package/dist/{entropy-RXESIYKC.js → entropy-FIEE4YDA.js} +3 -3
- package/dist/{feedback-HS7ZYXOU.js → feedback-7EC4T6XG.js} +1 -1
- package/dist/{generate-agent-definitions-I4B7CVOB.js → generate-agent-definitions-FOSFADNR.js} +4 -4
- package/dist/{graph-loader-TIVI6NQ7.js → graph-loader-QEI7SYOQ.js} +1 -1
- package/dist/hooks/adoption-tracker.js +36 -8
- package/dist/hooks/sentinel-pre.js +3 -2
- package/dist/hooks/telemetry-reporter.js +34 -15
- package/dist/index-builder-3AOQ3ZII.js +13 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +31 -30
- package/dist/{loader-CWAH6B4H.js → loader-HXUOBTYA.js} +3 -3
- package/dist/mcp-ICXMTAD3.js +41 -0
- package/dist/{performance-WDW5WS3J.js → performance-TYXBCTR7.js} +4 -4
- package/dist/{review-pipeline-QOZGWCTY.js → review-pipeline-B4WPU6BQ.js} +3 -3
- package/dist/{runtime-SFEFRPLX.js → runtime-HQQNOARD.js} +3 -3
- package/dist/{scan-IJ7EYTF7.js → scan-TX55R6QS.js} +1 -1
- package/dist/{security-2K7UUK6T.js → security-25VFLIAO.js} +1 -1
- package/dist/templates/orchestrator/harness.orchestrator.md +18 -8
- package/dist/{validate-HXM6DXRZ.js → validate-EAVGKZQB.js} +4 -4
- package/dist/{validate-cross-check-ULQLEOME.js → validate-cross-check-SPL3H4O5.js} +3 -3
- package/package.json +14 -6
- package/dist/agents/commands/claude-code/harness/harness.md +0 -36
- package/dist/agents/commands/codex/AGENTS.md +0 -39
- package/dist/agents/commands/codex/harness/add-harness-component/SKILL.md +0 -204
- package/dist/agents/commands/codex/harness/add-harness-component/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/cleanup-dead-code/SKILL.md +0 -257
- package/dist/agents/commands/codex/harness/cleanup-dead-code/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/detect-doc-drift/SKILL.md +0 -191
- package/dist/agents/commands/codex/harness/detect-doc-drift/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/enforce-architecture/SKILL.md +0 -289
- package/dist/agents/commands/codex/harness/enforce-architecture/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-architecture-advisor/SKILL.md +0 -442
- package/dist/agents/commands/codex/harness/harness-architecture-advisor/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-autopilot/SKILL.md +0 -929
- package/dist/agents/commands/codex/harness/harness-autopilot/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-brainstorming/SKILL.md +0 -418
- package/dist/agents/commands/codex/harness/harness-brainstorming/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-code-review/SKILL.md +0 -850
- package/dist/agents/commands/codex/harness/harness-code-review/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-codebase-cleanup/SKILL.md +0 -236
- package/dist/agents/commands/codex/harness/harness-codebase-cleanup/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-debugging/SKILL.md +0 -378
- package/dist/agents/commands/codex/harness/harness-debugging/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-dependency-health/SKILL.md +0 -190
- package/dist/agents/commands/codex/harness/harness-dependency-health/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-docs-pipeline/SKILL.md +0 -472
- package/dist/agents/commands/codex/harness/harness-docs-pipeline/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-execution/SKILL.md +0 -522
- package/dist/agents/commands/codex/harness/harness-execution/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-hotspot-detector/SKILL.md +0 -172
- package/dist/agents/commands/codex/harness/harness-hotspot-detector/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-impact-analysis/SKILL.md +0 -195
- package/dist/agents/commands/codex/harness/harness-impact-analysis/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-integrity/SKILL.md +0 -178
- package/dist/agents/commands/codex/harness/harness-integrity/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-onboarding/SKILL.md +0 -299
- package/dist/agents/commands/codex/harness/harness-onboarding/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-perf/SKILL.md +0 -272
- package/dist/agents/commands/codex/harness/harness-perf/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-planning/SKILL.md +0 -592
- package/dist/agents/commands/codex/harness/harness-planning/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-refactoring/SKILL.md +0 -181
- package/dist/agents/commands/codex/harness/harness-refactoring/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-release-readiness/SKILL.md +0 -701
- package/dist/agents/commands/codex/harness/harness-release-readiness/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-roadmap/SKILL.md +0 -607
- package/dist/agents/commands/codex/harness/harness-roadmap/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-security-scan/SKILL.md +0 -147
- package/dist/agents/commands/codex/harness/harness-security-scan/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-skill-authoring/SKILL.md +0 -314
- package/dist/agents/commands/codex/harness/harness-skill-authoring/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-soundness-review/SKILL.md +0 -1280
- package/dist/agents/commands/codex/harness/harness-soundness-review/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-supply-chain-audit/SKILL.md +0 -255
- package/dist/agents/commands/codex/harness/harness-supply-chain-audit/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-tdd/SKILL.md +0 -189
- package/dist/agents/commands/codex/harness/harness-tdd/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-test-advisor/SKILL.md +0 -171
- package/dist/agents/commands/codex/harness/harness-test-advisor/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-verification/SKILL.md +0 -433
- package/dist/agents/commands/codex/harness/harness-verification/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/harness-verify/SKILL.md +0 -170
- package/dist/agents/commands/codex/harness/harness-verify/agents/openai.yaml +0 -3
- package/dist/agents/commands/codex/harness/initialize-harness-project/SKILL.md +0 -244
- package/dist/agents/commands/codex/harness/initialize-harness-project/agents/openai.yaml +0 -3
- package/dist/agents/commands/cursor/harness/add-harness-component.mdc +0 -200
- package/dist/agents/commands/cursor/harness/cleanup-dead-code.mdc +0 -253
- package/dist/agents/commands/cursor/harness/detect-doc-drift.mdc +0 -187
- package/dist/agents/commands/cursor/harness/enforce-architecture.mdc +0 -304
- package/dist/agents/commands/cursor/harness/harness-architecture-advisor.mdc +0 -457
- package/dist/agents/commands/cursor/harness/harness-autopilot.mdc +0 -924
- package/dist/agents/commands/cursor/harness/harness-brainstorming.mdc +0 -414
- package/dist/agents/commands/cursor/harness/harness-code-review.mdc +0 -865
- package/dist/agents/commands/cursor/harness/harness-codebase-cleanup.mdc +0 -232
- package/dist/agents/commands/cursor/harness/harness-debugging.mdc +0 -374
- package/dist/agents/commands/cursor/harness/harness-dependency-health.mdc +0 -187
- package/dist/agents/commands/cursor/harness/harness-docs-pipeline.mdc +0 -468
- package/dist/agents/commands/cursor/harness/harness-execution.mdc +0 -518
- package/dist/agents/commands/cursor/harness/harness-hotspot-detector.mdc +0 -169
- package/dist/agents/commands/cursor/harness/harness-impact-analysis.mdc +0 -192
- package/dist/agents/commands/cursor/harness/harness-integrity.mdc +0 -175
- package/dist/agents/commands/cursor/harness/harness-onboarding.mdc +0 -296
- package/dist/agents/commands/cursor/harness/harness-perf.mdc +0 -268
- package/dist/agents/commands/cursor/harness/harness-planning.mdc +0 -587
- package/dist/agents/commands/cursor/harness/harness-refactoring.mdc +0 -177
- package/dist/agents/commands/cursor/harness/harness-release-readiness.mdc +0 -697
- package/dist/agents/commands/cursor/harness/harness-roadmap.mdc +0 -603
- package/dist/agents/commands/cursor/harness/harness-security-scan.mdc +0 -162
- package/dist/agents/commands/cursor/harness/harness-skill-authoring.mdc +0 -300
- package/dist/agents/commands/cursor/harness/harness-soundness-review.mdc +0 -1275
- package/dist/agents/commands/cursor/harness/harness-supply-chain-audit.mdc +0 -252
- package/dist/agents/commands/cursor/harness/harness-tdd.mdc +0 -185
- package/dist/agents/commands/cursor/harness/harness-test-advisor.mdc +0 -168
- package/dist/agents/commands/cursor/harness/harness-verification.mdc +0 -429
- package/dist/agents/commands/cursor/harness/harness-verify.mdc +0 -167
- package/dist/agents/commands/cursor/harness/initialize-harness-project.mdc +0 -240
- package/dist/agents/commands/gemini-cli/harness/harness.toml +0 -277
- package/dist/hooks/profiles.ts +0 -48
- package/dist/mcp-KHK5KYER.js +0 -39
- 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
|
|
37
|
-
import * as
|
|
40
|
+
import * as fs13 from "fs/promises";
|
|
41
|
+
import * as path14 from "path";
|
|
38
42
|
function streamGraphJson(filePath, nodes, edges) {
|
|
39
43
|
return new Promise((resolve, reject) => {
|
|
40
44
|
const stream = createWriteStream(filePath, { encoding: "utf-8" });
|
|
@@ -50,7 +54,7 @@ function streamGraphJson(filePath, nodes, edges) {
|
|
|
50
54
|
stream.write(JSON.stringify(edges[i]));
|
|
51
55
|
}
|
|
52
56
|
stream.write("]}");
|
|
53
|
-
stream.end(resolve);
|
|
57
|
+
stream.end(() => resolve());
|
|
54
58
|
});
|
|
55
59
|
}
|
|
56
60
|
async function saveGraph(dirPath, nodes, edges) {
|
|
@@ -73,15 +77,19 @@ async function loadGraph(dirPath) {
|
|
|
73
77
|
await access(metaPath);
|
|
74
78
|
await access(graphPath);
|
|
75
79
|
} catch {
|
|
76
|
-
return
|
|
80
|
+
return { status: "not_found" };
|
|
77
81
|
}
|
|
78
82
|
const metaContent = await readFile(metaPath, "utf-8");
|
|
79
83
|
const metadata = JSON.parse(metaContent);
|
|
80
84
|
if (metadata.schemaVersion !== CURRENT_SCHEMA_VERSION) {
|
|
81
|
-
return
|
|
85
|
+
return {
|
|
86
|
+
status: "schema_mismatch",
|
|
87
|
+
found: metadata.schemaVersion,
|
|
88
|
+
expected: CURRENT_SCHEMA_VERSION
|
|
89
|
+
};
|
|
82
90
|
}
|
|
83
91
|
const graphContent = await readFile(graphPath, "utf-8");
|
|
84
|
-
return JSON.parse(graphContent);
|
|
92
|
+
return { status: "loaded", graph: JSON.parse(graphContent) };
|
|
85
93
|
}
|
|
86
94
|
function safeMerge(target, source) {
|
|
87
95
|
for (const key of Object.keys(source)) {
|
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
1242
|
-
if (
|
|
1711
|
+
const result = await loadGraph(dirPath);
|
|
1712
|
+
if (result.status === "not_found") return false;
|
|
1713
|
+
if (result.status === "schema_mismatch") {
|
|
1714
|
+
console.warn(
|
|
1715
|
+
`[graph] Schema version mismatch: graph was saved with v${result.found} but current is v${result.expected}. Graph will be rebuilt from scratch. Run \`harness graph scan\` to repopulate.`
|
|
1716
|
+
);
|
|
1717
|
+
return false;
|
|
1718
|
+
}
|
|
1719
|
+
const data = result.graph;
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2660
|
-
featureDirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
3246
|
+
const entries = await fs5.readdir(specsDir, { withFileTypes: true });
|
|
3247
|
+
featureDirs = entries.filter((e) => e.isDirectory()).map((e) => path6.join(specsDir, e.name));
|
|
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 =
|
|
2680
|
-
const specPath =
|
|
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
|
|
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:
|
|
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/") &&
|
|
3402
|
+
const isCodeMatch = normalizedPath.includes("packages/") && path6.basename(normalizedPath).includes(featureName);
|
|
2816
3403
|
const isTestMatch = normalizedPath.includes("/tests/") && // platform-safe
|
|
2817
|
-
|
|
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
|
|
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
|
|
3462
|
+
return emptyResult2();
|
|
2876
3463
|
}
|
|
2877
|
-
const diagramType =
|
|
3464
|
+
const diagramType = detectDiagramType(trimmed);
|
|
2878
3465
|
switch (diagramType) {
|
|
2879
3466
|
case "flowchart":
|
|
2880
|
-
return
|
|
3467
|
+
return parseFlowchart(trimmed, diagramType);
|
|
2881
3468
|
case "sequence":
|
|
2882
|
-
return
|
|
3469
|
+
return parseSequence(trimmed, diagramType);
|
|
2883
3470
|
default:
|
|
2884
|
-
return
|
|
3471
|
+
return emptyResult2(diagramType);
|
|
2885
3472
|
}
|
|
2886
3473
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
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
|
-
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
const
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
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
|
-
|
|
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: "
|
|
3491
|
+
entities: Array.from(state.entities.values()),
|
|
3492
|
+
relationships: state.relationships,
|
|
3493
|
+
metadata: { format: "d2", diagramType: "architecture" }
|
|
3005
3494
|
};
|
|
3006
3495
|
}
|
|
3007
3496
|
};
|
|
3008
|
-
|
|
3497
|
+
PlantUmlParser = class {
|
|
3009
3498
|
canParse(_content, ext) {
|
|
3010
|
-
return ext === ".
|
|
3499
|
+
return ext === ".puml" || ext === ".plantuml";
|
|
3011
3500
|
}
|
|
3012
3501
|
parse(content, _filePath) {
|
|
3013
3502
|
const trimmed = content.trim();
|
|
3014
|
-
if (!trimmed)
|
|
3015
|
-
|
|
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
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
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 =
|
|
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
|
|
3170
|
-
const relPath =
|
|
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
|
-
|
|
3175
|
-
|
|
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
|
|
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 (!
|
|
3232
|
-
await walk(
|
|
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 =
|
|
3627
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
3236
3628
|
if (DIAGRAM_EXTENSIONS.has(ext)) {
|
|
3237
|
-
files.push(
|
|
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
|
-
|
|
3385
|
-
|
|
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
|
|
3420
|
-
const filePath =
|
|
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
|
|
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 =
|
|
3441
|
-
await
|
|
3442
|
-
const filePath =
|
|
3834
|
+
const stagedDir = path8.join(this.outputDir, "staged");
|
|
3835
|
+
await fs7.mkdir(stagedDir, { recursive: true });
|
|
3836
|
+
const filePath = path8.join(stagedDir, "linker-staged.jsonl");
|
|
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
|
|
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 =
|
|
3576
|
-
await
|
|
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
|
|
3980
|
+
await fs8.writeFile(path9.join(stagedDir, "pipeline-staged.jsonl"), jsonl, "utf-8");
|
|
3579
3981
|
return { staged: deduplicated.length };
|
|
3580
3982
|
}
|
|
3581
|
-
async
|
|
3582
|
-
const
|
|
3983
|
+
async extractDocName(filePath) {
|
|
3984
|
+
const raw = await fs8.readFile(filePath, "utf-8");
|
|
3985
|
+
const fmMatch = raw.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
3986
|
+
const body = fmMatch ? fmMatch[1] : raw;
|
|
3987
|
+
const titleMatch = body.match(/^#\s+(.+)$/m);
|
|
3988
|
+
return titleMatch ? titleMatch[1].trim() : path9.basename(filePath, ".md");
|
|
3989
|
+
}
|
|
3990
|
+
async generateGapReport(knowledgeDir, store) {
|
|
3991
|
+
const domainDocNames = /* @__PURE__ */ new Map();
|
|
3992
|
+
const domainEntryCounts = /* @__PURE__ */ new Map();
|
|
3583
3993
|
let totalEntries = 0;
|
|
3584
3994
|
try {
|
|
3585
|
-
const entries = await
|
|
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 =
|
|
3589
|
-
const files = await
|
|
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
|
-
|
|
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 =
|
|
3605
|
-
await
|
|
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
|
-
|
|
3617
|
-
lines.push(
|
|
4094
|
+
if (hasDifferential) {
|
|
4095
|
+
lines.push(
|
|
4096
|
+
"| Domain | Documented | Extracted | Gaps |",
|
|
4097
|
+
"| ------ | ---------- | --------- | ---- |"
|
|
4098
|
+
);
|
|
4099
|
+
for (const domain of report.domains) {
|
|
4100
|
+
lines.push(
|
|
4101
|
+
`| ${domain.domain} | ${domain.entryCount} | ${domain.extractedCount} | ${domain.gapCount} |`
|
|
4102
|
+
);
|
|
4103
|
+
}
|
|
4104
|
+
lines.push(
|
|
4105
|
+
"",
|
|
4106
|
+
`## Summary`,
|
|
4107
|
+
"",
|
|
4108
|
+
`- **Total Documented:** ${report.totalEntries}`,
|
|
4109
|
+
`- **Total Extracted:** ${report.totalExtracted}`,
|
|
4110
|
+
`- **Total Gaps:** ${report.totalGaps}`,
|
|
4111
|
+
""
|
|
4112
|
+
);
|
|
4113
|
+
} else {
|
|
4114
|
+
lines.push("| Domain | Entries |", "| ------ | ------- |");
|
|
4115
|
+
for (const domain of report.domains) {
|
|
4116
|
+
lines.push(`| ${domain.domain} | ${domain.entryCount} |`);
|
|
4117
|
+
}
|
|
4118
|
+
lines.push("", `## Total Entries: ${report.totalEntries}`, "");
|
|
3618
4119
|
}
|
|
3619
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
4191
|
+
const ext = path10.extname(filePath);
|
|
3692
4192
|
let content;
|
|
3693
4193
|
try {
|
|
3694
|
-
content = await
|
|
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 =
|
|
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 =
|
|
4238
|
+
const filePath = path10.join(outputDir, `${extractorName}.jsonl`);
|
|
3739
4239
|
const lines = records.map((r) => JSON.stringify(r));
|
|
3740
|
-
await
|
|
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
|
|
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 =
|
|
3818
|
-
if (entry.isDirectory() && !
|
|
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
|
|
4930
|
-
|
|
4931
|
-
|
|
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 =
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5129
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
|
5215
|
-
|
|
5781
|
+
const coverage = new CoverageScorer().score(this.store);
|
|
5782
|
+
let materialization;
|
|
5216
5783
|
let iterations = 1;
|
|
5217
5784
|
if (options.fix) {
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
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
|
|
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 =
|
|
5249
|
-
await
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
5468
|
-
await
|
|
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
|
|
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
|
|
5931
|
-
|
|
5932
|
-
|
|
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
|
|
5986
|
-
|
|
5987
|
-
|
|
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
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
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
|
|
6082
|
-
|
|
6083
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
7244
|
-
add(
|
|
7245
|
-
consumed.add(
|
|
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
|
|
7316
|
-
if (
|
|
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
|
|
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 \`${
|
|
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
|
-
|
|
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
|
|
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(
|
|
8025
|
-
this.ingestDesignIntent(
|
|
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
|
|
8256
|
-
if (!fileSet.has(
|
|
8257
|
-
if (!result.has(
|
|
8258
|
-
result.set(
|
|
8899
|
+
const path15 = n.path ?? n.id.replace(/^file:/, "");
|
|
8900
|
+
if (!fileSet.has(path15)) {
|
|
8901
|
+
if (!result.has(path15)) {
|
|
8902
|
+
result.set(path15, file);
|
|
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.
|
|
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,
|