@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/telemetry/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * OpenTelemetry instrumentation module.\n *\n * This module wraps OpenTelemetry SDK initialization and provides span creation utilities.\n * It is completely no-op when TELEMETRY_ENABLED is false, allowing complete elimination\n * of telemetry code from production bundles via dead code elimination.\n *\n * Pattern: All telemetry function calls must be directly guarded by `if (TELEMETRY_ENABLED)` checks\n * in calling code to enable tree-shaking and zero-cost abstractions in production builds.\n */\n\nimport type { Span } from '@opentelemetry/api';\nimport type { NodeSDK } from '@opentelemetry/sdk-node';\nimport type { Tracer } from '@opentelemetry/api';\nimport type { Logger as OTelLogger } from '@opentelemetry/api-logs';\n\n// Fallback for runtime environments where TELEMETRY_ENABLED is not defined by esbuild\n// This ensures tests and non-bundled code don't crash\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== 'undefined' ? TELEMETRY_ENABLED : false;\n\n// Module-level state: SDK and tracer instances\n// Only initialized when TELEMETRY_ENABLED is true\nlet sdk: NodeSDK | null = null;\nlet tracer: Tracer | null = null;\nlet loggerProvider: any = null; // LoggerProvider type\nlet logger: OTelLogger | null = null;\n\n// Cache OpenTelemetry API imports for synchronous access\nlet cachedContext: any = null;\nlet cachedTrace: any = null;\n\n/**\n * Initialize OpenTelemetry SDK. Must be called before creating spans.\n * No-op when TELEMETRY_ENABLED is false.\n *\n * Initializes both TracerProvider and LoggerProvider with:\n * - Service name: \"dr-cli\"\n * - Service version: from package.json\n * - Project name: from model manifest (if available)\n * - Processors: SimpleSpanProcessor and SimpleLogRecordProcessor\n * - Exporters: ResilientOTLPExporter with circuit-breaker pattern\n *\n * The exporters target http://localhost:4318/v1/traces and /v1/logs by default,\n * configurable via OTEL_EXPORTER_OTLP_ENDPOINT environment variable.\n * Log endpoint can be overridden with OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.\n */\nexport async function initTelemetry(): Promise<void> {\n if (isTelemetryEnabled) {\n // Dynamic imports ensure tree-shaking when TELEMETRY_ENABLED is false\n // These imports are completely eliminated from production builds\n const [\n { NodeSDK },\n { SimpleSpanProcessor },\n { SimpleLogRecordProcessor },\n otelApi,\n { Resource },\n { LoggerProvider },\n { ResilientOTLPExporter },\n { ResilientLogExporter },\n ] = await Promise.all([\n import('@opentelemetry/sdk-node'),\n import('@opentelemetry/sdk-trace-base'),\n import('@opentelemetry/sdk-logs'),\n import('@opentelemetry/api'),\n import('@opentelemetry/resources'),\n import('@opentelemetry/sdk-logs'),\n import('./resilient-exporter.js'),\n import('./resilient-log-exporter.js'),\n ]);\n\n // Cache API imports for synchronous access in other functions\n const { trace, context } = otelApi;\n cachedTrace = trace;\n cachedContext = context;\n\n // Attempt to get CLI version from package.json\n let cliVersion = '0.1.0';\n try {\n const pkg = await import('../../package.json', { assert: { type: 'json' } });\n cliVersion = pkg.default.version || '0.1.0';\n } catch {\n // Use default version if package.json not accessible\n }\n\n // Attempt to load project name from model manifest\n let projectName = 'unknown';\n try {\n // Try to load manifest - this is a best-effort operation\n // If it fails, we just use 'unknown'\n const [{ readFileSync, existsSync }, { parse }] = await Promise.all([\n import('fs'),\n import('yaml'),\n ]);\n\n // Resolve model root and load manifest\n const possiblePaths = [\n `${process.cwd()}/documentation-robotics/model/manifest.yaml`,\n `${process.cwd()}/model/manifest.yaml`,\n ];\n\n for (const manifestPath of possiblePaths) {\n try {\n if (existsSync(manifestPath)) {\n const content = readFileSync(manifestPath, 'utf-8');\n const data = parse(content);\n projectName = data.project?.name || 'unknown';\n break;\n }\n } catch {\n // Continue to next path\n }\n }\n } catch {\n // No manifest found or error loading - use 'unknown'\n }\n\n // Create resource with service attributes\n const resource = new Resource({\n 'service.name': process.env.OTEL_SERVICE_NAME || 'dr-cli',\n 'service.version': cliVersion,\n 'dr.project.name': projectName,\n });\n\n // Create trace exporter with circuit-breaker pattern\n const traceExporter = new ResilientOTLPExporter({\n url:\n process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||\n 'http://localhost:4318/v1/traces',\n timeoutMillis: 500,\n });\n\n // Initialize NodeSDK with SimpleSpanProcessor for immediate export\n const nodeSdk = new NodeSDK({\n resource,\n spanProcessor: new SimpleSpanProcessor(traceExporter),\n });\n\n // Start the SDK\n nodeSdk.start();\n sdk = nodeSdk;\n\n // Get tracer instance for span creation\n tracer = trace.getTracer('dr-cli', cliVersion);\n\n // Create log exporter with circuit-breaker pattern\n const logEndpoint =\n process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ||\n process.env.OTEL_EXPORTER_OTLP_ENDPOINT?.replace(/\\/v1\\/traces$/, '/v1/logs') ||\n 'http://localhost:4318/v1/logs';\n\n const logExporter = new ResilientLogExporter({\n url: logEndpoint,\n timeoutMillis: 500,\n });\n\n // Initialize LoggerProvider\n loggerProvider = new LoggerProvider({\n resource,\n });\n loggerProvider.addLogRecordProcessor(\n new SimpleLogRecordProcessor(logExporter)\n );\n\n // Get logger instance for log emission\n logger = loggerProvider.getLogger('dr-cli', cliVersion);\n }\n}\n\n/**\n * Create and start a new span with the given name and optional attributes.\n * Returns null when telemetry is disabled.\n *\n * This function respects the active context - if there's an active span,\n * the new span will be created as a child of it. This enables proper\n * trace hierarchy.\n *\n * IMPORTANT: This function does NOT set the span as active in the context.\n * For automatic context propagation, use `startActiveSpan()` instead.\n *\n * Must be called within an `if (TELEMETRY_ENABLED)` guard in calling code:\n *\n * ```typescript\n * if (TELEMETRY_ENABLED) {\n * const span = startSpan('operation', { 'attr.key': 'value' });\n * try {\n * // ... operation ...\n * } finally {\n * endSpan(span);\n * }\n * }\n * ```\n */\nexport function startSpan(\n name: string,\n attributes?: Record<string, any>\n): Span | null {\n if (isTelemetryEnabled && tracer && cachedContext) {\n // Start span with current context to respect parent-child relationships\n return tracer.startSpan(name, { attributes }, cachedContext.active());\n }\n return null;\n}\n\n/**\n * Execute a function with an active span that properly propagates context.\n * Child spans created within the callback will automatically be linked to this span.\n *\n * Returns the result of the callback function.\n * Returns the callback result directly when telemetry is disabled.\n *\n * ```typescript\n * const result = await startActiveSpan('operation', async (span) => {\n * // Child spans here will be properly linked\n * return doWork();\n * }, { 'attr.key': 'value' });\n * ```\n */\nexport async function startActiveSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T>,\n attributes?: Record<string, any>\n): Promise<T> {\n if (!isTelemetryEnabled || !tracer) {\n // Create a no-op span for the callback\n const noopSpan = {\n end: () => {},\n setAttribute: () => noopSpan,\n setAttributes: () => noopSpan,\n addEvent: () => noopSpan,\n setStatus: () => noopSpan,\n updateName: () => noopSpan,\n isRecording: () => false,\n recordException: () => {},\n spanContext: () => ({\n traceId: '',\n spanId: '',\n traceFlags: 0,\n }),\n } as any;\n return fn(noopSpan);\n }\n\n // Use tracer.startActiveSpan for proper context propagation\n return new Promise<T>((resolve, reject) => {\n tracer!.startActiveSpan(name, { attributes }, async (span) => {\n try {\n const result = await fn(span);\n span.end();\n resolve(result);\n } catch (error) {\n span.recordException(error as Error);\n span.end();\n reject(error);\n }\n });\n });\n}\n\n/**\n * End a span. Safe to call with null (no-op).\n *\n * Should be called in a finally block to ensure proper cleanup:\n *\n * ```typescript\n * const span = startSpan('operation');\n * try {\n * // ... operation ...\n * } finally {\n * endSpan(span);\n * }\n * ```\n */\nexport function endSpan(span: Span | null): void {\n if (isTelemetryEnabled && span) {\n span.end();\n }\n}\n\n/**\n * Emit a log record with optional severity level and attributes.\n * Automatically correlates logs with active spans by attaching traceId and spanId.\n * Returns without emitting if telemetry is disabled.\n *\n * ```typescript\n * import { SeverityNumber } from '@opentelemetry/api-logs';\n * emitLog(SeverityNumber.INFO, 'User logged in', { 'user.id': '12345' });\n * ```\n */\nexport function emitLog(\n severity: number,\n message: string,\n attributes?: Record<string, any>\n): void {\n if (!isTelemetryEnabled || !logger || !cachedTrace) {\n return;\n }\n\n const span = cachedTrace.getActiveSpan();\n const context = span?.spanContext();\n\n // Map severity number to text representation\n // Reference: https://opentelemetry.io/docs/reference/specification/logs/data-model/#severity_number\n const severityMap: Record<number, string> = {\n 0: 'UNSPECIFIED',\n 1: 'TRACE',\n 2: 'TRACE2',\n 3: 'TRACE3',\n 4: 'TRACE4',\n 5: 'DEBUG',\n 6: 'DEBUG2',\n 7: 'DEBUG3',\n 8: 'DEBUG4',\n 9: 'INFO',\n 10: 'INFO2',\n 11: 'INFO3',\n 12: 'INFO4',\n 13: 'WARN',\n 14: 'WARN2',\n 15: 'WARN3',\n 16: 'WARN4',\n 17: 'ERROR',\n 18: 'ERROR2',\n 19: 'ERROR3',\n 20: 'ERROR4',\n 21: 'FATAL',\n 22: 'FATAL2',\n 23: 'FATAL3',\n 24: 'FATAL4',\n };\n\n const severityText = severityMap[severity] || 'UNSPECIFIED';\n\n logger.emit({\n severityNumber: severity,\n severityText,\n body: message,\n attributes: {\n ...attributes,\n ...(context && {\n 'trace.id': context.traceId,\n 'span.id': context.spanId,\n }),\n },\n });\n}\n\n/**\n * Shutdown the OpenTelemetry SDK gracefully.\n * Should be called on process exit to ensure all pending spans and logs are exported.\n *\n * Ignores shutdown failures to avoid blocking process exit.\n */\nexport async function shutdownTelemetry(): Promise<void> {\n if (isTelemetryEnabled) {\n try {\n await sdk?.shutdown();\n } catch {\n // Silently ignore shutdown failures - don't block process exit\n }\n\n try {\n await loggerProvider?.shutdown();\n } catch {\n // Silently ignore shutdown failures - don't block process exit\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * OpenTelemetry instrumentation module.\n *\n * This module wraps OpenTelemetry SDK initialization and provides span creation utilities.\n * It is completely no-op when TELEMETRY_ENABLED is false, allowing complete elimination\n * of telemetry code from production bundles via dead code elimination.\n *\n * Pattern: All telemetry function calls must be directly guarded by `if (TELEMETRY_ENABLED)` checks\n * in calling code to enable tree-shaking and zero-cost abstractions in production builds.\n */\n\nimport type { Span } from \"@opentelemetry/api\";\nimport type { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport type { Tracer } from \"@opentelemetry/api\";\nimport type { Logger as OTelLogger } from \"@opentelemetry/api-logs\";\n\n// Re-export SeverityNumber for convenience\nexport { SeverityNumber } from \"@opentelemetry/api-logs\";\n\n// Fallback for runtime environments where TELEMETRY_ENABLED is not defined by esbuild\n// This ensures tests and non-bundled code don't crash\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\ndeclare const CLI_VERSION: string | undefined;\n\nexport const isTelemetryEnabled =\n typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\n// Module-level state: SDK and tracer instances\n// Only initialized when TELEMETRY_ENABLED is true\nlet sdk: NodeSDK | null = null;\nlet tracer: Tracer | null = null;\nlet loggerProvider: any = null; // LoggerProvider type\nlet logger: OTelLogger | null = null;\nlet spanProcessor: any = null; // SimpleSpanProcessor type - stored for explicit forceFlush\n\n// Cache OpenTelemetry API imports for synchronous access\nlet cachedContext: any = null;\nlet cachedTrace: any = null;\n\n/**\n * Initialize OpenTelemetry SDK. Must be called before creating spans.\n * No-op when TELEMETRY_ENABLED is false.\n *\n * Initializes both TracerProvider and LoggerProvider with:\n * - Service name: from environment/config (default \"dr-cli\")\n * - Service version: from package.json\n * - Project name: from model manifest (if available)\n * - Processors: SimpleSpanProcessor and SimpleLogRecordProcessor\n * - Exporters: ResilientOTLPExporter with circuit-breaker pattern\n *\n * OTLP configuration is loaded from multiple sources with precedence:\n * 1. Environment variables (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_SERVICE_NAME)\n * 2. ~/.dr-config.yaml telemetry.otlp section\n * 3. Hard-coded defaults (http://localhost:4318/v1/traces and /v1/logs)\n *\n * See: cli/src/telemetry/config.ts for configuration loading details\n */\nexport async function initTelemetry(): Promise<void> {\n if (isTelemetryEnabled) {\n // Load OTLP config FIRST to check if telemetry is actually configured\n const { loadOTLPConfig } = await import(\"./config.js\");\n const otlpConfig = await loadOTLPConfig();\n\n // Early return if not configured - avoid loading heavy OTEL dependencies\n // This prevents hanging at shutdown when no collector is running\n if (!otlpConfig.isExplicitlyConfigured) {\n return;\n }\n\n // Now load heavy dependencies only when telemetry is actually configured\n const [\n { NodeSDK },\n { BatchSpanProcessor },\n { SimpleLogRecordProcessor },\n otelApi,\n { Resource },\n { LoggerProvider },\n { ResilientOTLPExporter },\n { ResilientLogExporter },\n ] = await Promise.all([\n import(\"@opentelemetry/sdk-node\"),\n import(\"@opentelemetry/sdk-trace-base\"),\n import(\"@opentelemetry/sdk-logs\"),\n import(\"@opentelemetry/api\"),\n import(\"@opentelemetry/resources\"),\n import(\"@opentelemetry/sdk-logs\"),\n import(\"./resilient-exporter.js\"),\n import(\"./resilient-log-exporter.js\"),\n ]);\n\n // Cache API imports for synchronous access in other functions\n const { trace, context } = otelApi;\n cachedTrace = trace;\n cachedContext = context;\n\n // Debug: Log loaded configuration\n if (process.env.DR_TELEMETRY_DEBUG) {\n process.stderr.write(\"[TELEMETRY] Configuration loaded:\\n\");\n process.stderr.write(` - Traces endpoint: ${otlpConfig.endpoint}\\n`);\n process.stderr.write(` - Logs endpoint: ${otlpConfig.logsEndpoint}\\n`);\n process.stderr.write(` - Service name: ${otlpConfig.serviceName}\\n`);\n }\n\n // Use build-time constant (substituted by esbuild)\n const cliVersion = typeof CLI_VERSION !== \"undefined\" ? CLI_VERSION : \"0.1.0\";\n\n // Attempt to load project name from model manifest\n let projectName = \"unknown\";\n try {\n // Try to load manifest - this is a best-effort operation\n // If it fails, we just use 'unknown'\n const [{ readFileSync, existsSync }, { parse }] = await Promise.all([\n import(\"fs\"),\n import(\"yaml\"),\n ]);\n\n // Resolve model root and load manifest\n const possiblePaths = [\n `${process.cwd()}/documentation-robotics/model/manifest.yaml`,\n `${process.cwd()}/model/manifest.yaml`,\n ];\n\n for (const manifestPath of possiblePaths) {\n try {\n if (existsSync(manifestPath)) {\n const content = readFileSync(manifestPath, \"utf-8\");\n const data = parse(content);\n projectName = data.project?.name || \"unknown\";\n break;\n }\n } catch {\n // Continue to next path\n }\n }\n } catch {\n // No manifest found or error loading - use 'unknown'\n }\n\n // Create resource with service attributes\n const resource = new Resource({\n \"service.name\": otlpConfig.serviceName,\n \"service.version\": cliVersion,\n \"dr.project.name\": projectName,\n });\n\n // Create trace exporter with circuit-breaker pattern\n const traceExporter = new ResilientOTLPExporter({\n url: otlpConfig.endpoint,\n timeoutMillis: 10000, // 10s timeout for network requests (serialization + network latency)\n });\n\n // Create span processor with batching for CLI use case\n // Short delay (100ms) balances latency vs HTTP request overhead\n spanProcessor = new BatchSpanProcessor(traceExporter, {\n maxExportBatchSize: 32,\n maxQueueSize: 64,\n scheduledDelayMillis: 100, // Short delay for CLI\n exportTimeoutMillis: 5000,\n });\n\n // Initialize NodeSDK with BatchSpanProcessor for efficient export\n const nodeSdk = new NodeSDK({\n resource,\n spanProcessor,\n });\n\n // Start the SDK\n nodeSdk.start();\n sdk = nodeSdk;\n\n // Get tracer instance for span creation\n tracer = trace.getTracer(\"dr-cli\", cliVersion);\n\n // Debug: Log SDK initialization\n if (process.env.DR_TELEMETRY_DEBUG || process.env.DEBUG || process.env.VERBOSE) {\n process.stderr.write(\"[TELEMETRY] SDK initialized successfully\\n\");\n process.stderr.write(`[TELEMETRY] Tracer initialized: ${tracer !== null}\\n`);\n }\n\n // Create log exporter with circuit-breaker pattern\n const logExporter = new ResilientLogExporter({\n url: otlpConfig.logsEndpoint,\n timeoutMillis: 10000, // 10s timeout for network requests (serialization + network latency)\n });\n\n // Initialize LoggerProvider\n loggerProvider = new LoggerProvider({\n resource,\n });\n loggerProvider.addLogRecordProcessor(new SimpleLogRecordProcessor(logExporter));\n\n // Get logger instance for log emission\n logger = loggerProvider.getLogger(\"dr-cli\", cliVersion);\n }\n}\n\n/**\n * Create and start a new span with the given name and optional attributes.\n * Returns null when telemetry is disabled.\n *\n * This function respects the active context - if there's an active span,\n * the new span will be created as a child of it. This enables proper\n * trace hierarchy.\n *\n * IMPORTANT: This function does NOT set the span as active in the context.\n * For automatic context propagation, use `startActiveSpan()` instead.\n *\n * Must be called within an `if (TELEMETRY_ENABLED)` guard in calling code:\n *\n * ```typescript\n * if (TELEMETRY_ENABLED) {\n * const span = startSpan('operation', { 'attr.key': 'value' });\n * try {\n * // ... operation ...\n * } finally {\n * endSpan(span);\n * }\n * }\n * ```\n */\nexport function startSpan(name: string, attributes?: Record<string, any>): Span | null {\n if (isTelemetryEnabled && tracer && cachedContext) {\n // Start span with current context to respect parent-child relationships\n return tracer.startSpan(name, { attributes }, cachedContext.active());\n }\n\n // Debug: Log why span creation was skipped\n if (process.env.DR_TELEMETRY_DEBUG || process.env.DEBUG || process.env.VERBOSE) {\n if (!isTelemetryEnabled) {\n process.stderr.write(\n `[TELEMETRY] startSpan('${name}'): skipped - isTelemetryEnabled=false\\n`\n );\n } else if (!tracer) {\n process.stderr.write(\n `[TELEMETRY] startSpan('${name}'): skipped - tracer is null (initTelemetry not called?)\\n`\n );\n } else if (!cachedContext) {\n process.stderr.write(`[TELEMETRY] startSpan('${name}'): skipped - cachedContext is null\\n`);\n }\n }\n\n return null;\n}\n\n/**\n * Execute a function with an active span that properly propagates context.\n * Child spans created within the callback will automatically be linked to this span.\n *\n * Returns the result of the callback function.\n * Returns the callback result directly when telemetry is disabled.\n *\n * ```typescript\n * const result = await startActiveSpan('operation', async (span) => {\n * // Child spans here will be properly linked\n * return doWork();\n * }, { 'attr.key': 'value' });\n * ```\n */\nexport async function startActiveSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T>,\n attributes?: Record<string, any>\n): Promise<T> {\n if (!isTelemetryEnabled || !tracer) {\n // Create a no-op span for the callback\n const noopSpan = {\n end: () => {},\n setAttribute: () => noopSpan,\n setAttributes: () => noopSpan,\n addEvent: () => noopSpan,\n setStatus: () => noopSpan,\n updateName: () => noopSpan,\n isRecording: () => false,\n recordException: () => {},\n spanContext: () => ({\n traceId: \"\",\n spanId: \"\",\n traceFlags: 0,\n }),\n } as any;\n return fn(noopSpan);\n }\n\n // Use tracer.startActiveSpan for proper context propagation\n return new Promise<T>((resolve, reject) => {\n tracer!.startActiveSpan(name, { attributes }, async (span) => {\n try {\n const result = await fn(span);\n span.end();\n resolve(result);\n } catch (error) {\n span.recordException(error as Error);\n span.end();\n reject(error);\n }\n });\n });\n}\n\n/**\n * End a span. Safe to call with null (no-op).\n *\n * Should be called in a finally block to ensure proper cleanup:\n *\n * ```typescript\n * const span = startSpan('operation');\n * try {\n * // ... operation ...\n * } finally {\n * endSpan(span);\n * }\n * ```\n */\nexport function endSpan(span: Span | null): void {\n if (isTelemetryEnabled && span) {\n span.end();\n }\n}\n\n/**\n * Get the currently active span, if any.\n * Returns null when telemetry is disabled or no span is active.\n *\n * Use this to annotate the nearest enclosing active span with\n * command-specific attributes without creating a new span.\n *\n * ```typescript\n * const span = getActiveSpan();\n * span?.setAttribute(\"command.result\", \"success\");\n * ```\n */\nexport function getActiveSpan(): Span | null {\n if (isTelemetryEnabled && cachedTrace) {\n return cachedTrace.getActiveSpan?.() ?? null;\n }\n return null;\n}\n\n/**\n * Emit a log record with optional severity level and attributes.\n * Automatically correlates logs with active spans by attaching traceId and spanId.\n * Returns without emitting if telemetry is disabled.\n *\n * ```typescript\n * import { SeverityNumber } from '@opentelemetry/api-logs';\n * emitLog(SeverityNumber.INFO, 'User logged in', { 'user.id': '12345' });\n * ```\n */\nexport function emitLog(severity: number, message: string, attributes?: Record<string, any>): void {\n if (!isTelemetryEnabled || !logger || !cachedTrace) {\n return;\n }\n\n const span = cachedTrace.getActiveSpan();\n const context = span?.spanContext();\n\n // Map severity number to text representation\n // Reference: https://opentelemetry.io/docs/reference/specification/logs/data-model/#severity_number\n const severityMap: Record<number, string> = {\n 0: \"UNSPECIFIED\",\n 1: \"TRACE\",\n 2: \"TRACE2\",\n 3: \"TRACE3\",\n 4: \"TRACE4\",\n 5: \"DEBUG\",\n 6: \"DEBUG2\",\n 7: \"DEBUG3\",\n 8: \"DEBUG4\",\n 9: \"INFO\",\n 10: \"INFO2\",\n 11: \"INFO3\",\n 12: \"INFO4\",\n 13: \"WARN\",\n 14: \"WARN2\",\n 15: \"WARN3\",\n 16: \"WARN4\",\n 17: \"ERROR\",\n 18: \"ERROR2\",\n 19: \"ERROR3\",\n 20: \"ERROR4\",\n 21: \"FATAL\",\n 22: \"FATAL2\",\n 23: \"FATAL3\",\n 24: \"FATAL4\",\n };\n\n const severityText = severityMap[severity] || \"UNSPECIFIED\";\n\n logger.emit({\n severityNumber: severity,\n severityText,\n body: message,\n attributes: {\n ...attributes,\n ...(context && {\n \"trace.id\": context.traceId,\n \"span.id\": context.spanId,\n }),\n },\n });\n}\n\n/**\n * Flush pending telemetry spans without blocking.\n * Safe to call during idle periods (e.g., between chat messages).\n * Failures are silently ignored to avoid disrupting the application.\n *\n * Use cases:\n * - Long-running chat sessions (call between messages)\n * - Periodic checkpoints in interactive commands\n * - After completing a batch of operations\n *\n * @returns Promise that resolves when flush completes (or immediately if telemetry disabled)\n */\nexport async function flushTelemetry(): Promise<void> {\n if (isTelemetryEnabled && spanProcessor) {\n try {\n // Non-blocking flush of pending spans\n // This exports any spans that haven't been sent yet\n await spanProcessor.forceFlush();\n } catch (error) {\n // Silently ignore flush failures - telemetry should never break the app\n // Spans will be retried on next flush or at shutdown\n }\n }\n}\n\n/**\n * Set a span attribute with type safety.\n * Encapsulates the type cast and provides runtime safety checks.\n * Safe to call with null span (no-op).\n *\n * @param span - The span object (can be null)\n * @param key - The attribute key\n * @param value - The attribute value (string, number, boolean, or array)\n *\n * ```typescript\n * setSpanAttribute(span, \"operation.status\", \"completed\");\n * setSpanAttribute(span, \"element.count\", 42);\n * setSpanAttribute(span, \"is.cached\", true);\n * ```\n */\nexport function setSpanAttribute(\n span: Span | null,\n key: string,\n value: string | number | boolean | (string | number | boolean)[]\n): void {\n if (span && typeof span.setAttribute === \"function\") {\n try {\n (span as any).setAttribute(key, value);\n } catch {\n // Silently ignore attribute setting errors - telemetry should never break the app\n }\n }\n}\n\n/**\n * Shutdown the OpenTelemetry SDK gracefully.\n * Should be called on process exit to ensure all pending spans and logs are exported.\n *\n * Ignores shutdown failures to avoid blocking process exit.\n */\nexport async function shutdownTelemetry(): Promise<void> {\n if (isTelemetryEnabled) {\n try {\n // CRITICAL: Force flush span processor before SDK shutdown\n // This ensures all pending async span exports complete\n // See: https://github.com/open-telemetry/opentelemetry-js/issues/4249\n if (spanProcessor) {\n await spanProcessor.forceFlush();\n }\n\n // Shutdown SDK (will also flush, but forceFlush above ensures pending exports finish)\n await sdk?.shutdown();\n } catch {\n // Silently ignore shutdown failures - don't block process exit\n }\n\n try {\n await loggerProvider?.shutdown();\n } catch {\n // Silently ignore shutdown failures - don't block process exit\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAiBA,SAAS,sBAAsB;AAOxB,MAAM,qBACgC;AAI7C,IAAI,MAAsB,MACtB,SAAwB,MACxB,iBAAsB,MACtB,SAA4B,MAC5B,gBAAqB,MAGrB,gBAAqB,MACrB,cAAmB;AAoBvB,eAAsB,gBAAa;AACjC,MAAI,oBAAoB;AAEtB,UAAM,EAAE,eAAc,IAAK,MAAM,OAAO,aAAa,GAC/C,aAAa,MAAM,eAAc;AAIvC,QAAI,CAAC,WAAW;AACd;AAIF,UAAM,CACJ,EAAE,QAAO,GACT,EAAE,mBAAkB,GACpB,EAAE,yBAAwB,GAC1B,SACA,EAAE,SAAQ,GACV,EAAE,eAAc,GAChB,EAAE,sBAAqB,GACvB,EAAE,qBAAoB,CAAE,IACtB,MAAM,QAAQ,IAAI;MACpB,OAAO,yBAAyB;MAChC,OAAO,+BAA+B;MACtC,OAAO,yBAAyB;MAChC,OAAO,oBAAoB;MAC3B,OAAO,0BAA0B;MACjC,OAAO,yBAAyB;MAChC,OAAO,yBAAyB;MAChC,OAAO,6BAA6B;KACrC,GAGK,EAAE,OAAO,QAAO,IAAK;AAC3B,kBAAc,OACd,gBAAgB,SAGZ,QAAQ,IAAI,uBACd,QAAQ,OAAO,MAAM;CAAqC,GAC1D,QAAQ,OAAO,MAAM,wBAAwB,WAAW,QAAQ;CAAI,GACpE,QAAQ,OAAO,MAAM,sBAAsB,WAAW,YAAY;CAAI,GACtE,QAAQ,OAAO,MAAM,qBAAqB,WAAW,WAAW;CAAI;AAItE,UAAM,aAAkD;AAGxD,QAAI,cAAc;AAClB,QAAI;AAGF,YAAM,CAAC,EAAE,cAAc,WAAU,GAAI,EAAE,MAAK,CAAE,IAAI,MAAM,QAAQ,IAAI;QAClE,OAAO,IAAI;QACX,OAAO,MAAM;OACd,GAGK,gBAAgB;QACpB,GAAG,QAAQ,IAAG,CAAE;QAChB,GAAG,QAAQ,IAAG,CAAE;;AAGlB,iBAAW,gBAAgB;AACzB,YAAI;AACF,cAAI,WAAW,YAAY,GAAG;AAC5B,kBAAM,UAAU,aAAa,cAAc,OAAO;AAElD,0BADa,MAAM,OAAO,EACP,SAAS,QAAQ;AACpC;UACF;QACF,QAAQ;QAER;IAEJ,QAAQ;IAER;AAGA,UAAM,WAAW,IAAI,SAAS;MAC5B,gBAAgB,WAAW;MAC3B,mBAAmB;MACnB,mBAAmB;KACpB,GAGK,gBAAgB,IAAI,sBAAsB;MAC9C,KAAK,WAAW;MAChB,eAAe;;KAChB;AAID,oBAAgB,IAAI,mBAAmB,eAAe;MACpD,oBAAoB;MACpB,cAAc;MACd,sBAAsB;;MACtB,qBAAqB;KACtB;AAGD,UAAM,UAAU,IAAI,QAAQ;MAC1B;MACA;KACD;AAGD,YAAQ,MAAK,GACb,MAAM,SAGN,SAAS,MAAM,UAAU,UAAU,UAAU,IAGzC,QAAQ,IAAI,sBAAsB,QAAQ,IAAI,SAAS,QAAQ,IAAI,aACrE,QAAQ,OAAO,MAAM;CAA4C,GACjE,QAAQ,OAAO,MAAM,mCAAmC,WAAW,IAAI;CAAI;AAI7E,UAAM,cAAc,IAAI,qBAAqB;MAC3C,KAAK,WAAW;MAChB,eAAe;;KAChB;AAGD,qBAAiB,IAAI,eAAe;MAClC;KACD,GACD,eAAe,sBAAsB,IAAI,yBAAyB,WAAW,CAAC,GAG9E,SAAS,eAAe,UAAU,UAAU,UAAU;EACxD;AACF;AA0BM,SAAU,UAAU,MAAc,YAAgC;AACtE,SAAI,sBAAsB,UAAU,gBAE3B,OAAO,UAAU,MAAM,EAAE,WAAU,GAAI,cAAc,OAAM,CAAE,MAIlE,QAAQ,IAAI,sBAAsB,QAAQ,IAAI,SAAS,QAAQ,IAAI,aAChE,qBAIO,SAIA,iBACV,QAAQ,OAAO,MAAM,0BAA0B,IAAI;CAAuC,IAJ1F,QAAQ,OAAO,MACb,0BAA0B,IAAI;CAA4D,IAL5F,QAAQ,OAAO,MACb,0BAA0B,IAAI;CAA0C,IAWvE;AACT;AAgBA,eAAsB,gBACpB,MACA,IACA,YAAgC;AAEhC,MAAI,CAAC,sBAAsB,CAAC,QAAQ;AAElC,UAAM,WAAW;MACf,KAAK,MAAK;MAAE;MACZ,cAAc,MAAM;MACpB,eAAe,MAAM;MACrB,UAAU,MAAM;MAChB,WAAW,MAAM;MACjB,YAAY,MAAM;MAClB,aAAa,MAAM;MACnB,iBAAiB,MAAK;MAAE;MACxB,aAAa,OAAO;QAClB,SAAS;QACT,QAAQ;QACR,YAAY;;;AAGhB,WAAO,GAAG,QAAQ;EACpB;AAGA,SAAO,IAAI,QAAW,CAAC,SAAS,WAAU;AACxC,WAAQ,gBAAgB,MAAM,EAAE,WAAU,GAAI,OAAO,SAAQ;AAC3D,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,IAAI;AAC5B,aAAK,IAAG,GACR,QAAQ,MAAM;MAChB,SAAS,OAAO;AACd,aAAK,gBAAgB,KAAc,GACnC,KAAK,IAAG,GACR,OAAO,KAAK;MACd;IACF,CAAC;EACH,CAAC;AACH;AAgBM,SAAU,QAAQ,MAAiB;AACvC,EAAI,sBAAsB,QACxB,KAAK,IAAG;AAEZ;AAcM,SAAU,gBAAa;AAC3B,SAAI,sBAAsB,cACjB,YAAY,gBAAe,KAAM,OAEnC;AACT;AAYM,SAAU,QAAQ,UAAkB,SAAiB,YAAgC;AACzF,MAAI,CAAC,sBAAsB,CAAC,UAAU,CAAC;AACrC;AAIF,QAAM,UADO,YAAY,cAAa,GAChB,YAAW,GAgC3B,eA5BsC;IAC1C,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IAG2B,QAAQ,KAAK;AAE9C,SAAO,KAAK;IACV,gBAAgB;IAChB;IACA,MAAM;IACN,YAAY;MACV,GAAG;MACH,GAAI,WAAW;QACb,YAAY,QAAQ;QACpB,WAAW,QAAQ;;;GAGxB;AACH;AAcA,eAAsB,iBAAc;AAClC,MAAI,sBAAsB;AACxB,QAAI;AAGF,YAAM,cAAc,WAAU;IAChC,QAAgB;IAGhB;AAEJ;AAiBM,SAAU,iBACd,MACA,KACA,OAAgE;AAEhE,MAAI,QAAQ,OAAO,KAAK,gBAAiB;AACvC,QAAI;AACD,WAAa,aAAa,KAAK,KAAK;IACvC,QAAQ;IAER;AAEJ;AAQA,eAAsB,oBAAiB;AACrC,MAAI,oBAAoB;AACtB,QAAI;AAIF,MAAI,iBACF,MAAM,cAAc,WAAU,GAIhC,MAAM,KAAK,SAAQ;IACrB,QAAQ;IAER;AAEA,QAAI;AACF,YAAM,gBAAgB,SAAQ;IAChC,QAAQ;IAER;EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,24 +1,38 @@
|
|
|
1
1
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
2
2
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
3
|
+
import { FetchOTLPExporter } from "./fetch-otlp-exporter.js";
|
|
3
4
|
class ResilientOTLPExporter {
|
|
4
5
|
delegate;
|
|
5
6
|
retryAfter = 0;
|
|
6
7
|
// Circuit-breaker timestamp
|
|
8
|
+
url;
|
|
9
|
+
debug;
|
|
7
10
|
constructor(config) {
|
|
8
|
-
this.delegate = new
|
|
11
|
+
this.url = config?.url || "http://localhost:4318/v1/traces", this.debug = process.env.DR_TELEMETRY_DEBUG === "1" || process.env.DEBUG === "1" || process.env.VERBOSE === "1", typeof globalThis.Bun < "u" ? (this.delegate = new FetchOTLPExporter({
|
|
12
|
+
url: this.url,
|
|
13
|
+
timeoutMillis: config?.timeoutMillis ?? 1e4
|
|
14
|
+
}), this.debug && process.stderr.write(`[TELEMETRY] Using fetch-based exporter for Bun compatibility
|
|
15
|
+
`)) : (this.delegate = new OTLPTraceExporter({
|
|
9
16
|
...config,
|
|
10
|
-
url:
|
|
11
|
-
timeoutMillis: config?.timeoutMillis ?? 5e3
|
|
12
|
-
//
|
|
13
|
-
|
|
17
|
+
url: this.url,
|
|
18
|
+
timeoutMillis: config?.timeoutMillis ?? 5e3,
|
|
19
|
+
// Disable HTTP keepAlive for CLI use case
|
|
20
|
+
keepAlive: !1
|
|
21
|
+
}), this.debug && process.stderr.write(`[TELEMETRY] Trace exporter initialized: ${this.url}
|
|
22
|
+
`));
|
|
14
23
|
}
|
|
15
24
|
export(spans, resultCallback) {
|
|
16
25
|
if (Date.now() < this.retryAfter) {
|
|
17
|
-
|
|
26
|
+
this.debug && process.stderr.write(`[TELEMETRY] Circuit breaker OPEN - discarding ${spans.length} span(s)
|
|
27
|
+
`), resultCallback({ code: ExportResultCode.SUCCESS });
|
|
18
28
|
return;
|
|
19
29
|
}
|
|
20
|
-
this.
|
|
21
|
-
|
|
30
|
+
this.debug && process.stderr.write(`[TELEMETRY] Exporting ${spans.length} span(s) to ${this.url}
|
|
31
|
+
`), this.delegate.export(spans, (result) => {
|
|
32
|
+
result.code === ExportResultCode.FAILED ? (this.debug && (process.stderr.write(`[TELEMETRY] Export FAILED - circuit breaker opening for 30s
|
|
33
|
+
`), result.error && process.stderr.write(`[TELEMETRY] Error: ${result.error.message}
|
|
34
|
+
`)), this.retryAfter = Date.now() + 3e4, resultCallback({ code: ExportResultCode.SUCCESS })) : (this.debug && process.stderr.write(`[TELEMETRY] Export SUCCESS - ${spans.length} span(s) sent
|
|
35
|
+
`), this.retryAfter = 0, resultCallback(result));
|
|
22
36
|
});
|
|
23
37
|
}
|
|
24
38
|
async forceFlush() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/telemetry/resilient-exporter.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Resilient OTLP exporter with circuit-breaker pattern.\n * Gracefully handles missing OTLP collectors by silently discarding spans\n * instead of blocking CLI execution.\n */\n\nimport type {
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * Resilient OTLP exporter with circuit-breaker pattern.\n * Gracefully handles missing OTLP collectors by silently discarding spans\n * instead of blocking CLI execution.\n */\n\nimport type { SpanExporter, ReadableSpan } from \"@opentelemetry/sdk-trace-base\";\nimport type { ExportResult } from \"@opentelemetry/core\";\nimport { ExportResultCode } from \"@opentelemetry/core\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport { FetchOTLPExporter } from \"./fetch-otlp-exporter.js\";\n\n/**\n * OTLP exporter with circuit-breaker pattern for graceful failure.\n * Silently discards spans when collector is unavailable instead of blocking.\n *\n * Circuit breaker behavior:\n * - Initial state: Attempts export to configured OTLP endpoint\n * - On failure: Sets 30-second backoff window, silently discards spans\n * - Backoff expired: Retries export, resets on success or extends on failure\n * - Timeout: 5000ms timeout to accommodate network latency\n */\nexport class ResilientOTLPExporter implements SpanExporter {\n private delegate: SpanExporter;\n private retryAfter = 0; // Circuit-breaker timestamp\n private readonly url: string;\n private readonly debug: boolean;\n\n constructor(\n config?: Record<string, unknown> & {\n url?: string;\n timeoutMillis?: number;\n }\n ) {\n this.url = config?.url || \"http://localhost:4318/v1/traces\";\n // Enable debug logging if any debug flag is set (consistent with telemetry/index.ts)\n this.debug =\n process.env.DR_TELEMETRY_DEBUG === \"1\" ||\n process.env.DEBUG === \"1\" ||\n process.env.VERBOSE === \"1\";\n\n // Detect if running in Bun and use fetch-based exporter for compatibility\n const isBun = typeof (globalThis as any).Bun !== \"undefined\";\n\n if (isBun) {\n // Use fetch-based exporter for Bun (avoids http module compatibility issues)\n this.delegate = new FetchOTLPExporter({\n url: this.url,\n timeoutMillis: config?.timeoutMillis ?? 10000,\n });\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Using fetch-based exporter for Bun compatibility\\n`);\n }\n } else {\n // Use standard http-based exporter for Node.js with keepAlive disabled\n // for short-lived CLI processes to prevent hanging on shutdown\n this.delegate = new OTLPTraceExporter({\n ...config,\n url: this.url,\n timeoutMillis: config?.timeoutMillis ?? 5000,\n // Disable HTTP keepAlive for CLI use case\n keepAlive: false,\n });\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Trace exporter initialized: ${this.url}\\n`);\n }\n }\n }\n\n export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {\n // Circuit breaker: skip export if recently failed.\n // Boundary: when Date.now() === this.retryAfter, retry immediately (intended behavior).\n if (Date.now() < this.retryAfter) {\n // Pretend success and silently discard spans\n if (this.debug) {\n process.stderr.write(\n `[TELEMETRY] Circuit breaker OPEN - discarding ${spans.length} span(s)\\n`\n );\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Exporting ${spans.length} span(s) to ${this.url}\\n`);\n }\n\n this.delegate.export(spans, (result: ExportResult) => {\n if (result.code === ExportResultCode.FAILED) {\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Export FAILED - circuit breaker opening for 30s\\n`);\n if (result.error) {\n process.stderr.write(`[TELEMETRY] Error: ${result.error.message}\\n`);\n }\n }\n // Set 30-second backoff window after first failure\n this.retryAfter = Date.now() + 30000;\n // Report success to SDK so it doesn't queue/retry internally\n // The backoff mechanism prevents further export attempts\n resultCallback({ code: ExportResultCode.SUCCESS });\n } else {\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Export SUCCESS - ${spans.length} span(s) sent\\n`);\n }\n // Clear backoff on successful export\n this.retryAfter = 0;\n resultCallback(result);\n }\n });\n }\n\n async forceFlush(): Promise<void> {\n // Circuit breaker open: skip flush to avoid wasting resources\n if (Date.now() < this.retryAfter) {\n return;\n }\n\n try {\n await this.delegate.forceFlush?.();\n } catch {\n // Silently ignore flush failures - don't block shutdown\n }\n }\n\n async shutdown(): Promise<void> {\n try {\n await this.delegate.shutdown();\n } catch {\n // Silently ignore shutdown failures - don't let telemetry block process exit\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAY5B,MAAO,sBAAqB;EACxB;EACA,aAAa;;EACJ;EACA;EAEjB,YACE,QAGC;AAED,SAAK,MAAM,QAAQ,OAAO,mCAE1B,KAAK,QACH,QAAQ,IAAI,uBAAuB,OACnC,QAAQ,IAAI,UAAU,OACtB,QAAQ,IAAI,YAAY,KAGZ,OAAQ,WAAmB,MAAQ,OAI/C,KAAK,WAAW,IAAI,kBAAkB;MACpC,KAAK,KAAK;MACV,eAAe,QAAQ,iBAAiB;KACzC,GACG,KAAK,SACP,QAAQ,OAAO,MAAM;CAAgE,MAKvF,KAAK,WAAW,IAAI,kBAAkB;MACpC,GAAG;MACH,KAAK,KAAK;MACV,eAAe,QAAQ,iBAAiB;;MAExC,WAAW;KACZ,GACG,KAAK,SACP,QAAQ,OAAO,MAAM,2CAA2C,KAAK,GAAG;CAAI;EAGlF;EAEA,OAAO,OAAuB,gBAA8C;AAG1E,QAAI,KAAK,IAAG,IAAK,KAAK,YAAY;AAEhC,MAAI,KAAK,SACP,QAAQ,OAAO,MACb,iDAAiD,MAAM,MAAM;CAAY,GAG7E,eAAe,EAAE,MAAM,iBAAiB,QAAO,CAAE;AACjD;IACF;AAEA,IAAI,KAAK,SACP,QAAQ,OAAO,MAAM,yBAAyB,MAAM,MAAM,eAAe,KAAK,GAAG;CAAI,GAGvF,KAAK,SAAS,OAAO,OAAO,CAAC,WAAwB;AACnD,MAAI,OAAO,SAAS,iBAAiB,UAC/B,KAAK,UACP,QAAQ,OAAO,MAAM;CAA+D,GAChF,OAAO,SACT,QAAQ,OAAO,MAAM,sBAAsB,OAAO,MAAM,OAAO;CAAI,IAIvE,KAAK,aAAa,KAAK,IAAG,IAAK,KAG/B,eAAe,EAAE,MAAM,iBAAiB,QAAO,CAAE,MAE7C,KAAK,SACP,QAAQ,OAAO,MAAM,gCAAgC,MAAM,MAAM;CAAiB,GAGpF,KAAK,aAAa,GAClB,eAAe,MAAM;IAEzB,CAAC;EACH;EAEA,MAAM,aAAU;AAEd,QAAI,OAAK,IAAG,IAAK,KAAK;AAItB,UAAI;AACF,cAAM,KAAK,SAAS,aAAY;MAClC,QAAQ;MAER;EACF;EAEA,MAAM,WAAQ;AACZ,QAAI;AACF,YAAM,KAAK,SAAS,SAAQ;IAC9B,QAAQ;IAER;EACF;;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,21 +4,30 @@ class ResilientLogExporter {
|
|
|
4
4
|
delegate;
|
|
5
5
|
retryAfter = 0;
|
|
6
6
|
// Circuit-breaker timestamp
|
|
7
|
+
url;
|
|
8
|
+
debug;
|
|
7
9
|
constructor(config) {
|
|
8
|
-
this.delegate = new OTLPLogExporter({
|
|
10
|
+
this.url = config?.url || "http://localhost:4318/v1/logs", this.debug = process.env.DR_TELEMETRY_DEBUG === "1" || process.env.DEBUG === "1" || process.env.VERBOSE === "1", this.delegate = new OTLPLogExporter({
|
|
9
11
|
...config,
|
|
10
|
-
url:
|
|
11
|
-
timeoutMillis: config?.timeoutMillis ?? 5e3
|
|
12
|
-
//
|
|
13
|
-
|
|
12
|
+
url: this.url,
|
|
13
|
+
timeoutMillis: config?.timeoutMillis ?? 5e3,
|
|
14
|
+
// Disable HTTP keepAlive for CLI use case
|
|
15
|
+
keepAlive: !1
|
|
16
|
+
}), this.debug && process.stderr.write(`[TELEMETRY] Log exporter initialized: ${this.url}
|
|
17
|
+
`);
|
|
14
18
|
}
|
|
15
19
|
export(records, resultCallback) {
|
|
16
20
|
if (Date.now() < this.retryAfter) {
|
|
17
|
-
|
|
21
|
+
this.debug && process.stderr.write(`[TELEMETRY] Circuit breaker OPEN - discarding ${records.length} log(s)
|
|
22
|
+
`), resultCallback({ code: ExportResultCode.SUCCESS });
|
|
18
23
|
return;
|
|
19
24
|
}
|
|
20
|
-
this.
|
|
21
|
-
|
|
25
|
+
this.debug && process.stderr.write(`[TELEMETRY] Exporting ${records.length} log(s) to ${this.url}
|
|
26
|
+
`), this.delegate.export(records, (result) => {
|
|
27
|
+
result.code === ExportResultCode.FAILED ? (this.debug && (process.stderr.write(`[TELEMETRY] Export FAILED - circuit breaker opening for 30s
|
|
28
|
+
`), result.error && process.stderr.write(`[TELEMETRY] Error: ${result.error.message}
|
|
29
|
+
`)), this.retryAfter = Date.now() + 3e4, resultCallback({ code: ExportResultCode.SUCCESS })) : (this.debug && process.stderr.write(`[TELEMETRY] Export SUCCESS - ${records.length} log(s) sent
|
|
30
|
+
`), this.retryAfter = 0, resultCallback(result));
|
|
22
31
|
});
|
|
23
32
|
}
|
|
24
33
|
async forceFlush() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/telemetry/resilient-log-exporter.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Resilient OTLP log exporter with circuit-breaker pattern.\n * Gracefully handles missing OTLP collectors by silently discarding log records\n * instead of blocking CLI execution.\n */\n\nimport type {
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * Resilient OTLP log exporter with circuit-breaker pattern.\n * Gracefully handles missing OTLP collectors by silently discarding log records\n * instead of blocking CLI execution.\n */\n\nimport type { LogRecordExporter, ReadableLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type { ExportResult } from \"@opentelemetry/core\";\nimport { ExportResultCode } from \"@opentelemetry/core\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\n\n/**\n * OTLP log exporter with circuit-breaker pattern for graceful failure.\n * Silently discards log records when collector is unavailable instead of blocking.\n *\n * Circuit breaker behavior:\n * - Initial state: Attempts export to configured OTLP endpoint\n * - On failure: Sets 30-second backoff window, silently discards log records\n * - Backoff expired: Retries export, resets on success or extends on failure\n * - Timeout: 5000ms timeout to accommodate network latency\n */\nexport class ResilientLogExporter implements LogRecordExporter {\n private delegate: OTLPLogExporter;\n private retryAfter = 0; // Circuit-breaker timestamp\n private readonly url: string;\n private readonly debug: boolean;\n\n constructor(\n config?: Record<string, unknown> & {\n url?: string;\n timeoutMillis?: number;\n }\n ) {\n this.url = config?.url || \"http://localhost:4318/v1/logs\";\n // Enable debug logging if any debug flag is set (consistent with telemetry/index.ts)\n this.debug =\n process.env.DR_TELEMETRY_DEBUG === \"1\" ||\n process.env.DEBUG === \"1\" ||\n process.env.VERBOSE === \"1\";\n\n this.delegate = new OTLPLogExporter({\n ...config,\n url: this.url,\n timeoutMillis: config?.timeoutMillis ?? 5000,\n // Disable HTTP keepAlive for CLI use case\n keepAlive: false,\n });\n\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Log exporter initialized: ${this.url}\\n`);\n }\n }\n\n export(records: ReadableLogRecord[], resultCallback: (result: ExportResult) => void): void {\n // Circuit breaker: skip export if recently failed.\n // Boundary: when Date.now() === this.retryAfter, retry immediately (intended behavior).\n if (Date.now() < this.retryAfter) {\n // Pretend success and silently discard log records\n if (this.debug) {\n process.stderr.write(\n `[TELEMETRY] Circuit breaker OPEN - discarding ${records.length} log(s)\\n`\n );\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Exporting ${records.length} log(s) to ${this.url}\\n`);\n }\n\n this.delegate.export(records, (result: ExportResult) => {\n if (result.code === ExportResultCode.FAILED) {\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Export FAILED - circuit breaker opening for 30s\\n`);\n if (result.error) {\n process.stderr.write(`[TELEMETRY] Error: ${result.error.message}\\n`);\n }\n }\n // Set 30-second backoff window after first failure\n this.retryAfter = Date.now() + 30000;\n // Report success to SDK so it doesn't queue/retry internally\n // The backoff mechanism prevents further export attempts\n resultCallback({ code: ExportResultCode.SUCCESS });\n } else {\n if (this.debug) {\n process.stderr.write(`[TELEMETRY] Export SUCCESS - ${records.length} log(s) sent\\n`);\n }\n // Clear backoff on successful export\n this.retryAfter = 0;\n resultCallback(result);\n }\n });\n }\n\n async forceFlush(): Promise<void> {\n // Circuit breaker open: skip flush to avoid wasting resources\n if (Date.now() < this.retryAfter) {\n return;\n }\n\n try {\n await this.delegate.forceFlush?.();\n } catch {\n // Silently ignore flush failures - don't block shutdown\n }\n }\n\n async shutdown(): Promise<void> {\n try {\n await this.delegate.shutdown();\n } catch {\n // Silently ignore shutdown failures - don't let telemetry block process exit\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAQA,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAY1B,MAAO,qBAAoB;EACvB;EACA,aAAa;;EACJ;EACA;EAEjB,YACE,QAGC;AAED,SAAK,MAAM,QAAQ,OAAO,iCAE1B,KAAK,QACH,QAAQ,IAAI,uBAAuB,OACnC,QAAQ,IAAI,UAAU,OACtB,QAAQ,IAAI,YAAY,KAE1B,KAAK,WAAW,IAAI,gBAAgB;MAClC,GAAG;MACH,KAAK,KAAK;MACV,eAAe,QAAQ,iBAAiB;;MAExC,WAAW;KACZ,GAEG,KAAK,SACP,QAAQ,OAAO,MAAM,yCAAyC,KAAK,GAAG;CAAI;EAE9E;EAEA,OAAO,SAA8B,gBAA8C;AAGjF,QAAI,KAAK,IAAG,IAAK,KAAK,YAAY;AAEhC,MAAI,KAAK,SACP,QAAQ,OAAO,MACb,iDAAiD,QAAQ,MAAM;CAAW,GAG9E,eAAe,EAAE,MAAM,iBAAiB,QAAO,CAAE;AACjD;IACF;AAEA,IAAI,KAAK,SACP,QAAQ,OAAO,MAAM,yBAAyB,QAAQ,MAAM,cAAc,KAAK,GAAG;CAAI,GAGxF,KAAK,SAAS,OAAO,SAAS,CAAC,WAAwB;AACrD,MAAI,OAAO,SAAS,iBAAiB,UAC/B,KAAK,UACP,QAAQ,OAAO,MAAM;CAA+D,GAChF,OAAO,SACT,QAAQ,OAAO,MAAM,sBAAsB,OAAO,MAAM,OAAO;CAAI,IAIvE,KAAK,aAAa,KAAK,IAAG,IAAK,KAG/B,eAAe,EAAE,MAAM,iBAAiB,QAAO,CAAE,MAE7C,KAAK,SACP,QAAQ,OAAO,MAAM,gCAAgC,QAAQ,MAAM;CAAgB,GAGrF,KAAK,aAAa,GAClB,eAAe,MAAM;IAEzB,CAAC;EACH;EAEA,MAAM,aAAU;AAEd,QAAI,OAAK,IAAG,IAAK,KAAK;AAItB,UAAI;AACF,cAAM,KAAK,SAAS,aAAY;MAClC,QAAQ;MAER;EACF;EAEA,MAAM,WAAQ;AACZ,QAAI;AACF,YAAM,KAAK,SAAS,SAAQ;IAC9B,QAAQ;IAER;EACF;;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/telemetry/test-instrumentation.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Test instrumentation utilities for OpenTelemetry.\n *\n * Provides utilities for creating spans with test-specific attributes to enable\n * filtering test telemetry in observability platforms like SigNoz.\n *\n * Each test file execution creates a parent span with `test.file` and\n * `test.framework=bun` attributes. Each test case execution creates a child span\n * with `test.name` (test case name) and `test.suite` (describe block name) attributes.\n *\n * Failed test spans include `test.error.message` and `test.error.stack` attributes.\n * All test spans inherit `dr.project.name` from resource attributes.\n *\n * Pattern: All telemetry function calls must be directly guarded by\n * `if (TELEMETRY_ENABLED)` checks in calling code to enable tree-shaking\n * and zero-cost abstractions in production builds.\n */\n\nimport type { Span } from
|
|
5
|
-
"mappings": "AAsBA,MAAM,qBAAgE,IAOhE,cAA+B;EACnC,UAAU;EACV,iBAAiB;;AA0Bb,SAAU,kBAAkB,UAAgB;AACvB,SAAO;AAWlC;AAQM,SAAU,kBAAe;AAU/B;AAyBM,SAAU,
|
|
4
|
+
"sourcesContent": ["/**\n * Test instrumentation utilities for OpenTelemetry.\n *\n * Provides utilities for creating spans with test-specific attributes to enable\n * filtering test telemetry in observability platforms like SigNoz.\n *\n * Each test file execution creates a parent span with `test.file` and\n * `test.framework=bun` attributes. Each test case execution creates a child span\n * with `test.name` (test case name) and `test.suite` (describe block name) attributes.\n *\n * Failed test spans include `test.error.message` and `test.error.stack` attributes.\n * All test spans inherit `dr.project.name` from resource attributes.\n *\n * Pattern: All telemetry function calls must be directly guarded by\n * `if (TELEMETRY_ENABLED)` checks in calling code to enable tree-shaking\n * and zero-cost abstractions in production builds.\n */\n\nimport type { Span } from \"@opentelemetry/api\";\n\n// Fallback for runtime environments where TELEMETRY_ENABLED is not defined by esbuild\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\ninterface TestSpanContext {\n fileSpan: Span | null;\n currentTestFile: string;\n}\n\nconst testContext: TestSpanContext = {\n fileSpan: null,\n currentTestFile: \"unknown\",\n};\n\n/**\n * Start a file-level span for test suite execution.\n * Call this at the beginning of each test file (e.g., in beforeAll hook).\n *\n * Returns the span object for later cleanup, or null when telemetry is disabled.\n *\n * @param filePath - The path to the test file\n * @returns The started span, or null if telemetry is disabled\n *\n * @example\n * ```typescript\n * import { beforeAll, afterAll } from 'bun:test';\n * import { startTestFileSpan, endTestFileSpan } from '../../src/telemetry/test-instrumentation.js';\n *\n * beforeAll(() => {\n * startTestFileSpan('tests/unit/example.test.ts');\n * });\n *\n * afterAll(() => {\n * endTestFileSpan();\n * });\n * ```\n */\nexport function startTestFileSpan(filePath: string): Span | null {\n if (!isTelemetryEnabled) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { startSpan } = require(\"./index.js\");\n\n testContext.currentTestFile = filePath;\n testContext.fileSpan = startSpan(\"test.file\", {\n \"test.file\": filePath,\n \"test.framework\": \"bun\",\n });\n return testContext.fileSpan;\n}\n\n/**\n * End the file-level span.\n * Call this at the end of each test file (e.g., in afterAll hook).\n *\n * No-op when telemetry is disabled.\n */\nexport function endTestFileSpan(): void {\n if (!isTelemetryEnabled) return;\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { endSpan } = require(\"./index.js\");\n\n if (testContext.fileSpan) {\n endSpan(testContext.fileSpan);\n testContext.fileSpan = null;\n }\n}\n\n/**\n * Create a span for a test case execution.\n *\n * Should be called at the start of each test case. The returned span should be\n * passed to recordTestResult() after the test completes to record the result\n * and end the span.\n *\n * @param testName - The name of the test case\n * @param suiteName - Optional describe block name (suite name)\n * @returns The started span, or null if telemetry is disabled\n *\n * @example\n * ```typescript\n * const span = createTestCaseSpan('should validate input', 'ValidationTests');\n * try {\n * // test code here\n * recordTestResult(span, 'pass');\n * } catch (error) {\n * recordTestResult(span, 'fail', error);\n * throw error;\n * }\n * ```\n */\nexport function createTestCaseSpan(testName: string, suiteName?: string): Span | null {\n if (!isTelemetryEnabled) return null;\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { startSpan } = require(\"./index.js\");\n\n return startSpan(\"test.case\", {\n \"test.name\": testName,\n \"test.suite\": suiteName || \"\",\n \"test.file\": testContext.currentTestFile,\n });\n}\n\n/**\n * Record test result and end the test case span.\n *\n * Sets the `test.status` attribute and, for failed tests, records the error\n * message and stack trace. The span is ended after recording the result.\n *\n * Safe to call with null span (no-op).\n *\n * @param span - The test case span (from createTestCaseSpan)\n * @param status - Test result status: 'pass', 'fail', or 'skip'\n * @param error - Optional Error object for failed tests\n *\n * @example\n * ```typescript\n * const span = createTestCaseSpan('my test');\n * try {\n * // test logic\n * recordTestResult(span, 'pass');\n * } catch (error) {\n * recordTestResult(span, 'fail', error as Error);\n * throw error; // Re-throw to fail the test\n * }\n * ```\n */\nexport function recordTestResult(\n span: Span | null,\n status: \"pass\" | \"fail\" | \"skip\",\n error?: Error\n): void {\n if (!isTelemetryEnabled || !span) return;\n\n span.setAttribute(\"test.status\", status);\n\n if (error) {\n span.setAttribute(\"test.error.message\", error.message);\n span.setAttribute(\"test.error.stack\", error.stack || \"\");\n span.recordException(error);\n }\n\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { endSpan } = require(\"./index.js\");\n endSpan(span);\n}\n\n/**\n * Helper for wrapping Bun test cases with instrumentation.\n *\n * This helper wraps a test function to automatically create a test case span,\n * execute the test, record the result, and end the span. Failures are recorded\n * and re-thrown to properly fail the test.\n *\n * @param testName - The name of the test case\n * @param testFn - The test function (async or sync)\n * @param suiteName - Optional describe block name for organization\n * @returns A wrapped test function that handles instrumentation\n *\n * @example\n * ```typescript\n * import { test, describe } from 'bun:test';\n * import { instrumentTest } from '../../src/telemetry/test-instrumentation.js';\n *\n * describe('MyComponent', () => {\n * test('should work', instrumentTest('should work', async () => {\n * // Test logic here\n * }, 'MyComponent'));\n *\n * test('should handle errors', instrumentTest('should handle errors', async () => {\n * // More test logic\n * }, 'MyComponent'));\n * });\n * ```\n */\nexport function instrumentTest(\n testName: string,\n testFn: () => void | Promise<void>,\n suiteName?: string\n): () => Promise<void> {\n return async () => {\n const span = createTestCaseSpan(testName, suiteName);\n try {\n await testFn();\n recordTestResult(span, \"pass\");\n } catch (error) {\n recordTestResult(span, \"fail\", error as Error);\n throw error; // Re-throw to fail the test\n }\n };\n}\n"],
|
|
5
|
+
"mappings": "AAsBA,MAAM,qBAAgE,IAOhE,cAA+B;EACnC,UAAU;EACV,iBAAiB;;AA0Bb,SAAU,kBAAkB,UAAgB;AACvB,SAAO;AAWlC;AAQM,SAAU,kBAAe;AAU/B;AAyBM,SAAU,mBAAmB,UAAkB,WAAkB;AAC5C,SAAO;AAUlC;AA0BM,SAAU,iBACd,MACA,QACA,OAAa;AAef;AA8BM,SAAU,eACd,UACA,QACA,WAAkB;AAElB,SAAO,YAAW;AAChB,UAAM,OAAO,mBAAmB,UAAU,SAAS;AACnD,QAAI;AACF,YAAM,OAAM;IAEd,SAAS,OAAO;AACd,YACM;IACR;EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types/index.js
CHANGED
|
@@ -1 +1,85 @@
|
|
|
1
|
+
function createSha256Hash(hash) {
|
|
2
|
+
if (!/^[a-f0-9]{64}$/.test(hash))
|
|
3
|
+
throw new Error(`Invalid SHA256 hash format. Expected 64-character hex string, got: ${hash}`);
|
|
4
|
+
return hash;
|
|
5
|
+
}
|
|
6
|
+
function createBackupManifest(data) {
|
|
7
|
+
if (!data || typeof data != "object")
|
|
8
|
+
throw new Error("Backup manifest must be an object");
|
|
9
|
+
const obj = data;
|
|
10
|
+
if (!obj.files || !Array.isArray(obj.files))
|
|
11
|
+
throw new Error("Backup manifest must have a files array");
|
|
12
|
+
if (obj.timestamp && typeof obj.timestamp != "string")
|
|
13
|
+
throw new Error("Backup manifest timestamp must be a string if provided");
|
|
14
|
+
let timestamp;
|
|
15
|
+
if (obj.timestamp) {
|
|
16
|
+
const timestampDate = new Date(obj.timestamp);
|
|
17
|
+
if (isNaN(timestampDate.getTime()))
|
|
18
|
+
throw new Error(`Invalid timestamp format: ${obj.timestamp}. Expected ISO 8601 string.`);
|
|
19
|
+
timestamp = obj.timestamp;
|
|
20
|
+
} else
|
|
21
|
+
timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
22
|
+
return {
|
|
23
|
+
files: obj.files.map((file, index) => {
|
|
24
|
+
if (!file || typeof file != "object")
|
|
25
|
+
throw new Error(`Backup manifest file entry ${index} must be an object`);
|
|
26
|
+
const fileObj = file;
|
|
27
|
+
if (typeof fileObj.path != "string" || !fileObj.path)
|
|
28
|
+
throw new Error(`Backup manifest file entry ${index} must have a non-empty path string`);
|
|
29
|
+
if (typeof fileObj.checksum != "string")
|
|
30
|
+
throw new Error(`Backup manifest file entry ${index} must have a checksum string`);
|
|
31
|
+
if (typeof fileObj.size != "number" || fileObj.size < 0)
|
|
32
|
+
throw new Error(`Backup manifest file entry ${index} must have a non-negative size number`);
|
|
33
|
+
return {
|
|
34
|
+
path: fileObj.path,
|
|
35
|
+
checksum: createSha256Hash(fileObj.checksum),
|
|
36
|
+
size: fileObj.size
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
timestamp
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function verifyBackupIntegrity(manifest, readFile, fileExists, backupDir) {
|
|
43
|
+
const { createHash } = await import("crypto"), path = await import("path"), errors = [];
|
|
44
|
+
let filesChecked = 0;
|
|
45
|
+
try {
|
|
46
|
+
for (const file of manifest.files) {
|
|
47
|
+
filesChecked++;
|
|
48
|
+
const backupFilePath = path.join(backupDir, file.path);
|
|
49
|
+
if (!await fileExists(backupFilePath)) {
|
|
50
|
+
errors.push(`Missing: ${file.path}`);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const content = await readFile(backupFilePath), checksum = createHash("sha256").update(content).digest("hex");
|
|
55
|
+
if (checksum !== file.checksum) {
|
|
56
|
+
errors.push(`Checksum mismatch: ${file.path} (expected ${file.checksum}, got ${checksum})`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
content.length !== file.size && errors.push(`Size mismatch: ${file.path} (expected ${file.size}, got ${content.length})`);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
errors.push(`Failed to verify: ${file.path} (${err instanceof Error ? err.message : String(err)})`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
isValid: errors.length === 0,
|
|
66
|
+
filesChecked,
|
|
67
|
+
errors
|
|
68
|
+
};
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
isValid: !1,
|
|
72
|
+
filesChecked,
|
|
73
|
+
errors: [
|
|
74
|
+
...errors,
|
|
75
|
+
`Integrity verification failed: ${err instanceof Error ? err.message : String(err)}`
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
createBackupManifest,
|
|
82
|
+
createSha256Hash,
|
|
83
|
+
verifyBackupIntegrity
|
|
84
|
+
};
|
|
1
85
|
//# sourceMappingURL=index.js.map
|
package/dist/types/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [],
|
|
4
|
-
"sourcesContent": [],
|
|
5
|
-
"mappings": "",
|
|
3
|
+
"sources": ["../../src/types/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Core type definitions for Documentation Robotics CLI\n */\n\n// Import source reference types for use in Element interface\nimport type { SourceReference } from \"./source-reference.js\";\n\n// Export source reference types\nexport type {\n ProvenanceType,\n SourceLocation,\n RepositoryContext,\n SourceReference,\n} from \"./source-reference.js\";\n\n/**\n * Reference across layers\n */\nexport interface Reference {\n source: string; // Element ID\n target: string; // Element ID\n type: string; // Reference type\n description?: string;\n}\n\n/**\n * Intra-layer or cross-layer relationship\n */\nexport interface Relationship {\n source: string;\n target: string;\n predicate: string; // e.g., \"depends-on\", \"implements\"\n layer?: string; // Source element's layer ID\n targetLayer?: string; // Destination element's layer ID; present only for cross-layer relationships\n properties?: Record<string, unknown>;\n category?: \"structural\" | \"behavioral\"; // Relationship classification\n}\n\n/**\n * Element metadata for lifecycle and version tracking\n *\n * Note: created_at and updated_at should be treated as immutable after element initialization.\n * For safe external access with immutability guarantees, use Element.getMetadata() which\n * returns a defensive copy protecting against external mutations.\n */\nexport interface ElementMetadata {\n created_at?: string; // ISO 8601 timestamp (immutable after initialization)\n updated_at?: string; // ISO 8601 timestamp (immutable after initialization)\n created_by?: string;\n version?: number;\n}\n\n/**\n * Element representation\n *\n * Aligned with spec-node.schema.json structure for direct validation\n */\nexport interface Element {\n // Spec-node aligned fields (required in new format)\n id?: string; // UUIDv4 (e.g., \"550e8400-e29b-41d4-a716-446655440000\")\n path?: string; // Human-readable path: {layer}.{type}.{kebab-name} (e.g., \"motivation.goal.increase-revenue\")\n spec_node_id: string; // Reference to spec node type (e.g., \"motivation.goal\")\n type: string; // Denormalized node type (e.g., \"goal\", \"endpoint\")\n layer_id: string; // Denormalized layer ID (e.g., \"motivation\", \"api\")\n name: string;\n\n // Spec-node aligned optional fields\n description?: string;\n attributes?: Record<string, unknown>; // Type-specific properties\n source_reference?: SourceReference; // Provenance tracking\n metadata?: ElementMetadata; // Lifecycle tracking\n\n // Relationship tracking\n references?: Reference[];\n relationships?: Relationship[];\n\n // Internal tracking (unchanged)\n layer?: string;\n filePath?: string;\n rawData?: any;\n}\n\n/**\n * Layer container\n */\nexport interface LayerData {\n elements: Element[];\n metadata?: {\n layer: string;\n version: string;\n };\n}\n\n/**\n * Changeset application history entry\n * Records when changesets were committed or discarded in the model\n */\nexport interface ChangesetHistoryEntry {\n /** Name of the changeset that was committed or discarded */\n name: string;\n /** ISO 8601 timestamp when the action occurred */\n committed_at: string;\n /** Type of action performed */\n action: \"committed\" | \"discarded\";\n}\n\n/**\n * Manifest metadata\n */\nexport interface ManifestData {\n name: string;\n version: string;\n description?: string;\n author?: string;\n created?: string;\n modified?: string;\n specVersion?: string;\n /** Changeset history for the model */\n changeset_history?: ChangesetHistoryEntry[];\n}\n\n/**\n * Model options for customization\n */\nexport interface ModelOptions {\n enableCache?: boolean;\n lazyLoad?: boolean;\n referenceRegistry?: unknown; // Will be properly typed when implemented\n layers?: string[]; // If set, only these layers are loaded (selective loading)\n}\n\n/**\n * JSON Schema property definition\n * Represents a single property in a JSON Schema object\n */\nexport interface JsonSchemaProperty {\n type?: string | string[];\n description?: string;\n enum?: unknown[];\n default?: unknown;\n items?: JsonSchemaProperty;\n properties?: Record<string, JsonSchemaProperty>;\n required?: string[];\n [key: string]: unknown;\n}\n\n/**\n * Tool parameter definition for Claude AI tools\n * Describes a single input parameter for a tool\n */\nexport interface ToolParameter {\n type: string;\n description: string;\n enum?: string[];\n items?: { type: string };\n [key: string]: unknown;\n}\n\n/**\n * Tool input schema definition\n * Defines the structure of inputs for a Claude AI tool\n */\nexport interface ToolInputSchema {\n type: \"object\";\n properties: Record<string, ToolParameter>;\n required: string[];\n}\n\n/**\n * Tool definition for Claude AI integration\n * Represents a single tool that Claude can call\n */\nexport interface ToolDefinition {\n name: string;\n description: string;\n input_schema: ToolInputSchema;\n}\n\n/**\n * Result of executing a tool\n * All tool results are returned as JSON-serializable objects\n */\nexport interface ToolExecutionResult {\n [key: string]: unknown;\n error?: string;\n}\n\n/**\n * SHA256 hash branded type for compile-time safety\n * Prevents accidental misuse of arbitrary strings as cryptographic checksums\n */\nexport type Sha256Hash = string & { readonly __brand: \"Sha256Hash\" };\n\n/**\n * Create a Sha256Hash from a hex string\n * This is the only valid way to construct a Sha256Hash value\n *\n * @param hash - The hex string representation of a SHA256 hash\n * @returns A branded Sha256Hash type\n * @throws Error if the hash is not a valid 64-character hex string\n */\nexport function createSha256Hash(hash: string): Sha256Hash {\n if (!/^[a-f0-9]{64}$/.test(hash)) {\n throw new Error(`Invalid SHA256 hash format. Expected 64-character hex string, got: ${hash}`);\n }\n return hash as Sha256Hash;\n}\n\n/**\n * Backup manifest entry for a single file\n * All properties are readonly to enforce immutability of backup records\n */\nexport interface BackupManifestFile {\n readonly path: string; // Relative path to the file in the backup directory\n readonly checksum: Sha256Hash; // SHA256 hash of the file contents (branded type)\n readonly size: number; // File size in bytes\n}\n\n/**\n * Backup manifest tracking files and creation timestamp\n * Used for backup integrity validation and recovery\n * All properties are readonly to enforce immutability of backup records\n */\nexport interface BackupManifest {\n readonly files: ReadonlyArray<BackupManifestFile>; // Array of backed up files with metadata\n readonly timestamp: string; // ISO 8601 timestamp when backup was created\n}\n\n/**\n * Create and validate a BackupManifest from raw data\n * Validates structure and transforms string checksums to Sha256Hash branded type\n *\n * @param data - Unknown data to validate (typically from JSON parsing)\n * @returns Validated and typed BackupManifest object\n * @throws Error if data doesn't match expected structure or contains invalid checksums\n */\nexport function createBackupManifest(data: unknown): BackupManifest {\n if (!data || typeof data !== \"object\") {\n throw new Error(\"Backup manifest must be an object\");\n }\n\n const obj = data as Record<string, unknown>;\n\n if (!obj.files || !Array.isArray(obj.files)) {\n throw new Error(\"Backup manifest must have a files array\");\n }\n\n if (obj.timestamp && typeof obj.timestamp !== \"string\") {\n throw new Error(\"Backup manifest timestamp must be a string if provided\");\n }\n\n // Validate ISO 8601 timestamp if provided\n let timestamp: string;\n if (obj.timestamp) {\n const timestampDate = new Date(obj.timestamp as string);\n if (isNaN(timestampDate.getTime())) {\n throw new Error(`Invalid timestamp format: ${obj.timestamp}. Expected ISO 8601 string.`);\n }\n timestamp = obj.timestamp as string;\n } else {\n timestamp = new Date().toISOString();\n }\n\n // Validate and transform each file entry\n const files: readonly BackupManifestFile[] = obj.files.map((file: unknown, index: number) => {\n if (!file || typeof file !== \"object\") {\n throw new Error(`Backup manifest file entry ${index} must be an object`);\n }\n\n const fileObj = file as Record<string, unknown>;\n\n if (typeof fileObj.path !== \"string\" || !fileObj.path) {\n throw new Error(`Backup manifest file entry ${index} must have a non-empty path string`);\n }\n\n if (typeof fileObj.checksum !== \"string\") {\n throw new Error(`Backup manifest file entry ${index} must have a checksum string`);\n }\n\n if (typeof fileObj.size !== \"number\" || fileObj.size < 0) {\n throw new Error(`Backup manifest file entry ${index} must have a non-negative size number`);\n }\n\n return {\n path: fileObj.path,\n checksum: createSha256Hash(fileObj.checksum),\n size: fileObj.size,\n };\n });\n\n return {\n files,\n timestamp,\n };\n}\n\n/**\n * Verify backup manifest against actual file checksums\n * Checks that all files in the manifest exist and have matching checksums\n *\n * @param manifest - The backup manifest to verify\n * @param readFile - Function to read file contents by path\n * @param fileExists - Function to check if a file exists by path\n * @param backupDir - Base directory path for resolving relative file paths\n * @returns Promise resolving to verification result with detailed error information\n */\nexport async function verifyBackupIntegrity(\n manifest: BackupManifest,\n readFile: (path: string) => Promise<string>,\n fileExists: (path: string) => Promise<boolean>,\n backupDir: string\n): Promise<{\n isValid: boolean;\n filesChecked: number;\n errors: string[];\n}> {\n const { createHash } = await import(\"crypto\");\n const path = await import(\"path\");\n\n const errors: string[] = [];\n let filesChecked = 0;\n\n try {\n // Verify each file in the manifest\n for (const file of manifest.files) {\n filesChecked++;\n const backupFilePath = path.join(backupDir, file.path);\n\n // Check file exists\n if (!(await fileExists(backupFilePath))) {\n errors.push(`Missing: ${file.path}`);\n continue;\n }\n\n try {\n // Read file and verify checksum\n const content = await readFile(backupFilePath);\n const checksum = createHash(\"sha256\").update(content).digest(\"hex\") as Sha256Hash;\n\n if (checksum !== file.checksum) {\n errors.push(\n `Checksum mismatch: ${file.path} (expected ${file.checksum}, got ${checksum})`\n );\n continue;\n }\n\n // Verify size matches\n if (content.length !== file.size) {\n errors.push(`Size mismatch: ${file.path} (expected ${file.size}, got ${content.length})`);\n }\n } catch (err) {\n errors.push(\n `Failed to verify: ${file.path} (${err instanceof Error ? err.message : String(err)})`\n );\n }\n }\n\n return {\n isValid: errors.length === 0,\n filesChecked,\n errors,\n };\n } catch (err) {\n return {\n isValid: false,\n filesChecked,\n errors: [\n ...errors,\n `Integrity verification failed: ${err instanceof Error ? err.message : String(err)}`,\n ],\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "AAyMM,SAAU,iBAAiB,MAAY;AAC3C,MAAI,CAAC,iBAAiB,KAAK,IAAI;AAC7B,UAAM,IAAI,MAAM,sEAAsE,IAAI,EAAE;AAE9F,SAAO;AACT;AA8BM,SAAU,qBAAqB,MAAa;AAChD,MAAI,CAAC,QAAQ,OAAO,QAAS;AAC3B,UAAM,IAAI,MAAM,mCAAmC;AAGrD,QAAM,MAAM;AAEZ,MAAI,CAAC,IAAI,SAAS,CAAC,MAAM,QAAQ,IAAI,KAAK;AACxC,UAAM,IAAI,MAAM,yCAAyC;AAG3D,MAAI,IAAI,aAAa,OAAO,IAAI,aAAc;AAC5C,UAAM,IAAI,MAAM,wDAAwD;AAI1E,MAAI;AACJ,MAAI,IAAI,WAAW;AACjB,UAAM,gBAAgB,IAAI,KAAK,IAAI,SAAmB;AACtD,QAAI,MAAM,cAAc,QAAO,CAAE;AAC/B,YAAM,IAAI,MAAM,6BAA6B,IAAI,SAAS,6BAA6B;AAEzF,gBAAY,IAAI;EAClB;AACE,iBAAY,oBAAI,KAAI,GAAG,YAAW;AA8BpC,SAAO;IACL,OA3B2C,IAAI,MAAM,IAAI,CAAC,MAAe,UAAiB;AAC1F,UAAI,CAAC,QAAQ,OAAO,QAAS;AAC3B,cAAM,IAAI,MAAM,8BAA8B,KAAK,oBAAoB;AAGzE,YAAM,UAAU;AAEhB,UAAI,OAAO,QAAQ,QAAS,YAAY,CAAC,QAAQ;AAC/C,cAAM,IAAI,MAAM,8BAA8B,KAAK,oCAAoC;AAGzF,UAAI,OAAO,QAAQ,YAAa;AAC9B,cAAM,IAAI,MAAM,8BAA8B,KAAK,8BAA8B;AAGnF,UAAI,OAAO,QAAQ,QAAS,YAAY,QAAQ,OAAO;AACrD,cAAM,IAAI,MAAM,8BAA8B,KAAK,uCAAuC;AAG5F,aAAO;QACL,MAAM,QAAQ;QACd,UAAU,iBAAiB,QAAQ,QAAQ;QAC3C,MAAM,QAAQ;;IAElB,CAAC;IAIC;;AAEJ;AAYA,eAAsB,sBACpB,UACA,UACA,YACA,WAAiB;AAMjB,QAAM,EAAE,WAAU,IAAK,MAAM,OAAO,QAAQ,GACtC,OAAO,MAAM,OAAO,MAAM,GAE1B,SAAmB,CAAA;AACzB,MAAI,eAAe;AAEnB,MAAI;AAEF,eAAW,QAAQ,SAAS,OAAO;AACjC;AACA,YAAM,iBAAiB,KAAK,KAAK,WAAW,KAAK,IAAI;AAGrD,UAAI,CAAE,MAAM,WAAW,cAAc,GAAI;AACvC,eAAO,KAAK,YAAY,KAAK,IAAI,EAAE;AACnC;MACF;AAEA,UAAI;AAEF,cAAM,UAAU,MAAM,SAAS,cAAc,GACvC,WAAW,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAElE,YAAI,aAAa,KAAK,UAAU;AAC9B,iBAAO,KACL,sBAAsB,KAAK,IAAI,cAAc,KAAK,QAAQ,SAAS,QAAQ,GAAG;AAEhF;QACF;AAGA,QAAI,QAAQ,WAAW,KAAK,QAC1B,OAAO,KAAK,kBAAkB,KAAK,IAAI,cAAc,KAAK,IAAI,SAAS,QAAQ,MAAM,GAAG;MAE5F,SAAS,KAAK;AACZ,eAAO,KACL,qBAAqB,KAAK,IAAI,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,GAAG;MAE1F;IACF;AAEA,WAAO;MACL,SAAS,OAAO,WAAW;MAC3B;MACA;;EAEJ,SAAS,KAAK;AACZ,WAAO;MACL,SAAS;MACT;MACA,QAAQ;QACN,GAAG;QACH,kCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;;;EAGxF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=source-reference.js.map
|