@documentation-robotics/cli 0.1.0 → 0.1.5
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 +206 -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 +155 -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 +37 -13
- 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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/types.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Shared types for analyzer adapters\n *\n * These types define the contract between:\n * - The analyzer backend implementations\n * - The registry and commands\n * - The mapping loader\n */\n\nimport type { SourceReference } from \"../types/source-reference.js\";\n\n/**\n * Binary detection result - whether an analyzer is installed and functional\n * Discriminated union on `installed` field to make binary_path required when installed\n */\nexport type DetectionResult =\n | {\n installed: true;\n binary_path: string;\n version?: string;\n mcp_registered?: boolean;\n contract_ok?: boolean;\n }\n | {\n installed: false;\n binary_path?: never;\n version?: never;\n mcp_registered?: never;\n contract_ok?: never;\n };\n\n/**\n * Information about an available analyzer\n * Used in discovery results to report analyzer availability and installation status\n */\nexport interface AvailableAnalyzer {\n name: string;\n display_name: string;\n description: string;\n homepage: string;\n installed: boolean;\n}\n\n/**\n * Result of analyzer discovery\n * Lists all available analyzers and their installation status\n */\nexport interface DiscoveryResult {\n found: AvailableAnalyzer[];\n installed_count: number;\n selected?: string | null;\n}\n\n/**\n * Confidence level for mappings and detections\n */\nexport type ConfidenceLevel = \"high\" | \"medium\" | \"low\";\n\n/**\n * Valid HTTP methods - single source of truth for both type and runtime validation\n */\nexport const VALID_HTTP_METHODS = [\n \"GET\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"PATCH\",\n \"HEAD\",\n \"OPTIONS\",\n \"TRACE\",\n \"CONNECT\",\n] as const;\n\n/**\n * Heuristic parameters - shape depends on heuristic type\n * Known parameter types:\n * - min_fan_in: { threshold: number }\n * - directory_match: { patterns: string[] }\n * - naming_patterns: { service_suffixes: string[] }\n * - class_is_service: { threshold: number }\n * - service_class_naming: { service_method_prefixes: string[] }\n * - is_entry_point: { entry_point_patterns: string[] }\n * - datastore_detection: { patterns: string[], naming_indicators: string[] }\n * - handles_route: (no parameters required)\n */\nexport interface HeuristicParameters {\n threshold?: number;\n patterns?: string[];\n service_suffixes?: string[];\n service_method_prefixes?: string[];\n entry_point_patterns?: string[];\n naming_indicators?: string[];\n [key: string]: unknown;\n}\n\n// TODO: MappingConditions currently remain untyped as Record<string, unknown> because the\n// mapping schema defines conditions as a flexible object without known fields. If specific\n// condition properties are identified in future mapping iterations, define them here.\n\n/**\n * HTTP method type derived from VALID_HTTP_METHODS constant\n */\nexport type HttpMethod = (typeof VALID_HTTP_METHODS)[number];\n\n/**\n * An API endpoint candidate discovered from code analysis\n * Maps to `dr add api operation` command arguments\n *\n * These 13 fields directly support the downstream extraction workflow:\n * - Layer: api\n * - Element type: operation\n * - Name: constructed from suggested_name\n * - ID fragment: suggested_id_fragment (kebab-case for element ID)\n * - HTTP method/path: required for OpenAPI compliance\n * - Source information: for traceability and provenance\n */\nexport interface EndpointCandidate {\n /** Relative path from project root */\n source_file: string;\n /** Confidence in the detection (high, medium, low) */\n confidence: ConfidenceLevel;\n /** Suggested DR layer for this endpoint (must be \"api\") */\n suggested_layer: \"api\";\n /** Suggested element type for this endpoint (must be \"operation\") */\n suggested_element_type: \"operation\";\n /** Suggested kebab-case name for the endpoint */\n suggested_name: string;\n /** Suggested kebab-case ID fragment for constructing element ID */\n suggested_id_fragment: string;\n /** HTTP method (GET, POST, PUT, DELETE, etc.) - REQUIRED */\n http_method: HttpMethod;\n /** HTTP path or resource identifier - REQUIRED */\n http_path: string;\n /** Qualified name of the handler/implementation */\n handler_qualified_name: string;\n /** Source symbol or identifier where handler is defined */\n source_symbol: string;\n /** Starting line number of handler in source file */\n source_start_line: number;\n /** Ending line number of handler in source file */\n source_end_line: number;\n /** Source reference linking this endpoint to its implementation */\n source_reference: SourceReference;\n}\n\n/**\n * Index metadata - tracks when a project was indexed and freshness\n */\nexport interface IndexMeta {\n git_head: string;\n timestamp: string;\n node_count: number;\n edge_count: number;\n}\n\n/**\n * Result of an index operation\n * Contains counts and metadata from indexing.\n * Failures are signaled by throwing CLIError, not by success: false.\n */\nexport interface IndexResult {\n git_head: string;\n timestamp: string;\n node_count: number;\n edge_count: number;\n}\n\n/**\n * Current status of an analyzer for a project\n * Discriminated union on `indexed` field to enforce index_meta presence when indexed: true\n */\nexport type AnalyzerStatus =\n | {\n detected: DetectionResult;\n indexed: true;\n index_meta: IndexMeta;\n fresh: boolean;\n last_indexed?: string;\n }\n | {\n detected: DetectionResult;\n indexed: false;\n index_meta?: never;\n fresh: boolean;\n last_indexed?: string;\n };\n\n/**\n * Session state - tracks which analyzer is currently active\n */\nexport interface SessionState {\n active_analyzer: string;\n selected_at: string;\n}\n\n/**\n * Mapping for a single CBM node label to DR element type\n * Matches the structure in cbm.json nodes_by_label\n */\nexport interface AnalyzerNodeMapping {\n cbm_label: string;\n dr_layer: string;\n dr_element_type: string;\n confidence: ConfidenceLevel;\n description: string;\n source_reference: {\n provenance: string;\n file: {\n from: string;\n };\n symbol?: {\n from: string;\n };\n };\n dr_element_fields?: Record<\n string,\n {\n name_template?: string;\n template?: string;\n id_source?: string;\n id_transform?: string;\n }\n >;\n verify_role?: string;\n attribute_mappings?: Array<{\n from: string;\n dr_attribute: string;\n }>;\n dr_element_type_promoted?: string;\n promotion_heuristics?: string[];\n evidence_fields?: Record<string, { from: string }>;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Mapping for a single CBM edge type to DR relationship\n * Matches the structure in cbm.json edges_by_type\n */\nexport interface AnalyzerEdgeMapping {\n cbm_edge: string;\n dr_relationship: string | null;\n confidence: ConfidenceLevel;\n description: string;\n usage: string;\n dr_scope?: string;\n direction?: string;\n confidence_from_property?: string;\n dr_pattern?: string;\n implementation_note?: string;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Single heuristic rule from the mapping\n */\nexport interface AnalyzerHeuristic {\n name: string;\n description: string;\n applies_to?: string[];\n parameters?: HeuristicParameters;\n rule?: string;\n confidence_adjustment?: number;\n conditions?: Record<string, unknown>;\n}\n\n/**\n * Confidence rubric - threshold ranges for confidence levels\n */\nexport interface ConfidenceRubric {\n high: {\n min: number;\n max: number;\n description: string;\n };\n medium: {\n min: number;\n max: number;\n description: string;\n };\n low: {\n min: number;\n max: number;\n description: string;\n };\n}\n\n/**\n * Filtering rule to exclude mappings\n */\nexport interface FilteringRule {\n name: string;\n description: string;\n filter_type: string;\n threshold?: number;\n pattern?: string;\n enabled: boolean;\n}\n\n/**\n * A service candidate discovered from code analysis\n * Maps to `dr add application service` or similar command arguments\n */\nexport interface ServiceCandidate {\n /** Suggested DR layer for this service (always \"application\") */\n suggested_layer: \"application\";\n /** Suggested element type for this service */\n suggested_element_type: \"applicationservice\" | \"applicationcomponent\";\n /** Suggested kebab-case ID fragment for constructing element ID */\n suggested_id_fragment: string;\n /** Suggested name for the service */\n suggested_name: string;\n /** Relative path from project root */\n source_file: string;\n /** Source symbol or identifier where service is defined */\n source_symbol: string;\n /** Fully qualified name of the service */\n qualified_name: string;\n /** List of heuristics that qualified this as a service candidate */\n qualifying_heuristics: string[];\n /** Confidence in the detection (medium or low only) */\n confidence: \"medium\" | \"low\";\n /** Number of incoming calls/references */\n fan_in: number;\n /** Number of outgoing calls/references */\n fan_out: number;\n}\n\n/**\n * A datastore candidate inferred from code analysis\n */\nexport interface DatastoreCandidate {\n /** Suggested DR layer for this datastore (always \"data-store\") */\n suggested_layer: \"data-store\";\n /** Suggested name for the datastore */\n suggested_name: string;\n /** Array of evidence sources for this datastore inference */\n inferred_from: Array<{\n /** Relative path from project root */\n source_file: string;\n /** Pattern used to match imports (e.g., \"mongodb\", \"pg\") */\n import_pattern: string;\n /** Array of function/method call patterns that suggest datastore usage */\n function_patterns: string[];\n }>;\n /** Confidence in the detection (always \"low\") */\n confidence: \"low\";\n /** Additional notes about the inference */\n notes: string;\n}\n\n/**\n * A node in the call graph\n */\nexport interface CallGraphNode {\n /** Fully qualified name of the node */\n qualified_name: string;\n /** Relative path from project root */\n source_file: string;\n /** Source symbol or identifier */\n source_symbol: string;\n /** Depth in the call graph (0 = root node) */\n depth: number;\n /** Type of edge (CALLS, HTTP_CALLS, HANDLES) */\n edge_type: \"CALLS\" | \"HTTP_CALLS\" | \"HANDLES\";\n}\n\n/**\n * Options for the verify operation\n */\nexport interface VerifyOptions {\n /** Array of layer names to verify (empty = verify all layers) */\n layers?: string[];\n /** Whether to use changeset view if active (default: true) */\n changesetAware?: boolean;\n /** Optional path to custom ignore file */\n ignoreFilePath?: string;\n}\n\n/**\n * Entry for a matched route/element in verify report\n */\nexport interface MatchedEntry {\n /** Identifier for the matched entry */\n id: string;\n /** Type of the entry (e.g., \"operation\") */\n type: string;\n /** Source file where this entry is located */\n source_file: string;\n /** Source symbol */\n source_symbol: string;\n}\n\n/**\n * Entry for routes found in graph but not in model\n */\nexport interface GraphOnlyEntry {\n /** Identifier for the entry */\n id: string;\n /** HTTP method (GET, POST, etc.) */\n http_method?: string;\n /** HTTP path */\n http_path?: string;\n /** Source file where this route is located */\n source_file: string;\n /** Source symbol */\n source_symbol: string;\n}\n\n/**\n * Entry for elements in model but not found in graph\n */\nexport interface ModelOnlyEntry {\n /** Identifier for the model element */\n id: string;\n /** Type of the element (e.g., \"operation\") */\n type: string;\n /** Source file referenced in the model */\n source_file: string;\n /** Source symbol referenced in the model */\n source_symbol: string;\n}\n\n/**\n * Entry for matches that were ignored by rules\n */\nexport interface IgnoredEntry {\n /** Identifier for the ignored entry */\n id: string;\n /** Type of the entry (route or element) */\n entry_type: \"route\" | \"element\";\n /** Reason for ignoring */\n reason: string;\n}\n\n/**\n * Summary statistics for verify report\n */\nexport interface VerifySummary {\n /** Number of routes matched to model elements */\n matched_count: number;\n /** Number of routes found in graph only (gaps in model) */\n gap_count: number;\n /** Number of elements found in model only (drift from model) */\n drift_count: number;\n /** Number of entries ignored by rules */\n ignored_count: number;\n /** Total graph entries analyzed */\n total_graph_entries: number;\n /** Total elements in the API layer (all elements, regardless of source attribution) */\n total_model_entries: number;\n /** Elements excluded from drift detection because they have no source_reference.\n * These are documentation-only elements with no code linkage. */\n elements_without_source_ref: number;\n}\n\n/**\n * Changeset context information in verify report\n * Discriminated union to prevent impossible states like active_changeset: \"cs-1\" with verified_against: \"base_model\"\n */\nexport type VerifyChangesetContext =\n | {\n /** ID of the active changeset */\n active_changeset: string;\n /** Report was verified against changeset view */\n verified_against: \"changeset_view\";\n }\n | {\n /** No active changeset */\n active_changeset: null;\n /** Report was verified against base model */\n verified_against: \"base_model\";\n };\n\n/**\n * Buckets of categorized entries from verify operation\n */\nexport interface VerifyBuckets {\n /** Routes matched to model elements */\n matched: MatchedEntry[];\n /** Routes found in graph only */\n in_graph_only: GraphOnlyEntry[];\n /** Elements in model only */\n in_model_only: ModelOnlyEntry[];\n /** Entries ignored by rules */\n ignored: IgnoredEntry[];\n}\n\n/**\n * Detailed verification report for a project\n *\n * This is a public API contract consumed by /dr-verify Claude Code command.\n * Treat as stable from issue #654 forward.\n */\nexport interface VerifyReport {\n /** ISO 8601 timestamp when report was generated */\n generated_at: string;\n /** Absolute path to the project root */\n project_root: string;\n /** Name of the analyzer used */\n analyzer: string;\n /** ISO 8601 timestamp when analyzer was indexed */\n analyzer_indexed_at: string;\n /** Changeset context information */\n changeset_context: VerifyChangesetContext;\n /** Array of layer names that were verified */\n layers_verified: string[];\n /** Buckets of categorized entries */\n buckets: VerifyBuckets;\n /** Summary statistics */\n summary: VerifySummary;\n /** Actionable hints for resolving gaps or drift, present when issues are detected */\n hints?: string[];\n}\n"],
|
|
5
|
+
"mappings": "AA6DO,MAAM,qBAAqB;EAChC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { access } from "fs/promises";
|
|
3
|
+
import { Model } from "../core/model.js";
|
|
4
|
+
import { IgnoreFileLoader } from "./verify-ignore.js";
|
|
5
|
+
import { SOURCE_CODE_EXT_RE } from "./cbm-analyzer.js";
|
|
6
|
+
class VerifyEngine {
|
|
7
|
+
/**
|
|
8
|
+
* Compute a comprehensive verification report
|
|
9
|
+
*
|
|
10
|
+
* Implements the five-step algorithm:
|
|
11
|
+
* 1. Resolve model view via Model.load() + getActiveChangesetId() + getLayer("api")
|
|
12
|
+
* 2. Build primary index on source_reference.locations[0].file + ":" + symbol
|
|
13
|
+
* and optional secondary index on http_method + ":" + http_path
|
|
14
|
+
* 3. Load ignore rules
|
|
15
|
+
* 4. Compute four buckets (matched, in_graph_only, in_model_only, ignored)
|
|
16
|
+
* 5. Assemble VerifyReport
|
|
17
|
+
*
|
|
18
|
+
* @param projectRoot Absolute path to the project root
|
|
19
|
+
* @param routes Array of discovered routes from the graph
|
|
20
|
+
* @param options Verification options
|
|
21
|
+
* @returns Comprehensive verification report
|
|
22
|
+
*/
|
|
23
|
+
async computeReport(projectRoot, routes, options, analyzerName = "codebase-memory-mcp", indexMeta) {
|
|
24
|
+
let model;
|
|
25
|
+
try {
|
|
26
|
+
model = await Model.load(projectRoot);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const err = error;
|
|
29
|
+
throw err.code === "ENOENT" || err.code === "ENOTDIR" ? new Error(`Failed to load DR model at ${projectRoot}: Model directory not found or inaccessible. Please run 'dr init' to initialize the documentation robotics model.`) : new Error(`Failed to load DR model at ${projectRoot}: ${err.message}. Please check that the model directory exists and is accessible.`);
|
|
30
|
+
}
|
|
31
|
+
const activeChangesetId = model.getActiveChangesetId(), apiLayer = options.changesetAware === !1 ? await model.getBaseLayer("api") : await model.getLayer("api");
|
|
32
|
+
if (!apiLayer)
|
|
33
|
+
return this.assembleReport(projectRoot, model, {
|
|
34
|
+
matched: [],
|
|
35
|
+
in_graph_only: routes.map((r) => ({
|
|
36
|
+
id: r.id,
|
|
37
|
+
http_method: r.http_method,
|
|
38
|
+
http_path: r.http_path,
|
|
39
|
+
source_file: r.source_file || "",
|
|
40
|
+
source_symbol: r.source_symbol || ""
|
|
41
|
+
})),
|
|
42
|
+
in_model_only: [],
|
|
43
|
+
ignored: []
|
|
44
|
+
}, activeChangesetId, options, analyzerName, indexMeta);
|
|
45
|
+
const primaryIndex = /* @__PURE__ */ new Map(), secondaryIndex = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const element of apiLayer.elements.values()) {
|
|
47
|
+
const firstLocation = element.source_reference?.locations?.[0];
|
|
48
|
+
if (firstLocation && firstLocation.file) {
|
|
49
|
+
const symbol = firstLocation.symbol || "", key = `${firstLocation.file}:${symbol}`;
|
|
50
|
+
primaryIndex.set(key, {
|
|
51
|
+
modelEntry: {
|
|
52
|
+
id: element.id,
|
|
53
|
+
source_file: firstLocation.file,
|
|
54
|
+
source_symbol: symbol
|
|
55
|
+
},
|
|
56
|
+
key
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const attrs = element.attributes;
|
|
60
|
+
if (attrs?.http_method && attrs?.http_path) {
|
|
61
|
+
const key = `${attrs.http_method}:${attrs.http_path}`;
|
|
62
|
+
secondaryIndex.set(key, {
|
|
63
|
+
modelEntry: {
|
|
64
|
+
id: element.id,
|
|
65
|
+
http_method: attrs.http_method,
|
|
66
|
+
http_path: attrs.http_path
|
|
67
|
+
},
|
|
68
|
+
key
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const emptySymbolCount = routes.filter((r) => !r.source_symbol).length;
|
|
73
|
+
emptySymbolCount > 0 && console.warn(`
|
|
74
|
+
Warning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_symbol. Primary-index matching by (source_file, source_symbol) will skip them.
|
|
75
|
+
Likely cause: the code graph analyzer did not extract handler function names for this language.
|
|
76
|
+
Secondary-index matching (http_method + http_path) requires those attributes on model operations.
|
|
77
|
+
To fix: re-index after the analyzer is updated, or manually set http_method/http_path on operations.
|
|
78
|
+
`);
|
|
79
|
+
const ignoreFilePath = options.ignoreFilePath || path.join(projectRoot, ".dr-verify-ignore.yaml"), ignoreRules = await IgnoreFileLoader.load(ignoreFilePath), matched = [], inGraphOnly = [], ignored = [], matchedRouteIds = /* @__PURE__ */ new Set();
|
|
80
|
+
for (const route of routes) {
|
|
81
|
+
let matchedElement;
|
|
82
|
+
if (route.source_file && route.source_symbol) {
|
|
83
|
+
const key = `${route.source_file}:${route.source_symbol}`, primaryMatch = primaryIndex.get(key);
|
|
84
|
+
primaryMatch && (matchedElement = primaryMatch.modelEntry);
|
|
85
|
+
}
|
|
86
|
+
if (!matchedElement && route.http_method && route.http_path) {
|
|
87
|
+
const key = `${route.http_method}:${route.http_path}`, secondaryMatch = secondaryIndex.get(key);
|
|
88
|
+
secondaryMatch && (matchedElement = secondaryMatch.modelEntry);
|
|
89
|
+
}
|
|
90
|
+
const ignoreReason = IgnoreFileLoader.matches({
|
|
91
|
+
handler: route.handler,
|
|
92
|
+
path: route.http_path,
|
|
93
|
+
element_id: route.id
|
|
94
|
+
}, "route", ignoreRules);
|
|
95
|
+
ignoreReason ? (ignored.push({
|
|
96
|
+
id: route.id,
|
|
97
|
+
entry_type: "route",
|
|
98
|
+
reason: ignoreReason
|
|
99
|
+
}), matchedElement && matchedRouteIds.add(matchedElement.id)) : matchedElement ? (matched.push({
|
|
100
|
+
id: matchedElement.id,
|
|
101
|
+
type: "operation",
|
|
102
|
+
source_file: route.source_file || "",
|
|
103
|
+
source_symbol: route.source_symbol || ""
|
|
104
|
+
}), matchedRouteIds.add(matchedElement.id)) : inGraphOnly.push({
|
|
105
|
+
id: route.id,
|
|
106
|
+
http_method: route.http_method,
|
|
107
|
+
http_path: route.http_path,
|
|
108
|
+
source_file: route.source_file || "",
|
|
109
|
+
source_symbol: route.source_symbol || ""
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const inModelOnly = [], fileChecks = [], elementsToCheck = [];
|
|
113
|
+
let elementsWithoutSourceRef = 0, elementsWithNonIndexableSource = 0;
|
|
114
|
+
for (const element of apiLayer.elements.values()) {
|
|
115
|
+
if (matchedRouteIds.has(element.id))
|
|
116
|
+
continue;
|
|
117
|
+
const firstLocation = element.source_reference?.locations?.[0];
|
|
118
|
+
if (!firstLocation || !firstLocation.file) {
|
|
119
|
+
elementsWithoutSourceRef++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!SOURCE_CODE_EXT_RE.test(firstLocation.file)) {
|
|
123
|
+
elementsWithNonIndexableSource++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
elementsToCheck.push({
|
|
127
|
+
id: element.id,
|
|
128
|
+
path: element.path,
|
|
129
|
+
file: firstLocation.file,
|
|
130
|
+
symbol: firstLocation.symbol || ""
|
|
131
|
+
});
|
|
132
|
+
const filePath = path.join(projectRoot, firstLocation.file);
|
|
133
|
+
fileChecks.push(access(filePath).then(() => !0).catch((err) => err.code !== "ENOENT"));
|
|
134
|
+
}
|
|
135
|
+
elementsWithoutSourceRef > 0 && console.warn(`
|
|
136
|
+
Warning: ${elementsWithoutSourceRef} API model element(s) excluded from drift detection \u2014 they have no source_reference with a file location.
|
|
137
|
+
Drift detection requires a linked source file to compare against the code graph.
|
|
138
|
+
Fix: dr update <element-id> --source-file <relative-path> --source-symbol <symbol>
|
|
139
|
+
Elements ingested via /dr-map or dr analyzer endpoints are automatically attributed.
|
|
140
|
+
`), elementsWithNonIndexableSource > 0 && console.warn(`
|
|
141
|
+
Warning: ${elementsWithNonIndexableSource} API model element(s) excluded from drift detection \u2014 their source files are not indexable source-code files.
|
|
142
|
+
Point source_reference to a source-code file (.ts, .js, .py, etc.) for drift detection,
|
|
143
|
+
or use dr analyzer endpoints to discover routes automatically.
|
|
144
|
+
`);
|
|
145
|
+
const fileExistsResults = await Promise.all(fileChecks);
|
|
146
|
+
for (let i = 0; i < elementsToCheck.length; i++)
|
|
147
|
+
if (fileExistsResults[i]) {
|
|
148
|
+
const elem = elementsToCheck[i], ignoreReason = IgnoreFileLoader.matches({
|
|
149
|
+
element_id: elem.path
|
|
150
|
+
}, "element", ignoreRules);
|
|
151
|
+
ignoreReason ? ignored.push({
|
|
152
|
+
id: elem.id,
|
|
153
|
+
entry_type: "element",
|
|
154
|
+
reason: ignoreReason
|
|
155
|
+
}) : inModelOnly.push({
|
|
156
|
+
id: elem.id,
|
|
157
|
+
type: "operation",
|
|
158
|
+
source_file: elem.file,
|
|
159
|
+
source_symbol: elem.symbol
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const buckets = {
|
|
163
|
+
matched,
|
|
164
|
+
in_graph_only: inGraphOnly,
|
|
165
|
+
in_model_only: inModelOnly,
|
|
166
|
+
ignored
|
|
167
|
+
};
|
|
168
|
+
return this.assembleReport(projectRoot, model, buckets, activeChangesetId, options, analyzerName, indexMeta, elementsWithoutSourceRef + elementsWithNonIndexableSource);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Assemble the final verification report
|
|
172
|
+
*
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
assembleReport(projectRoot, model, buckets, activeChangesetId, options, analyzerName = "codebase-memory-mcp", indexMeta, elementsWithoutSourceRef = 0) {
|
|
176
|
+
const matchedElements = buckets.matched.length, inModelOnly = buckets.in_model_only.length, ignoredElements = buckets.ignored.filter((e) => e.entry_type === "element").length, summary = {
|
|
177
|
+
matched_count: matchedElements,
|
|
178
|
+
gap_count: buckets.in_graph_only.length,
|
|
179
|
+
drift_count: inModelOnly,
|
|
180
|
+
ignored_count: buckets.ignored.length,
|
|
181
|
+
total_graph_entries: matchedElements + buckets.in_graph_only.length + buckets.ignored.filter((e) => e.entry_type === "route").length,
|
|
182
|
+
// Count all API layer elements: those that participated in matching plus those
|
|
183
|
+
// excluded because they lack source_reference (documentation-only elements).
|
|
184
|
+
total_model_entries: matchedElements + inModelOnly + ignoredElements + elementsWithoutSourceRef,
|
|
185
|
+
elements_without_source_ref: elementsWithoutSourceRef
|
|
186
|
+
}, hasActiveChangeset = (options.changesetAware ?? !0) && activeChangesetId !== null, analyzerIndexedAt = indexMeta?.timestamp || model.manifest.modified, changeset_context = hasActiveChangeset ? {
|
|
187
|
+
active_changeset: activeChangesetId,
|
|
188
|
+
verified_against: "changeset_view"
|
|
189
|
+
} : {
|
|
190
|
+
active_changeset: null,
|
|
191
|
+
verified_against: "base_model"
|
|
192
|
+
}, hints = [];
|
|
193
|
+
return buckets.in_graph_only.length > 0 && hints.push("To close gaps with source attribution, use /dr-map or `dr analyzer endpoints` ingestion \u2014 manual `dr add` operations cannot be matched by the verifier."), {
|
|
194
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
195
|
+
project_root: projectRoot,
|
|
196
|
+
analyzer: analyzerName,
|
|
197
|
+
analyzer_indexed_at: analyzerIndexedAt,
|
|
198
|
+
changeset_context,
|
|
199
|
+
layers_verified: ["api"],
|
|
200
|
+
buckets,
|
|
201
|
+
summary,
|
|
202
|
+
...hints.length > 0 ? { hints } : {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
VerifyEngine
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=verify-engine.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/verify-engine.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Verify Engine - computes verification reports for API endpoints\n *\n * Implements the core algorithm for comparing graph-discovered routes against\n * model-defined operations, supporting dual-index matching and changeset awareness.\n */\n\nimport * as path from \"path\";\nimport { access } from \"fs/promises\";\nimport { Model } from \"../core/model.js\";\nimport type {\n VerifyReport,\n VerifyOptions,\n MatchedEntry,\n GraphOnlyEntry,\n ModelOnlyEntry,\n IgnoredEntry,\n VerifyBuckets,\n VerifySummary,\n IndexMeta,\n VerifyChangesetContext,\n} from \"./types.js\";\nimport { IgnoreFileLoader } from \"./verify-ignore.js\";\nimport { SOURCE_CODE_EXT_RE } from \"./cbm-analyzer.js\";\n\n/**\n * A discovered route from the graph\n */\nexport interface DiscoveredRoute {\n id: string;\n http_method?: string;\n http_path?: string;\n handler?: string;\n source_file?: string;\n source_symbol?: string;\n}\n\n/**\n * Model element reference extracted from the API layer\n * Contains the minimal set of fields needed for dual-index matching\n */\ninterface ModelElementRef {\n /** Element ID from the model */\n id: string;\n /** Source file path for primary index matching */\n source_file?: string;\n /** Source symbol for primary index matching */\n source_symbol?: string;\n /** HTTP method for secondary index matching */\n http_method?: string;\n /** HTTP path for secondary index matching */\n http_path?: string;\n}\n\n/**\n * Primary or secondary index entry (holds model element data, not discovered route data)\n */\ninterface IndexEntry {\n modelEntry: ModelElementRef;\n key: string;\n}\n\n/**\n * VerifyEngine - computes verification reports for API endpoints\n */\nexport class VerifyEngine {\n /**\n * Compute a comprehensive verification report\n *\n * Implements the five-step algorithm:\n * 1. Resolve model view via Model.load() + getActiveChangesetId() + getLayer(\"api\")\n * 2. Build primary index on source_reference.locations[0].file + \":\" + symbol\n * and optional secondary index on http_method + \":\" + http_path\n * 3. Load ignore rules\n * 4. Compute four buckets (matched, in_graph_only, in_model_only, ignored)\n * 5. Assemble VerifyReport\n *\n * @param projectRoot Absolute path to the project root\n * @param routes Array of discovered routes from the graph\n * @param options Verification options\n * @returns Comprehensive verification report\n */\n async computeReport(\n projectRoot: string,\n routes: DiscoveredRoute[],\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta\n ): Promise<VerifyReport> {\n // Step 1: Resolve model view\n let model: Model;\n try {\n model = await Model.load(projectRoot);\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n // Provide helpful error message if model directory is missing or corrupted\n if (err.code === \"ENOENT\" || err.code === \"ENOTDIR\") {\n throw new Error(\n `Failed to load DR model at ${projectRoot}: Model directory not found or inaccessible. ` +\n `Please run 'dr init' to initialize the documentation robotics model.`\n );\n }\n throw new Error(\n `Failed to load DR model at ${projectRoot}: ${err.message}. ` +\n `Please check that the model directory exists and is accessible.`\n );\n }\n\n const activeChangesetId = model.getActiveChangesetId();\n\n // Use base layer if changesetAware is explicitly false\n const apiLayer = options.changesetAware === false\n ? await model.getBaseLayer(\"api\")\n : await model.getLayer(\"api\");\n\n if (!apiLayer) {\n // No API layer found - all routes are in_graph_only\n return this.assembleReport(\n projectRoot,\n model,\n {\n matched: [],\n in_graph_only: routes.map((r) => ({\n id: r.id,\n http_method: r.http_method,\n http_path: r.http_path,\n source_file: r.source_file || \"\",\n source_symbol: r.source_symbol || \"\",\n })),\n in_model_only: [],\n ignored: [],\n },\n activeChangesetId,\n options,\n analyzerName,\n indexMeta\n );\n }\n\n // Step 2: Build dual-index\n const primaryIndex = new Map<string, IndexEntry>();\n const secondaryIndex = new Map<string, IndexEntry>();\n\n for (const element of apiLayer.elements.values()) {\n const firstLocation = element.source_reference?.locations?.[0];\n\n // Primary index: file:symbol\n if (firstLocation && firstLocation.file) {\n const symbol = firstLocation.symbol || \"\";\n const key = `${firstLocation.file}:${symbol}`;\n primaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n source_file: firstLocation.file,\n source_symbol: symbol,\n },\n key,\n });\n }\n\n // Secondary index: http_method:http_path\n const attrs = element.attributes as any;\n if (attrs?.http_method && attrs?.http_path) {\n const key = `${attrs.http_method}:${attrs.http_path}`;\n secondaryIndex.set(key, {\n modelEntry: {\n id: element.id,\n http_method: attrs.http_method,\n http_path: attrs.http_path,\n },\n key,\n });\n }\n }\n\n // Warn if any graph routes have no source_symbol \u2014 they will be skipped by primary-index matching\n const emptySymbolCount = routes.filter((r) => !r.source_symbol).length;\n if (emptySymbolCount > 0) {\n console.warn(\n `\\nWarning: ${emptySymbolCount}/${routes.length} graph route(s) have empty source_symbol.` +\n ` Primary-index matching by (source_file, source_symbol) will skip them.\\n` +\n ` Likely cause: the code graph analyzer did not extract handler function names for this language.\\n` +\n ` Secondary-index matching (http_method + http_path) requires those attributes on model operations.\\n` +\n ` To fix: re-index after the analyzer is updated, or manually set http_method/http_path on operations.\\n`\n );\n }\n\n // Step 3: Load ignore rules\n const ignoreFilePath =\n options.ignoreFilePath || path.join(projectRoot, \".dr-verify-ignore.yaml\");\n const ignoreRules = await IgnoreFileLoader.load(ignoreFilePath);\n\n // Step 4: Compute buckets\n const matched: MatchedEntry[] = [];\n const inGraphOnly: GraphOnlyEntry[] = [];\n const ignored: IgnoredEntry[] = [];\n const matchedRouteIds = new Set<string>();\n\n for (const route of routes) {\n let matchedElement: { id: string } | undefined;\n\n // Try primary index match\n if (route.source_file && route.source_symbol) {\n const key = `${route.source_file}:${route.source_symbol}`;\n const primaryMatch = primaryIndex.get(key);\n if (primaryMatch) {\n matchedElement = primaryMatch.modelEntry;\n }\n }\n\n // Fallback to secondary index match\n if (!matchedElement && route.http_method && route.http_path) {\n const key = `${route.http_method}:${route.http_path}`;\n const secondaryMatch = secondaryIndex.get(key);\n if (secondaryMatch) {\n matchedElement = secondaryMatch.modelEntry;\n }\n }\n\n // Check if ignored\n const ignoreReason = IgnoreFileLoader.matches(\n {\n handler: route.handler,\n path: route.http_path,\n element_id: route.id,\n },\n \"route\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: route.id,\n entry_type: \"route\",\n reason: ignoreReason,\n });\n // Update matchedRouteIds even when ignored to prevent false drift\n if (matchedElement) {\n matchedRouteIds.add(matchedElement.id);\n }\n } else if (matchedElement) {\n matched.push({\n id: matchedElement.id,\n type: \"operation\",\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n matchedRouteIds.add(matchedElement.id);\n } else {\n inGraphOnly.push({\n id: route.id,\n http_method: route.http_method,\n http_path: route.http_path,\n source_file: route.source_file || \"\",\n source_symbol: route.source_symbol || \"\",\n });\n }\n }\n\n // Compute in_model_only: model elements not matched in graph\n const inModelOnly: ModelOnlyEntry[] = [];\n\n // Batch file-existence checks\n const fileChecks: Promise<boolean>[] = [];\n const elementsToCheck: Array<{ id: string; path: string; file: string; symbol: string }> = [];\n let elementsWithoutSourceRef = 0;\n let elementsWithNonIndexableSource = 0;\n\n for (const element of apiLayer.elements.values()) {\n if (matchedRouteIds.has(element.id)) {\n continue; // Already matched\n }\n\n const firstLocation = element.source_reference?.locations?.[0];\n\n if (!firstLocation || !firstLocation.file) {\n // Skip elements with no source_reference file - they cannot drift since they have no code linkage.\n // This includes manually-created elements that are documentation-only.\n elementsWithoutSourceRef++;\n continue;\n }\n\n // CBM only indexes source-code files \u2014 non-code source files (e.g. openapi.yaml)\n // will never appear in the graph, so including them in drift detection produces false positives.\n if (!SOURCE_CODE_EXT_RE.test(firstLocation.file)) {\n elementsWithNonIndexableSource++;\n continue;\n }\n\n elementsToCheck.push({\n id: element.id,\n path: element.path,\n file: firstLocation.file,\n symbol: firstLocation.symbol || \"\",\n });\n\n const filePath = path.join(projectRoot, firstLocation.file);\n fileChecks.push(\n access(filePath)\n .then(() => true)\n .catch((err: NodeJS.ErrnoException) => {\n // Only treat ENOENT (file not found) as \"file doesn't exist\"\n // Other errors (EACCES, EMFILE, etc.) are treated as \"file exists\" to avoid false negatives\n if (err.code === \"ENOENT\") {\n return false;\n }\n // For permission errors and other issues, assume file exists to be conservative\n return true;\n })\n );\n }\n\n if (elementsWithoutSourceRef > 0) {\n console.warn(\n `\\nWarning: ${elementsWithoutSourceRef} API model element(s) excluded from drift detection` +\n ` \u2014 they have no source_reference with a file location.\\n` +\n ` Drift detection requires a linked source file to compare against the code graph.\\n` +\n ` Fix: dr update <element-id> --source-file <relative-path> --source-symbol <symbol>\\n` +\n ` Elements ingested via /dr-map or dr analyzer endpoints are automatically attributed.\\n`\n );\n }\n\n if (elementsWithNonIndexableSource > 0) {\n console.warn(\n `\\nWarning: ${elementsWithNonIndexableSource} API model element(s) excluded from drift detection` +\n ` \u2014 their source files are not indexable source-code files.\\n` +\n ` Point source_reference to a source-code file (.ts, .js, .py, etc.) for drift detection,\\n` +\n ` or use dr analyzer endpoints to discover routes automatically.\\n`\n );\n }\n\n // Wait for all file checks. All promises have .catch() handlers that return booleans,\n // so they will never reject - Promise.all() is safe and simpler than allSettled.\n const fileExistsResults = await Promise.all(fileChecks);\n\n // Add to in_model_only only if file exists, then check against ignore rules\n for (let i = 0; i < elementsToCheck.length; i++) {\n const fileExists = fileExistsResults[i];\n\n if (fileExists) {\n const elem = elementsToCheck[i];\n\n // Check if element matches any ignore rule\n // Use element.path (semantic path) instead of elem.id (UUID)\n const ignoreReason = IgnoreFileLoader.matches(\n {\n element_id: elem.path,\n },\n \"element\",\n ignoreRules\n );\n\n if (ignoreReason) {\n ignored.push({\n id: elem.id,\n entry_type: \"element\",\n reason: ignoreReason,\n });\n } else {\n inModelOnly.push({\n id: elem.id,\n type: \"operation\",\n source_file: elem.file,\n source_symbol: elem.symbol,\n });\n }\n }\n }\n\n // Step 5: Assemble report\n const buckets: VerifyBuckets = {\n matched,\n in_graph_only: inGraphOnly,\n in_model_only: inModelOnly,\n ignored,\n };\n\n return this.assembleReport(\n projectRoot,\n model,\n buckets,\n activeChangesetId,\n options,\n analyzerName,\n indexMeta,\n elementsWithoutSourceRef + elementsWithNonIndexableSource\n );\n }\n\n /**\n * Assemble the final verification report\n *\n * @private\n */\n private assembleReport(\n projectRoot: string,\n model: Model,\n buckets: VerifyBuckets,\n activeChangesetId: string | null,\n options: VerifyOptions,\n analyzerName: string = \"codebase-memory-mcp\",\n indexMeta?: IndexMeta,\n elementsWithoutSourceRef: number = 0\n ): VerifyReport {\n const matchedElements = buckets.matched.length;\n const inModelOnly = buckets.in_model_only.length;\n const ignoredElements = buckets.ignored.filter((e) => e.entry_type === \"element\").length;\n const summary: VerifySummary = {\n matched_count: matchedElements,\n gap_count: buckets.in_graph_only.length,\n drift_count: inModelOnly,\n ignored_count: buckets.ignored.length,\n total_graph_entries: matchedElements + buckets.in_graph_only.length + buckets.ignored.filter((e) => e.entry_type === \"route\").length,\n // Count all API layer elements: those that participated in matching plus those\n // excluded because they lack source_reference (documentation-only elements).\n total_model_entries: matchedElements + inModelOnly + ignoredElements + elementsWithoutSourceRef,\n elements_without_source_ref: elementsWithoutSourceRef,\n };\n\n // Determine changeset context\n const changesetAware = options.changesetAware ?? true;\n const hasActiveChangeset = changesetAware && activeChangesetId !== null;\n\n // Use analyzer index timestamp if provided, otherwise fall back to model manifest modified time\n const analyzerIndexedAt = indexMeta?.timestamp || model.manifest.modified;\n\n // Construct changeset context as discriminated union\n const changeset_context: VerifyChangesetContext = hasActiveChangeset\n ? {\n active_changeset: activeChangesetId,\n verified_against: \"changeset_view\",\n }\n : {\n active_changeset: null,\n verified_against: \"base_model\",\n };\n\n const hints: string[] = [];\n if (buckets.in_graph_only.length > 0) {\n hints.push(\n \"To close gaps with source attribution, use /dr-map or `dr analyzer endpoints` ingestion \u2014 \"\n + \"manual `dr add` operations cannot be matched by the verifier.\"\n );\n }\n\n return {\n generated_at: new Date().toISOString(),\n project_root: projectRoot,\n analyzer: analyzerName,\n analyzer_indexed_at: analyzerIndexedAt,\n changeset_context,\n layers_verified: [\"api\"],\n buckets,\n summary,\n ...(hints.length > 0 ? { hints } : {}),\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import yaml from "yaml";
|
|
3
|
+
import { minimatch } from "minimatch";
|
|
4
|
+
class IgnoreFileLoader {
|
|
5
|
+
/**
|
|
6
|
+
* Load ignore rules from a YAML file
|
|
7
|
+
*
|
|
8
|
+
* Returns an empty array if the file doesn't exist (no error).
|
|
9
|
+
* Throws if the file exists but contains structural errors:
|
|
10
|
+
* - Invalid YAML syntax
|
|
11
|
+
* - Parsed YAML is not an object with version and ignore fields
|
|
12
|
+
* - version != 1
|
|
13
|
+
* - ignore is not an array
|
|
14
|
+
* - Rule missing required fields (patterns, reason, match)
|
|
15
|
+
* - Rule has unknown fields (only patterns, reason, match, element_ids allowed)
|
|
16
|
+
* - Pattern objects with unsupported fields (only handler, path allowed)
|
|
17
|
+
* - Invalid match values
|
|
18
|
+
* - element_ids not an array of strings
|
|
19
|
+
*
|
|
20
|
+
* @param filePath Path to the .dr-verify-ignore.yaml file
|
|
21
|
+
* @returns Array of validated ignore rules
|
|
22
|
+
* @throws Error on structural issues with the ignore file
|
|
23
|
+
*/
|
|
24
|
+
static async load(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
const content = await readFile(filePath, "utf-8"), parsed = yaml.parse(content);
|
|
27
|
+
if (!parsed || typeof parsed != "object")
|
|
28
|
+
throw new Error("Invalid ignore file: expected an object with 'version' and 'ignore' fields");
|
|
29
|
+
const { version, ignore } = parsed;
|
|
30
|
+
if (version !== 1)
|
|
31
|
+
throw new Error(`Invalid ignore file version: expected 1, got ${version ?? "missing"}`);
|
|
32
|
+
if (!Array.isArray(ignore))
|
|
33
|
+
throw new Error("Invalid ignore file: 'ignore' field must be an array of rules");
|
|
34
|
+
const rules = [];
|
|
35
|
+
for (let i = 0; i < ignore.length; i++) {
|
|
36
|
+
const ruleObj = ignore[i];
|
|
37
|
+
if (!ruleObj || typeof ruleObj != "object")
|
|
38
|
+
throw new Error(`Rule at index ${i} is not an object`);
|
|
39
|
+
const rule = ruleObj, reason = rule.reason, match = rule.match, patterns = rule.patterns, elementIds = rule.element_ids;
|
|
40
|
+
if (!reason || typeof reason != "string")
|
|
41
|
+
throw new Error(`Rule at index ${i} missing required field: 'reason' (must be a string)`);
|
|
42
|
+
if (!match || typeof match != "string")
|
|
43
|
+
throw new Error(`Rule at index ${i} missing required field: 'match' (must be a string)`);
|
|
44
|
+
if (match !== "graph_only" && match !== "model_only")
|
|
45
|
+
throw new Error(`Rule at index ${i} has invalid 'match' value: '${match}' (must be 'graph_only' or 'model_only')`);
|
|
46
|
+
if (!Array.isArray(patterns))
|
|
47
|
+
throw new Error(`Rule at index ${i} missing required field: 'patterns' (must be an array of pattern objects)`);
|
|
48
|
+
if (patterns.length === 0)
|
|
49
|
+
throw new Error(`Rule at index ${i} has empty 'patterns' array (must contain at least one pattern)`);
|
|
50
|
+
let normalizedElementIds;
|
|
51
|
+
if (elementIds !== void 0) {
|
|
52
|
+
if (!Array.isArray(elementIds))
|
|
53
|
+
throw new Error(`Rule at index ${i}: 'element_ids' must be an array`);
|
|
54
|
+
if (!elementIds.every((id) => typeof id == "string"))
|
|
55
|
+
throw new Error(`Rule at index ${i}: 'element_ids' must contain only strings`);
|
|
56
|
+
normalizedElementIds = elementIds;
|
|
57
|
+
}
|
|
58
|
+
const validRuleFields = /* @__PURE__ */ new Set(["patterns", "reason", "match", "element_ids"]);
|
|
59
|
+
for (const field of Object.keys(rule))
|
|
60
|
+
if (!validRuleFields.has(field))
|
|
61
|
+
throw new Error(`Rule at index ${i}: unknown field '${field}' (valid fields: patterns, reason, match, element_ids)`);
|
|
62
|
+
const normalizedPatterns = [];
|
|
63
|
+
for (let j = 0; j < patterns.length; j++) {
|
|
64
|
+
const patternObj = patterns[j];
|
|
65
|
+
if (!patternObj || typeof patternObj != "object")
|
|
66
|
+
throw new Error(`Rule at index ${i}, pattern at index ${j} is not an object`);
|
|
67
|
+
const pattern = patternObj, normalizedPattern = {};
|
|
68
|
+
if ("handler" in pattern) {
|
|
69
|
+
if (typeof pattern.handler != "string")
|
|
70
|
+
throw new Error(`Rule at index ${i}, pattern at index ${j}: 'handler' must be a string`);
|
|
71
|
+
normalizedPattern.handler = pattern.handler;
|
|
72
|
+
}
|
|
73
|
+
if ("path" in pattern) {
|
|
74
|
+
if (typeof pattern.path != "string")
|
|
75
|
+
throw new Error(`Rule at index ${i}, pattern at index ${j}: 'path' must be a string`);
|
|
76
|
+
normalizedPattern.path = pattern.path;
|
|
77
|
+
}
|
|
78
|
+
const validFields = /* @__PURE__ */ new Set(["handler", "path"]);
|
|
79
|
+
for (const field of Object.keys(pattern))
|
|
80
|
+
if (!validFields.has(field))
|
|
81
|
+
throw new Error(`Rule at index ${i}, pattern at index ${j}: unknown field '${field}' (valid fields: handler, path)`);
|
|
82
|
+
if (Object.keys(normalizedPattern).length === 0)
|
|
83
|
+
throw new Error(`Rule at index ${i}, pattern at index ${j} has no matching criteria (must have at least one of: handler, path)`);
|
|
84
|
+
normalizedPatterns.push(normalizedPattern);
|
|
85
|
+
}
|
|
86
|
+
const ignoreRule = {
|
|
87
|
+
patterns: normalizedPatterns,
|
|
88
|
+
reason,
|
|
89
|
+
match
|
|
90
|
+
};
|
|
91
|
+
normalizedElementIds !== void 0 && (ignoreRule.element_ids = normalizedElementIds), rules.push(ignoreRule);
|
|
92
|
+
}
|
|
93
|
+
return rules;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error.code === "ENOENT")
|
|
96
|
+
return [];
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if an entry matches any ignore rule and return the matching rule's reason
|
|
102
|
+
*
|
|
103
|
+
* Only considers rules that apply to the specified entry type:
|
|
104
|
+
* - "route" entries: only rules with match: "graph_only"
|
|
105
|
+
* - "element" entries: only rules with match: "model_only"
|
|
106
|
+
*
|
|
107
|
+
* A rule matches if ANY of its patterns match (OR semantics within patterns),
|
|
108
|
+
* or if the entry's element_id matches the rule's element_ids list.
|
|
109
|
+
*
|
|
110
|
+
* @param entry Object with optional handler, path, and element_id properties
|
|
111
|
+
* @param type Type of entry ("route" for graph entries, "element" for model entries)
|
|
112
|
+
* @param rules Array of ignore rules
|
|
113
|
+
* @returns The reason string from the matched rule, or null if no match
|
|
114
|
+
*/
|
|
115
|
+
static matches(entry, type, rules) {
|
|
116
|
+
for (const rule of rules)
|
|
117
|
+
if (!(type === "route" && rule.match !== "graph_only") && !(type === "element" && rule.match !== "model_only")) {
|
|
118
|
+
if (rule.element_ids && entry.element_id && rule.element_ids.includes(entry.element_id))
|
|
119
|
+
return rule.reason;
|
|
120
|
+
for (const pattern of rule.patterns)
|
|
121
|
+
if (pattern.handler && entry.handler && minimatch(entry.handler, pattern.handler) || pattern.path && entry.path && entry.path === pattern.path)
|
|
122
|
+
return rule.reason;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
IgnoreFileLoader
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=verify-ignore.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/verify-ignore.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Ignore file loader for verify operations\n *\n * Parses .dr-verify-ignore.yaml and provides pattern matching for routes/elements\n * to exclude from verification reports.\n */\n\nimport { readFile } from \"fs/promises\";\nimport yaml from \"yaml\";\nimport { minimatch } from \"minimatch\";\n\n/**\n * A pattern within an ignore rule\n *\n * Each pattern object contains one or more matching criteria.\n */\nexport interface IgnorePattern {\n handler?: string;\n path?: string;\n}\n\n/**\n * A single ignore rule from the ignore file\n *\n * Rules must specify:\n * - patterns: list of matching criteria (OR semantics - any match applies the rule)\n * - reason: explanation for why the rule exists\n * - match: scope of the rule (graph_only for routes, model_only for elements)\n *\n * Optional fields:\n * - element_ids: list of element IDs to ignore (only used with match: model_only)\n */\nexport interface IgnoreRule {\n patterns: IgnorePattern[];\n element_ids?: string[];\n reason: string;\n match: \"graph_only\" | \"model_only\";\n}\n\n/**\n * Loader for .dr-verify-ignore.yaml files\n *\n * Supports:\n * - Glob patterns for handler names (e.g., \"*HealthHandler*\")\n * - Exact matching for paths (e.g., \"/health\")\n * - Exact matching for element IDs (e.g., \"api.operation.get-health\")\n * - Bucket scoping via match field (graph_only for routes, model_only for elements)\n *\n * Expected YAML format:\n * ```yaml\n * version: 1\n * ignore:\n * - patterns:\n * - handler: \"*HealthHandler*\"\n * - path: \"/health\"\n * reason: \"Health check endpoints\"\n * match: \"graph_only\"\n * - patterns:\n * - handler: \"*StatusHandler*\"\n * element_ids: [\"api.operation.get-status\"]\n * reason: \"Status endpoint ignored\"\n * match: \"model_only\"\n * ```\n */\nexport class IgnoreFileLoader {\n /**\n * Load ignore rules from a YAML file\n *\n * Returns an empty array if the file doesn't exist (no error).\n * Throws if the file exists but contains structural errors:\n * - Invalid YAML syntax\n * - Parsed YAML is not an object with version and ignore fields\n * - version != 1\n * - ignore is not an array\n * - Rule missing required fields (patterns, reason, match)\n * - Rule has unknown fields (only patterns, reason, match, element_ids allowed)\n * - Pattern objects with unsupported fields (only handler, path allowed)\n * - Invalid match values\n * - element_ids not an array of strings\n *\n * @param filePath Path to the .dr-verify-ignore.yaml file\n * @returns Array of validated ignore rules\n * @throws Error on structural issues with the ignore file\n */\n static async load(filePath: string): Promise<IgnoreRule[]> {\n try {\n const content = await readFile(filePath, \"utf-8\");\n const parsed = yaml.parse(content);\n\n // Validate schema: expect version 1 and an ignore array\n if (!parsed || typeof parsed !== \"object\") {\n throw new Error(\n \"Invalid ignore file: expected an object with 'version' and 'ignore' fields\"\n );\n }\n\n const { version, ignore } = parsed as { version?: number; ignore?: unknown[] };\n\n if (version !== 1) {\n throw new Error(\n `Invalid ignore file version: expected 1, got ${version ?? \"missing\"}`\n );\n }\n\n if (!Array.isArray(ignore)) {\n throw new Error(\n \"Invalid ignore file: 'ignore' field must be an array of rules\"\n );\n }\n\n // Validate and collect rules\n const rules: IgnoreRule[] = [];\n for (let i = 0; i < ignore.length; i++) {\n const ruleObj = ignore[i];\n\n // Validate rule is an object\n if (!ruleObj || typeof ruleObj !== \"object\") {\n throw new Error(\n `Rule at index ${i} is not an object`\n );\n }\n\n const rule = ruleObj as any;\n const reason = rule.reason as string | undefined;\n const match = rule.match as string | undefined;\n const patterns = rule.patterns as unknown[] | undefined;\n const elementIds = rule.element_ids as unknown[] | undefined;\n\n // Validate required fields\n if (!reason || typeof reason !== \"string\") {\n throw new Error(\n `Rule at index ${i} missing required field: 'reason' (must be a string)`\n );\n }\n\n if (!match || typeof match !== \"string\") {\n throw new Error(\n `Rule at index ${i} missing required field: 'match' (must be a string)`\n );\n }\n\n if (match !== \"graph_only\" && match !== \"model_only\") {\n throw new Error(\n `Rule at index ${i} has invalid 'match' value: '${match}' (must be 'graph_only' or 'model_only')`\n );\n }\n\n if (!Array.isArray(patterns)) {\n throw new Error(\n `Rule at index ${i} missing required field: 'patterns' (must be an array of pattern objects)`\n );\n }\n\n if (patterns.length === 0) {\n throw new Error(\n `Rule at index ${i} has empty 'patterns' array (must contain at least one pattern)`\n );\n }\n\n // Validate element_ids if present\n let normalizedElementIds: string[] | undefined;\n if (elementIds !== undefined) {\n if (!Array.isArray(elementIds)) {\n throw new Error(\n `Rule at index ${i}: 'element_ids' must be an array`\n );\n }\n if (!elementIds.every((id: unknown) => typeof id === \"string\")) {\n throw new Error(\n `Rule at index ${i}: 'element_ids' must contain only strings`\n );\n }\n normalizedElementIds = elementIds;\n }\n\n // Check for unknown fields at rule level\n const validRuleFields = new Set([\"patterns\", \"reason\", \"match\", \"element_ids\"]);\n for (const field of Object.keys(rule)) {\n if (!validRuleFields.has(field)) {\n throw new Error(\n `Rule at index ${i}: unknown field '${field}' (valid fields: patterns, reason, match, element_ids)`\n );\n }\n }\n\n // Validate and normalize patterns\n const normalizedPatterns: IgnorePattern[] = [];\n for (let j = 0; j < patterns.length; j++) {\n const patternObj = patterns[j];\n\n if (!patternObj || typeof patternObj !== \"object\") {\n throw new Error(\n `Rule at index ${i}, pattern at index ${j} is not an object`\n );\n }\n\n const pattern = patternObj as any;\n const normalizedPattern: IgnorePattern = {};\n\n // Extract handler if present\n if (\"handler\" in pattern) {\n if (typeof pattern.handler !== \"string\") {\n throw new Error(\n `Rule at index ${i}, pattern at index ${j}: 'handler' must be a string`\n );\n }\n normalizedPattern.handler = pattern.handler;\n }\n\n // Extract path if present\n if (\"path\" in pattern) {\n if (typeof pattern.path !== \"string\") {\n throw new Error(\n `Rule at index ${i}, pattern at index ${j}: 'path' must be a string`\n );\n }\n normalizedPattern.path = pattern.path;\n }\n\n // Check for unknown fields (element_ids should not be in patterns)\n const validFields = new Set([\"handler\", \"path\"]);\n for (const field of Object.keys(pattern)) {\n if (!validFields.has(field)) {\n throw new Error(\n `Rule at index ${i}, pattern at index ${j}: unknown field '${field}' (valid fields: handler, path)`\n );\n }\n }\n\n // At least one matching criterion must be present\n if (Object.keys(normalizedPattern).length === 0) {\n throw new Error(\n `Rule at index ${i}, pattern at index ${j} has no matching criteria (must have at least one of: handler, path)`\n );\n }\n\n normalizedPatterns.push(normalizedPattern);\n }\n\n const ignoreRule: IgnoreRule = {\n patterns: normalizedPatterns,\n reason,\n match,\n };\n\n if (normalizedElementIds !== undefined) {\n ignoreRule.element_ids = normalizedElementIds;\n }\n\n rules.push(ignoreRule);\n }\n\n return rules;\n } catch (error) {\n // File not found is expected (return empty rules, no error)\n const err = error as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\") {\n return [];\n }\n\n // YAML parse errors and validation errors should be thrown\n throw error;\n }\n }\n\n /**\n * Check if an entry matches any ignore rule and return the matching rule's reason\n *\n * Only considers rules that apply to the specified entry type:\n * - \"route\" entries: only rules with match: \"graph_only\"\n * - \"element\" entries: only rules with match: \"model_only\"\n *\n * A rule matches if ANY of its patterns match (OR semantics within patterns),\n * or if the entry's element_id matches the rule's element_ids list.\n *\n * @param entry Object with optional handler, path, and element_id properties\n * @param type Type of entry (\"route\" for graph entries, \"element\" for model entries)\n * @param rules Array of ignore rules\n * @returns The reason string from the matched rule, or null if no match\n */\n static matches(\n entry: { handler?: string; path?: string; element_id?: string },\n type: \"route\" | \"element\",\n rules: IgnoreRule[]\n ): string | null {\n for (const rule of rules) {\n // Filter by match bucket type\n if (type === \"route\" && rule.match !== \"graph_only\") {\n continue;\n }\n if (type === \"element\" && rule.match !== \"model_only\") {\n continue;\n }\n\n // Check rule-level element_ids (exact match)\n if (rule.element_ids && entry.element_id) {\n if (rule.element_ids.includes(entry.element_id)) {\n return rule.reason;\n }\n }\n\n // Check if any pattern in this rule matches (OR semantics)\n for (const pattern of rule.patterns) {\n // Check handler glob pattern\n if (pattern.handler && entry.handler) {\n if (minimatch(entry.handler, pattern.handler)) {\n return rule.reason;\n }\n }\n\n // Check path exact match\n if (pattern.path && entry.path) {\n if (entry.path === pattern.path) {\n return rule.reason;\n }\n }\n }\n }\n\n return null;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/analyzers/version.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * CLI Version - used by analyzers for MCP client initialization\n *\n * Re-exports the CLI version from spec-version.ts, which is the single source of truth.\n * This ensures consistency across all components (CLI commands, integrations, analyzers).\n */\n\nimport { getCliVersion as getCliVersionImpl } from \"../utils/spec-version.js\";\n\n/**\n * Get the CLI version for analyzer initialization\n * Wrapped as async for compatibility with existing analyzer code,\n * but delegates to the synchronous source-of-truth in spec-version.ts\n */\nexport async function getCliVersion(): Promise<string> {\n return getCliVersionImpl();\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,iBAAiB,yBAAyB;AAOnD,eAAsB,gBAAa;AACjC,SAAO,kBAAiB;AAC1B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { invokeClaudeStreaming } from "../../utils/claude-stream.js";
|
|
2
|
+
class AIEvaluationAbortError extends Error {
|
|
3
|
+
constructor(maxFailures, cause) {
|
|
4
|
+
super(`AI evaluation aborted after ${maxFailures} consecutive failures. Is Claude CLI installed and authenticated? Run 'claude --version' to verify.`, { cause }), this.name = "AIEvaluationAbortError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
class AuditAIRunner {
|
|
8
|
+
rateLimitMs;
|
|
9
|
+
maxConsecutiveFailures;
|
|
10
|
+
consecutiveFailures = 0;
|
|
11
|
+
constructor(rateLimitMs = 1500, maxConsecutiveFailures = 3) {
|
|
12
|
+
this.rateLimitMs = rateLimitMs, this.maxConsecutiveFailures = maxConsecutiveFailures;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Invoke Claude with a prompt, streaming output and returning the response text.
|
|
16
|
+
* Adds a rate-limiting delay after each successful invocation.
|
|
17
|
+
* Throws AIEvaluationAbortError after maxConsecutiveFailures consecutive failures.
|
|
18
|
+
*/
|
|
19
|
+
async invoke(prompt, label, timeoutMs) {
|
|
20
|
+
try {
|
|
21
|
+
const invocation = invokeClaudeStreaming(prompt), result = timeoutMs ? await Promise.race([
|
|
22
|
+
invocation,
|
|
23
|
+
this.rejectAfter(timeoutMs, label ?? "AI invocation")
|
|
24
|
+
]) : await invocation;
|
|
25
|
+
return this.consecutiveFailures = 0, await this.delay(this.rateLimitMs), result;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw this.consecutiveFailures++, this.consecutiveFailures >= this.maxConsecutiveFailures ? new AIEvaluationAbortError(this.maxConsecutiveFailures, error) : error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
delay(ms) {
|
|
31
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
32
|
+
}
|
|
33
|
+
rejectAfter(ms, label) {
|
|
34
|
+
return new Promise((_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
AIEvaluationAbortError,
|
|
39
|
+
AuditAIRunner
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/audit/ai/runner.ts"],
|
|
4
|
+
"sourcesContent": ["import { invokeClaudeStreaming } from \"../../utils/claude-stream.js\";\n\n/**\n * Thrown by AuditAIRunner when consecutive failure limit is reached.\n * Use `instanceof AIEvaluationAbortError` in catch blocks to distinguish\n * this hard abort from transient per-item failures.\n */\nexport class AIEvaluationAbortError extends Error {\n constructor(maxFailures: number, cause: unknown) {\n super(\n `AI evaluation aborted after ${maxFailures} consecutive failures. ` +\n `Is Claude CLI installed and authenticated? Run 'claude --version' to verify.`,\n { cause }\n );\n this.name = \"AIEvaluationAbortError\";\n }\n}\n\n/**\n * Shared AI invocation runner for audit evaluators.\n * Wraps invokeClaudeStreaming with rate limiting and fail-fast on consecutive failures.\n */\nexport class AuditAIRunner {\n private consecutiveFailures = 0;\n\n constructor(\n private readonly rateLimitMs: number = 1500,\n private readonly maxConsecutiveFailures: number = 3\n ) {}\n\n /**\n * Invoke Claude with a prompt, streaming output and returning the response text.\n * Adds a rate-limiting delay after each successful invocation.\n * Throws AIEvaluationAbortError after maxConsecutiveFailures consecutive failures.\n */\n async invoke(prompt: string, label?: string, timeoutMs?: number): Promise<string> {\n try {\n const invocation = invokeClaudeStreaming(prompt);\n const result = timeoutMs\n ? await Promise.race([\n invocation,\n this.rejectAfter(timeoutMs, label ?? \"AI invocation\"),\n ])\n : await invocation;\n this.consecutiveFailures = 0;\n await this.delay(this.rateLimitMs);\n return result;\n } catch (error: unknown) {\n this.consecutiveFailures++;\n if (this.consecutiveFailures >= this.maxConsecutiveFailures) {\n throw new AIEvaluationAbortError(this.maxConsecutiveFailures, error);\n }\n throw error;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n private rejectAfter(ms: number, label: string): Promise<never> {\n return new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,6BAA6B;AAOhC,MAAO,+BAA+B,MAAK;EAC/C,YAAY,aAAqB,OAAc;AAC7C,UACE,+BAA+B,WAAW,uGAE1C,EAAE,MAAK,CAAE,GAEX,KAAK,OAAO;EACd;;AAOI,MAAO,cAAa;EAIL;EACA;EAJX,sBAAsB;EAE9B,YACmB,cAAsB,MACtB,yBAAiC,GAAC;AADlC,SAAA,cAAA,aACA,KAAA,yBAAA;EAChB;;;;;;EAOH,MAAM,OAAO,QAAgB,OAAgB,WAAkB;AAC7D,QAAI;AACF,YAAM,aAAa,sBAAsB,MAAM,GACzC,SAAS,YACX,MAAM,QAAQ,KAAK;QACjB;QACA,KAAK,YAAY,WAAW,SAAS,eAAe;OACrD,IACD,MAAM;AACV,kBAAK,sBAAsB,GAC3B,MAAM,KAAK,MAAM,KAAK,WAAW,GAC1B;IACT,SAAS,OAAgB;AAEvB,YADA,KAAK,uBACD,KAAK,uBAAuB,KAAK,yBAC7B,IAAI,uBAAuB,KAAK,wBAAwB,KAAK,IAE/D;IACR;EACF;EAEQ,MAAM,IAAU;AACtB,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;EACzD;EAEQ,YAAY,IAAY,OAAa;AAC3C,WAAO,IAAI,QAAQ,CAAC,GAAG,WACrB,WAAW,MAAM,OAAO,IAAI,MAAM,GAAG,KAAK,oBAAoB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;EAE/E;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|