@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/commands/visualize.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Visualize command - Launch visualization server\n */\n\nimport ansis from 'ansis';\nimport { Model } from '../core/model.js';\nimport { VisualizationServer } from '../server/server.js';\nimport { logVerbose, logDebug } from '../utils/globals.js';\n\nexport interface VisualizeOptions {\n port?: number;\n noBrowser?: boolean;\n noAuth?: boolean;\n token?: string;\n verbose?: boolean;\n debug?: boolean;\n}\n\nexport async function visualizeCommand(\n options: VisualizeOptions & { port?: string }\n): Promise<void> {\n try {\n logDebug('Loading model for visualization...');\n\n // Load model with full content (no lazy loading)\n const model = await Model.load(process.cwd(), { lazyLoad: false });\n\n logVerbose(`Model loaded: ${model.manifest.name}`);\n\n // Parse and validate port\n let port = 8080;\n if (options.port) {\n const portNum = parseInt(String(options.port), 10);\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(\n `Invalid port number: ${options.port}. Port must be between 1 and 65535.`\n );\n }\n port = portNum;\n }\n\n // Determine auth settings\n const authEnabled = !options.noAuth; // Auth enabled by default unless --no-auth flag\n const authToken = options.token; // Optional token for testing\n\n // Create and start server\n const server = new VisualizationServer(model, { authEnabled, authToken });\n\n logDebug(`Starting visualization server on port ${port}`);\n logDebug(`Authentication: ${authEnabled ? 'enabled' : 'disabled'}`);\n if (authEnabled && authToken) {\n logDebug(`Using provided token for authentication`);\n } else if (authEnabled) {\n logDebug(`Token will be auto-generated`);\n }\n\n await server.start(port);\n\n console.log(ansis.green(`\u2713 Visualization server started`));\n\n // Display access information\n if (authEnabled) {\n const token = server.getAuthToken();\n console.log(ansis.cyan(` Access URL: http://localhost:${port}?token=${token}`));\n console.log(ansis.dim(` Auth token: ${token}`));\n } else {\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`));\n console.log(ansis.yellow(` \u26A0 Authentication disabled`));\n }\n\n logVerbose(` Model: ${model.manifest.name} (${model.getLayerNames().length} layers)`);\n\n // Optionally open browser\n const shouldOpenBrowser = !options.noBrowser;\n logDebug(`Browser auto-open: ${shouldOpenBrowser ? 'enabled' : 'disabled'} (noBrowser=${options.noBrowser})`);\n if (shouldOpenBrowser) {\n // Only open browser if explicitly enabled (not in tests or CI)\n try {\n const command = getOpenCommand();\n // Include token in URL if auth is enabled\n const url = authEnabled\n ? `http://localhost:${port}?token=${server.getAuthToken()}`\n : `http://localhost:${port}`;\n logDebug(`Opening browser with command: ${command} ${url}`);\n Bun.spawn([command, url], {\n stdout: 'ignore',\n stderr: 'ignore'\n });\n } catch (error) {\n logVerbose('Could not auto-open browser (not critical)');\n }\n } else {\n logDebug('Browser auto-open disabled');\n }\n\n // Keep server running\n await new Promise(() => {\n // Never resolves - keeps process alive\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(ansis.red(`Error: ${message}`));\n logDebug(`Stack: ${error instanceof Error ? error.stack : 'N/A'}`);\n process.exit(1);\n }\n}\n\n/**\n * Get the appropriate command to open a URL\n */\nfunction getOpenCommand(): string {\n const platform = process.platform;\n\n if (platform === 'darwin') {\n return 'open';\n }\n if (platform === 'win32') {\n return 'start';\n }\n // Linux and others\n return 'xdg-open';\n}\n"],
|
|
5
|
-
"mappings": "AAIA,OAAO,WAAW;AAClB,SAAS,aAAa;AACtB,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * Visualize command - Launch visualization server\n */\n\nimport ansis from \"ansis\"\nimport { spawn } from \"child_process\"\nimport { fileURLToPath } from \"node:url\"\nimport { dirname } from \"node:path\"\nimport { Model } from \"../core/model.js\"\nimport { logVerbose, logDebug } from \"../utils/globals.js\"\nimport { CLIError } from \"../utils/errors.js\"\nimport { getSpecReferencePath, getModelPath } from \"../utils/project-paths.js\"\nimport { getErrorMessage } from \"../utils/errors.js\"\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false\n\nexport interface VisualizeOptions {\n port?: number\n noBrowser?: boolean\n noAuth?: boolean\n token?: string\n verbose?: boolean\n debug?: boolean\n withDanger?: boolean\n viewerPath?: string\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n const { createServer } = await import(\"net\")\n for (let port = startPort; port <= 65535; port++) {\n const available = await new Promise<boolean>((resolve) => {\n const server = createServer()\n server.once(\"error\", () => resolve(false))\n server.once(\"listening\", () => { server.close(); resolve(true) })\n server.listen(port, \"127.0.0.1\")\n })\n if (available) return port\n }\n throw new Error(\"No available ports found\")\n}\n\nexport async function visualizeCommand(\n options: VisualizeOptions & { port?: string }\n): Promise<void> {\n try {\n logDebug(\"Loading model for validation...\")\n\n // Load model to validate it exists (without full content to be quick)\n const model = await Model.load(process.cwd(), { lazyLoad: true })\n\n logVerbose(`Model validated: ${model.manifest.name}`)\n\n // Parse and validate port\n let port = 8080\n if (options.port) {\n const portNum = parseInt(String(options.port), 10)\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${options.port}. Port must be between 1 and 65535.`)\n }\n port = portNum\n }\n\n // Find an available port starting from the requested port\n port = await findAvailablePort(port)\n if (port !== (options.port ? parseInt(String(options.port), 10) : 8080)) {\n console.log(ansis.yellow(` Port ${options.port ?? 8080} is in use, using port ${port} instead`))\n }\n\n // Determine auth settings\n const authEnabled = !options.noAuth // Auth enabled by default unless --no-auth flag\n const authToken = options.token // Optional token for testing\n const withDanger = options.withDanger || false // Danger mode disabled by default\n\n logDebug(`Starting visualization server on port ${port}`)\n logDebug(`Authentication: ${authEnabled ? \"enabled\" : \"disabled\"}`)\n if (authEnabled && authToken) {\n logDebug(`Using provided token for authentication`)\n } else if (authEnabled) {\n logDebug(`Token will be auto-generated`)\n }\n if (withDanger) {\n logDebug(`Danger mode enabled - chat permissions will be skipped`)\n }\n\n // Get the directory where this command file is located (dist/commands/)\n const commandDir = dirname(fileURLToPath(import.meta.url))\n const distDir = dirname(commandDir) // Go up to dist/\n const serverEntryPath = `${distDir}/server-entry.js`\n\n logDebug(`Server entry path: ${serverEntryPath}`)\n\n // Create telemetry span for server startup\n let serverStartupSpan: any = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Creating visualize.server.startup span\")\n const { startSpan } = await import(\"../telemetry/index.js\")\n serverStartupSpan = startSpan(\"visualize.server.startup\", {\n \"server.port\": port,\n \"server.auth_enabled\": authEnabled,\n \"server.with_danger\": withDanger,\n })\n logDebug(`[Telemetry] Span created: ${serverStartupSpan ? \"success\" : \"failed\"}`)\n } else {\n logDebug(\"[Telemetry] TELEMETRY_ENABLED is false - no spans will be created\")\n }\n\n // Spawn server in Bun subprocess with environment variables\n const env: Record<string, string> = {\n ...process.env,\n DR_VISUALIZE_PORT: String(port),\n DR_PROJECT_PATH: process.cwd(),\n DR_AUTH_ENABLED: String(authEnabled),\n DR_WITH_DANGER: String(withDanger),\n }\n\n // Pass debug/verbose flags to subprocess\n // The CLI --debug flag sets globalOptions.debug, but subprocess checks process.env.DEBUG\n const { isDebug: getDebugState, isVerbose } = await import(\"../utils/globals.js\")\n if (getDebugState()) {\n env.DEBUG = \"1\"\n env.DR_TELEMETRY_DEBUG = \"1\" // Enable telemetry exporter debug logging\n logDebug(\"Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess\")\n }\n if (isVerbose()) {\n env.VERBOSE = \"1\"\n logDebug(\"Passing VERBOSE=1 to subprocess\")\n }\n\n // Only set auth token if provided\n if (authToken) {\n env.DR_AUTH_TOKEN = authToken\n }\n\n // Set custom viewer path if provided\n if (options.viewerPath) {\n env.DR_VIEWER_PATH = options.viewerPath\n logDebug(`Using custom viewer from: ${options.viewerPath}`)\n }\n\n const serverProcess = spawn(\"bun\", [\"run\", serverEntryPath], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env,\n })\n\n let authTokenFromServer = \"\"\n let serverOutput = \"\"\n let serverStartupShown = false\n\n // Capture output from server process\n if (serverProcess.stdout) {\n serverProcess.stdout.on(\"data\", async (data) => {\n const chunk = data.toString()\n serverOutput += chunk\n\n // Process each line individually: Bun's pipe buffering often batches multiple\n // console.log calls (e.g. \"running at\" + \"TOKEN:\") into a single data event.\n // Using startsWith/substring on the whole chunk would either miss the TOKEN line\n // (when \"running at\" is first) or extract a malformed token (when trailing lines\n // are appended), causing the browser to open with the wrong token and get a 403.\n const lines = chunk.split(\"\\n\")\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) continue\n\n // Extract token from output\n if (line.startsWith(\"TOKEN:\")) {\n authTokenFromServer = line.substring(6)\n logDebug(`Received auth token from server: ${authTokenFromServer.substring(0, 8)}...`)\n\n // If we haven't shown startup info yet and now have the token, show it\n if (serverStartupShown && authEnabled && authTokenFromServer) {\n // Update the URL display with actual token\n console.log(\n ansis.cyan(` Access URL: http://localhost:${port}?token=${authTokenFromServer}`)\n )\n console.log(ansis.dim(` Auth token: ${authTokenFromServer}`))\n\n // Now that we have the token, open browser if needed\n if (!options.noBrowser) {\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}?token=${authTokenFromServer}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n }\n }\n }\n\n // Server started message - defer detailed output if auth enabled\n if (line.includes(\"running at http://localhost\")) {\n serverStartupShown = true\n\n // End telemetry span for server startup\n if (isTelemetryEnabled && serverStartupSpan) {\n logDebug(\n \"[Telemetry] Ending visualize.server.startup span (server started successfully)\"\n )\n const { endSpan } = await import(\"../telemetry/index.js\")\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n logDebug(\"[Telemetry] Span ended\")\n }\n\n console.log(ansis.green(`\u2713 Visualization server started`))\n\n // Display access information (but may be incomplete if auth enabled and token not yet received)\n if (authEnabled) {\n const token = authTokenFromServer || authToken || \"(pending)\"\n if (authTokenFromServer) {\n // Token already received (same chunk), show full info immediately\n console.log(ansis.cyan(` Access URL: http://localhost:${port}?token=${token}`))\n console.log(ansis.dim(` Auth token: ${token}`))\n } else {\n // Token not yet received, just show port for now\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n logDebug(` (waiting for auth token...)`)\n }\n } else {\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n console.log(ansis.yellow(` \u26A0 Authentication disabled`))\n }\n\n if (withDanger) {\n console.log(\n ansis.yellow(` \u26A0 Danger mode enabled - chat permissions will be skipped`)\n )\n }\n\n console.log(ansis.blueBright(`Model name: ${model.manifest.name}`))\n const drFolder = await getSpecReferencePath()\n console.log(\n `DR folder: ${ansis.cyan(drFolder || \"Not found\")}`\n )\n console.log(`Model path: ${ansis.cyan((await getModelPath()) || \"Not found\")}`)\n\n // Optionally open browser\n // If auth is enabled, delay opening until we have the token\n const shouldOpenBrowser = !options.noBrowser\n logDebug(\n `Browser auto-open: ${shouldOpenBrowser ? \"enabled\" : \"disabled\"} (noBrowser=${options.noBrowser})`\n )\n\n if (shouldOpenBrowser && !authEnabled) {\n // Auth disabled: open immediately\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n } else if (shouldOpenBrowser && authEnabled && !authTokenFromServer) {\n // Auth enabled but token not yet in this chunk: will open when TOKEN line is processed\n logDebug(\"Browser auto-open deferred until token is received\")\n } else {\n logDebug(\"Browser auto-open disabled\")\n }\n }\n\n // Log other output\n if (!line.startsWith(\"TOKEN:\")) {\n logDebug(`[server] ${line}`)\n }\n }\n })\n }\n\n // Capture stderr\n if (serverProcess.stderr) {\n serverProcess.stderr.on(\"data\", (data) => {\n const output = data.toString().trim()\n if (output) {\n serverOutput += output + \"\\n\"\n // Log stderr at debug level so --debug flag shows telemetry exporter logs\n logDebug(`[server stderr] ${output}`)\n }\n })\n }\n\n // Handle process close (fires after all stdio streams have closed)\n let rejectKeepAlive: ((reason: Error) => void) | null = null\n\n serverProcess.on(\"close\", (code) => {\n if (code !== 0) {\n // Include server output in error message if available (avoid separate logDebug to prevent minification issues)\n const message = serverOutput\n ? `Server exited with code ${code}\\nServer output:\\n${serverOutput}`\n : `Server exited with code ${code}`\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(new Error(message));\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Print error before rejecting (CLI wrapper expects CLIError messages to be pre-printed)\n console.error(message)\n const error = new CLIError(message, code || 1)\n if (rejectKeepAlive) {\n rejectKeepAlive(error)\n }\n }\n })\n\n // Handle process errors\n serverProcess.on(\"error\", (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n let message: string\n let suggestions: string[] | undefined\n\n if (errorCode === \"ENOENT\") {\n // `dr visualize` currently spawns its server as a Bun subprocess (see #800 for\n // the plan to remove this dependency). On a fresh `npm install -g` there's no\n // reason Bun would be present, so this is an expected, actionable failure mode\n // rather than an unexpected crash - explain it instead of surfacing the raw\n // \"spawn bun ENOENT\".\n message = \"dr visualize requires the Bun runtime, which was not found on this system.\"\n suggestions = [\n \"Install Bun: https://bun.sh/docs/installation\",\n \" curl -fsSL https://bun.sh/install | bash (macOS/Linux)\",\n \" powershell -c \\\"irm bun.sh/install.ps1 | iex\\\" (Windows)\",\n \"After installing, make sure `bun` is on your PATH, then re-run `dr visualize`.\",\n \"Tracking issue: https://github.com/tinkermonkey/documentation_robotics/issues/800\",\n ]\n } else if (errorCode === \"EACCES\") {\n message = \"dr visualize could not run the Bun runtime: permission denied.\"\n suggestions = [\"Check that the `bun` executable on your PATH is executable (chmod +x).\"]\n } else {\n message = `Failed to start visualization server: ${error.message}`\n }\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(error);\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Don't print here: rejecting propagates this CLIError up through the catch block\n // below to the top-level CLI handler, which prints `.format()` (including\n // `suggestions`) exactly once. Printing here too would duplicate the whole block.\n const cliError = new CLIError(message, 1, suggestions)\n if (rejectKeepAlive) {\n rejectKeepAlive(cliError)\n }\n })\n\n // Setup periodic telemetry flushing for long-running command\n // Since the visualize command never completes (keep-alive pattern),\n // the cli.execute span would never end and export. We flush periodically\n // to ensure active spans are exported to the telemetry backend.\n let flushInterval: NodeJS.Timeout | null = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Setting up periodic flush (every 15 seconds)\")\n const { flushTelemetry } = await import(\"../telemetry/index.js\")\n\n flushInterval = setInterval(async () => {\n logDebug(\"[Telemetry] Periodic flush: flushing pending spans...\")\n try {\n await flushTelemetry()\n logDebug(\"[Telemetry] Periodic flush complete\")\n } catch (error) {\n // Silently ignore flush errors - telemetry should never break the app\n logDebug(`[Telemetry] Periodic flush failed: ${error}`)\n }\n }, 15000) // Flush every 15 seconds\n }\n\n // Handle graceful shutdown on SIGINT (Ctrl-C) and SIGTERM\n const handleShutdown = async (signal: string) => {\n logDebug(`\\n[${signal}] Shutting down visualization server gracefully...`)\n\n // Clear periodic flush interval\n if (flushInterval) {\n logDebug(\"[Telemetry] Stopping periodic flush\")\n clearInterval(flushInterval)\n flushInterval = null\n }\n\n // Forward signal to server process for graceful shutdown\n if (serverProcess && !serverProcess.killed) {\n logDebug(\"Sending termination signal to server process...\")\n serverProcess.kill(signal as NodeJS.Signals)\n\n // Wait for server to exit (with timeout)\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n logDebug(\"Server shutdown timeout, forcing exit...\")\n if (!serverProcess.killed) {\n serverProcess.kill(\"SIGKILL\")\n }\n resolve()\n }, 3000) // 3 second timeout\n\n serverProcess.on(\"exit\", () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n logDebug(\"Visualization server stopped\")\n process.exit(0)\n }\n\n // Register signal handlers\n process.on(\"SIGINT\", () => handleShutdown(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handleShutdown(\"SIGTERM\"))\n\n // Keep parent process alive - reject promise if server fails\n await new Promise((_, reject) => {\n rejectKeepAlive = reject\n })\n } catch (error) {\n // Throw CLIError instead of process.exit to allow telemetry shutdown\n // Note: Avoid side effects (like logDebug) before throw to prevent minification issues\n if (error instanceof CLIError) {\n // CLIError should have already printed its message\n throw error\n }\n // Non-CLIError needs to be printed and wrapped\n const message = getErrorMessage(error)\n console.error(message)\n throw new CLIError(message, 1)\n }\n}\n\n/**\n * Get the appropriate command to open a URL\n */\nfunction getOpenCommand(): string {\n const platform = process.platform\n\n if (platform === \"darwin\") {\n return \"open\"\n }\n if (platform === \"win32\") {\n return \"start\"\n }\n // Linux and others\n return \"xdg-open\"\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
class BaseSnapshotManager {
|
|
3
|
+
/**
|
|
4
|
+
* Capture current model state as a SHA-256 hash
|
|
5
|
+
*
|
|
6
|
+
* Hashes:
|
|
7
|
+
* 1. Manifest content
|
|
8
|
+
* 2. All loaded layer data in sorted order
|
|
9
|
+
* 3. Creates reproducible snapshot hash
|
|
10
|
+
*/
|
|
11
|
+
async captureSnapshot(model) {
|
|
12
|
+
const hash = createHash("sha256"), manifestData = JSON.stringify(model.manifest.toJSON(), null, 2);
|
|
13
|
+
hash.update(manifestData);
|
|
14
|
+
const layerNames = Array.from(model.layers.keys()).sort();
|
|
15
|
+
for (const layerName of layerNames) {
|
|
16
|
+
const layer = model.layers.get(layerName);
|
|
17
|
+
if (layer) {
|
|
18
|
+
const elements = layer.listElements().sort((a, b) => a.id.localeCompare(b.id)), layerData = JSON.stringify({
|
|
19
|
+
name: layerName,
|
|
20
|
+
elements: elements.map((e) => e.toJSON())
|
|
21
|
+
}, null, 2);
|
|
22
|
+
hash.update(layerData);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return `sha256:${hash.digest("hex")}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Detect drift between expected snapshot and current model
|
|
29
|
+
*
|
|
30
|
+
* Returns a report indicating whether drift occurred.
|
|
31
|
+
*
|
|
32
|
+
* NOTE: This method only identifies that the model has changed (hasDrift: true/false).
|
|
33
|
+
* It cannot determine which specific layers/elements changed without storing
|
|
34
|
+
* detailed snapshot data. When drift is detected, potentiallyAffectedLayers and
|
|
35
|
+
* potentiallyAffectedElements contain ALL current layers/elements as a conservative
|
|
36
|
+
* estimate. For precise change detection, implement snapshot comparison with
|
|
37
|
+
* stored layer/element state data.
|
|
38
|
+
*/
|
|
39
|
+
async detectDrift(expectedSnapshot, currentModel) {
|
|
40
|
+
const currentSnapshot = await this.captureSnapshot(currentModel);
|
|
41
|
+
if (!(expectedSnapshot !== currentSnapshot))
|
|
42
|
+
return {
|
|
43
|
+
baseSnapshotHash: expectedSnapshot,
|
|
44
|
+
currentModelHash: currentSnapshot,
|
|
45
|
+
hasDrift: !1,
|
|
46
|
+
potentiallyAffectedLayers: [],
|
|
47
|
+
potentiallyAffectedElements: []
|
|
48
|
+
};
|
|
49
|
+
const potentiallyAffectedLayers = [], potentiallyAffectedElements = [];
|
|
50
|
+
for (const layerName of currentModel.layers.keys()) {
|
|
51
|
+
potentiallyAffectedLayers.push(layerName);
|
|
52
|
+
const layer = currentModel.layers.get(layerName);
|
|
53
|
+
layer && layer.listElements().forEach((e) => {
|
|
54
|
+
potentiallyAffectedElements.push(e.id);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
baseSnapshotHash: expectedSnapshot,
|
|
59
|
+
currentModelHash: currentSnapshot,
|
|
60
|
+
hasDrift: !0,
|
|
61
|
+
potentiallyAffectedLayers,
|
|
62
|
+
potentiallyAffectedElements
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compare two model snapshots and return detailed diff information
|
|
67
|
+
*
|
|
68
|
+
* Note: This would require storing both snapshots to provide detailed diffs.
|
|
69
|
+
* For now, we provide a simpler comparison.
|
|
70
|
+
*/
|
|
71
|
+
async compareSnapshots(snapshot1, snapshot2) {
|
|
72
|
+
return snapshot1 === snapshot2 ? {
|
|
73
|
+
identical: !0,
|
|
74
|
+
difference: null
|
|
75
|
+
} : {
|
|
76
|
+
identical: !1,
|
|
77
|
+
difference: `Snapshots differ: ${snapshot1} vs ${snapshot2}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
BaseSnapshotManager
|
|
83
|
+
};
|
|
84
|
+
//# sourceMappingURL=base-snapshot-manager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/base-snapshot-manager.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * BaseSnapshotManager - Manages model snapshots and drift detection\n *\n * Captures the state of a model at a specific point in time (e.g., when a changeset is created)\n * and detects if the base model has drifted from that state (allowing drift warnings on commit).\n */\n\nimport { createHash } from \"crypto\";\nimport type { Model } from \"./model.js\";\n\n/**\n * Report on model drift between snapshots\n *\n * NOTE: Current implementation identifies that drift occurred but not the specific\n * changes. To identify actual changes, we would need to store detailed snapshot data\n * (not just hashes). The affectedLayers and affectedElements fields report ALL\n * layers/elements when drift is detected, not specifically what changed.\n */\nexport interface DriftReport {\n baseSnapshotHash: string;\n currentModelHash: string;\n hasDrift: boolean;\n // When hasDrift is true, these contain ALL current layers/elements (not specific changes)\n // Full diff detection would require storing complete snapshot data\n potentiallyAffectedLayers: string[];\n potentiallyAffectedElements: string[];\n}\n\n/**\n * Manager for capturing and comparing model snapshots\n */\nexport class BaseSnapshotManager {\n /**\n * Capture current model state as a SHA-256 hash\n *\n * Hashes:\n * 1. Manifest content\n * 2. All loaded layer data in sorted order\n * 3. Creates reproducible snapshot hash\n */\n async captureSnapshot(model: Model): Promise<string> {\n const hash = createHash(\"sha256\");\n\n // Hash manifest data first (stable ordering)\n const manifestData = JSON.stringify(model.manifest.toJSON(), null, 2);\n hash.update(manifestData);\n\n // Hash each layer in sorted order\n const layerNames = Array.from(model.layers.keys()).sort();\n for (const layerName of layerNames) {\n const layer = model.layers.get(layerName);\n if (layer) {\n // Get all elements and sort them by ID for reproducibility\n const elements = layer.listElements().sort((a, b) => a.id.localeCompare(b.id));\n const layerData = JSON.stringify(\n {\n name: layerName,\n elements: elements.map((e) => e.toJSON()),\n },\n null,\n 2\n );\n hash.update(layerData);\n }\n }\n\n return `sha256:${hash.digest(\"hex\")}`;\n }\n\n /**\n * Detect drift between expected snapshot and current model\n *\n * Returns a report indicating whether drift occurred.\n *\n * NOTE: This method only identifies that the model has changed (hasDrift: true/false).\n * It cannot determine which specific layers/elements changed without storing\n * detailed snapshot data. When drift is detected, potentiallyAffectedLayers and\n * potentiallyAffectedElements contain ALL current layers/elements as a conservative\n * estimate. For precise change detection, implement snapshot comparison with\n * stored layer/element state data.\n */\n async detectDrift(expectedSnapshot: string, currentModel: Model): Promise<DriftReport> {\n const currentSnapshot = await this.captureSnapshot(currentModel);\n const hasDrift = expectedSnapshot !== currentSnapshot;\n\n // If no drift, return quickly\n if (!hasDrift) {\n return {\n baseSnapshotHash: expectedSnapshot,\n currentModelHash: currentSnapshot,\n hasDrift: false,\n potentiallyAffectedLayers: [],\n potentiallyAffectedElements: [],\n };\n }\n\n // Drift detected but we cannot pinpoint exact changes without stored snapshot data\n // Return all current layers/elements as potentially affected\n const potentiallyAffectedLayers: string[] = [];\n const potentiallyAffectedElements: string[] = [];\n\n for (const layerName of currentModel.layers.keys()) {\n potentiallyAffectedLayers.push(layerName);\n const layer = currentModel.layers.get(layerName);\n if (layer) {\n layer.listElements().forEach((e) => {\n potentiallyAffectedElements.push(e.id);\n });\n }\n }\n\n return {\n baseSnapshotHash: expectedSnapshot,\n currentModelHash: currentSnapshot,\n hasDrift: true,\n potentiallyAffectedLayers,\n potentiallyAffectedElements,\n };\n }\n\n /**\n * Compare two model snapshots and return detailed diff information\n *\n * Note: This would require storing both snapshots to provide detailed diffs.\n * For now, we provide a simpler comparison.\n */\n async compareSnapshots(\n snapshot1: string,\n snapshot2: string\n ): Promise<{ identical: boolean; difference: string | null }> {\n if (snapshot1 === snapshot2) {\n return {\n identical: true,\n difference: null,\n };\n }\n\n return {\n identical: false,\n difference: `Snapshots differ: ${snapshot1} vs ${snapshot2}`,\n };\n }\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,kBAAkB;AAwBrB,MAAO,oBAAmB;;;;;;;;;EAS9B,MAAM,gBAAgB,OAAY;AAChC,UAAM,OAAO,WAAW,QAAQ,GAG1B,eAAe,KAAK,UAAU,MAAM,SAAS,OAAM,GAAI,MAAM,CAAC;AACpE,SAAK,OAAO,YAAY;AAGxB,UAAM,aAAa,MAAM,KAAK,MAAM,OAAO,KAAI,CAAE,EAAE,KAAI;AACvD,eAAW,aAAa,YAAY;AAClC,YAAM,QAAQ,MAAM,OAAO,IAAI,SAAS;AACxC,UAAI,OAAO;AAET,cAAM,WAAW,MAAM,aAAY,EAAG,KAAK,CAAC,GAAG,MAAM,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC,GACvE,YAAY,KAAK,UACrB;UACE,MAAM;UACN,UAAU,SAAS,IAAI,CAAC,MAAM,EAAE,OAAM,CAAE;WAE1C,MACA,CAAC;AAEH,aAAK,OAAO,SAAS;MACvB;IACF;AAEA,WAAO,UAAU,KAAK,OAAO,KAAK,CAAC;EACrC;;;;;;;;;;;;;EAcA,MAAM,YAAY,kBAA0B,cAAmB;AAC7D,UAAM,kBAAkB,MAAM,KAAK,gBAAgB,YAAY;AAI/D,QAAI,EAHa,qBAAqB;AAIpC,aAAO;QACL,kBAAkB;QAClB,kBAAkB;QAClB,UAAU;QACV,2BAA2B,CAAA;QAC3B,6BAA6B,CAAA;;AAMjC,UAAM,4BAAsC,CAAA,GACtC,8BAAwC,CAAA;AAE9C,eAAW,aAAa,aAAa,OAAO,KAAI,GAAI;AAClD,gCAA0B,KAAK,SAAS;AACxC,YAAM,QAAQ,aAAa,OAAO,IAAI,SAAS;AAC/C,MAAI,SACF,MAAM,aAAY,EAAG,QAAQ,CAAC,MAAK;AACjC,oCAA4B,KAAK,EAAE,EAAE;MACvC,CAAC;IAEL;AAEA,WAAO;MACL,kBAAkB;MAClB,kBAAkB;MAClB,UAAU;MACV;MACA;;EAEJ;;;;;;;EAQA,MAAM,iBACJ,WACA,WAAiB;AAEjB,WAAI,cAAc,YACT;MACL,WAAW;MACX,YAAY;QAIT;MACL,WAAW;MACX,YAAY,qBAAqB,SAAS,OAAO,SAAS;;EAE9D;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { fileExists } from "../utils/file-io.js";
|
|
3
|
+
import yaml from "yaml";
|
|
4
|
+
import { Changeset } from "./changeset.js";
|
|
5
|
+
import { StagedChangesetStorage } from "./staged-changeset-storage.js";
|
|
6
|
+
import { BaseSnapshotManager } from "./base-snapshot-manager.js";
|
|
7
|
+
class ChangesetExporter {
|
|
8
|
+
storage;
|
|
9
|
+
snapshotManager;
|
|
10
|
+
constructor(rootPath) {
|
|
11
|
+
this.storage = new StagedChangesetStorage(rootPath), this.snapshotManager = new BaseSnapshotManager();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Export changeset to string in specified format
|
|
15
|
+
*/
|
|
16
|
+
async export(changesetId, format = "yaml") {
|
|
17
|
+
const changeset = await this.storage.load(changesetId);
|
|
18
|
+
if (!changeset)
|
|
19
|
+
throw new Error(`Changeset '${changesetId}' not found`);
|
|
20
|
+
switch (format) {
|
|
21
|
+
case "yaml":
|
|
22
|
+
return this.exportToYaml(changeset);
|
|
23
|
+
case "json":
|
|
24
|
+
return this.exportToJson(changeset);
|
|
25
|
+
case "patch":
|
|
26
|
+
return this.exportToPatch(changeset);
|
|
27
|
+
default:
|
|
28
|
+
throw new Error(`Unsupported export format: ${format}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Export changeset to file
|
|
33
|
+
*/
|
|
34
|
+
async exportToFile(changesetId, outputPath, format = "yaml") {
|
|
35
|
+
const content = await this.export(changesetId, format);
|
|
36
|
+
await writeFile(outputPath, content, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Import changeset from string
|
|
40
|
+
*/
|
|
41
|
+
async import(data, format) {
|
|
42
|
+
switch (format || (format = this.detectFormat(data)), format) {
|
|
43
|
+
case "yaml":
|
|
44
|
+
return this.importFromYaml(data);
|
|
45
|
+
case "json":
|
|
46
|
+
return this.importFromJson(data);
|
|
47
|
+
case "patch":
|
|
48
|
+
return this.importFromPatch(data);
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(`Unsupported import format: ${format}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Import changeset from file
|
|
55
|
+
*/
|
|
56
|
+
async importFromFile(inputPath) {
|
|
57
|
+
if (!await fileExists(inputPath))
|
|
58
|
+
throw new Error(`File not found: ${inputPath}`);
|
|
59
|
+
const data = await readFile(inputPath, "utf-8"), format = this.detectFormat(data);
|
|
60
|
+
return this.import(data, format);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate imported changeset compatibility with current model
|
|
64
|
+
*/
|
|
65
|
+
async validateCompatibility(changeset, currentModel) {
|
|
66
|
+
const report = {
|
|
67
|
+
compatible: !0,
|
|
68
|
+
baseSnapshotMatch: !0,
|
|
69
|
+
missingElements: [],
|
|
70
|
+
warnings: [],
|
|
71
|
+
affectedLayers: []
|
|
72
|
+
};
|
|
73
|
+
if (changeset.baseSnapshot) {
|
|
74
|
+
const currentSnapshot = await this.snapshotManager.captureSnapshot(currentModel);
|
|
75
|
+
changeset.baseSnapshot !== currentSnapshot && (report.baseSnapshotMatch = !1, report.warnings.push("Base model has changed since changeset was created"));
|
|
76
|
+
}
|
|
77
|
+
const affectedLayerSet = /* @__PURE__ */ new Set();
|
|
78
|
+
for (const change of changeset.changes || []) {
|
|
79
|
+
if (affectedLayerSet.add(change.layerName), change.type === "update" || change.type === "delete") {
|
|
80
|
+
const layer = await currentModel.getLayer(change.layerName);
|
|
81
|
+
(!layer || !layer.getElement(change.elementId)) && (change.type === "delete" ? report.warnings.push(`Element ${change.elementId} not found in layer ${change.layerName} (will be skipped)`) : (report.compatible = !1, report.missingElements.push(change.elementId), report.warnings.push(`Cannot update non-existent element ${change.elementId}`)));
|
|
82
|
+
}
|
|
83
|
+
if (change.type === "add") {
|
|
84
|
+
const layer = await currentModel.getLayer(change.layerName);
|
|
85
|
+
layer && layer.getElement(change.elementId) && (report.compatible = !1, report.warnings.push(`Element ${change.elementId} already exists in layer ${change.layerName}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return report.affectedLayers = Array.from(affectedLayerSet), report;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Export changeset to YAML format
|
|
92
|
+
*/
|
|
93
|
+
exportToYaml(changeset) {
|
|
94
|
+
const exportData = {
|
|
95
|
+
id: changeset.id,
|
|
96
|
+
name: changeset.name,
|
|
97
|
+
description: changeset.description,
|
|
98
|
+
created: changeset.created,
|
|
99
|
+
modified: changeset.modified,
|
|
100
|
+
status: changeset.status,
|
|
101
|
+
baseSnapshot: changeset.baseSnapshot,
|
|
102
|
+
export: {
|
|
103
|
+
version: "0.1.0",
|
|
104
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
|
+
format: "yaml"
|
|
106
|
+
},
|
|
107
|
+
stats: changeset.stats || {
|
|
108
|
+
additions: 0,
|
|
109
|
+
modifications: 0,
|
|
110
|
+
deletions: 0
|
|
111
|
+
},
|
|
112
|
+
changes: changeset.changes || []
|
|
113
|
+
};
|
|
114
|
+
return yaml.stringify(exportData, {
|
|
115
|
+
indent: 2,
|
|
116
|
+
lineWidth: 120
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Export changeset to JSON format
|
|
121
|
+
*/
|
|
122
|
+
exportToJson(changeset) {
|
|
123
|
+
const exportData = {
|
|
124
|
+
id: changeset.id,
|
|
125
|
+
name: changeset.name,
|
|
126
|
+
description: changeset.description,
|
|
127
|
+
created: changeset.created,
|
|
128
|
+
modified: changeset.modified,
|
|
129
|
+
status: changeset.status,
|
|
130
|
+
baseSnapshot: changeset.baseSnapshot,
|
|
131
|
+
export: {
|
|
132
|
+
version: "0.1.0",
|
|
133
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
134
|
+
format: "json"
|
|
135
|
+
},
|
|
136
|
+
stats: changeset.stats || {
|
|
137
|
+
additions: 0,
|
|
138
|
+
modifications: 0,
|
|
139
|
+
deletions: 0
|
|
140
|
+
},
|
|
141
|
+
changes: changeset.changes || []
|
|
142
|
+
};
|
|
143
|
+
return JSON.stringify(exportData, null, 2);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Export changeset to patch format (Git-style unified diff)
|
|
147
|
+
*/
|
|
148
|
+
exportToPatch(changeset) {
|
|
149
|
+
const lines = [];
|
|
150
|
+
lines.push(`From: ${changeset.id}`), lines.push(`Date: ${(/* @__PURE__ */ new Date()).toISOString()}`), changeset.baseSnapshot && lines.push(`Base: ${changeset.baseSnapshot}`), lines.push(`Subject: ${changeset.name}`), lines.push(""), changeset.description && (lines.push(changeset.description), lines.push(""));
|
|
151
|
+
const stats = changeset.stats || {
|
|
152
|
+
additions: 0,
|
|
153
|
+
modifications: 0,
|
|
154
|
+
deletions: 0
|
|
155
|
+
};
|
|
156
|
+
lines.push("---"), lines.push(` ${stats.additions + stats.modifications + stats.deletions} file${stats.additions + stats.modifications + stats.deletions !== 1 ? "s" : ""} changed, ${stats.additions} insertions(+), ${stats.deletions} deletions(-)`), lines.push("");
|
|
157
|
+
const layerMap = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const change of changeset.changes || [])
|
|
159
|
+
layerMap.has(change.layerName) || layerMap.set(change.layerName, []), layerMap.get(change.layerName).push(change);
|
|
160
|
+
for (const [layerName, changes] of layerMap) {
|
|
161
|
+
const filePath = `${layerName}/elements.yaml`;
|
|
162
|
+
lines.push(`diff --git a/${filePath} b/${filePath}`), lines.push("index abc123..def456 100644"), lines.push(`--- a/${filePath}`), lines.push(`+++ b/${filePath}`), lines.push(`@@ -10,3 +10,${changes.length + 3} @@`);
|
|
163
|
+
for (const change of changes)
|
|
164
|
+
if (change.type === "add") {
|
|
165
|
+
if (lines.push(`+ - id: ${change.elementId}`), change.after) {
|
|
166
|
+
const afterData = change.after;
|
|
167
|
+
afterData.name && lines.push(`+ name: "${afterData.name}"`), afterData.type && lines.push(`+ type: ${afterData.type}`), afterData.attributes && lines.push(`+ attributes: ${JSON.stringify(afterData.attributes)}`);
|
|
168
|
+
}
|
|
169
|
+
} else if (change.type === "delete") {
|
|
170
|
+
if (lines.push(`- - id: ${change.elementId}`), change.before) {
|
|
171
|
+
const beforeData = change.before;
|
|
172
|
+
beforeData.name && lines.push(`- name: "${beforeData.name}"`), beforeData.type && lines.push(`- type: ${beforeData.type}`), beforeData.attributes && lines.push(`- attributes: ${JSON.stringify(beforeData.attributes)}`);
|
|
173
|
+
}
|
|
174
|
+
} else if (change.type === "update" && (lines.push(` - id: ${change.elementId}`), change.before && change.after)) {
|
|
175
|
+
const beforeData = change.before, afterData = change.after;
|
|
176
|
+
beforeData.name !== afterData.name && (lines.push(`- name: "${beforeData.name}"`), lines.push(`+ name: "${afterData.name}"`)), JSON.stringify(beforeData.attributes) !== JSON.stringify(afterData.attributes) && (beforeData.attributes && lines.push(`- attributes: ${JSON.stringify(beforeData.attributes)}`), afterData.attributes && lines.push(`+ attributes: ${JSON.stringify(afterData.attributes)}`));
|
|
177
|
+
}
|
|
178
|
+
lines.push("");
|
|
179
|
+
}
|
|
180
|
+
return lines.join(`
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Import changeset from YAML format
|
|
185
|
+
*/
|
|
186
|
+
importFromYaml(data) {
|
|
187
|
+
const parsed = yaml.parse(data);
|
|
188
|
+
if (!parsed || typeof parsed != "object")
|
|
189
|
+
throw new Error("Invalid YAML format");
|
|
190
|
+
const parsedRecord = parsed, { id, name, description, created, modified, status, baseSnapshot, changes = [], stats } = parsedRecord;
|
|
191
|
+
if (!id || !name || !created || !modified)
|
|
192
|
+
throw new Error("Missing required fields: id, name, created, modified");
|
|
193
|
+
const statsRecord = stats, changesetStats = {
|
|
194
|
+
additions: typeof statsRecord?.additions == "number" ? statsRecord.additions : 0,
|
|
195
|
+
modifications: typeof statsRecord?.modifications == "number" ? statsRecord.modifications : 0,
|
|
196
|
+
deletions: typeof statsRecord?.deletions == "number" ? statsRecord.deletions : 0
|
|
197
|
+
}, changesetSnapshot = typeof baseSnapshot == "string" ? baseSnapshot : "unknown";
|
|
198
|
+
if (!["staged", "committed", "discarded"].includes(String(status)))
|
|
199
|
+
throw new Error(`Invalid changeset status '${status}' from YAML import. Expected: staged, committed, or discarded.`);
|
|
200
|
+
return new Changeset({
|
|
201
|
+
id: String(id),
|
|
202
|
+
name: String(name),
|
|
203
|
+
description: typeof description == "string" ? description : void 0,
|
|
204
|
+
created: String(created),
|
|
205
|
+
modified: String(modified),
|
|
206
|
+
status,
|
|
207
|
+
baseSnapshot: changesetSnapshot,
|
|
208
|
+
changes: changes || [],
|
|
209
|
+
stats: changesetStats
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Import changeset from JSON format
|
|
214
|
+
*/
|
|
215
|
+
importFromJson(data) {
|
|
216
|
+
const parsed = JSON.parse(data);
|
|
217
|
+
if (!parsed || typeof parsed != "object")
|
|
218
|
+
throw new Error("Invalid JSON format");
|
|
219
|
+
const parsedRecord = parsed, { id, name, description, created, modified, status, baseSnapshot, changes = [], stats } = parsedRecord;
|
|
220
|
+
if (!id || !name || !created || !modified)
|
|
221
|
+
throw new Error("Missing required fields: id, name, created, modified");
|
|
222
|
+
const statsRecord = stats, changesetStats = {
|
|
223
|
+
additions: typeof statsRecord?.additions == "number" ? statsRecord.additions : 0,
|
|
224
|
+
modifications: typeof statsRecord?.modifications == "number" ? statsRecord.modifications : 0,
|
|
225
|
+
deletions: typeof statsRecord?.deletions == "number" ? statsRecord.deletions : 0
|
|
226
|
+
}, changesetSnapshot = typeof baseSnapshot == "string" ? baseSnapshot : "unknown";
|
|
227
|
+
if (!["staged", "committed", "discarded"].includes(String(status)))
|
|
228
|
+
throw new Error(`Invalid changeset status '${status}' from JSON import. Expected: staged, committed, or discarded.`);
|
|
229
|
+
return new Changeset({
|
|
230
|
+
id: String(id),
|
|
231
|
+
name: String(name),
|
|
232
|
+
description: typeof description == "string" ? description : void 0,
|
|
233
|
+
created: String(created),
|
|
234
|
+
modified: String(modified),
|
|
235
|
+
status,
|
|
236
|
+
baseSnapshot: changesetSnapshot,
|
|
237
|
+
changes: changes || [],
|
|
238
|
+
stats: changesetStats
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Import changeset from patch format
|
|
243
|
+
*
|
|
244
|
+
* NOTE: Patch format import extracts metadata and base snapshot from headers only.
|
|
245
|
+
* The unified diff format makes it impractical to reconstruct full change details
|
|
246
|
+
* without complex diff parsing. For this reason, patch format import returns a
|
|
247
|
+
* changeset with empty changes array and is primarily useful for preserving metadata.
|
|
248
|
+
*
|
|
249
|
+
* Use YAML or JSON formats for changesets that need to be re-imported with full
|
|
250
|
+
* change data. Patch format is recommended for email-based sharing and archival.
|
|
251
|
+
*/
|
|
252
|
+
importFromPatch(data) {
|
|
253
|
+
const lines = data.split(`
|
|
254
|
+
`), metadata = {}, changes = [];
|
|
255
|
+
let i = 0;
|
|
256
|
+
for (; i < lines.length && !lines[i].startsWith("---") && lines[i].trim() !== ""; ) {
|
|
257
|
+
const line = lines[i];
|
|
258
|
+
line.startsWith("From:") ? metadata.id = line.substring(5).trim() : line.startsWith("Date:") ? metadata.exportedAt = line.substring(5).trim() : line.startsWith("Base:") ? metadata.baseSnapshot = line.substring(5).trim() : line.startsWith("Subject:") && (metadata.name = line.substring(8).trim()), i++;
|
|
259
|
+
}
|
|
260
|
+
for (; i < lines.length && lines[i].trim() === ""; )
|
|
261
|
+
i++;
|
|
262
|
+
const descriptionLines = [];
|
|
263
|
+
for (; i < lines.length && !lines[i].startsWith("---"); )
|
|
264
|
+
descriptionLines.push(lines[i]), i++;
|
|
265
|
+
if (descriptionLines.length > 0 && (metadata.description = descriptionLines.join(`
|
|
266
|
+
`).trim()), !metadata.id || !metadata.name)
|
|
267
|
+
throw new Error("Missing required fields in patch header: id, name");
|
|
268
|
+
return new Changeset({
|
|
269
|
+
id: metadata.id,
|
|
270
|
+
name: metadata.name,
|
|
271
|
+
description: metadata.description,
|
|
272
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
273
|
+
modified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
274
|
+
status: "staged",
|
|
275
|
+
baseSnapshot: metadata.baseSnapshot || "unknown",
|
|
276
|
+
changes,
|
|
277
|
+
stats: {
|
|
278
|
+
additions: 0,
|
|
279
|
+
modifications: 0,
|
|
280
|
+
deletions: 0
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Detect export format from content
|
|
286
|
+
*/
|
|
287
|
+
detectFormat(data) {
|
|
288
|
+
const trimmed = data.trim();
|
|
289
|
+
if (trimmed.startsWith("{"))
|
|
290
|
+
try {
|
|
291
|
+
return JSON.parse(trimmed), "json";
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
if (trimmed.includes("id:") && trimmed.includes("name:"))
|
|
295
|
+
try {
|
|
296
|
+
return yaml.parse(trimmed), "yaml";
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
return trimmed.startsWith("From:") ? "patch" : "yaml";
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export {
|
|
303
|
+
ChangesetExporter
|
|
304
|
+
};
|
|
305
|
+
//# sourceMappingURL=changeset-exporter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-exporter.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * ChangesetExporter - Export changesets to portable formats\n *\n * Supports exporting changesets in multiple formats:\n * - YAML: Human-readable format with full changeset metadata\n * - JSON: Machine-readable format for programmatic use\n * - Patch: Git-style unified diff format for version control\n */\n\nimport { readFile, writeFile } from \"fs/promises\";\nimport { fileExists } from \"../utils/file-io.js\";\nimport yaml from \"yaml\";\nimport { Changeset, type Change } from \"./changeset.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport type { Model } from \"./model.js\";\nimport { BaseSnapshotManager } from \"./base-snapshot-manager.js\";\n\n/**\n * Export format type\n */\nexport type ExportFormat = \"yaml\" | \"json\" | \"patch\";\n\n/**\n * Compatibility report for imported changesets\n */\nexport interface CompatibilityReport {\n compatible: boolean;\n baseSnapshotMatch: boolean;\n missingElements: string[];\n warnings: string[];\n affectedLayers: string[];\n}\n\n/**\n * Exporter for changesets in portable formats\n */\nexport class ChangesetExporter {\n private storage: StagedChangesetStorage;\n private snapshotManager: BaseSnapshotManager;\n\n constructor(rootPath: string) {\n this.storage = new StagedChangesetStorage(rootPath);\n this.snapshotManager = new BaseSnapshotManager();\n }\n\n /**\n * Export changeset to string in specified format\n */\n async export(changesetId: string, format: ExportFormat = \"yaml\"): Promise<string> {\n const changeset = await this.storage.load(changesetId);\n if (!changeset) {\n throw new Error(`Changeset '${changesetId}' not found`);\n }\n\n switch (format) {\n case \"yaml\":\n return this.exportToYaml(changeset);\n case \"json\":\n return this.exportToJson(changeset);\n case \"patch\":\n return this.exportToPatch(changeset);\n default:\n throw new Error(`Unsupported export format: ${format}`);\n }\n }\n\n /**\n * Export changeset to file\n */\n async exportToFile(\n changesetId: string,\n outputPath: string,\n format: ExportFormat = \"yaml\"\n ): Promise<void> {\n const content = await this.export(changesetId, format);\n await writeFile(outputPath, content, \"utf-8\");\n }\n\n /**\n * Import changeset from string\n */\n async import(data: string, format?: ExportFormat): Promise<Changeset> {\n // Auto-detect format if not specified\n if (!format) {\n format = this.detectFormat(data);\n }\n\n switch (format) {\n case \"yaml\":\n return this.importFromYaml(data);\n case \"json\":\n return this.importFromJson(data);\n case \"patch\":\n return this.importFromPatch(data);\n default:\n throw new Error(`Unsupported import format: ${format}`);\n }\n }\n\n /**\n * Import changeset from file\n */\n async importFromFile(inputPath: string): Promise<Changeset> {\n if (!(await fileExists(inputPath))) {\n throw new Error(`File not found: ${inputPath}`);\n }\n\n const data = await readFile(inputPath, \"utf-8\");\n const format = this.detectFormat(data);\n\n return this.import(data, format);\n }\n\n /**\n * Validate imported changeset compatibility with current model\n */\n async validateCompatibility(\n changeset: Changeset,\n currentModel: Model\n ): Promise<CompatibilityReport> {\n const report: CompatibilityReport = {\n compatible: true,\n baseSnapshotMatch: true,\n missingElements: [],\n warnings: [],\n affectedLayers: [],\n };\n\n // Check base snapshot match\n if (changeset.baseSnapshot) {\n const currentSnapshot = await this.snapshotManager.captureSnapshot(currentModel);\n if (changeset.baseSnapshot !== currentSnapshot) {\n report.baseSnapshotMatch = false;\n report.warnings.push(\"Base model has changed since changeset was created\");\n }\n }\n\n // Validate all elements referenced in changes exist in current model\n // (for updates and deletes)\n const affectedLayerSet = new Set<string>();\n\n for (const change of changeset.changes || []) {\n affectedLayerSet.add(change.layerName);\n\n if (change.type === \"update\" || change.type === \"delete\") {\n // For updates and deletes, element must exist in current model\n const layer = await currentModel.getLayer(change.layerName);\n if (!layer || !layer.getElement(change.elementId)) {\n if (change.type === \"delete\") {\n // Deletes of non-existent elements are warnings, not blockers\n report.warnings.push(\n `Element ${change.elementId} not found in layer ${change.layerName} (will be skipped)`\n );\n } else {\n // Updates of non-existent elements are errors\n report.compatible = false;\n report.missingElements.push(change.elementId);\n report.warnings.push(`Cannot update non-existent element ${change.elementId}`);\n }\n }\n }\n\n // For adds, element must not exist\n if (change.type === \"add\") {\n const layer = await currentModel.getLayer(change.layerName);\n if (layer && layer.getElement(change.elementId)) {\n report.compatible = false;\n report.warnings.push(\n `Element ${change.elementId} already exists in layer ${change.layerName}`\n );\n }\n }\n }\n\n report.affectedLayers = Array.from(affectedLayerSet);\n\n return report;\n }\n\n /**\n * Export changeset to YAML format\n */\n private exportToYaml(changeset: Changeset): string {\n const exportData = {\n id: changeset.id,\n name: changeset.name,\n description: changeset.description,\n created: changeset.created,\n modified: changeset.modified,\n status: changeset.status,\n baseSnapshot: changeset.baseSnapshot,\n export: {\n version: \"0.1.0\",\n exportedAt: new Date().toISOString(),\n format: \"yaml\",\n },\n stats: changeset.stats || {\n additions: 0,\n modifications: 0,\n deletions: 0,\n },\n changes: changeset.changes || [],\n };\n\n return yaml.stringify(exportData, {\n indent: 2,\n lineWidth: 120,\n });\n }\n\n /**\n * Export changeset to JSON format\n */\n private exportToJson(changeset: Changeset): string {\n const exportData = {\n id: changeset.id,\n name: changeset.name,\n description: changeset.description,\n created: changeset.created,\n modified: changeset.modified,\n status: changeset.status,\n baseSnapshot: changeset.baseSnapshot,\n export: {\n version: \"0.1.0\",\n exportedAt: new Date().toISOString(),\n format: \"json\",\n },\n stats: changeset.stats || {\n additions: 0,\n modifications: 0,\n deletions: 0,\n },\n changes: changeset.changes || [],\n };\n\n return JSON.stringify(exportData, null, 2);\n }\n\n /**\n * Export changeset to patch format (Git-style unified diff)\n */\n private exportToPatch(changeset: Changeset): string {\n const lines: string[] = [];\n\n // Patch header\n lines.push(`From: ${changeset.id}`);\n lines.push(`Date: ${new Date().toISOString()}`);\n if (changeset.baseSnapshot) {\n lines.push(`Base: ${changeset.baseSnapshot}`);\n }\n lines.push(`Subject: ${changeset.name}`);\n lines.push(\"\");\n if (changeset.description) {\n lines.push(changeset.description);\n lines.push(\"\");\n }\n\n // Summary statistics\n const stats = changeset.stats || {\n additions: 0,\n modifications: 0,\n deletions: 0,\n };\n lines.push(\"---\");\n lines.push(\n ` ${stats.additions + stats.modifications + stats.deletions} file${stats.additions + stats.modifications + stats.deletions !== 1 ? \"s\" : \"\"} changed, ${stats.additions} insertions(+), ${stats.deletions} deletions(-)`\n );\n lines.push(\"\");\n\n // Group changes by layer for diff-style output\n const layerMap = new Map<string, (typeof changeset.changes)[number][]>();\n for (const change of changeset.changes || []) {\n if (!layerMap.has(change.layerName)) {\n layerMap.set(change.layerName, []);\n }\n layerMap.get(change.layerName)!.push(change);\n }\n\n // Generate diff-style output for each layer\n for (const [layerName, changes] of layerMap) {\n const filePath = `${layerName}/elements.yaml`;\n lines.push(`diff --git a/${filePath} b/${filePath}`);\n lines.push(`index abc123..def456 100644`);\n lines.push(`--- a/${filePath}`);\n lines.push(`+++ b/${filePath}`);\n\n // Add hunk header\n lines.push(`@@ -10,3 +10,${changes.length + 3} @@`);\n\n // Show context and changes\n for (const change of changes) {\n if (change.type === \"add\") {\n lines.push(`+ - id: ${change.elementId}`);\n if (change.after) {\n const afterData = change.after as Record<string, unknown>;\n if (afterData.name) {\n lines.push(`+ name: \"${afterData.name}\"`);\n }\n if (afterData.type) {\n lines.push(`+ type: ${afterData.type}`);\n }\n if (afterData.attributes) {\n lines.push(`+ attributes: ${JSON.stringify(afterData.attributes)}`);\n }\n }\n } else if (change.type === \"delete\") {\n lines.push(`- - id: ${change.elementId}`);\n if (change.before) {\n const beforeData = change.before as Record<string, unknown>;\n if (beforeData.name) {\n lines.push(`- name: \"${beforeData.name}\"`);\n }\n if (beforeData.type) {\n lines.push(`- type: ${beforeData.type}`);\n }\n // Also check attributes for consistency\n if (beforeData.attributes) {\n lines.push(`- attributes: ${JSON.stringify(beforeData.attributes)}`);\n }\n }\n } else if (change.type === \"update\") {\n // Show context line and changes\n lines.push(` - id: ${change.elementId}`);\n if (change.before && change.after) {\n const beforeData = change.before as Record<string, unknown>;\n const afterData = change.after as Record<string, unknown>;\n if (beforeData.name !== afterData.name) {\n lines.push(`- name: \"${beforeData.name}\"`);\n lines.push(`+ name: \"${afterData.name}\"`);\n }\n // Also check attributes if they changed\n if (JSON.stringify(beforeData.attributes) !== JSON.stringify(afterData.attributes)) {\n if (beforeData.attributes) {\n lines.push(`- attributes: ${JSON.stringify(beforeData.attributes)}`);\n }\n if (afterData.attributes) {\n lines.push(`+ attributes: ${JSON.stringify(afterData.attributes)}`);\n }\n }\n }\n }\n }\n\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n }\n\n /**\n * Import changeset from YAML format\n */\n private importFromYaml(data: string): Changeset {\n const parsed = yaml.parse(data);\n\n if (!parsed || typeof parsed !== \"object\") {\n throw new Error(\"Invalid YAML format\");\n }\n\n const parsedRecord = parsed as Record<string, unknown>;\n const {\n id,\n name,\n description,\n created,\n modified,\n status,\n baseSnapshot,\n changes = [],\n stats,\n } = parsedRecord;\n\n if (!id || !name || !created || !modified) {\n throw new Error(\"Missing required fields: id, name, created, modified\");\n }\n\n const statsRecord = stats as Record<string, unknown> | undefined;\n const changesetStats = {\n additions: typeof statsRecord?.additions === \"number\" ? statsRecord.additions : 0,\n modifications: typeof statsRecord?.modifications === \"number\" ? statsRecord.modifications : 0,\n deletions: typeof statsRecord?.deletions === \"number\" ? statsRecord.deletions : 0,\n };\n const changesetSnapshot = typeof baseSnapshot === \"string\" ? baseSnapshot : \"unknown\";\n\n // Validate status against known values\n if (![\"staged\", \"committed\", \"discarded\"].includes(String(status))) {\n throw new Error(\n `Invalid changeset status '${status}' from YAML import. ` +\n `Expected: staged, committed, or discarded.`\n );\n }\n\n const changeset = new Changeset({\n id: String(id),\n name: String(name),\n description: typeof description === \"string\" ? description : undefined,\n created: String(created),\n modified: String(modified),\n status: status as \"staged\" | \"committed\" | \"discarded\",\n baseSnapshot: changesetSnapshot,\n changes: (changes as Change[]) || [],\n stats: changesetStats,\n });\n\n return changeset;\n }\n\n /**\n * Import changeset from JSON format\n */\n private importFromJson(data: string): Changeset {\n const parsed = JSON.parse(data);\n\n if (!parsed || typeof parsed !== \"object\") {\n throw new Error(\"Invalid JSON format\");\n }\n\n const parsedRecord = parsed as Record<string, unknown>;\n const {\n id,\n name,\n description,\n created,\n modified,\n status,\n baseSnapshot,\n changes = [],\n stats,\n } = parsedRecord;\n\n if (!id || !name || !created || !modified) {\n throw new Error(\"Missing required fields: id, name, created, modified\");\n }\n\n const statsRecord = stats as Record<string, unknown> | undefined;\n const changesetStats = {\n additions: typeof statsRecord?.additions === \"number\" ? statsRecord.additions : 0,\n modifications: typeof statsRecord?.modifications === \"number\" ? statsRecord.modifications : 0,\n deletions: typeof statsRecord?.deletions === \"number\" ? statsRecord.deletions : 0,\n };\n const changesetSnapshot = typeof baseSnapshot === \"string\" ? baseSnapshot : \"unknown\";\n\n // Validate status against known values\n if (![\"staged\", \"committed\", \"discarded\"].includes(String(status))) {\n throw new Error(\n `Invalid changeset status '${status}' from JSON import. ` +\n `Expected: staged, committed, or discarded.`\n );\n }\n\n const changeset = new Changeset({\n id: String(id),\n name: String(name),\n description: typeof description === \"string\" ? description : undefined,\n created: String(created),\n modified: String(modified),\n status: status as \"staged\" | \"committed\" | \"discarded\",\n baseSnapshot: changesetSnapshot,\n changes: (changes as Change[]) || [],\n stats: changesetStats,\n });\n\n return changeset;\n }\n\n /**\n * Import changeset from patch format\n *\n * NOTE: Patch format import extracts metadata and base snapshot from headers only.\n * The unified diff format makes it impractical to reconstruct full change details\n * without complex diff parsing. For this reason, patch format import returns a\n * changeset with empty changes array and is primarily useful for preserving metadata.\n *\n * Use YAML or JSON formats for changesets that need to be re-imported with full\n * change data. Patch format is recommended for email-based sharing and archival.\n */\n private importFromPatch(data: string): Changeset {\n const lines = data.split(\"\\n\");\n const metadata: Record<string, string> = {};\n const changes: Change[] = [];\n\n let i = 0;\n // Parse header lines - continue until we hit the --- marker or blank line\n while (i < lines.length && !lines[i].startsWith(\"---\") && lines[i].trim() !== \"\") {\n const line = lines[i];\n if (line.startsWith(\"From:\")) {\n metadata.id = line.substring(\"From:\".length).trim();\n } else if (line.startsWith(\"Date:\")) {\n metadata.exportedAt = line.substring(\"Date:\".length).trim();\n } else if (line.startsWith(\"Base:\")) {\n metadata.baseSnapshot = line.substring(\"Base:\".length).trim();\n } else if (line.startsWith(\"Subject:\")) {\n metadata.name = line.substring(\"Subject:\".length).trim();\n }\n i++;\n }\n\n // Skip blank lines\n while (i < lines.length && lines[i].trim() === \"\") {\n i++;\n }\n\n // Description is between header and --- marker\n const descriptionLines: string[] = [];\n while (i < lines.length && !lines[i].startsWith(\"---\")) {\n descriptionLines.push(lines[i]);\n i++;\n }\n if (descriptionLines.length > 0) {\n metadata.description = descriptionLines.join(\"\\n\").trim();\n }\n\n // Validate required metadata\n if (!metadata.id || !metadata.name) {\n throw new Error(\"Missing required fields in patch header: id, name\");\n }\n\n // Create changeset with extracted metadata\n // Note: changes array remains empty as diff parsing is complex\n const changeset = new Changeset({\n id: metadata.id,\n name: metadata.name,\n description: metadata.description,\n created: new Date().toISOString(),\n modified: new Date().toISOString(),\n status: \"staged\",\n baseSnapshot: metadata.baseSnapshot || \"unknown\",\n changes,\n stats: {\n additions: 0,\n modifications: 0,\n deletions: 0,\n },\n });\n\n return changeset;\n }\n\n /**\n * Detect export format from content\n */\n private detectFormat(data: string): ExportFormat {\n const trimmed = data.trim();\n\n // Check for JSON\n if (trimmed.startsWith(\"{\")) {\n try {\n JSON.parse(trimmed);\n return \"json\";\n } catch {\n // Not valid JSON, try next format\n }\n }\n\n // Check for YAML\n if (trimmed.includes(\"id:\") && trimmed.includes(\"name:\")) {\n try {\n yaml.parse(trimmed);\n return \"yaml\";\n } catch {\n // Not valid YAML, try next format\n }\n }\n\n // Check for patch format\n if (trimmed.startsWith(\"From:\")) {\n return \"patch\";\n }\n\n // Default to YAML\n return \"yaml\";\n }\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,UAAU,iBAAiB;AACpC,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AACjB,SAAS,iBAA8B;AACvC,SAAS,8BAA8B;AAEvC,SAAS,2BAA2B;AAqB9B,MAAO,kBAAiB;EACpB;EACA;EAER,YAAY,UAAgB;AAC1B,SAAK,UAAU,IAAI,uBAAuB,QAAQ,GAClD,KAAK,kBAAkB,IAAI,oBAAmB;EAChD;;;;EAKA,MAAM,OAAO,aAAqB,SAAuB,QAAM;AAC7D,UAAM,YAAY,MAAM,KAAK,QAAQ,KAAK,WAAW;AACrD,QAAI,CAAC;AACH,YAAM,IAAI,MAAM,cAAc,WAAW,aAAa;AAGxD,YAAQ,QAAQ;MACd,KAAK;AACH,eAAO,KAAK,aAAa,SAAS;MACpC,KAAK;AACH,eAAO,KAAK,aAAa,SAAS;MACpC,KAAK;AACH,eAAO,KAAK,cAAc,SAAS;MACrC;AACE,cAAM,IAAI,MAAM,8BAA8B,MAAM,EAAE;IAC1D;EACF;;;;EAKA,MAAM,aACJ,aACA,YACA,SAAuB,QAAM;AAE7B,UAAM,UAAU,MAAM,KAAK,OAAO,aAAa,MAAM;AACrD,UAAM,UAAU,YAAY,SAAS,OAAO;EAC9C;;;;EAKA,MAAM,OAAO,MAAc,QAAqB;AAM9C,YAJK,WACH,SAAS,KAAK,aAAa,IAAI,IAGzB,QAAQ;MACd,KAAK;AACH,eAAO,KAAK,eAAe,IAAI;MACjC,KAAK;AACH,eAAO,KAAK,eAAe,IAAI;MACjC,KAAK;AACH,eAAO,KAAK,gBAAgB,IAAI;MAClC;AACE,cAAM,IAAI,MAAM,8BAA8B,MAAM,EAAE;IAC1D;EACF;;;;EAKA,MAAM,eAAe,WAAiB;AACpC,QAAI,CAAE,MAAM,WAAW,SAAS;AAC9B,YAAM,IAAI,MAAM,mBAAmB,SAAS,EAAE;AAGhD,UAAM,OAAO,MAAM,SAAS,WAAW,OAAO,GACxC,SAAS,KAAK,aAAa,IAAI;AAErC,WAAO,KAAK,OAAO,MAAM,MAAM;EACjC;;;;EAKA,MAAM,sBACJ,WACA,cAAmB;AAEnB,UAAM,SAA8B;MAClC,YAAY;MACZ,mBAAmB;MACnB,iBAAiB,CAAA;MACjB,UAAU,CAAA;MACV,gBAAgB,CAAA;;AAIlB,QAAI,UAAU,cAAc;AAC1B,YAAM,kBAAkB,MAAM,KAAK,gBAAgB,gBAAgB,YAAY;AAC/E,MAAI,UAAU,iBAAiB,oBAC7B,OAAO,oBAAoB,IAC3B,OAAO,SAAS,KAAK,oDAAoD;IAE7E;AAIA,UAAM,mBAAmB,oBAAI,IAAG;AAEhC,eAAW,UAAU,UAAU,WAAW,CAAA,GAAI;AAG5C,UAFA,iBAAiB,IAAI,OAAO,SAAS,GAEjC,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU;AAExD,cAAM,QAAQ,MAAM,aAAa,SAAS,OAAO,SAAS;AAC1D,SAAI,CAAC,SAAS,CAAC,MAAM,WAAW,OAAO,SAAS,OAC1C,OAAO,SAAS,WAElB,OAAO,SAAS,KACd,WAAW,OAAO,SAAS,uBAAuB,OAAO,SAAS,oBAAoB,KAIxF,OAAO,aAAa,IACpB,OAAO,gBAAgB,KAAK,OAAO,SAAS,GAC5C,OAAO,SAAS,KAAK,sCAAsC,OAAO,SAAS,EAAE;MAGnF;AAGA,UAAI,OAAO,SAAS,OAAO;AACzB,cAAM,QAAQ,MAAM,aAAa,SAAS,OAAO,SAAS;AAC1D,QAAI,SAAS,MAAM,WAAW,OAAO,SAAS,MAC5C,OAAO,aAAa,IACpB,OAAO,SAAS,KACd,WAAW,OAAO,SAAS,4BAA4B,OAAO,SAAS,EAAE;MAG/E;IACF;AAEA,kBAAO,iBAAiB,MAAM,KAAK,gBAAgB,GAE5C;EACT;;;;EAKQ,aAAa,WAAoB;AACvC,UAAM,aAAa;MACjB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,aAAa,UAAU;MACvB,SAAS,UAAU;MACnB,UAAU,UAAU;MACpB,QAAQ,UAAU;MAClB,cAAc,UAAU;MACxB,QAAQ;QACN,SAAS;QACT,aAAY,oBAAI,KAAI,GAAG,YAAW;QAClC,QAAQ;;MAEV,OAAO,UAAU,SAAS;QACxB,WAAW;QACX,eAAe;QACf,WAAW;;MAEb,SAAS,UAAU,WAAW,CAAA;;AAGhC,WAAO,KAAK,UAAU,YAAY;MAChC,QAAQ;MACR,WAAW;KACZ;EACH;;;;EAKQ,aAAa,WAAoB;AACvC,UAAM,aAAa;MACjB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,aAAa,UAAU;MACvB,SAAS,UAAU;MACnB,UAAU,UAAU;MACpB,QAAQ,UAAU;MAClB,cAAc,UAAU;MACxB,QAAQ;QACN,SAAS;QACT,aAAY,oBAAI,KAAI,GAAG,YAAW;QAClC,QAAQ;;MAEV,OAAO,UAAU,SAAS;QACxB,WAAW;QACX,eAAe;QACf,WAAW;;MAEb,SAAS,UAAU,WAAW,CAAA;;AAGhC,WAAO,KAAK,UAAU,YAAY,MAAM,CAAC;EAC3C;;;;EAKQ,cAAc,WAAoB;AACxC,UAAM,QAAkB,CAAA;AAGxB,UAAM,KAAK,SAAS,UAAU,EAAE,EAAE,GAClC,MAAM,KAAK,UAAS,oBAAI,KAAI,GAAG,YAAW,CAAE,EAAE,GAC1C,UAAU,gBACZ,MAAM,KAAK,SAAS,UAAU,YAAY,EAAE,GAE9C,MAAM,KAAK,YAAY,UAAU,IAAI,EAAE,GACvC,MAAM,KAAK,EAAE,GACT,UAAU,gBACZ,MAAM,KAAK,UAAU,WAAW,GAChC,MAAM,KAAK,EAAE;AAIf,UAAM,QAAQ,UAAU,SAAS;MAC/B,WAAW;MACX,eAAe;MACf,WAAW;;AAEb,UAAM,KAAK,KAAK,GAChB,MAAM,KACJ,IAAI,MAAM,YAAY,MAAM,gBAAgB,MAAM,SAAS,QAAQ,MAAM,YAAY,MAAM,gBAAgB,MAAM,cAAc,IAAI,MAAM,EAAE,aAAa,MAAM,SAAS,mBAAmB,MAAM,SAAS,eAAe,GAE1N,MAAM,KAAK,EAAE;AAGb,UAAM,WAAW,oBAAI,IAAG;AACxB,eAAW,UAAU,UAAU,WAAW,CAAA;AACxC,MAAK,SAAS,IAAI,OAAO,SAAS,KAChC,SAAS,IAAI,OAAO,WAAW,CAAA,CAAE,GAEnC,SAAS,IAAI,OAAO,SAAS,EAAG,KAAK,MAAM;AAI7C,eAAW,CAAC,WAAW,OAAO,KAAK,UAAU;AAC3C,YAAM,WAAW,GAAG,SAAS;AAC7B,YAAM,KAAK,gBAAgB,QAAQ,MAAM,QAAQ,EAAE,GACnD,MAAM,KAAK,6BAA6B,GACxC,MAAM,KAAK,SAAS,QAAQ,EAAE,GAC9B,MAAM,KAAK,SAAS,QAAQ,EAAE,GAG9B,MAAM,KAAK,gBAAgB,QAAQ,SAAS,CAAC,KAAK;AAGlD,iBAAW,UAAU;AACnB,YAAI,OAAO,SAAS;AAElB,cADA,MAAM,KAAK,WAAW,OAAO,SAAS,EAAE,GACpC,OAAO,OAAO;AAChB,kBAAM,YAAY,OAAO;AACzB,YAAI,UAAU,QACZ,MAAM,KAAK,cAAc,UAAU,IAAI,GAAG,GAExC,UAAU,QACZ,MAAM,KAAK,aAAa,UAAU,IAAI,EAAE,GAEtC,UAAU,cACZ,MAAM,KAAK,mBAAmB,KAAK,UAAU,UAAU,UAAU,CAAC,EAAE;UAExE;mBACS,OAAO,SAAS;AAEzB,cADA,MAAM,KAAK,WAAW,OAAO,SAAS,EAAE,GACpC,OAAO,QAAQ;AACjB,kBAAM,aAAa,OAAO;AAC1B,YAAI,WAAW,QACb,MAAM,KAAK,cAAc,WAAW,IAAI,GAAG,GAEzC,WAAW,QACb,MAAM,KAAK,aAAa,WAAW,IAAI,EAAE,GAGvC,WAAW,cACb,MAAM,KAAK,mBAAmB,KAAK,UAAU,WAAW,UAAU,CAAC,EAAE;UAEzE;mBACS,OAAO,SAAS,aAEzB,MAAM,KAAK,WAAW,OAAO,SAAS,EAAE,GACpC,OAAO,UAAU,OAAO,QAAO;AACjC,gBAAM,aAAa,OAAO,QACpB,YAAY,OAAO;AACzB,UAAI,WAAW,SAAS,UAAU,SAChC,MAAM,KAAK,cAAc,WAAW,IAAI,GAAG,GAC3C,MAAM,KAAK,cAAc,UAAU,IAAI,GAAG,IAGxC,KAAK,UAAU,WAAW,UAAU,MAAM,KAAK,UAAU,UAAU,UAAU,MAC3E,WAAW,cACb,MAAM,KAAK,mBAAmB,KAAK,UAAU,WAAW,UAAU,CAAC,EAAE,GAEnE,UAAU,cACZ,MAAM,KAAK,mBAAmB,KAAK,UAAU,UAAU,UAAU,CAAC,EAAE;QAG1E;AAIJ,YAAM,KAAK,EAAE;IACf;AAEA,WAAO,MAAM,KAAK;CAAI;EACxB;;;;EAKQ,eAAe,MAAY;AACjC,UAAM,SAAS,KAAK,MAAM,IAAI;AAE9B,QAAI,CAAC,UAAU,OAAO,UAAW;AAC/B,YAAM,IAAI,MAAM,qBAAqB;AAGvC,UAAM,eAAe,QACf,EACJ,IACA,MACA,aACA,SACA,UACA,QACA,cACA,UAAU,CAAA,GACV,MAAK,IACH;AAEJ,QAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;AAC/B,YAAM,IAAI,MAAM,sDAAsD;AAGxE,UAAM,cAAc,OACd,iBAAiB;MACrB,WAAW,OAAO,aAAa,aAAc,WAAW,YAAY,YAAY;MAChF,eAAe,OAAO,aAAa,iBAAkB,WAAW,YAAY,gBAAgB;MAC5F,WAAW,OAAO,aAAa,aAAc,WAAW,YAAY,YAAY;OAE5E,oBAAoB,OAAO,gBAAiB,WAAW,eAAe;AAG5E,QAAI,CAAC,CAAC,UAAU,aAAa,WAAW,EAAE,SAAS,OAAO,MAAM,CAAC;AAC/D,YAAM,IAAI,MACR,6BAA6B,MAAM,gEACS;AAgBhD,WAZkB,IAAI,UAAU;MAC9B,IAAI,OAAO,EAAE;MACb,MAAM,OAAO,IAAI;MACjB,aAAa,OAAO,eAAgB,WAAW,cAAc;MAC7D,SAAS,OAAO,OAAO;MACvB,UAAU,OAAO,QAAQ;MACzB;MACA,cAAc;MACd,SAAU,WAAwB,CAAA;MAClC,OAAO;KACR;EAGH;;;;EAKQ,eAAe,MAAY;AACjC,UAAM,SAAS,KAAK,MAAM,IAAI;AAE9B,QAAI,CAAC,UAAU,OAAO,UAAW;AAC/B,YAAM,IAAI,MAAM,qBAAqB;AAGvC,UAAM,eAAe,QACf,EACJ,IACA,MACA,aACA,SACA,UACA,QACA,cACA,UAAU,CAAA,GACV,MAAK,IACH;AAEJ,QAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;AAC/B,YAAM,IAAI,MAAM,sDAAsD;AAGxE,UAAM,cAAc,OACd,iBAAiB;MACrB,WAAW,OAAO,aAAa,aAAc,WAAW,YAAY,YAAY;MAChF,eAAe,OAAO,aAAa,iBAAkB,WAAW,YAAY,gBAAgB;MAC5F,WAAW,OAAO,aAAa,aAAc,WAAW,YAAY,YAAY;OAE5E,oBAAoB,OAAO,gBAAiB,WAAW,eAAe;AAG5E,QAAI,CAAC,CAAC,UAAU,aAAa,WAAW,EAAE,SAAS,OAAO,MAAM,CAAC;AAC/D,YAAM,IAAI,MACR,6BAA6B,MAAM,gEACS;AAgBhD,WAZkB,IAAI,UAAU;MAC9B,IAAI,OAAO,EAAE;MACb,MAAM,OAAO,IAAI;MACjB,aAAa,OAAO,eAAgB,WAAW,cAAc;MAC7D,SAAS,OAAO,OAAO;MACvB,UAAU,OAAO,QAAQ;MACzB;MACA,cAAc;MACd,SAAU,WAAwB,CAAA;MAClC,OAAO;KACR;EAGH;;;;;;;;;;;;EAaQ,gBAAgB,MAAY;AAClC,UAAM,QAAQ,KAAK,MAAM;CAAI,GACvB,WAAmC,CAAA,GACnC,UAAoB,CAAA;AAE1B,QAAI,IAAI;AAER,WAAO,IAAI,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,KAAK,MAAM,CAAC,EAAE,KAAI,MAAO,MAAI;AAChF,YAAM,OAAO,MAAM,CAAC;AACpB,MAAI,KAAK,WAAW,OAAO,IACzB,SAAS,KAAK,KAAK,UAAU,CAAc,EAAE,KAAI,IACxC,KAAK,WAAW,OAAO,IAChC,SAAS,aAAa,KAAK,UAAU,CAAc,EAAE,KAAI,IAChD,KAAK,WAAW,OAAO,IAChC,SAAS,eAAe,KAAK,UAAU,CAAc,EAAE,KAAI,IAClD,KAAK,WAAW,UAAU,MACnC,SAAS,OAAO,KAAK,UAAU,CAAiB,EAAE,KAAI,IAExD;IACF;AAGA,WAAO,IAAI,MAAM,UAAU,MAAM,CAAC,EAAE,KAAI,MAAO;AAC7C;AAIF,UAAM,mBAA6B,CAAA;AACnC,WAAO,IAAI,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK;AACnD,uBAAiB,KAAK,MAAM,CAAC,CAAC,GAC9B;AAOF,QALI,iBAAiB,SAAS,MAC5B,SAAS,cAAc,iBAAiB,KAAK;CAAI,EAAE,KAAI,IAIrD,CAAC,SAAS,MAAM,CAAC,SAAS;AAC5B,YAAM,IAAI,MAAM,mDAAmD;AAqBrE,WAhBkB,IAAI,UAAU;MAC9B,IAAI,SAAS;MACb,MAAM,SAAS;MACf,aAAa,SAAS;MACtB,UAAS,oBAAI,KAAI,GAAG,YAAW;MAC/B,WAAU,oBAAI,KAAI,GAAG,YAAW;MAChC,QAAQ;MACR,cAAc,SAAS,gBAAgB;MACvC;MACA,OAAO;QACL,WAAW;QACX,eAAe;QACf,WAAW;;KAEd;EAGH;;;;EAKQ,aAAa,MAAY;AAC/B,UAAM,UAAU,KAAK,KAAI;AAGzB,QAAI,QAAQ,WAAW,GAAG;AACxB,UAAI;AACF,oBAAK,MAAM,OAAO,GACX;MACT,QAAQ;MAER;AAIF,QAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,OAAO;AACrD,UAAI;AACF,oBAAK,MAAM,OAAO,GACX;MACT,QAAQ;MAER;AAIF,WAAI,QAAQ,WAAW,OAAO,IACrB,UAIF;EACT;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { VirtualProjectionEngine } from "./virtual-projection.js";
|
|
2
|
+
import { StagedChangesetStorage } from "./staged-changeset-storage.js";
|
|
3
|
+
import { Validator } from "../validators/validator.js";
|
|
4
|
+
import { ValidationResult } from "../validators/types.js";
|
|
5
|
+
class ChangesetValidator {
|
|
6
|
+
projectionEngine;
|
|
7
|
+
storage;
|
|
8
|
+
validator;
|
|
9
|
+
constructor(rootPath) {
|
|
10
|
+
this.projectionEngine = new VirtualProjectionEngine(rootPath), this.storage = new StagedChangesetStorage(rootPath), this.validator = new Validator();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validate a changeset by running the full 4-stage pipeline on its projected model.
|
|
14
|
+
*
|
|
15
|
+
* Only errors affecting elements touched by the changeset (add/update) block the
|
|
16
|
+
* commit. Pre-existing errors in unchanged elements are demoted to warnings so that
|
|
17
|
+
* incremental fixes can be committed without requiring the entire model to be valid
|
|
18
|
+
* first.
|
|
19
|
+
*
|
|
20
|
+
* @param baseModel - The base model to project
|
|
21
|
+
* @param changesetId - The changeset to validate
|
|
22
|
+
* @returns ValidationResult from all 4 stages
|
|
23
|
+
*/
|
|
24
|
+
async validateChangeset(baseModel, changesetId) {
|
|
25
|
+
const changeset = await this.storage.load(changesetId), changedElementIds = /* @__PURE__ */ new Set();
|
|
26
|
+
if (changeset)
|
|
27
|
+
for (const change of changeset.changes)
|
|
28
|
+
(change.type === "add" || change.type === "update") && changedElementIds.add(change.elementId);
|
|
29
|
+
const projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId), fullResult = await this.validator.validateModel(projectedModel);
|
|
30
|
+
if (changedElementIds.size === 0)
|
|
31
|
+
return fullResult;
|
|
32
|
+
const filtered = new ValidationResult();
|
|
33
|
+
for (const error of fullResult.errors)
|
|
34
|
+
!error.elementId || changedElementIds.has(error.elementId) ? filtered.errors.push(error) : filtered.warnings.push({ ...error, severity: "warning" });
|
|
35
|
+
return filtered.warnings.push(...fullResult.warnings), filtered;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
ChangesetValidator
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=changeset-validator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-validator.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * ChangesetValidator - Validates staged changes against projected model\n *\n * Runs the complete 4-stage validation pipeline against a merged view\n * of the base model with staged changes applied.\n */\n\nimport { VirtualProjectionEngine } from \"./virtual-projection.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { Validator } from \"../validators/validator.js\";\nimport { ValidationResult } from \"../validators/types.js\";\nimport type { Model } from \"./model.js\";\n\n/**\n * Validates changesets by running validation pipeline on projected models\n */\nexport class ChangesetValidator {\n private projectionEngine: VirtualProjectionEngine;\n private storage: StagedChangesetStorage;\n private validator: Validator;\n\n constructor(rootPath: string) {\n this.projectionEngine = new VirtualProjectionEngine(rootPath);\n this.storage = new StagedChangesetStorage(rootPath);\n this.validator = new Validator();\n }\n\n /**\n * Validate a changeset by running the full 4-stage pipeline on its projected model.\n *\n * Only errors affecting elements touched by the changeset (add/update) block the\n * commit. Pre-existing errors in unchanged elements are demoted to warnings so that\n * incremental fixes can be committed without requiring the entire model to be valid\n * first.\n *\n * @param baseModel - The base model to project\n * @param changesetId - The changeset to validate\n * @returns ValidationResult from all 4 stages\n */\n async validateChangeset(baseModel: Model, changesetId: string): Promise<ValidationResult> {\n // Collect element IDs touched by this changeset (add/update only)\n const changeset = await this.storage.load(changesetId);\n const changedElementIds = new Set<string>();\n if (changeset) {\n for (const change of changeset.changes) {\n if (change.type === \"add\" || change.type === \"update\") {\n changedElementIds.add(change.elementId);\n }\n }\n }\n\n // Project the model with staged changes applied\n const projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId);\n\n // Run unified validator on projected model\n const fullResult = await this.validator.validateModel(projectedModel as unknown as Model);\n\n if (changedElementIds.size === 0) {\n return fullResult;\n }\n\n // Partition errors: block on errors for changed elements; demote pre-existing\n // errors in unchanged elements to warnings so they don't block this commit.\n const filtered = new ValidationResult();\n for (const error of fullResult.errors) {\n if (!error.elementId || changedElementIds.has(error.elementId)) {\n filtered.errors.push(error);\n } else {\n filtered.warnings.push({ ...error, severity: \"warning\" });\n }\n }\n filtered.warnings.push(...fullResult.warnings);\n\n return filtered;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAOA,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,iBAAiB;AAC1B,SAAS,wBAAwB;AAM3B,MAAO,mBAAkB;EACrB;EACA;EACA;EAER,YAAY,UAAgB;AAC1B,SAAK,mBAAmB,IAAI,wBAAwB,QAAQ,GAC5D,KAAK,UAAU,IAAI,uBAAuB,QAAQ,GAClD,KAAK,YAAY,IAAI,UAAS;EAChC;;;;;;;;;;;;;EAcA,MAAM,kBAAkB,WAAkB,aAAmB;AAE3D,UAAM,YAAY,MAAM,KAAK,QAAQ,KAAK,WAAW,GAC/C,oBAAoB,oBAAI,IAAG;AACjC,QAAI;AACF,iBAAW,UAAU,UAAU;AAC7B,SAAI,OAAO,SAAS,SAAS,OAAO,SAAS,aAC3C,kBAAkB,IAAI,OAAO,SAAS;AAM5C,UAAM,iBAAiB,MAAM,KAAK,iBAAiB,aAAa,WAAW,WAAW,GAGhF,aAAa,MAAM,KAAK,UAAU,cAAc,cAAkC;AAExF,QAAI,kBAAkB,SAAS;AAC7B,aAAO;AAKT,UAAM,WAAW,IAAI,iBAAgB;AACrC,eAAW,SAAS,WAAW;AAC7B,MAAI,CAAC,MAAM,aAAa,kBAAkB,IAAI,MAAM,SAAS,IAC3D,SAAS,OAAO,KAAK,KAAK,IAE1B,SAAS,SAAS,KAAK,EAAE,GAAG,OAAO,UAAU,UAAS,CAAE;AAG5D,oBAAS,SAAS,KAAK,GAAG,WAAW,QAAQ,GAEtC;EACT;;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|