@documentation-robotics/cli 0.1.0 → 0.1.7
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/LICENSE +21 -0
- package/README.md +466 -693
- package/dist/analysis/data-model-analyzer.js +189 -0
- package/dist/analysis/data-model-analyzer.js.map +7 -0
- package/dist/analysis/index.js +3 -0
- package/dist/analysis/index.js.map +7 -0
- package/dist/analysis/relationship-classifier.js +181 -0
- package/dist/analysis/relationship-classifier.js.map +7 -0
- package/dist/analyzers/base-analyzer.js +1 -0
- package/dist/analyzers/base-analyzer.js.map +7 -0
- package/dist/analyzers/call-graph-utils.js +39 -0
- package/dist/analyzers/call-graph-utils.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +1205 -0
- package/dist/analyzers/cbm-analyzer.js.map +7 -0
- package/dist/analyzers/heuristic-utils.js +55 -0
- package/dist/analyzers/heuristic-utils.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +146 -0
- package/dist/analyzers/mapping-loader.js.map +7 -0
- package/dist/analyzers/registry.js +124 -0
- package/dist/analyzers/registry.js.map +7 -0
- package/dist/analyzers/session-state.js +176 -0
- package/dist/analyzers/session-state.js.map +7 -0
- package/dist/analyzers/stdio-client.js +208 -0
- package/dist/analyzers/stdio-client.js.map +7 -0
- package/dist/analyzers/types.js +15 -0
- package/dist/analyzers/types.js.map +7 -0
- package/dist/analyzers/verify-engine.js +209 -0
- package/dist/analyzers/verify-engine.js.map +7 -0
- package/dist/analyzers/verify-ignore.js +130 -0
- package/dist/analyzers/verify-ignore.js.map +7 -0
- package/dist/analyzers/version.js +8 -0
- package/dist/analyzers/version.js.map +7 -0
- package/dist/audit/ai/runner.js +41 -0
- package/dist/audit/ai/runner.js.map +7 -0
- package/dist/audit/differential-analyzer.js +183 -0
- package/dist/audit/differential-analyzer.js.map +7 -0
- package/dist/audit/index.js +32 -0
- package/dist/audit/index.js.map +7 -0
- package/dist/audit/nodes/ai/evaluator.js +49 -0
- package/dist/audit/nodes/ai/evaluator.js.map +7 -0
- package/dist/audit/nodes/ai/parser.js +55 -0
- package/dist/audit/nodes/ai/parser.js.map +7 -0
- package/dist/audit/nodes/ai/prompts.js +103 -0
- package/dist/audit/nodes/ai/prompts.js.map +7 -0
- package/dist/audit/nodes/analysis/node-completeness-checker.js +32 -0
- package/dist/audit/nodes/analysis/node-completeness-checker.js.map +7 -0
- package/dist/audit/nodes/analysis/node-definition-analyzer.js +47 -0
- package/dist/audit/nodes/analysis/node-definition-analyzer.js.map +7 -0
- package/dist/audit/nodes/analysis/node-overlap-detector.js +79 -0
- package/dist/audit/nodes/analysis/node-overlap-detector.js.map +7 -0
- package/dist/audit/nodes/model/orchestrator.js +82 -0
- package/dist/audit/nodes/model/orchestrator.js.map +7 -0
- package/dist/audit/nodes/spec/loader.js +116 -0
- package/dist/audit/nodes/spec/loader.js.map +7 -0
- package/dist/audit/nodes/spec/orchestrator.js +68 -0
- package/dist/audit/nodes/spec/orchestrator.js.map +7 -0
- package/dist/audit/nodes/types.js +1 -0
- package/dist/audit/nodes/types.js.map +7 -0
- package/dist/audit/pipeline/index.js +5 -0
- package/dist/audit/pipeline/index.js.map +7 -0
- package/dist/audit/pipeline/pipeline-orchestrator.js +218 -0
- package/dist/audit/pipeline/pipeline-orchestrator.js.map +7 -0
- package/dist/audit/relationships/ai/evaluator.js +235 -0
- package/dist/audit/relationships/ai/evaluator.js.map +7 -0
- package/dist/audit/relationships/ai/parser.js +197 -0
- package/dist/audit/relationships/ai/parser.js.map +7 -0
- package/dist/audit/relationships/ai/prompts.js +274 -0
- package/dist/audit/relationships/ai/prompts.js.map +7 -0
- package/dist/audit/relationships/analysis/balance-assessor.js +115 -0
- package/dist/audit/relationships/analysis/balance-assessor.js.map +7 -0
- package/dist/audit/relationships/analysis/coverage-analyzer.js +99 -0
- package/dist/audit/relationships/analysis/coverage-analyzer.js.map +7 -0
- package/dist/audit/relationships/analysis/duplicate-detector.js +95 -0
- package/dist/audit/relationships/analysis/duplicate-detector.js.map +7 -0
- package/dist/audit/relationships/analysis/gap-analyzer.js +99 -0
- package/dist/audit/relationships/analysis/gap-analyzer.js.map +7 -0
- package/dist/audit/relationships/analysis/layer-templates.js +294 -0
- package/dist/audit/relationships/analysis/layer-templates.js.map +7 -0
- package/dist/audit/relationships/graph/connectivity.js +129 -0
- package/dist/audit/relationships/graph/connectivity.js.map +7 -0
- package/dist/audit/relationships/graph/relationship-graph.js +179 -0
- package/dist/audit/relationships/graph/relationship-graph.js.map +7 -0
- package/dist/audit/relationships/model/balance-assessor.js +49 -0
- package/dist/audit/relationships/model/balance-assessor.js.map +7 -0
- package/dist/audit/relationships/model/coverage-analyzer.js +34 -0
- package/dist/audit/relationships/model/coverage-analyzer.js.map +7 -0
- package/dist/audit/relationships/model/duplicate-detector.js +68 -0
- package/dist/audit/relationships/model/duplicate-detector.js.map +7 -0
- package/dist/audit/relationships/model/gap-analyzer.js +41 -0
- package/dist/audit/relationships/model/gap-analyzer.js.map +7 -0
- package/dist/audit/relationships/model/orchestrator.js +126 -0
- package/dist/audit/relationships/model/orchestrator.js.map +7 -0
- package/dist/audit/relationships/spec/orchestrator.js +157 -0
- package/dist/audit/relationships/spec/orchestrator.js.map +7 -0
- package/dist/audit/reports/index.js +5 -0
- package/dist/audit/reports/index.js.map +7 -0
- package/dist/audit/reports/report-generator.js +118 -0
- package/dist/audit/reports/report-generator.js.map +7 -0
- package/dist/audit/snapshot-storage.js +163 -0
- package/dist/audit/snapshot-storage.js.map +7 -0
- package/dist/audit/types.js +21 -0
- package/dist/audit/types.js.map +7 -0
- package/dist/cli.js +223 -92
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +147 -0
- package/dist/coding-agents/agents/claude-code.js.map +7 -0
- package/dist/coding-agents/agents/index.js +5 -0
- package/dist/coding-agents/agents/index.js.map +7 -0
- package/dist/coding-agents/agents/types.js +1 -0
- package/dist/coding-agents/agents/types.js.map +7 -0
- package/dist/coding-agents/base-chat-client.js +45 -0
- package/dist/coding-agents/base-chat-client.js.map +7 -0
- package/dist/coding-agents/chat-utils.js +23 -0
- package/dist/coding-agents/chat-utils.js.map +7 -0
- package/dist/coding-agents/claude-code-client.js +246 -0
- package/dist/coding-agents/claude-code-client.js.map +7 -0
- package/dist/coding-agents/copilot-client.js +209 -0
- package/dist/coding-agents/copilot-client.js.map +7 -0
- package/dist/commands/add.js +95 -33
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +511 -0
- package/dist/commands/analyzer.js.map +7 -0
- package/dist/commands/audit-diff.js +49 -0
- package/dist/commands/audit-diff.js.map +7 -0
- package/dist/commands/audit-snapshots.js +62 -0
- package/dist/commands/audit-snapshots.js.map +7 -0
- package/dist/commands/audit.js +81 -0
- package/dist/commands/audit.js.map +7 -0
- package/dist/commands/catalog.js +248 -0
- package/dist/commands/catalog.js.map +7 -0
- package/dist/commands/changeset.js +577 -66
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/chat.js +106 -83
- package/dist/commands/chat.js.map +2 -2
- package/dist/commands/claude.js +74 -0
- package/dist/commands/claude.js.map +7 -0
- package/dist/commands/conformance.js +71 -68
- package/dist/commands/conformance.js.map +2 -2
- package/dist/commands/copilot.js +75 -0
- package/dist/commands/copilot.js.map +7 -0
- package/dist/commands/delete.js +125 -11
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/discover-logic.js +44 -0
- package/dist/commands/discover-logic.js.map +7 -0
- package/dist/commands/docs.js +87 -0
- package/dist/commands/docs.js.map +7 -0
- package/dist/commands/element.js +14 -20
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/export.js +13 -3
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/import.js +51 -0
- package/dist/commands/import.js.map +7 -0
- package/dist/commands/info.js +30 -24
- package/dist/commands/info.js.map +2 -2
- package/dist/commands/init.js +27 -11
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/list.js +63 -13
- package/dist/commands/list.js.map +3 -3
- package/dist/commands/relationship.js +225 -37
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/report.js +54 -0
- package/dist/commands/report.js.map +7 -0
- package/dist/commands/reports.js +45 -0
- package/dist/commands/reports.js.map +7 -0
- package/dist/commands/schema.js +135 -0
- package/dist/commands/schema.js.map +7 -0
- package/dist/commands/search.js +40 -12
- package/dist/commands/search.js.map +2 -2
- package/dist/commands/show.js +58 -27
- package/dist/commands/show.js.map +2 -2
- package/dist/commands/stats.js +35 -0
- package/dist/commands/stats.js.map +7 -0
- package/dist/commands/trace.js +39 -16
- package/dist/commands/trace.js.map +2 -2
- package/dist/commands/update.js +141 -14
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +210 -46
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +142 -23
- package/dist/commands/validate.js.map +3 -3
- package/dist/commands/version.js +8 -12
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +162 -25
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/base-snapshot-manager.js +84 -0
- package/dist/core/base-snapshot-manager.js.map +7 -0
- package/dist/core/changeset-exporter.js +305 -0
- package/dist/core/changeset-exporter.js.map +7 -0
- package/dist/core/changeset-validator.js +41 -0
- package/dist/core/changeset-validator.js.map +7 -0
- package/dist/core/changeset.js +88 -173
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/dependency-tracker.js +4 -2
- package/dist/core/dependency-tracker.js.map +2 -2
- package/dist/core/element.js +98 -27
- package/dist/core/element.js.map +2 -2
- package/dist/core/golden-copy-cache.js +290 -0
- package/dist/core/golden-copy-cache.js.map +7 -0
- package/dist/core/graph-model.js +266 -0
- package/dist/core/graph-model.js.map +7 -0
- package/dist/core/layer.js +159 -16
- package/dist/core/layer.js.map +2 -2
- package/dist/core/layers.js +32 -0
- package/dist/core/layers.js.map +7 -0
- package/dist/core/manifest.js +32 -11
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +56 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +424 -217
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +179 -0
- package/dist/core/mutation-handler.js.map +7 -0
- package/dist/core/reference-registry.js +29 -21
- package/dist/core/reference-registry.js.map +2 -2
- package/dist/core/relationship-catalog.js +189 -0
- package/dist/core/relationship-catalog.js.map +7 -0
- package/dist/core/relationship-registry.js.map +2 -2
- package/dist/core/relationships.js.map +1 -1
- package/dist/core/report-data-model.js +285 -0
- package/dist/core/report-data-model.js.map +7 -0
- package/dist/core/spec-data-service.js +185 -0
- package/dist/core/spec-data-service.js.map +7 -0
- package/dist/core/spec-loader-types.js +1 -0
- package/dist/core/spec-loader-types.js.map +7 -0
- package/dist/core/spec-loader.js +234 -0
- package/dist/core/spec-loader.js.map +7 -0
- package/dist/core/staged-changeset-storage.js +282 -0
- package/dist/core/staged-changeset-storage.js.map +7 -0
- package/dist/core/staging-area.js +1002 -0
- package/dist/core/staging-area.js.map +7 -0
- package/dist/core/stats-collector.js +169 -0
- package/dist/core/stats-collector.js.map +7 -0
- package/dist/core/virtual-projection.js +443 -0
- package/dist/core/virtual-projection.js.map +7 -0
- package/dist/export/archimate-exporter.js +99 -60
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/export/audit-formatters.js +479 -0
- package/dist/export/audit-formatters.js.map +7 -0
- package/dist/export/diff-formatters.js +99 -0
- package/dist/export/diff-formatters.js.map +7 -0
- package/dist/export/export-manager.js.map +2 -2
- package/dist/export/graphml-exporter.js +44 -27
- package/dist/export/graphml-exporter.js.map +2 -2
- package/dist/export/index.js +4 -0
- package/dist/export/index.js.map +2 -2
- package/dist/export/json-schema-exporter.js +55 -42
- package/dist/export/json-schema-exporter.js.map +2 -2
- package/dist/export/markdown-exporter.js +92 -70
- package/dist/export/markdown-exporter.js.map +2 -2
- package/dist/export/markdown-generator.js +248 -0
- package/dist/export/markdown-generator.js.map +7 -0
- package/dist/export/markdown-utils.js +30 -0
- package/dist/export/markdown-utils.js.map +7 -0
- package/dist/export/mermaid-markdown-exporter.js +37 -0
- package/dist/export/mermaid-markdown-exporter.js.map +7 -0
- package/dist/export/openapi-exporter.js +119 -71
- package/dist/export/openapi-exporter.js.map +2 -2
- package/dist/export/plantuml-exporter.js +104 -39
- package/dist/export/plantuml-exporter.js.map +2 -2
- package/dist/export/report-formatters.js +111 -0
- package/dist/export/report-formatters.js.map +7 -0
- package/dist/export/stats-formatters.js +98 -0
- package/dist/export/stats-formatters.js.map +7 -0
- package/dist/export/types.js +2 -14
- package/dist/export/types.js.map +2 -2
- package/dist/export/verify-formatters.js +153 -0
- package/dist/export/verify-formatters.js.map +7 -0
- package/dist/generated/compiled-validators.js +17 -0
- package/dist/generated/compiled-validators.js.map +7 -0
- package/dist/generated/index.js +31 -0
- package/dist/generated/index.js.map +7 -0
- package/dist/generated/layer-registry.js +190 -0
- package/dist/generated/layer-registry.js.map +7 -0
- package/dist/generated/layer-types.js +7 -0
- package/dist/generated/layer-types.js.map +7 -0
- package/dist/generated/node-types.js +1719 -0
- package/dist/generated/node-types.js.map +7 -0
- package/dist/generated/relationship-index.js +14552 -0
- package/dist/generated/relationship-index.js.map +7 -0
- package/dist/import/archimate-importer.js +166 -0
- package/dist/import/archimate-importer.js.map +7 -0
- package/dist/import/index.js +47 -0
- package/dist/import/index.js.map +7 -0
- package/dist/import/openapi-importer.js +143 -0
- package/dist/import/openapi-importer.js.map +7 -0
- package/dist/import/types.js +1 -0
- package/dist/import/types.js.map +7 -0
- package/dist/integrations/base-manager.js +311 -0
- package/dist/integrations/base-manager.js.map +7 -0
- package/dist/integrations/claude-manager.js +360 -0
- package/dist/integrations/claude-manager.js.map +7 -0
- package/dist/integrations/claude_code/agents/dr-advisor.md +253 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +278 -0
- package/dist/integrations/claude_code/agents/dr-extractor.md +286 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +657 -0
- package/dist/integrations/claude_code/commands/dr-design.md +373 -0
- package/dist/integrations/claude_code/commands/dr-info.md +132 -0
- package/dist/integrations/claude_code/commands/dr-init.md +251 -0
- package/dist/integrations/claude_code/commands/dr-map.md +936 -0
- package/dist/integrations/claude_code/commands/dr-model.md +493 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +814 -0
- package/dist/integrations/claude_code/commands/dr-sync.md +279 -0
- package/dist/integrations/claude_code/commands/dr-validate.md +654 -0
- package/dist/integrations/claude_code/commands/dr-verify.md +559 -0
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +903 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +271 -0
- package/dist/integrations/claude_code/docs/README.md +347 -0
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +550 -0
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +620 -0
- package/dist/integrations/claude_code/skills/dr_03_security_layer/SKILL.md +642 -0
- package/dist/integrations/claude_code/skills/dr_04_application_layer/SKILL.md +911 -0
- package/dist/integrations/claude_code/skills/dr_05_technology_layer/SKILL.md +836 -0
- package/dist/integrations/claude_code/skills/dr_06_api_layer/SKILL.md +933 -0
- package/dist/integrations/claude_code/skills/dr_07_data_model_layer/SKILL.md +210 -0
- package/dist/integrations/claude_code/skills/dr_08_dataset_layer/SKILL.md +319 -0
- package/dist/integrations/claude_code/skills/dr_09_ux_layer/SKILL.md +319 -0
- package/dist/integrations/claude_code/skills/dr_10_navigation_layer/SKILL.md +318 -0
- package/dist/integrations/claude_code/skills/dr_11_apm_layer/SKILL.md +273 -0
- package/dist/integrations/claude_code/skills/dr_12_testing_layer/SKILL.md +273 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +187 -0
- package/dist/integrations/claude_code/skills/dr_codebase_memory/SKILL.md +590 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +295 -0
- package/dist/integrations/copilot-manager.js +328 -0
- package/dist/integrations/copilot-manager.js.map +7 -0
- package/dist/integrations/github_copilot/agents/dr-advisor.md +243 -0
- package/dist/integrations/github_copilot/agents/dr-architect.md +1567 -0
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +491 -0
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +539 -0
- package/dist/integrations/github_copilot/skills/dr_03_security_layer/SKILL.md +667 -0
- package/dist/integrations/github_copilot/skills/dr_04_application_layer/SKILL.md +670 -0
- package/dist/integrations/github_copilot/skills/dr_05_technology_layer/SKILL.md +660 -0
- package/dist/integrations/github_copilot/skills/dr_06_api_layer/SKILL.md +855 -0
- package/dist/integrations/github_copilot/skills/dr_07_data_model_layer/SKILL.md +171 -0
- package/dist/integrations/github_copilot/skills/dr_08_dataset_layer/SKILL.md +216 -0
- package/dist/integrations/github_copilot/skills/dr_09_ux_layer/SKILL.md +230 -0
- package/dist/integrations/github_copilot/skills/dr_10_navigation_layer/SKILL.md +228 -0
- package/dist/integrations/github_copilot/skills/dr_11_apm_layer/SKILL.md +234 -0
- package/dist/integrations/github_copilot/skills/dr_12_testing_layer/SKILL.md +230 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +187 -0
- package/dist/integrations/hash-utils.js +51 -0
- package/dist/integrations/hash-utils.js.map +7 -0
- package/dist/integrations/types.js +16 -0
- package/dist/integrations/types.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +278 -0
- package/dist/reports/model-layer-report-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +57 -0
- package/dist/reports/model-report-data.js.map +7 -0
- package/dist/reports/model-report-orchestrator.js +103 -0
- package/dist/reports/model-report-orchestrator.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +595 -0
- package/dist/schemas/bundled/analyzers/manifest.json +9 -0
- package/dist/schemas/bundled/api.json +3411 -0
- package/dist/schemas/bundled/apm.json +2371 -0
- package/dist/schemas/bundled/application.json +1253 -0
- package/dist/schemas/bundled/base.json +1622 -0
- package/dist/schemas/bundled/business.json +1279 -0
- package/dist/schemas/bundled/data-model.json +1298 -0
- package/dist/schemas/bundled/data-store.json +2025 -0
- package/dist/schemas/bundled/manifest.json +93 -0
- package/dist/schemas/bundled/motivation.json +1360 -0
- package/dist/schemas/bundled/navigation.json +1874 -0
- package/dist/schemas/bundled/security.json +3488 -0
- package/dist/schemas/bundled/technology.json +2067 -0
- package/dist/schemas/bundled/testing.json +3120 -0
- package/dist/schemas/bundled/ux.json +3592 -0
- package/dist/server/schemas.js +202 -0
- package/dist/server/schemas.js.map +7 -0
- package/dist/server/server.js +1336 -252
- package/dist/server/server.js.map +3 -3
- package/dist/server/telemetry-middleware.js +22 -15
- package/dist/server/telemetry-middleware.js.map +2 -2
- package/dist/server-entry.js +65 -0
- package/dist/server-entry.js.map +7 -0
- package/dist/telemetry/config.js +89 -0
- package/dist/telemetry/config.js.map +7 -0
- package/dist/telemetry/console-interceptor.js.map +1 -1
- package/dist/telemetry/detect.js +24 -0
- package/dist/telemetry/detect.js.map +7 -0
- package/dist/telemetry/fetch-otlp-exporter.js +112 -0
- package/dist/telemetry/fetch-otlp-exporter.js.map +7 -0
- package/dist/telemetry/index.js +153 -3
- package/dist/telemetry/index.js.map +2 -2
- package/dist/telemetry/resilient-exporter.js +22 -8
- package/dist/telemetry/resilient-exporter.js.map +2 -2
- package/dist/telemetry/resilient-log-exporter.js +17 -8
- package/dist/telemetry/resilient-log-exporter.js.map +2 -2
- package/dist/telemetry/test-instrumentation.js.map +2 -2
- package/dist/types/index.js +84 -0
- package/dist/types/index.js.map +3 -3
- package/dist/types/source-reference.js +1 -0
- package/dist/types/source-reference.js.map +7 -0
- package/dist/utils/chat-logger.js +292 -0
- package/dist/utils/chat-logger.js.map +7 -0
- package/dist/utils/claude-stream.js +8 -0
- package/dist/utils/claude-stream.js.map +7 -0
- package/dist/utils/date-utils.js +7 -0
- package/dist/utils/date-utils.js.map +7 -0
- package/dist/utils/element-utils.js.map +1 -1
- package/dist/utils/errors.js +88 -15
- package/dist/utils/errors.js.map +3 -3
- package/dist/utils/export-python-annotations.js +82 -0
- package/dist/utils/export-python-annotations.js.map +7 -0
- package/dist/utils/file-io.js +7 -2
- package/dist/utils/file-io.js.map +2 -2
- package/dist/utils/file-lock.js +156 -0
- package/dist/utils/file-lock.js.map +7 -0
- package/dist/utils/id-generator.js +18 -0
- package/dist/utils/id-generator.js.map +7 -0
- package/dist/utils/layer-name-formatter.js +16 -0
- package/dist/utils/layer-name-formatter.js.map +7 -0
- package/dist/utils/markdown-anchor.js +7 -0
- package/dist/utils/markdown-anchor.js.map +7 -0
- package/dist/utils/markdown-link.js +12 -0
- package/dist/utils/markdown-link.js.map +7 -0
- package/dist/utils/markdown-table.js +17 -0
- package/dist/utils/markdown-table.js.map +7 -0
- package/dist/utils/mermaid-utils.js +11 -0
- package/dist/utils/mermaid-utils.js.map +7 -0
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +52 -0
- package/dist/utils/source-reference.js.map +7 -0
- package/dist/utils/spec-installer.js +40 -53
- package/dist/utils/spec-installer.js.map +2 -2
- package/dist/utils/spec-version.js +5 -1
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/utils/string-utils.js +12 -0
- package/dist/utils/string-utils.js.map +7 -0
- package/dist/utils/table-formatting.js +19 -0
- package/dist/utils/table-formatting.js.map +7 -0
- package/dist/validators/index.js +4 -0
- package/dist/validators/index.js.map +2 -2
- package/dist/validators/naming-validator.js +20 -23
- package/dist/validators/naming-validator.js.map +2 -2
- package/dist/validators/reference-validator.js +23 -38
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +243 -0
- package/dist/validators/relationship-schema-validator.js.map +7 -0
- package/dist/validators/schema-validator.js +168 -68
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/semantic-validator.js +36 -60
- package/dist/validators/semantic-validator.js.map +2 -2
- package/dist/validators/types.js.map +2 -2
- package/dist/validators/validation-formatter.js +159 -0
- package/dist/validators/validation-formatter.js.map +7 -0
- package/dist/validators/validator.js +24 -38
- package/dist/validators/validator.js.map +2 -2
- package/dist/viewer/README.md +139 -0
- package/dist/viewer/assets/main-CY0Xufuj.css +1 -0
- package/dist/viewer/assets/main-DtgErnwF.js +81 -0
- package/dist/viewer/assets/vendor-CxKvDp2x.js +17 -0
- package/dist/viewer/favicon.svg +11 -0
- package/dist/viewer/fonts/inter/Inter-Black.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Bold.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-ExtraBold.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Light.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Medium.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Regular.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-SemiBold.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
- package/dist/viewer/index.html +15 -0
- package/dist/viewer/manifest.json +99 -0
- package/dist/viewer/version.json +1 -0
- package/package.json +42 -16
- package/PYTHON_CLI_PARITY.md +0 -265
- package/PYTHON_CLI_STRUCTURE_UPDATE.md +0 -246
- package/biome.json +0 -42
- package/bunfig.toml +0 -3
- package/dist/ai/claude-client.js +0 -71
- package/dist/ai/claude-client.js.map +0 -7
- package/dist/ai/context-provider.js +0 -129
- package/dist/ai/context-provider.js.map +0 -7
- package/dist/ai/tools.js +0 -231
- package/dist/ai/tools.js.map +0 -7
- package/dist/commands/migrate.js +0 -36
- package/dist/commands/migrate.js.map +0 -7
- package/dist/commands/project.js +0 -74
- package/dist/commands/project.js.map +0 -7
- package/dist/core/active-changeset.js +0 -52
- package/dist/core/active-changeset.js.map +0 -7
- package/dist/core/projection-engine.js +0 -288
- package/dist/core/projection-engine.js.map +0 -7
- package/dist/schemas/bundled/01-motivation-layer.schema.json +0 -942
- package/dist/schemas/bundled/02-business-layer.schema.json +0 -1293
- package/dist/schemas/bundled/03-security-layer.schema.json +0 -2177
- package/dist/schemas/bundled/04-application-layer.schema.json +0 -1328
- package/dist/schemas/bundled/05-technology-layer.schema.json +0 -1465
- package/dist/schemas/bundled/06-api-layer.schema.json +0 -1743
- package/dist/schemas/bundled/07-data-model-layer.schema.json +0 -1253
- package/dist/schemas/bundled/08-datastore-layer.schema.json +0 -892
- package/dist/schemas/bundled/09-ux-layer.schema.json +0 -1506
- package/dist/schemas/bundled/10-navigation-layer.schema.json +0 -843
- package/dist/schemas/bundled/11-apm-observability-layer.schema.json +0 -1430
- package/dist/schemas/bundled/12-testing-layer.schema.json +0 -1091
- package/dist/schemas/bundled/common/layer-extensions.schema.json +0 -266
- package/dist/schemas/bundled/common/predicates.schema.json +0 -493
- package/dist/schemas/bundled/common/relationships.schema.json +0 -791
- package/dist/schemas/bundled/common/source-references.schema.json +0 -71
- package/dist/schemas/bundled/link-registry.json +0 -1839
- package/dist/schemas/bundled/link-registry.schema.json +0 -339
- package/dist/schemas/bundled/relationship-catalog.json +0 -1302
- package/dist/schemas/bundled/relationship-type.schema.json +0 -221
- package/dist/utils/model-path.js +0 -22
- package/dist/utils/model-path.js.map +0 -7
- package/docs/TEST-INSTRUMENTATION.md +0 -405
- package/docs/telemetry.md +0 -427
- package/test-debug.js +0 -33
- package/test-special.mjs +0 -46
- package/test_runner.ts +0 -7
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { CLIError, ErrorCategory } from "../utils/errors.js";
|
|
5
|
+
import { CbmAnalyzer } from "./cbm-analyzer.js";
|
|
6
|
+
import { MappingLoader } from "./mapping-loader.js";
|
|
7
|
+
const ANALYZER_CLASSES = {
|
|
8
|
+
cbm: CbmAnalyzer
|
|
9
|
+
};
|
|
10
|
+
class AnalyzerRegistry {
|
|
11
|
+
static instance = null;
|
|
12
|
+
manifest = null;
|
|
13
|
+
mappers = /* @__PURE__ */ new Map();
|
|
14
|
+
analyzers = /* @__PURE__ */ new Map();
|
|
15
|
+
/**
|
|
16
|
+
* Get the singleton registry instance
|
|
17
|
+
*/
|
|
18
|
+
static getInstance() {
|
|
19
|
+
return AnalyzerRegistry.instance || (AnalyzerRegistry.instance = new AnalyzerRegistry()), AnalyzerRegistry.instance;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reset the registry singleton for testing
|
|
23
|
+
*
|
|
24
|
+
* This method clears the singleton instance and all cached state.
|
|
25
|
+
* Use this in test teardown to ensure a clean registry for each test.
|
|
26
|
+
*
|
|
27
|
+
* @internal For testing only
|
|
28
|
+
*/
|
|
29
|
+
static resetForTesting() {
|
|
30
|
+
AnalyzerRegistry.instance = null;
|
|
31
|
+
}
|
|
32
|
+
constructor() {
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the registry by loading the manifest
|
|
36
|
+
*
|
|
37
|
+
* @throws CLIError if manifest is missing or malformed
|
|
38
|
+
*/
|
|
39
|
+
async initialize() {
|
|
40
|
+
this.manifest || await this.loadManifest();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Load the analyzer manifest from bundled schemas
|
|
44
|
+
*
|
|
45
|
+
* @private
|
|
46
|
+
* @throws CLIError if manifest is missing or malformed
|
|
47
|
+
*/
|
|
48
|
+
async loadManifest() {
|
|
49
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url)), manifestPath = path.join(currentDir, "../schemas/bundled/analyzers/manifest.json");
|
|
50
|
+
let content;
|
|
51
|
+
try {
|
|
52
|
+
const fileContent = await fs.readFile(manifestPath, "utf-8");
|
|
53
|
+
content = JSON.parse(fileContent);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error && "code" in error && error.code === "ENOENT" ? `Analyzer manifest not found: ${manifestPath}` : `Failed to read analyzer manifest: ${error instanceof Error ? error.message : String(error)}`;
|
|
56
|
+
throw new CLIError(message, ErrorCategory.NOT_FOUND, [
|
|
57
|
+
"Run `npm run build:spec` to compile the analyzer artifacts",
|
|
58
|
+
"Check that spec/analyzers/ sources exist"
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
if (!content || typeof content != "object")
|
|
62
|
+
throw new CLIError("Invalid analyzer manifest structure: expected object", ErrorCategory.VALIDATION, ["Run `npm run build:spec` to recompile the analyzer artifacts"]);
|
|
63
|
+
const obj = content;
|
|
64
|
+
if (!Array.isArray(obj.analyzers))
|
|
65
|
+
throw new CLIError("Invalid analyzer manifest: analyzers must be an array", ErrorCategory.VALIDATION, ["Run `npm run build:spec` to recompile the analyzer artifacts"]);
|
|
66
|
+
if (typeof obj.specVersion != "string")
|
|
67
|
+
throw new CLIError("Invalid analyzer manifest: specVersion must be a string", ErrorCategory.VALIDATION, ["Run `npm run build:spec` to recompile the analyzer artifacts"]);
|
|
68
|
+
this.manifest = {
|
|
69
|
+
specVersion: obj.specVersion,
|
|
70
|
+
analyzers: obj.analyzers
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get an analyzer by name
|
|
75
|
+
*
|
|
76
|
+
* @param name Analyzer name (e.g., "cbm")
|
|
77
|
+
* @returns AnalyzerBackend instance, or undefined if not found
|
|
78
|
+
* @throws CLIError if manifest is not initialized or mapping cannot be loaded
|
|
79
|
+
*/
|
|
80
|
+
async getAnalyzer(name) {
|
|
81
|
+
if (await this.initialize(), this.analyzers.has(name))
|
|
82
|
+
return this.analyzers.get(name);
|
|
83
|
+
if (!this.manifest.analyzers.find((a) => a.name === name))
|
|
84
|
+
return;
|
|
85
|
+
let mapper = this.mappers.get(name);
|
|
86
|
+
mapper || (mapper = await MappingLoader.load(name), this.mappers.set(name, mapper));
|
|
87
|
+
const AnalyzerClass = ANALYZER_CLASSES[name];
|
|
88
|
+
if (!AnalyzerClass)
|
|
89
|
+
throw new CLIError(`Unknown analyzer: ${name}`, ErrorCategory.NOT_FOUND, [
|
|
90
|
+
"Check the analyzer name is spelled correctly",
|
|
91
|
+
"Ensure the analyzer class is registered in ANALYZER_CLASSES in registry.ts",
|
|
92
|
+
"Use getAnalyzerNames() to list available analyzers"
|
|
93
|
+
]);
|
|
94
|
+
const analyzer = new AnalyzerClass(mapper);
|
|
95
|
+
return this.analyzers.set(name, analyzer), analyzer;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get all available analyzers
|
|
99
|
+
*
|
|
100
|
+
* @returns Array of all analyzer instances
|
|
101
|
+
* @throws CLIError if manifest is not initialized or mapping cannot be loaded
|
|
102
|
+
*/
|
|
103
|
+
async getAllAnalyzers() {
|
|
104
|
+
const names = await this.getAnalyzerNames(), analyzers = [];
|
|
105
|
+
for (const name of names) {
|
|
106
|
+
const analyzer = await this.getAnalyzer(name);
|
|
107
|
+
analyzer && analyzers.push(analyzer);
|
|
108
|
+
}
|
|
109
|
+
return analyzers;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get all available analyzer names
|
|
113
|
+
*
|
|
114
|
+
* @returns Array of analyzer names
|
|
115
|
+
* @throws CLIError if manifest cannot be loaded
|
|
116
|
+
*/
|
|
117
|
+
async getAnalyzerNames() {
|
|
118
|
+
return await this.initialize(), this.manifest.analyzers.map((a) => a.name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
AnalyzerRegistry
|
|
123
|
+
};
|
|
124
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/registry.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Analyzer Registry\n *\n * Reads the analyzer manifest from bundled schemas and constructs analyzer instances.\n * The registry maps analyzer names to their backend implementations and metadata.\n *\n * To add a new analyzer:\n * 1. Create a new class implementing AnalyzerBackend (e.g., NewAnalyzer)\n * 2. Create analyzer definition in spec/analyzers/{name}/analyzer.json\n * 3. Add mapping for the new analyzer class in the ANALYZER_CLASSES map below\n * 4. Run `npm run build:spec` to compile the analyzer artifacts\n * 5. Run `npm run build` in the cli/ directory to sync bundled schemas\n */\n\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\nimport type { AnalyzerBackend } from \"./base-analyzer.js\";\nimport { CbmAnalyzer } from \"./cbm-analyzer.js\";\nimport { MappingLoader } from \"./mapping-loader.js\";\n\n/**\n * Type for analyzer class constructor\n */\ntype AnalyzerConstructor = new (mapper: MappingLoader) => AnalyzerBackend;\n\n/**\n * Mapping of analyzer names to their class implementations\n *\n * Add entries here when implementing a new analyzer.\n * The manifest (spec/dist/analyzers/manifest.json) defines which analyzers are available,\n * and this map provides the runtime bindings to construct instances.\n */\nconst ANALYZER_CLASSES: Record<string, AnalyzerConstructor> = {\n cbm: CbmAnalyzer,\n};\n\n/**\n * Analyzer metadata from manifest\n */\ninterface AnalyzerMetadata {\n name: string;\n version: string;\n}\n\n/**\n * Analyzer registry manifest structure\n */\ninterface RegistryManifest {\n specVersion: string;\n analyzers: AnalyzerMetadata[];\n}\n\n/**\n * Analyzer Registry - constructs and manages analyzer instances\n */\nexport class AnalyzerRegistry {\n private static instance: AnalyzerRegistry | null = null;\n private manifest: RegistryManifest | null = null;\n private mappers: Map<string, MappingLoader> = new Map();\n private analyzers: Map<string, AnalyzerBackend> = new Map();\n\n /**\n * Get the singleton registry instance\n */\n static getInstance(): AnalyzerRegistry {\n if (!AnalyzerRegistry.instance) {\n AnalyzerRegistry.instance = new AnalyzerRegistry();\n }\n return AnalyzerRegistry.instance;\n }\n\n /**\n * Reset the registry singleton for testing\n *\n * This method clears the singleton instance and all cached state.\n * Use this in test teardown to ensure a clean registry for each test.\n *\n * @internal For testing only\n */\n static resetForTesting(): void {\n AnalyzerRegistry.instance = null;\n }\n\n private constructor() {}\n\n /**\n * Initialize the registry by loading the manifest\n *\n * @throws CLIError if manifest is missing or malformed\n */\n async initialize(): Promise<void> {\n if (this.manifest) {\n return; // Already initialized\n }\n\n await this.loadManifest();\n }\n\n /**\n * Load the analyzer manifest from bundled schemas\n *\n * @private\n * @throws CLIError if manifest is missing or malformed\n */\n private async loadManifest(): Promise<void> {\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const manifestPath = path.join(\n currentDir,\n \"../schemas/bundled/analyzers/manifest.json\"\n );\n\n let content: unknown;\n try {\n const fileContent = await fs.readFile(manifestPath, \"utf-8\");\n content = JSON.parse(fileContent);\n } catch (error) {\n const message =\n error instanceof Error && \"code\" in error && (error as { code: string }).code === \"ENOENT\"\n ? `Analyzer manifest not found: ${manifestPath}`\n : `Failed to read analyzer manifest: ${error instanceof Error ? error.message : String(error)}`;\n\n throw new CLIError(message, ErrorCategory.NOT_FOUND, [\n \"Run `npm run build:spec` to compile the analyzer artifacts\",\n \"Check that spec/analyzers/ sources exist\",\n ]);\n }\n\n if (!content || typeof content !== \"object\") {\n throw new CLIError(\n \"Invalid analyzer manifest structure: expected object\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n const obj = content as Record<string, unknown>;\n if (!Array.isArray(obj.analyzers)) {\n throw new CLIError(\n \"Invalid analyzer manifest: analyzers must be an array\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n if (typeof obj.specVersion !== \"string\") {\n throw new CLIError(\n \"Invalid analyzer manifest: specVersion must be a string\",\n ErrorCategory.VALIDATION,\n [\"Run `npm run build:spec` to recompile the analyzer artifacts\"]\n );\n }\n\n this.manifest = {\n specVersion: obj.specVersion,\n analyzers: obj.analyzers as AnalyzerMetadata[],\n };\n }\n\n /**\n * Get an analyzer by name\n *\n * @param name Analyzer name (e.g., \"cbm\")\n * @returns AnalyzerBackend instance, or undefined if not found\n * @throws CLIError if manifest is not initialized or mapping cannot be loaded\n */\n async getAnalyzer(name: string): Promise<AnalyzerBackend | undefined> {\n await this.initialize();\n\n // Check cache first\n if (this.analyzers.has(name)) {\n return this.analyzers.get(name);\n }\n\n // Check if analyzer exists in manifest\n const metadata = this.manifest!.analyzers.find((a) => a.name === name);\n if (!metadata) {\n return undefined;\n }\n\n // Load the mapping\n let mapper = this.mappers.get(name);\n if (!mapper) {\n mapper = await MappingLoader.load(name);\n this.mappers.set(name, mapper);\n }\n\n // Construct the appropriate analyzer instance using the class map\n const AnalyzerClass = ANALYZER_CLASSES[name];\n if (!AnalyzerClass) {\n throw new CLIError(\n `Unknown analyzer: ${name}`,\n ErrorCategory.NOT_FOUND,\n [\n \"Check the analyzer name is spelled correctly\",\n \"Ensure the analyzer class is registered in ANALYZER_CLASSES in registry.ts\",\n \"Use getAnalyzerNames() to list available analyzers\",\n ]\n );\n }\n\n const analyzer = new AnalyzerClass(mapper);\n this.analyzers.set(name, analyzer);\n return analyzer;\n }\n\n /**\n * Get all available analyzers\n *\n * @returns Array of all analyzer instances\n * @throws CLIError if manifest is not initialized or mapping cannot be loaded\n */\n async getAllAnalyzers(): Promise<AnalyzerBackend[]> {\n const names = await this.getAnalyzerNames();\n const analyzers: AnalyzerBackend[] = [];\n\n for (const name of names) {\n const analyzer = await this.getAnalyzer(name);\n if (analyzer) {\n analyzers.push(analyzer);\n }\n }\n\n return analyzers;\n }\n\n /**\n * Get all available analyzer names\n *\n * @returns Array of analyzer names\n * @throws CLIError if manifest cannot be loaded\n */\n async getAnalyzerNames(): Promise<string[]> {\n await this.initialize();\n\n return this.manifest!.analyzers.map((a) => a.name);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAcA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,UAAU,qBAAqB;AAExC,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAc9B,MAAM,mBAAwD;EAC5D,KAAK;;AAsBD,MAAO,iBAAgB;EACnB,OAAO,WAAoC;EAC3C,WAAoC;EACpC,UAAsC,oBAAI,IAAG;EAC7C,YAA0C,oBAAI,IAAG;;;;EAKzD,OAAO,cAAW;AAChB,WAAK,iBAAiB,aACpB,iBAAiB,WAAW,IAAI,iBAAgB,IAE3C,iBAAiB;EAC1B;;;;;;;;;EAUA,OAAO,kBAAe;AACpB,qBAAiB,WAAW;EAC9B;EAEA,cAAA;EAAuB;;;;;;EAOvB,MAAM,aAAU;AACd,IAAI,KAAK,YAIT,MAAM,KAAK,aAAY;EACzB;;;;;;;EAQQ,MAAM,eAAY;AACxB,UAAM,aAAa,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GACxD,eAAe,KAAK,KACxB,YACA,4CAA4C;AAG9C,QAAI;AACJ,QAAI;AACF,YAAM,cAAc,MAAM,GAAG,SAAS,cAAc,OAAO;AAC3D,gBAAU,KAAK,MAAM,WAAW;IAClC,SAAS,OAAO;AACd,YAAM,UACJ,iBAAiB,SAAS,UAAU,SAAU,MAA2B,SAAS,WAC9E,gCAAgC,YAAY,KAC5C,qCAAqC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAEjG,YAAM,IAAI,SAAS,SAAS,cAAc,WAAW;QACnD;QACA;OACD;IACH;AAEA,QAAI,CAAC,WAAW,OAAO,WAAY;AACjC,YAAM,IAAI,SACR,wDACA,cAAc,YACd,CAAC,8DAA8D,CAAC;AAIpE,UAAM,MAAM;AACZ,QAAI,CAAC,MAAM,QAAQ,IAAI,SAAS;AAC9B,YAAM,IAAI,SACR,yDACA,cAAc,YACd,CAAC,8DAA8D,CAAC;AAIpE,QAAI,OAAO,IAAI,eAAgB;AAC7B,YAAM,IAAI,SACR,2DACA,cAAc,YACd,CAAC,8DAA8D,CAAC;AAIpE,SAAK,WAAW;MACd,aAAa,IAAI;MACjB,WAAW,IAAI;;EAEnB;;;;;;;;EASA,MAAM,YAAY,MAAY;AAI5B,QAHA,MAAM,KAAK,WAAU,GAGjB,KAAK,UAAU,IAAI,IAAI;AACzB,aAAO,KAAK,UAAU,IAAI,IAAI;AAKhC,QAAI,CADa,KAAK,SAAU,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAEnE;AAIF,QAAI,SAAS,KAAK,QAAQ,IAAI,IAAI;AAClC,IAAK,WACH,SAAS,MAAM,cAAc,KAAK,IAAI,GACtC,KAAK,QAAQ,IAAI,MAAM,MAAM;AAI/B,UAAM,gBAAgB,iBAAiB,IAAI;AAC3C,QAAI,CAAC;AACH,YAAM,IAAI,SACR,qBAAqB,IAAI,IACzB,cAAc,WACd;QACE;QACA;QACA;OACD;AAIL,UAAM,WAAW,IAAI,cAAc,MAAM;AACzC,gBAAK,UAAU,IAAI,MAAM,QAAQ,GAC1B;EACT;;;;;;;EAQA,MAAM,kBAAe;AACnB,UAAM,QAAQ,MAAM,KAAK,iBAAgB,GACnC,YAA+B,CAAA;AAErC,eAAW,QAAQ,OAAO;AACxB,YAAM,WAAW,MAAM,KAAK,YAAY,IAAI;AAC5C,MAAI,YACF,UAAU,KAAK,QAAQ;IAE3B;AAEA,WAAO;EACT;;;;;;;EAQA,MAAM,mBAAgB;AACpB,iBAAM,KAAK,WAAU,GAEd,KAAK,SAAU,UAAU,IAAI,CAAC,MAAM,EAAE,IAAI;EACnD;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
function validateAnalyzerName(analyzerName) {
|
|
4
|
+
if (!analyzerName)
|
|
5
|
+
throw new Error("Analyzer name cannot be empty");
|
|
6
|
+
if (analyzerName.includes("/") || analyzerName.includes("\\"))
|
|
7
|
+
throw new Error("Analyzer name cannot contain path separators (/ or \\)");
|
|
8
|
+
if (analyzerName.includes(".."))
|
|
9
|
+
throw new Error("Analyzer name cannot contain parent directory references (..)");
|
|
10
|
+
}
|
|
11
|
+
function validateSessionState(data) {
|
|
12
|
+
if (!data || typeof data != "object")
|
|
13
|
+
throw new Error("SessionState must be an object");
|
|
14
|
+
const obj = data;
|
|
15
|
+
if (typeof obj.active_analyzer != "string")
|
|
16
|
+
throw new Error("SessionState.active_analyzer must be a string");
|
|
17
|
+
if (typeof obj.selected_at != "string")
|
|
18
|
+
throw new Error("SessionState.selected_at must be a string");
|
|
19
|
+
return {
|
|
20
|
+
active_analyzer: obj.active_analyzer,
|
|
21
|
+
selected_at: obj.selected_at
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function validateIndexMeta(data) {
|
|
25
|
+
if (!data || typeof data != "object")
|
|
26
|
+
throw new Error("IndexMeta must be an object");
|
|
27
|
+
const obj = data;
|
|
28
|
+
if (typeof obj.git_head != "string")
|
|
29
|
+
throw new Error("IndexMeta.git_head must be a string");
|
|
30
|
+
if (typeof obj.timestamp != "string")
|
|
31
|
+
throw new Error("IndexMeta.timestamp must be a string");
|
|
32
|
+
if (typeof obj.node_count != "number")
|
|
33
|
+
throw new Error("IndexMeta.node_count must be a number");
|
|
34
|
+
if (typeof obj.edge_count != "number")
|
|
35
|
+
throw new Error("IndexMeta.edge_count must be a number");
|
|
36
|
+
return {
|
|
37
|
+
git_head: obj.git_head,
|
|
38
|
+
timestamp: obj.timestamp,
|
|
39
|
+
node_count: obj.node_count,
|
|
40
|
+
edge_count: obj.edge_count
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function validateAnalyzerStatus(data) {
|
|
44
|
+
if (!data || typeof data != "object")
|
|
45
|
+
throw new Error("AnalyzerStatus must be an object");
|
|
46
|
+
const obj = data;
|
|
47
|
+
if (!obj.detected || typeof obj.detected != "object")
|
|
48
|
+
throw new Error("AnalyzerStatus.detected must be an object");
|
|
49
|
+
const detected = obj.detected;
|
|
50
|
+
if (typeof detected.installed != "boolean")
|
|
51
|
+
throw new Error("AnalyzerStatus.detected.installed must be a boolean");
|
|
52
|
+
const installed = detected.installed;
|
|
53
|
+
if (installed) {
|
|
54
|
+
if (typeof detected.binary_path != "string")
|
|
55
|
+
throw new Error("AnalyzerStatus.detected.binary_path must be a string when installed: true");
|
|
56
|
+
if (detected.version !== void 0 && typeof detected.version != "string")
|
|
57
|
+
throw new Error("AnalyzerStatus.detected.version must be a string");
|
|
58
|
+
if (detected.mcp_registered !== void 0 && typeof detected.mcp_registered != "boolean")
|
|
59
|
+
throw new Error("AnalyzerStatus.detected.mcp_registered must be a boolean");
|
|
60
|
+
if (detected.contract_ok !== void 0 && typeof detected.contract_ok != "boolean")
|
|
61
|
+
throw new Error("AnalyzerStatus.detected.contract_ok must be a boolean");
|
|
62
|
+
} else if (detected.binary_path !== void 0)
|
|
63
|
+
throw new Error("AnalyzerStatus.detected.binary_path must not be present when installed: false");
|
|
64
|
+
if (typeof obj.indexed != "boolean")
|
|
65
|
+
throw new Error("AnalyzerStatus.indexed must be a boolean");
|
|
66
|
+
if (typeof obj.fresh != "boolean")
|
|
67
|
+
throw new Error("AnalyzerStatus.fresh must be a boolean");
|
|
68
|
+
if (obj.indexed) {
|
|
69
|
+
if (obj.index_meta === void 0)
|
|
70
|
+
throw new Error("AnalyzerStatus.index_meta must be present when indexed: true");
|
|
71
|
+
const validatedIndexMeta = validateIndexMeta(obj.index_meta);
|
|
72
|
+
if (obj.last_indexed !== void 0 && typeof obj.last_indexed != "string")
|
|
73
|
+
throw new Error("AnalyzerStatus.last_indexed must be a string");
|
|
74
|
+
return {
|
|
75
|
+
detected: installed ? {
|
|
76
|
+
installed: !0,
|
|
77
|
+
binary_path: detected.binary_path,
|
|
78
|
+
version: detected.version,
|
|
79
|
+
mcp_registered: detected.mcp_registered,
|
|
80
|
+
contract_ok: detected.contract_ok
|
|
81
|
+
} : {
|
|
82
|
+
installed: !1
|
|
83
|
+
},
|
|
84
|
+
indexed: !0,
|
|
85
|
+
index_meta: validatedIndexMeta,
|
|
86
|
+
fresh: obj.fresh,
|
|
87
|
+
last_indexed: obj.last_indexed
|
|
88
|
+
};
|
|
89
|
+
} else {
|
|
90
|
+
if (obj.index_meta !== void 0)
|
|
91
|
+
throw new Error("AnalyzerStatus.index_meta must not be present when indexed: false");
|
|
92
|
+
if (obj.last_indexed !== void 0 && typeof obj.last_indexed != "string")
|
|
93
|
+
throw new Error("AnalyzerStatus.last_indexed must be a string");
|
|
94
|
+
return {
|
|
95
|
+
detected: installed ? {
|
|
96
|
+
installed: !0,
|
|
97
|
+
binary_path: detected.binary_path,
|
|
98
|
+
version: detected.version,
|
|
99
|
+
mcp_registered: detected.mcp_registered,
|
|
100
|
+
contract_ok: detected.contract_ok
|
|
101
|
+
} : {
|
|
102
|
+
installed: !1
|
|
103
|
+
},
|
|
104
|
+
indexed: !1,
|
|
105
|
+
fresh: obj.fresh,
|
|
106
|
+
last_indexed: obj.last_indexed
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function getStateDir(baseDir, analyzerName) {
|
|
111
|
+
const base = baseDir ?? process.cwd(), analyzersDir = path.join(base, ".dr", "analyzers");
|
|
112
|
+
return analyzerName !== void 0 ? (validateAnalyzerName(analyzerName), path.join(analyzersDir, analyzerName)) : analyzersDir;
|
|
113
|
+
}
|
|
114
|
+
async function ensureStateDir(baseDir, analyzerName) {
|
|
115
|
+
await fs.mkdir(getStateDir(baseDir, analyzerName), { recursive: !0 });
|
|
116
|
+
}
|
|
117
|
+
function getStatePath(filename, baseDir, analyzerName) {
|
|
118
|
+
return path.join(getStateDir(baseDir, analyzerName), filename);
|
|
119
|
+
}
|
|
120
|
+
async function readSession(baseDir) {
|
|
121
|
+
const filePath = getStatePath("session.json", baseDir);
|
|
122
|
+
try {
|
|
123
|
+
const content = await fs.readFile(filePath, "utf-8"), parsed = JSON.parse(content);
|
|
124
|
+
return validateSessionState(parsed);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
127
|
+
return null;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function writeSession(state, baseDir) {
|
|
132
|
+
await ensureStateDir(baseDir);
|
|
133
|
+
const filePath = getStatePath("session.json", baseDir), content = JSON.stringify(state, null, 2);
|
|
134
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
135
|
+
}
|
|
136
|
+
async function readIndexMeta(baseDir, analyzerName) {
|
|
137
|
+
const filePath = getStatePath("index-meta.json", baseDir, analyzerName);
|
|
138
|
+
try {
|
|
139
|
+
const content = await fs.readFile(filePath, "utf-8"), parsed = JSON.parse(content);
|
|
140
|
+
return validateIndexMeta(parsed);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
143
|
+
return null;
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function writeIndexMeta(meta, baseDir, analyzerName) {
|
|
148
|
+
await ensureStateDir(baseDir, analyzerName);
|
|
149
|
+
const filePath = getStatePath("index-meta.json", baseDir, analyzerName), content = JSON.stringify(meta, null, 2);
|
|
150
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
async function readStatus(baseDir, analyzerName) {
|
|
153
|
+
const filePath = getStatePath("status.json", baseDir, analyzerName);
|
|
154
|
+
try {
|
|
155
|
+
const content = await fs.readFile(filePath, "utf-8"), parsed = JSON.parse(content);
|
|
156
|
+
return validateAnalyzerStatus(parsed);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
159
|
+
return null;
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function writeStatus(status, baseDir, analyzerName) {
|
|
164
|
+
await ensureStateDir(baseDir, analyzerName);
|
|
165
|
+
const filePath = getStatePath("status.json", baseDir, analyzerName), content = JSON.stringify(status, null, 2);
|
|
166
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
readIndexMeta,
|
|
170
|
+
readSession,
|
|
171
|
+
readStatus,
|
|
172
|
+
writeIndexMeta,
|
|
173
|
+
writeSession,
|
|
174
|
+
writeStatus
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=session-state.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/session-state.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Session state and metadata persistence\n *\n * Provides file-based storage for analyzer session state, index metadata,\n * and analyzer status information in `.dr/analyzers/` directory.\n *\n * Per-analyzer files (index-meta.json, status.json) are stored in subdirectories:\n * - `.dr/analyzers/{analyzerName}/index-meta.json`\n * - `.dr/analyzers/{analyzerName}/status.json`\n *\n * Global files (session.json) are stored at the base level:\n * - `.dr/analyzers/session.json`\n *\n * All operations are async and use JSON for serialization.\n * Missing files gracefully return null rather than throwing errors.\n * Read operations validate JSON structure and throw errors for corrupted files.\n */\n\nimport { promises as fs } from \"fs\";\nimport * as path from \"path\";\nimport type { SessionState, IndexMeta, AnalyzerStatus } from \"./types.js\";\n\n/**\n * Validate analyzer name to prevent path traversal attacks\n * Rejects names containing `/`, `\\`, or `..`\n *\n * @param analyzerName Analyzer name to validate\n * @throws Error if name contains path traversal characters\n */\nfunction validateAnalyzerName(analyzerName: string): void {\n if (!analyzerName) {\n throw new Error(\"Analyzer name cannot be empty\");\n }\n\n if (analyzerName.includes(\"/\") || analyzerName.includes(\"\\\\\")) {\n throw new Error(\"Analyzer name cannot contain path separators (/ or \\\\)\");\n }\n\n if (analyzerName.includes(\"..\")) {\n throw new Error(\"Analyzer name cannot contain parent directory references (..)\");\n }\n}\n\n/**\n * Validate SessionState structure\n * @throws Error if data is missing required fields\n */\nfunction validateSessionState(data: unknown): SessionState {\n if (!data || typeof data !== \"object\") {\n throw new Error(\"SessionState must be an object\");\n }\n\n const obj = data as Record<string, unknown>;\n\n if (typeof obj.active_analyzer !== \"string\") {\n throw new Error(\"SessionState.active_analyzer must be a string\");\n }\n\n if (typeof obj.selected_at !== \"string\") {\n throw new Error(\"SessionState.selected_at must be a string\");\n }\n\n return {\n active_analyzer: obj.active_analyzer,\n selected_at: obj.selected_at,\n };\n}\n\n/**\n * Validate IndexMeta structure\n * @throws Error if data is missing required fields\n */\nfunction validateIndexMeta(data: unknown): IndexMeta {\n if (!data || typeof data !== \"object\") {\n throw new Error(\"IndexMeta must be an object\");\n }\n\n const obj = data as Record<string, unknown>;\n\n if (typeof obj.git_head !== \"string\") {\n throw new Error(\"IndexMeta.git_head must be a string\");\n }\n\n if (typeof obj.timestamp !== \"string\") {\n throw new Error(\"IndexMeta.timestamp must be a string\");\n }\n\n if (typeof obj.node_count !== \"number\") {\n throw new Error(\"IndexMeta.node_count must be a number\");\n }\n\n if (typeof obj.edge_count !== \"number\") {\n throw new Error(\"IndexMeta.edge_count must be a number\");\n }\n\n return {\n git_head: obj.git_head,\n timestamp: obj.timestamp,\n node_count: obj.node_count as number,\n edge_count: obj.edge_count as number,\n };\n}\n\n/**\n * Validate AnalyzerStatus structure\n * Enforces discriminated union constraints:\n * - When installed: true, binary_path must be present\n * - When installed: false, binary_path must not be present\n * - When indexed: true, index_meta must be present\n * - When indexed: false, index_meta must not be present\n *\n * @throws Error if data is missing required fields or violates discriminated union constraints\n */\nfunction validateAnalyzerStatus(data: unknown): AnalyzerStatus {\n if (!data || typeof data !== \"object\") {\n throw new Error(\"AnalyzerStatus must be an object\");\n }\n\n const obj = data as Record<string, unknown>;\n\n if (!obj.detected || typeof obj.detected !== \"object\") {\n throw new Error(\"AnalyzerStatus.detected must be an object\");\n }\n\n const detected = obj.detected as Record<string, unknown>;\n if (typeof detected.installed !== \"boolean\") {\n throw new Error(\"AnalyzerStatus.detected.installed must be a boolean\");\n }\n\n // Validate DetectionResult discriminated union\n const installed = detected.installed as boolean;\n\n if (installed) {\n // When installed: true, binary_path is required\n if (typeof detected.binary_path !== \"string\") {\n throw new Error(\"AnalyzerStatus.detected.binary_path must be a string when installed: true\");\n }\n\n // Validate optional fields\n if (detected.version !== undefined && typeof detected.version !== \"string\") {\n throw new Error(\"AnalyzerStatus.detected.version must be a string\");\n }\n\n if (detected.mcp_registered !== undefined && typeof detected.mcp_registered !== \"boolean\") {\n throw new Error(\"AnalyzerStatus.detected.mcp_registered must be a boolean\");\n }\n\n if (detected.contract_ok !== undefined && typeof detected.contract_ok !== \"boolean\") {\n throw new Error(\"AnalyzerStatus.detected.contract_ok must be a boolean\");\n }\n } else {\n // When installed: false, binary_path must not be present\n if (detected.binary_path !== undefined) {\n throw new Error(\"AnalyzerStatus.detected.binary_path must not be present when installed: false\");\n }\n }\n\n if (typeof obj.indexed !== \"boolean\") {\n throw new Error(\"AnalyzerStatus.indexed must be a boolean\");\n }\n\n if (typeof obj.fresh !== \"boolean\") {\n throw new Error(\"AnalyzerStatus.fresh must be a boolean\");\n }\n\n // Validate AnalyzerStatus discriminated union\n const indexed = obj.indexed as boolean;\n if (indexed) {\n // When indexed: true, index_meta is required\n if (obj.index_meta === undefined) {\n throw new Error(\"AnalyzerStatus.index_meta must be present when indexed: true\");\n }\n const validatedIndexMeta = validateIndexMeta(obj.index_meta);\n\n // Validate optional last_indexed if present\n if (obj.last_indexed !== undefined && typeof obj.last_indexed !== \"string\") {\n throw new Error(\"AnalyzerStatus.last_indexed must be a string\");\n }\n\n // Build detected from validated detection data based on installed flag\n const detectedResult = installed\n ? {\n installed: true,\n binary_path: detected.binary_path as string,\n version: detected.version as string | undefined,\n mcp_registered: detected.mcp_registered as boolean | undefined,\n contract_ok: detected.contract_ok as boolean | undefined,\n }\n : {\n installed: false,\n };\n\n return {\n detected: detectedResult,\n indexed: true,\n index_meta: validatedIndexMeta,\n fresh: obj.fresh as boolean,\n last_indexed: obj.last_indexed as string | undefined,\n } as AnalyzerStatus;\n } else {\n // When indexed: false, index_meta must not be present\n if (obj.index_meta !== undefined) {\n throw new Error(\"AnalyzerStatus.index_meta must not be present when indexed: false\");\n }\n\n // Validate optional last_indexed if present\n if (obj.last_indexed !== undefined && typeof obj.last_indexed !== \"string\") {\n throw new Error(\"AnalyzerStatus.last_indexed must be a string\");\n }\n\n // Build detected from validated detection data based on installed flag\n const detectedResult = installed\n ? {\n installed: true,\n binary_path: detected.binary_path as string,\n version: detected.version as string | undefined,\n mcp_registered: detected.mcp_registered as boolean | undefined,\n contract_ok: detected.contract_ok as boolean | undefined,\n }\n : {\n installed: false,\n };\n\n return {\n detected: detectedResult,\n indexed: false,\n fresh: obj.fresh as boolean,\n last_indexed: obj.last_indexed as string | undefined,\n } as AnalyzerStatus;\n }\n}\n\n/**\n * Get the base directory for analyzer state files\n * Resolves to `.dr/analyzers/` (and optionally `{analyzerName}/` subdirectory)\n *\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Optional analyzer name for per-analyzer subdirectory\n * @throws Error if analyzerName is provided but empty or contains path traversal characters\n */\nfunction getStateDir(baseDir?: string, analyzerName?: string): string {\n const base = baseDir ?? process.cwd();\n const analyzersDir = path.join(base, \".dr\", \"analyzers\");\n\n if (analyzerName !== undefined) {\n validateAnalyzerName(analyzerName);\n return path.join(analyzersDir, analyzerName);\n }\n\n return analyzersDir;\n}\n\n/**\n * Ensure the state directory exists\n * Creates intermediate directories with recursive flag\n *\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Optional analyzer name for per-analyzer subdirectory\n */\nasync function ensureStateDir(baseDir?: string, analyzerName?: string): Promise<void> {\n await fs.mkdir(getStateDir(baseDir, analyzerName), { recursive: true });\n}\n\n/**\n * Get the full path to a state file\n *\n * @param filename Name of the state file\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Optional analyzer name for per-analyzer subdirectory\n * @returns Full path to the file\n */\nfunction getStatePath(filename: string, baseDir?: string, analyzerName?: string): string {\n return path.join(getStateDir(baseDir, analyzerName), filename);\n}\n\n/**\n * Read session state from `.dr/analyzers/session.json`\n *\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @returns Parsed and validated session state, or null if file does not exist\n * @throws Error if file exists but is malformed JSON or invalid structure\n */\nexport async function readSession(baseDir?: string): Promise<SessionState | null> {\n const filePath = getStatePath(\"session.json\", baseDir);\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(content);\n return validateSessionState(parsed);\n } catch (error) {\n if (error instanceof Error && \"code\" in error && (error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Write session state to `.dr/analyzers/session.json`\n *\n * Creates the state directory if it does not exist.\n * Writes with pretty-printed JSON (2-space indentation).\n *\n * @param state Session state to persist\n * @param baseDir Optional base directory. Defaults to process.cwd()\n */\nexport async function writeSession(state: SessionState, baseDir?: string): Promise<void> {\n await ensureStateDir(baseDir);\n const filePath = getStatePath(\"session.json\", baseDir);\n const content = JSON.stringify(state, null, 2);\n await fs.writeFile(filePath, content, \"utf-8\");\n}\n\n/**\n * Read index metadata from `.dr/analyzers/{analyzerName}/index-meta.json`\n *\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Analyzer name for per-analyzer subdirectory (required)\n * @returns Parsed and validated index metadata, or null if file does not exist\n * @throws Error if file exists but is malformed JSON or invalid structure\n */\nexport async function readIndexMeta(\n baseDir: string | undefined,\n analyzerName: string\n): Promise<IndexMeta | null> {\n const filePath = getStatePath(\"index-meta.json\", baseDir, analyzerName);\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(content);\n return validateIndexMeta(parsed);\n } catch (error) {\n if (error instanceof Error && \"code\" in error && (error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Write index metadata to `.dr/analyzers/{analyzerName}/index-meta.json`\n *\n * Creates the state directory (including analyzer subdirectory) if it does not exist.\n * Writes with pretty-printed JSON (2-space indentation).\n *\n * @param meta Index metadata to persist\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Analyzer name for per-analyzer subdirectory (required)\n */\nexport async function writeIndexMeta(\n meta: IndexMeta,\n baseDir: string | undefined,\n analyzerName: string\n): Promise<void> {\n await ensureStateDir(baseDir, analyzerName);\n const filePath = getStatePath(\"index-meta.json\", baseDir, analyzerName);\n const content = JSON.stringify(meta, null, 2);\n await fs.writeFile(filePath, content, \"utf-8\");\n}\n\n/**\n * Read analyzer status from `.dr/analyzers/{analyzerName}/status.json`\n *\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Analyzer name for per-analyzer subdirectory (required)\n * @returns Parsed and validated analyzer status, or null if file does not exist\n * @throws Error if file exists but is malformed JSON or invalid structure\n */\nexport async function readStatus(\n baseDir: string | undefined,\n analyzerName: string\n): Promise<AnalyzerStatus | null> {\n const filePath = getStatePath(\"status.json\", baseDir, analyzerName);\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n const parsed = JSON.parse(content);\n return validateAnalyzerStatus(parsed);\n } catch (error) {\n if (error instanceof Error && \"code\" in error && (error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n}\n\n/**\n * Write analyzer status to `.dr/analyzers/{analyzerName}/status.json`\n *\n * Creates the state directory (including analyzer subdirectory) if it does not exist.\n * Writes with pretty-printed JSON (2-space indentation).\n *\n * @param status Analyzer status to persist\n * @param baseDir Optional base directory. Defaults to process.cwd()\n * @param analyzerName Analyzer name for per-analyzer subdirectory (required)\n */\nexport async function writeStatus(\n status: AnalyzerStatus,\n baseDir: string | undefined,\n analyzerName: string\n): Promise<void> {\n await ensureStateDir(baseDir, analyzerName);\n const filePath = getStatePath(\"status.json\", baseDir, analyzerName);\n const content = JSON.stringify(status, null, 2);\n await fs.writeFile(filePath, content, \"utf-8\");\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
class StdioClient {
|
|
3
|
+
proc = null;
|
|
4
|
+
requestId = 0;
|
|
5
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Spawn an analyzer process
|
|
8
|
+
*
|
|
9
|
+
* @param binaryPath Path to the analyzer binary
|
|
10
|
+
* @param args Optional command-line arguments (or env object for backwards compatibility)
|
|
11
|
+
* @param env Optional environment variables (merged with process.env)
|
|
12
|
+
* @throws Error if spawn fails
|
|
13
|
+
*/
|
|
14
|
+
spawn(binaryPath, args, env) {
|
|
15
|
+
if (this.proc)
|
|
16
|
+
throw new Error("Process already spawned");
|
|
17
|
+
let actualArgs = [], actualEnv = process.env;
|
|
18
|
+
if (Array.isArray(args) ? (actualArgs = args, actualEnv = { ...actualEnv, ...env }) : args && typeof args == "object" && (actualEnv = { ...actualEnv, ...args }), this.proc = spawn(binaryPath, actualArgs, {
|
|
19
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
20
|
+
env: actualEnv
|
|
21
|
+
}), this.proc.stdout) {
|
|
22
|
+
this.proc.stdout.setEncoding("utf-8");
|
|
23
|
+
let buffer = "";
|
|
24
|
+
this.proc.stdout.on("data", (chunk) => {
|
|
25
|
+
buffer += chunk;
|
|
26
|
+
const lines = buffer.split(`
|
|
27
|
+
`);
|
|
28
|
+
buffer = lines.pop() || "";
|
|
29
|
+
for (const line of lines)
|
|
30
|
+
if (line.trim())
|
|
31
|
+
try {
|
|
32
|
+
const message = JSON.parse(line);
|
|
33
|
+
this.handleMessage(message);
|
|
34
|
+
} catch {
|
|
35
|
+
const firstKey = this.pendingRequests.keys().next().value;
|
|
36
|
+
if (firstKey !== void 0) {
|
|
37
|
+
const pending = this.pendingRequests.get(firstKey);
|
|
38
|
+
pending && (clearTimeout(pending.timeout), this.pendingRequests.delete(firstKey), pending.reject(new Error(`Failed to parse JSON-RPC response from analyzer: ${line}`)));
|
|
39
|
+
} else
|
|
40
|
+
console.error(`Failed to parse JSON-RPC message from analyzer: ${line}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
this.proc.stderr && (this.proc.stderr.setEncoding("utf-8"), this.proc.stderr.on("data", (chunk) => {
|
|
45
|
+
console.error(`[Analyzer stderr] ${chunk}`);
|
|
46
|
+
})), this.proc.on("exit", (code, signal) => {
|
|
47
|
+
this.proc = null;
|
|
48
|
+
for (const [, { reject, timeout }] of this.pendingRequests)
|
|
49
|
+
clearTimeout(timeout), reject(new Error(`Process exited with code ${code} (signal: ${signal})`));
|
|
50
|
+
this.pendingRequests.clear();
|
|
51
|
+
}), this.proc.on("error", (error) => {
|
|
52
|
+
for (const [, { reject, timeout }] of this.pendingRequests)
|
|
53
|
+
clearTimeout(timeout), reject(error);
|
|
54
|
+
this.pendingRequests.clear();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Initialize the analyzer with client information
|
|
59
|
+
*
|
|
60
|
+
* Sends an initialize request and waits for the response.
|
|
61
|
+
* Times out after 5 seconds if no response is received. Was 2 seconds,
|
|
62
|
+
* but that became too tight for slower analyzer binary cold starts
|
|
63
|
+
* (observed failing in CI against codebase-memory-mcp 0.10.x - see #802).
|
|
64
|
+
*
|
|
65
|
+
* @param clientInfo Client information object
|
|
66
|
+
* @throws Error if initialize times out or fails
|
|
67
|
+
*/
|
|
68
|
+
async initialize(clientInfo) {
|
|
69
|
+
return this.callTool("initialize", clientInfo, { timeout: 5e3 });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Call a tool on the analyzer
|
|
73
|
+
*
|
|
74
|
+
* Sends a JSON-RPC request and waits for the response.
|
|
75
|
+
* Defaults to 30-second timeout for graph queries.
|
|
76
|
+
*
|
|
77
|
+
* @param name Tool name (method)
|
|
78
|
+
* @param params Tool parameters
|
|
79
|
+
* @param options Optional configuration (timeout in milliseconds)
|
|
80
|
+
* @returns Tool result
|
|
81
|
+
* @throws Error if tool call fails or times out
|
|
82
|
+
*/
|
|
83
|
+
async callTool(name, params, options) {
|
|
84
|
+
if (!this.proc)
|
|
85
|
+
throw new Error("Process not spawned");
|
|
86
|
+
const timeout = options?.timeout ?? 3e4, id = ++this.requestId, request = {
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
method: name,
|
|
89
|
+
params,
|
|
90
|
+
id
|
|
91
|
+
};
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const timeoutHandle = setTimeout(() => {
|
|
94
|
+
this.pendingRequests.delete(id), reject(new Error(`Tool call '${name}' timed out after ${timeout}ms`));
|
|
95
|
+
}, timeout);
|
|
96
|
+
this.pendingRequests.set(id, { resolve, reject, timeout: timeoutHandle });
|
|
97
|
+
try {
|
|
98
|
+
const line = JSON.stringify(request) + `
|
|
99
|
+
`;
|
|
100
|
+
if (!this.proc || !this.proc.stdin) {
|
|
101
|
+
clearTimeout(timeoutHandle), this.pendingRequests.delete(id), reject(new Error("Process or stdin closed before write could complete"));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.proc.stdin.write(line) || this.proc.stdin.once("drain", () => {
|
|
105
|
+
});
|
|
106
|
+
} catch (error) {
|
|
107
|
+
clearTimeout(timeoutHandle), this.pendingRequests.delete(id), reject(error);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Close the connection and clean up
|
|
113
|
+
*
|
|
114
|
+
* Sends a shutdown notification and terminates the process.
|
|
115
|
+
* Sends SIGTERM first, then escalates to SIGKILL after 1 second if needed.
|
|
116
|
+
* Cleans up all pending requests and event listeners.
|
|
117
|
+
*/
|
|
118
|
+
/**
|
|
119
|
+
* Invoke a tool on the analyzer via the MCP tools/call protocol
|
|
120
|
+
*
|
|
121
|
+
* Sends a tools/call request with {name, arguments} and unwraps the
|
|
122
|
+
* MCP content envelope from the response (content[0].text parsed as JSON).
|
|
123
|
+
*
|
|
124
|
+
* Use this for all tool invocations. Use callTool() only for MCP protocol
|
|
125
|
+
* methods (initialize, tools/list).
|
|
126
|
+
*
|
|
127
|
+
* @param name Tool name (e.g., "search_graph", "index_repository")
|
|
128
|
+
* @param args Tool arguments to pass as the arguments field
|
|
129
|
+
* @param options Optional configuration (timeout in milliseconds)
|
|
130
|
+
* @returns Parsed tool result unwrapped from MCP content envelope
|
|
131
|
+
*/
|
|
132
|
+
async invokeTool(name, args, options) {
|
|
133
|
+
const response = await this.callTool("tools/call", { name, arguments: args ?? {} }, options);
|
|
134
|
+
if (response?.content && Array.isArray(response.content) && response.content.length > 0) {
|
|
135
|
+
const first = response.content[0];
|
|
136
|
+
if (first.type === "text" && typeof first.text == "string")
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(first.text);
|
|
139
|
+
} catch {
|
|
140
|
+
return first.text;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
close() {
|
|
146
|
+
if (!this.proc)
|
|
147
|
+
return;
|
|
148
|
+
const procToClose = this.proc;
|
|
149
|
+
this.proc = null;
|
|
150
|
+
try {
|
|
151
|
+
const line = JSON.stringify({
|
|
152
|
+
jsonrpc: "2.0",
|
|
153
|
+
method: "shutdown"
|
|
154
|
+
}) + `
|
|
155
|
+
`;
|
|
156
|
+
procToClose.stdin?.write(line);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
for (const [, { reject, timeout }] of this.pendingRequests)
|
|
160
|
+
clearTimeout(timeout), reject(new Error("Client closed"));
|
|
161
|
+
this.pendingRequests.clear(), procToClose.stdout && procToClose.stdout.removeAllListeners(), procToClose.stderr && procToClose.stderr.removeAllListeners(), procToClose.removeAllListeners();
|
|
162
|
+
try {
|
|
163
|
+
procToClose.stdin?.end(), procToClose.killed || (procToClose.kill("SIGTERM"), setTimeout(() => {
|
|
164
|
+
if (procToClose.exitCode === null)
|
|
165
|
+
try {
|
|
166
|
+
procToClose.kill("SIGKILL");
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}, 1e3).unref());
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Handle a JSON-RPC message from the analyzer
|
|
175
|
+
*
|
|
176
|
+
* Matches responses to pending requests by ID and resolves/rejects accordingly.
|
|
177
|
+
* Extracts error information from JSON-RPC error objects.
|
|
178
|
+
*
|
|
179
|
+
* @param message Parsed JSON-RPC message
|
|
180
|
+
*/
|
|
181
|
+
handleMessage(message) {
|
|
182
|
+
if (!message || typeof message != "object")
|
|
183
|
+
return;
|
|
184
|
+
const msg = message;
|
|
185
|
+
if (!("id" in msg) || typeof msg.id != "number")
|
|
186
|
+
return;
|
|
187
|
+
const msgId = msg.id, pending = this.pendingRequests.get(msgId);
|
|
188
|
+
if (!pending) {
|
|
189
|
+
console.warn(`Received response with unmatched ID ${msgId} (type: ${typeof msgId}). Pending request IDs: [${Array.from(this.pendingRequests.keys()).join(", ")}]. This may indicate a type mismatch or stale response.`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const { resolve, reject, timeout } = pending;
|
|
193
|
+
if (clearTimeout(timeout), this.pendingRequests.delete(msgId), "error" in msg && msg.error !== null && typeof msg.error == "object") {
|
|
194
|
+
const error = msg.error, errMessage = error.message || "Unknown error", code = error.code ?? -1, data = error.data, errorMessage = data ? `${errMessage}: ${JSON.stringify(data)}` : errMessage;
|
|
195
|
+
reject(new Error(`[Error ${code}] ${errorMessage}`));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if ("result" in msg) {
|
|
199
|
+
resolve(msg.result);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
reject(new Error("Invalid JSON-RPC response: no result or error field"));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
StdioClient
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=stdio-client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/stdio-client.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * MCP JSON-RPC 2.0 stdio client\n *\n * Minimal implementation for communicating with analyzers via JSON-RPC over stdio.\n * Handles process spawning, request/response correlation, timeouts, and clean shutdown.\n *\n * Protocol: JSON-RPC 2.0 with line-delimited JSON framing.\n * - Each request/response is a complete JSON object on a single line\n * - Request IDs are integers for correlation\n * - Errors are extracted from JSON-RPC error response objects\n */\n\nimport { spawn, ChildProcess } from \"child_process\";\n\n/**\n * JSON-RPC 2.0 request structure\n */\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n id: number;\n}\n\n/**\n * JSON-RPC notification (no id field)\n */\ninterface JsonRpcNotification {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n}\n\n/**\n * MCP client for communicating with analyzer backends via JSON-RPC 2.0 stdio\n */\nexport class StdioClient {\n private proc: ChildProcess | null = null;\n private requestId: number = 0;\n private pendingRequests: Map<number, { resolve: (value: unknown) => void; reject: (reason: Error) => void; timeout: NodeJS.Timeout }> = new Map();\n\n /**\n * Spawn an analyzer process\n *\n * @param binaryPath Path to the analyzer binary\n * @param args Optional command-line arguments (or env object for backwards compatibility)\n * @param env Optional environment variables (merged with process.env)\n * @throws Error if spawn fails\n */\n spawn(binaryPath: string, args?: string[] | Record<string, string>, env?: Record<string, string>): void {\n if (this.proc) {\n throw new Error(\"Process already spawned\");\n }\n\n // Handle backwards compatibility: if args is an object, treat it as env\n let actualArgs: string[] = [];\n let actualEnv = process.env as Record<string, string>;\n\n if (Array.isArray(args)) {\n actualArgs = args;\n actualEnv = { ...actualEnv, ...env };\n } else if (args && typeof args === \"object\") {\n actualEnv = { ...actualEnv, ...args };\n }\n\n this.proc = spawn(binaryPath, actualArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: actualEnv,\n });\n\n // Set up stdout listener for responses\n if (this.proc.stdout) {\n this.proc.stdout.setEncoding(\"utf-8\");\n let buffer = \"\";\n\n this.proc.stdout.on(\"data\", (chunk: string) => {\n buffer += chunk;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const message = JSON.parse(line);\n this.handleMessage(message);\n } catch (error) {\n // If we have pending requests, reject the oldest one since it likely\n // sent the request that produced this invalid response\n const firstKey = this.pendingRequests.keys().next().value;\n if (firstKey !== undefined) {\n const pending = this.pendingRequests.get(firstKey);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(firstKey);\n pending.reject(new Error(`Failed to parse JSON-RPC response from analyzer: ${line}`));\n }\n } else {\n // No pending requests, just log the error\n console.error(`Failed to parse JSON-RPC message from analyzer: ${line}`);\n }\n }\n }\n });\n }\n\n // Set up stderr listener\n if (this.proc.stderr) {\n this.proc.stderr.setEncoding(\"utf-8\");\n this.proc.stderr.on(\"data\", (chunk: string) => {\n console.error(`[Analyzer stderr] ${chunk}`);\n });\n }\n\n // Handle process exit\n this.proc.on(\"exit\", (code: number | null, signal: string | null) => {\n this.proc = null;\n // Reject all pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(new Error(`Process exited with code ${code} (signal: ${signal})`));\n }\n this.pendingRequests.clear();\n });\n\n this.proc.on(\"error\", (error: Error) => {\n // Reject all pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(error);\n }\n this.pendingRequests.clear();\n });\n }\n\n /**\n * Initialize the analyzer with client information\n *\n * Sends an initialize request and waits for the response.\n * Times out after 5 seconds if no response is received. Was 2 seconds,\n * but that became too tight for slower analyzer binary cold starts\n * (observed failing in CI against codebase-memory-mcp 0.10.x - see #802).\n *\n * @param clientInfo Client information object\n * @throws Error if initialize times out or fails\n */\n async initialize(clientInfo: Record<string, unknown>): Promise<unknown> {\n return this.callTool(\"initialize\", clientInfo, { timeout: 5000 });\n }\n\n /**\n * Call a tool on the analyzer\n *\n * Sends a JSON-RPC request and waits for the response.\n * Defaults to 30-second timeout for graph queries.\n *\n * @param name Tool name (method)\n * @param params Tool parameters\n * @param options Optional configuration (timeout in milliseconds)\n * @returns Tool result\n * @throws Error if tool call fails or times out\n */\n async callTool(\n name: string,\n params?: unknown,\n options?: { timeout?: number }\n ): Promise<unknown> {\n if (!this.proc) {\n throw new Error(\"Process not spawned\");\n }\n\n const timeout = options?.timeout ?? 30000;\n const id = ++this.requestId;\n\n const request: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n method: name,\n params,\n id,\n };\n\n return new Promise((resolve, reject) => {\n const timeoutHandle = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Tool call '${name}' timed out after ${timeout}ms`));\n }, timeout);\n\n this.pendingRequests.set(id, { resolve, reject, timeout: timeoutHandle });\n\n try {\n const line = JSON.stringify(request) + \"\\n\";\n // Explicit null checks to prevent race condition with exit handler\n if (!this.proc || !this.proc.stdin) {\n clearTimeout(timeoutHandle);\n this.pendingRequests.delete(id);\n reject(new Error(\"Process or stdin closed before write could complete\"));\n return;\n }\n\n if (!this.proc.stdin.write(line)) {\n // Write returned false (backpressure), but data is already in the buffer.\n // No action needed \u2014 the data will be flushed automatically.\n this.proc.stdin.once(\"drain\", () => {\n // Drain event listener registered but no action needed\n });\n }\n } catch (error) {\n clearTimeout(timeoutHandle);\n this.pendingRequests.delete(id);\n reject(error);\n }\n });\n }\n\n /**\n * Close the connection and clean up\n *\n * Sends a shutdown notification and terminates the process.\n * Sends SIGTERM first, then escalates to SIGKILL after 1 second if needed.\n * Cleans up all pending requests and event listeners.\n */\n\n /**\n * Invoke a tool on the analyzer via the MCP tools/call protocol\n *\n * Sends a tools/call request with {name, arguments} and unwraps the\n * MCP content envelope from the response (content[0].text parsed as JSON).\n *\n * Use this for all tool invocations. Use callTool() only for MCP protocol\n * methods (initialize, tools/list).\n *\n * @param name Tool name (e.g., \"search_graph\", \"index_repository\")\n * @param args Tool arguments to pass as the arguments field\n * @param options Optional configuration (timeout in milliseconds)\n * @returns Parsed tool result unwrapped from MCP content envelope\n */\n async invokeTool(\n name: string,\n args?: unknown,\n options?: { timeout?: number }\n ): Promise<unknown> {\n const response = await this.callTool(\n \"tools/call\",\n { name, arguments: args ?? {} },\n options\n ) as { content?: Array<{ type: string; text?: string }>; isError?: boolean };\n\n // Unwrap MCP tools/call envelope: result is JSON-encoded in content[0].text\n if (response?.content && Array.isArray(response.content) && response.content.length > 0) {\n const first = response.content[0];\n if (first.type === \"text\" && typeof first.text === \"string\") {\n try {\n return JSON.parse(first.text);\n } catch {\n return first.text;\n }\n }\n }\n return response;\n }\n\n close(): void {\n if (!this.proc) {\n return;\n }\n\n const procToClose = this.proc;\n this.proc = null;\n\n // Send shutdown notification\n try {\n const notification: JsonRpcNotification = {\n jsonrpc: \"2.0\",\n method: \"shutdown\",\n };\n const line = JSON.stringify(notification) + \"\\n\";\n procToClose.stdin?.write(line);\n } catch (error) {\n // Ignore write errors during shutdown\n }\n\n // Clean up pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(new Error(\"Client closed\"));\n }\n this.pendingRequests.clear();\n\n // Remove listeners\n if (procToClose.stdout) {\n procToClose.stdout.removeAllListeners();\n }\n if (procToClose.stderr) {\n procToClose.stderr.removeAllListeners();\n }\n procToClose.removeAllListeners();\n\n // Terminate process with SIGKILL fallback\n try {\n procToClose.stdin?.end();\n if (!procToClose.killed) {\n procToClose.kill(\"SIGTERM\");\n\n // Schedule SIGKILL fallback after 1 second if process doesn't exit\n const killTimer = setTimeout(() => {\n // Check if process has actually exited (exitCode is non-null once exit event fires)\n if (procToClose.exitCode === null) {\n try {\n procToClose.kill(\"SIGKILL\");\n } catch {\n // Process may have already exited\n }\n }\n }, 1000);\n\n // Unreference the timer so it doesn't keep the event loop alive\n killTimer.unref();\n }\n } catch (error) {\n // Ignore errors during shutdown\n }\n }\n\n /**\n * Handle a JSON-RPC message from the analyzer\n *\n * Matches responses to pending requests by ID and resolves/rejects accordingly.\n * Extracts error information from JSON-RPC error objects.\n *\n * @param message Parsed JSON-RPC message\n */\n private handleMessage(message: unknown): void {\n if (!message || typeof message !== \"object\") {\n return;\n }\n\n const msg = message as Record<string, unknown>;\n\n // Ignore notifications (no id field)\n if (!(\"id\" in msg) || typeof msg.id !== \"number\") {\n return;\n }\n\n const msgId = msg.id as number;\n const pending = this.pendingRequests.get(msgId);\n\n if (!pending) {\n // Log diagnostic information for unmatched response IDs\n console.warn(\n `Received response with unmatched ID ${msgId} (type: ${typeof msgId}). ` +\n `Pending request IDs: [${Array.from(this.pendingRequests.keys()).join(\", \")}]. ` +\n `This may indicate a type mismatch or stale response.`\n );\n return;\n }\n\n const { resolve, reject, timeout } = pending;\n clearTimeout(timeout);\n this.pendingRequests.delete(msgId);\n\n // Check for error response\n if (\"error\" in msg && msg.error !== null && typeof msg.error === \"object\") {\n const error = msg.error as Record<string, unknown>;\n const errMessage = (error.message as string) || \"Unknown error\";\n const code = (error.code as number) ?? -1;\n const data = error.data;\n\n const errorMessage = data ? `${errMessage}: ${JSON.stringify(data)}` : errMessage;\n reject(new Error(`[Error ${code}] ${errorMessage}`));\n return;\n }\n\n // Success response\n if (\"result\" in msg) {\n resolve(msg.result);\n return;\n }\n\n // Invalid response\n reject(new Error(\"Invalid JSON-RPC response: no result or error field\"));\n }\n}\n"],
|
|
5
|
+
"mappings": "AAYA,SAAS,aAA2B;AAwB9B,MAAO,YAAW;EACd,OAA4B;EAC5B,YAAoB;EACpB,kBAAgI,oBAAI,IAAG;;;;;;;;;EAU/I,MAAM,YAAoB,MAA0C,KAA4B;AAC9F,QAAI,KAAK;AACP,YAAM,IAAI,MAAM,yBAAyB;AAI3C,QAAI,aAAuB,CAAA,GACvB,YAAY,QAAQ;AAexB,QAbI,MAAM,QAAQ,IAAI,KACpB,aAAa,MACb,YAAY,EAAE,GAAG,WAAW,GAAG,IAAG,KACzB,QAAQ,OAAO,QAAS,aACjC,YAAY,EAAE,GAAG,WAAW,GAAG,KAAI,IAGrC,KAAK,OAAO,MAAM,YAAY,YAAY;MACxC,OAAO,CAAC,QAAQ,QAAQ,MAAM;MAC9B,KAAK;KACN,GAGG,KAAK,KAAK,QAAQ;AACpB,WAAK,KAAK,OAAO,YAAY,OAAO;AACpC,UAAI,SAAS;AAEb,WAAK,KAAK,OAAO,GAAG,QAAQ,CAAC,UAAiB;AAC5C,kBAAU;AACV,cAAM,QAAQ,OAAO,MAAM;CAAI;AAC/B,iBAAS,MAAM,IAAG,KAAM;AAExB,mBAAW,QAAQ;AACjB,cAAK,KAAK,KAAI;AAEd,gBAAI;AACF,oBAAM,UAAU,KAAK,MAAM,IAAI;AAC/B,mBAAK,cAAc,OAAO;YAC5B,QAAgB;AAGd,oBAAM,WAAW,KAAK,gBAAgB,KAAI,EAAG,KAAI,EAAG;AACpD,kBAAI,aAAa,QAAW;AAC1B,sBAAM,UAAU,KAAK,gBAAgB,IAAI,QAAQ;AACjD,gBAAI,YACF,aAAa,QAAQ,OAAO,GAC5B,KAAK,gBAAgB,OAAO,QAAQ,GACpC,QAAQ,OAAO,IAAI,MAAM,oDAAoD,IAAI,EAAE,CAAC;cAExF;AAEE,wBAAQ,MAAM,mDAAmD,IAAI,EAAE;YAE3E;MAEJ,CAAC;IACH;AAGA,IAAI,KAAK,KAAK,WACZ,KAAK,KAAK,OAAO,YAAY,OAAO,GACpC,KAAK,KAAK,OAAO,GAAG,QAAQ,CAAC,UAAiB;AAC5C,cAAQ,MAAM,qBAAqB,KAAK,EAAE;IAC5C,CAAC,IAIH,KAAK,KAAK,GAAG,QAAQ,CAAC,MAAqB,WAAyB;AAClE,WAAK,OAAO;AAEZ,iBAAW,CAAC,EAAE,EAAE,QAAQ,QAAO,CAAE,KAAK,KAAK;AACzC,qBAAa,OAAO,GACpB,OAAO,IAAI,MAAM,4BAA4B,IAAI,aAAa,MAAM,GAAG,CAAC;AAE1E,WAAK,gBAAgB,MAAK;IAC5B,CAAC,GAED,KAAK,KAAK,GAAG,SAAS,CAAC,UAAgB;AAErC,iBAAW,CAAC,EAAE,EAAE,QAAQ,QAAO,CAAE,KAAK,KAAK;AACzC,qBAAa,OAAO,GACpB,OAAO,KAAK;AAEd,WAAK,gBAAgB,MAAK;IAC5B,CAAC;EACH;;;;;;;;;;;;EAaA,MAAM,WAAW,YAAmC;AAClD,WAAO,KAAK,SAAS,cAAc,YAAY,EAAE,SAAS,IAAI,CAAE;EAClE;;;;;;;;;;;;;EAcA,MAAM,SACJ,MACA,QACA,SAA8B;AAE9B,QAAI,CAAC,KAAK;AACR,YAAM,IAAI,MAAM,qBAAqB;AAGvC,UAAM,UAAU,SAAS,WAAW,KAC9B,KAAK,EAAE,KAAK,WAEZ,UAA0B;MAC9B,SAAS;MACT,QAAQ;MACR;MACA;;AAGF,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAU;AACrC,YAAM,gBAAgB,WAAW,MAAK;AACpC,aAAK,gBAAgB,OAAO,EAAE,GAC9B,OAAO,IAAI,MAAM,cAAc,IAAI,qBAAqB,OAAO,IAAI,CAAC;MACtE,GAAG,OAAO;AAEV,WAAK,gBAAgB,IAAI,IAAI,EAAE,SAAS,QAAQ,SAAS,cAAa,CAAE;AAExE,UAAI;AACF,cAAM,OAAO,KAAK,UAAU,OAAO,IAAI;;AAEvC,YAAI,CAAC,KAAK,QAAQ,CAAC,KAAK,KAAK,OAAO;AAClC,uBAAa,aAAa,GAC1B,KAAK,gBAAgB,OAAO,EAAE,GAC9B,OAAO,IAAI,MAAM,qDAAqD,CAAC;AACvE;QACF;AAEA,QAAK,KAAK,KAAK,MAAM,MAAM,IAAI,KAG7B,KAAK,KAAK,MAAM,KAAK,SAAS,MAAK;QAEnC,CAAC;MAEL,SAAS,OAAO;AACd,qBAAa,aAAa,GAC1B,KAAK,gBAAgB,OAAO,EAAE,GAC9B,OAAO,KAAK;MACd;IACF,CAAC;EACH;;;;;;;;;;;;;;;;;;;;;;EAwBA,MAAM,WACJ,MACA,MACA,SAA8B;AAE9B,UAAM,WAAW,MAAM,KAAK,SAC1B,cACA,EAAE,MAAM,WAAW,QAAQ,CAAA,EAAE,GAC7B,OAAO;AAIT,QAAI,UAAU,WAAW,MAAM,QAAQ,SAAS,OAAO,KAAK,SAAS,QAAQ,SAAS,GAAG;AACvF,YAAM,QAAQ,SAAS,QAAQ,CAAC;AAChC,UAAI,MAAM,SAAS,UAAU,OAAO,MAAM,QAAS;AACjD,YAAI;AACF,iBAAO,KAAK,MAAM,MAAM,IAAI;QAC9B,QAAQ;AACN,iBAAO,MAAM;QACf;IAEJ;AACA,WAAO;EACT;EAEA,QAAK;AACH,QAAI,CAAC,KAAK;AACR;AAGF,UAAM,cAAc,KAAK;AACzB,SAAK,OAAO;AAGZ,QAAI;AAKF,YAAM,OAAO,KAAK,UAJwB;QACxC,SAAS;QACT,QAAQ;OAE8B,IAAI;;AAC5C,kBAAY,OAAO,MAAM,IAAI;IAC/B,QAAgB;IAEhB;AAGA,eAAW,CAAC,EAAE,EAAE,QAAQ,QAAO,CAAE,KAAK,KAAK;AACzC,mBAAa,OAAO,GACpB,OAAO,IAAI,MAAM,eAAe,CAAC;AAEnC,SAAK,gBAAgB,MAAK,GAGtB,YAAY,UACd,YAAY,OAAO,mBAAkB,GAEnC,YAAY,UACd,YAAY,OAAO,mBAAkB,GAEvC,YAAY,mBAAkB;AAG9B,QAAI;AACF,kBAAY,OAAO,IAAG,GACjB,YAAY,WACf,YAAY,KAAK,SAAS,GAGR,WAAW,MAAK;AAEhC,YAAI,YAAY,aAAa;AAC3B,cAAI;AACF,wBAAY,KAAK,SAAS;UAC5B,QAAQ;UAER;MAEJ,GAAG,GAAI,EAGG,MAAK;IAEnB,QAAgB;IAEhB;EACF;;;;;;;;;EAUQ,cAAc,SAAgB;AACpC,QAAI,CAAC,WAAW,OAAO,WAAY;AACjC;AAGF,UAAM,MAAM;AAGZ,QAAI,EAAE,QAAQ,QAAQ,OAAO,IAAI,MAAO;AACtC;AAGF,UAAM,QAAQ,IAAI,IACZ,UAAU,KAAK,gBAAgB,IAAI,KAAK;AAE9C,QAAI,CAAC,SAAS;AAEZ,cAAQ,KACN,uCAAuC,KAAK,WAAW,OAAO,KAAK,4BAC1C,MAAM,KAAK,KAAK,gBAAgB,KAAI,CAAE,EAAE,KAAK,IAAI,CAAC,yDACrB;AAExD;IACF;AAEA,UAAM,EAAE,SAAS,QAAQ,QAAO,IAAK;AAKrC,QAJA,aAAa,OAAO,GACpB,KAAK,gBAAgB,OAAO,KAAK,GAG7B,WAAW,OAAO,IAAI,UAAU,QAAQ,OAAO,IAAI,SAAU,UAAU;AACzE,YAAM,QAAQ,IAAI,OACZ,aAAc,MAAM,WAAsB,iBAC1C,OAAQ,MAAM,QAAmB,IACjC,OAAO,MAAM,MAEb,eAAe,OAAO,GAAG,UAAU,KAAK,KAAK,UAAU,IAAI,CAAC,KAAK;AACvE,aAAO,IAAI,MAAM,UAAU,IAAI,KAAK,YAAY,EAAE,CAAC;AACnD;IACF;AAGA,QAAI,YAAY,KAAK;AACnB,cAAQ,IAAI,MAAM;AAClB;IACF;AAGA,WAAO,IAAI,MAAM,qDAAqD,CAAC;EACzE;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|