@documentation-robotics/cli 0.1.0 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +466 -693
- package/dist/analysis/data-model-analyzer.js +189 -0
- package/dist/analysis/data-model-analyzer.js.map +7 -0
- package/dist/analysis/index.js +3 -0
- package/dist/analysis/index.js.map +7 -0
- package/dist/analysis/relationship-classifier.js +181 -0
- package/dist/analysis/relationship-classifier.js.map +7 -0
- package/dist/analyzers/base-analyzer.js +1 -0
- package/dist/analyzers/base-analyzer.js.map +7 -0
- package/dist/analyzers/call-graph-utils.js +39 -0
- package/dist/analyzers/call-graph-utils.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +1205 -0
- package/dist/analyzers/cbm-analyzer.js.map +7 -0
- package/dist/analyzers/heuristic-utils.js +55 -0
- package/dist/analyzers/heuristic-utils.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +146 -0
- package/dist/analyzers/mapping-loader.js.map +7 -0
- package/dist/analyzers/registry.js +124 -0
- package/dist/analyzers/registry.js.map +7 -0
- package/dist/analyzers/session-state.js +176 -0
- package/dist/analyzers/session-state.js.map +7 -0
- package/dist/analyzers/stdio-client.js +206 -0
- package/dist/analyzers/stdio-client.js.map +7 -0
- package/dist/analyzers/types.js +15 -0
- package/dist/analyzers/types.js.map +7 -0
- package/dist/analyzers/verify-engine.js +209 -0
- package/dist/analyzers/verify-engine.js.map +7 -0
- package/dist/analyzers/verify-ignore.js +130 -0
- package/dist/analyzers/verify-ignore.js.map +7 -0
- package/dist/analyzers/version.js +8 -0
- package/dist/analyzers/version.js.map +7 -0
- package/dist/audit/ai/runner.js +41 -0
- package/dist/audit/ai/runner.js.map +7 -0
- package/dist/audit/differential-analyzer.js +183 -0
- package/dist/audit/differential-analyzer.js.map +7 -0
- package/dist/audit/index.js +32 -0
- package/dist/audit/index.js.map +7 -0
- package/dist/audit/nodes/ai/evaluator.js +49 -0
- package/dist/audit/nodes/ai/evaluator.js.map +7 -0
- package/dist/audit/nodes/ai/parser.js +55 -0
- package/dist/audit/nodes/ai/parser.js.map +7 -0
- package/dist/audit/nodes/ai/prompts.js +103 -0
- package/dist/audit/nodes/ai/prompts.js.map +7 -0
- package/dist/audit/nodes/analysis/node-completeness-checker.js +32 -0
- package/dist/audit/nodes/analysis/node-completeness-checker.js.map +7 -0
- package/dist/audit/nodes/analysis/node-definition-analyzer.js +47 -0
- package/dist/audit/nodes/analysis/node-definition-analyzer.js.map +7 -0
- package/dist/audit/nodes/analysis/node-overlap-detector.js +79 -0
- package/dist/audit/nodes/analysis/node-overlap-detector.js.map +7 -0
- package/dist/audit/nodes/model/orchestrator.js +82 -0
- package/dist/audit/nodes/model/orchestrator.js.map +7 -0
- package/dist/audit/nodes/spec/loader.js +116 -0
- package/dist/audit/nodes/spec/loader.js.map +7 -0
- package/dist/audit/nodes/spec/orchestrator.js +68 -0
- package/dist/audit/nodes/spec/orchestrator.js.map +7 -0
- package/dist/audit/nodes/types.js +1 -0
- package/dist/audit/nodes/types.js.map +7 -0
- package/dist/audit/pipeline/index.js +5 -0
- package/dist/audit/pipeline/index.js.map +7 -0
- package/dist/audit/pipeline/pipeline-orchestrator.js +218 -0
- package/dist/audit/pipeline/pipeline-orchestrator.js.map +7 -0
- package/dist/audit/relationships/ai/evaluator.js +235 -0
- package/dist/audit/relationships/ai/evaluator.js.map +7 -0
- package/dist/audit/relationships/ai/parser.js +197 -0
- package/dist/audit/relationships/ai/parser.js.map +7 -0
- package/dist/audit/relationships/ai/prompts.js +274 -0
- package/dist/audit/relationships/ai/prompts.js.map +7 -0
- package/dist/audit/relationships/analysis/balance-assessor.js +115 -0
- package/dist/audit/relationships/analysis/balance-assessor.js.map +7 -0
- package/dist/audit/relationships/analysis/coverage-analyzer.js +99 -0
- package/dist/audit/relationships/analysis/coverage-analyzer.js.map +7 -0
- package/dist/audit/relationships/analysis/duplicate-detector.js +95 -0
- package/dist/audit/relationships/analysis/duplicate-detector.js.map +7 -0
- package/dist/audit/relationships/analysis/gap-analyzer.js +99 -0
- package/dist/audit/relationships/analysis/gap-analyzer.js.map +7 -0
- package/dist/audit/relationships/analysis/layer-templates.js +294 -0
- package/dist/audit/relationships/analysis/layer-templates.js.map +7 -0
- package/dist/audit/relationships/graph/connectivity.js +129 -0
- package/dist/audit/relationships/graph/connectivity.js.map +7 -0
- package/dist/audit/relationships/graph/relationship-graph.js +179 -0
- package/dist/audit/relationships/graph/relationship-graph.js.map +7 -0
- package/dist/audit/relationships/model/balance-assessor.js +49 -0
- package/dist/audit/relationships/model/balance-assessor.js.map +7 -0
- package/dist/audit/relationships/model/coverage-analyzer.js +34 -0
- package/dist/audit/relationships/model/coverage-analyzer.js.map +7 -0
- package/dist/audit/relationships/model/duplicate-detector.js +68 -0
- package/dist/audit/relationships/model/duplicate-detector.js.map +7 -0
- package/dist/audit/relationships/model/gap-analyzer.js +41 -0
- package/dist/audit/relationships/model/gap-analyzer.js.map +7 -0
- package/dist/audit/relationships/model/orchestrator.js +126 -0
- package/dist/audit/relationships/model/orchestrator.js.map +7 -0
- package/dist/audit/relationships/spec/orchestrator.js +157 -0
- package/dist/audit/relationships/spec/orchestrator.js.map +7 -0
- package/dist/audit/reports/index.js +5 -0
- package/dist/audit/reports/index.js.map +7 -0
- package/dist/audit/reports/report-generator.js +118 -0
- package/dist/audit/reports/report-generator.js.map +7 -0
- package/dist/audit/snapshot-storage.js +163 -0
- package/dist/audit/snapshot-storage.js.map +7 -0
- package/dist/audit/types.js +21 -0
- package/dist/audit/types.js.map +7 -0
- package/dist/cli.js +223 -92
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +147 -0
- package/dist/coding-agents/agents/claude-code.js.map +7 -0
- package/dist/coding-agents/agents/index.js +5 -0
- package/dist/coding-agents/agents/index.js.map +7 -0
- package/dist/coding-agents/agents/types.js +1 -0
- package/dist/coding-agents/agents/types.js.map +7 -0
- package/dist/coding-agents/base-chat-client.js +45 -0
- package/dist/coding-agents/base-chat-client.js.map +7 -0
- package/dist/coding-agents/chat-utils.js +23 -0
- package/dist/coding-agents/chat-utils.js.map +7 -0
- package/dist/coding-agents/claude-code-client.js +246 -0
- package/dist/coding-agents/claude-code-client.js.map +7 -0
- package/dist/coding-agents/copilot-client.js +209 -0
- package/dist/coding-agents/copilot-client.js.map +7 -0
- package/dist/commands/add.js +95 -33
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +511 -0
- package/dist/commands/analyzer.js.map +7 -0
- package/dist/commands/audit-diff.js +49 -0
- package/dist/commands/audit-diff.js.map +7 -0
- package/dist/commands/audit-snapshots.js +62 -0
- package/dist/commands/audit-snapshots.js.map +7 -0
- package/dist/commands/audit.js +81 -0
- package/dist/commands/audit.js.map +7 -0
- package/dist/commands/catalog.js +248 -0
- package/dist/commands/catalog.js.map +7 -0
- package/dist/commands/changeset.js +577 -66
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/chat.js +106 -83
- package/dist/commands/chat.js.map +2 -2
- package/dist/commands/claude.js +74 -0
- package/dist/commands/claude.js.map +7 -0
- package/dist/commands/conformance.js +71 -68
- package/dist/commands/conformance.js.map +2 -2
- package/dist/commands/copilot.js +75 -0
- package/dist/commands/copilot.js.map +7 -0
- package/dist/commands/delete.js +125 -11
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/discover-logic.js +44 -0
- package/dist/commands/discover-logic.js.map +7 -0
- package/dist/commands/docs.js +87 -0
- package/dist/commands/docs.js.map +7 -0
- package/dist/commands/element.js +14 -20
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/export.js +13 -3
- package/dist/commands/export.js.map +2 -2
- package/dist/commands/import.js +51 -0
- package/dist/commands/import.js.map +7 -0
- package/dist/commands/info.js +30 -24
- package/dist/commands/info.js.map +2 -2
- package/dist/commands/init.js +27 -11
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/list.js +63 -13
- package/dist/commands/list.js.map +3 -3
- package/dist/commands/relationship.js +225 -37
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/report.js +54 -0
- package/dist/commands/report.js.map +7 -0
- package/dist/commands/reports.js +45 -0
- package/dist/commands/reports.js.map +7 -0
- package/dist/commands/schema.js +135 -0
- package/dist/commands/schema.js.map +7 -0
- package/dist/commands/search.js +40 -12
- package/dist/commands/search.js.map +2 -2
- package/dist/commands/show.js +58 -27
- package/dist/commands/show.js.map +2 -2
- package/dist/commands/stats.js +35 -0
- package/dist/commands/stats.js.map +7 -0
- package/dist/commands/trace.js +39 -16
- package/dist/commands/trace.js.map +2 -2
- package/dist/commands/update.js +141 -14
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +210 -46
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +142 -23
- package/dist/commands/validate.js.map +3 -3
- package/dist/commands/version.js +8 -12
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +155 -25
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/base-snapshot-manager.js +84 -0
- package/dist/core/base-snapshot-manager.js.map +7 -0
- package/dist/core/changeset-exporter.js +305 -0
- package/dist/core/changeset-exporter.js.map +7 -0
- package/dist/core/changeset-validator.js +41 -0
- package/dist/core/changeset-validator.js.map +7 -0
- package/dist/core/changeset.js +88 -173
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/dependency-tracker.js +4 -2
- package/dist/core/dependency-tracker.js.map +2 -2
- package/dist/core/element.js +98 -27
- package/dist/core/element.js.map +2 -2
- package/dist/core/golden-copy-cache.js +290 -0
- package/dist/core/golden-copy-cache.js.map +7 -0
- package/dist/core/graph-model.js +266 -0
- package/dist/core/graph-model.js.map +7 -0
- package/dist/core/layer.js +159 -16
- package/dist/core/layer.js.map +2 -2
- package/dist/core/layers.js +32 -0
- package/dist/core/layers.js.map +7 -0
- package/dist/core/manifest.js +32 -11
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +56 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +424 -217
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +179 -0
- package/dist/core/mutation-handler.js.map +7 -0
- package/dist/core/reference-registry.js +29 -21
- package/dist/core/reference-registry.js.map +2 -2
- package/dist/core/relationship-catalog.js +189 -0
- package/dist/core/relationship-catalog.js.map +7 -0
- package/dist/core/relationship-registry.js.map +2 -2
- package/dist/core/relationships.js.map +1 -1
- package/dist/core/report-data-model.js +285 -0
- package/dist/core/report-data-model.js.map +7 -0
- package/dist/core/spec-data-service.js +185 -0
- package/dist/core/spec-data-service.js.map +7 -0
- package/dist/core/spec-loader-types.js +1 -0
- package/dist/core/spec-loader-types.js.map +7 -0
- package/dist/core/spec-loader.js +234 -0
- package/dist/core/spec-loader.js.map +7 -0
- package/dist/core/staged-changeset-storage.js +282 -0
- package/dist/core/staged-changeset-storage.js.map +7 -0
- package/dist/core/staging-area.js +1002 -0
- package/dist/core/staging-area.js.map +7 -0
- package/dist/core/stats-collector.js +169 -0
- package/dist/core/stats-collector.js.map +7 -0
- package/dist/core/virtual-projection.js +443 -0
- package/dist/core/virtual-projection.js.map +7 -0
- package/dist/export/archimate-exporter.js +99 -60
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/export/audit-formatters.js +479 -0
- package/dist/export/audit-formatters.js.map +7 -0
- package/dist/export/diff-formatters.js +99 -0
- package/dist/export/diff-formatters.js.map +7 -0
- package/dist/export/export-manager.js.map +2 -2
- package/dist/export/graphml-exporter.js +44 -27
- package/dist/export/graphml-exporter.js.map +2 -2
- package/dist/export/index.js +4 -0
- package/dist/export/index.js.map +2 -2
- package/dist/export/json-schema-exporter.js +55 -42
- package/dist/export/json-schema-exporter.js.map +2 -2
- package/dist/export/markdown-exporter.js +92 -70
- package/dist/export/markdown-exporter.js.map +2 -2
- package/dist/export/markdown-generator.js +248 -0
- package/dist/export/markdown-generator.js.map +7 -0
- package/dist/export/markdown-utils.js +30 -0
- package/dist/export/markdown-utils.js.map +7 -0
- package/dist/export/mermaid-markdown-exporter.js +37 -0
- package/dist/export/mermaid-markdown-exporter.js.map +7 -0
- package/dist/export/openapi-exporter.js +119 -71
- package/dist/export/openapi-exporter.js.map +2 -2
- package/dist/export/plantuml-exporter.js +104 -39
- package/dist/export/plantuml-exporter.js.map +2 -2
- package/dist/export/report-formatters.js +111 -0
- package/dist/export/report-formatters.js.map +7 -0
- package/dist/export/stats-formatters.js +98 -0
- package/dist/export/stats-formatters.js.map +7 -0
- package/dist/export/types.js +2 -14
- package/dist/export/types.js.map +2 -2
- package/dist/export/verify-formatters.js +153 -0
- package/dist/export/verify-formatters.js.map +7 -0
- package/dist/generated/compiled-validators.js +17 -0
- package/dist/generated/compiled-validators.js.map +7 -0
- package/dist/generated/index.js +31 -0
- package/dist/generated/index.js.map +7 -0
- package/dist/generated/layer-registry.js +190 -0
- package/dist/generated/layer-registry.js.map +7 -0
- package/dist/generated/layer-types.js +7 -0
- package/dist/generated/layer-types.js.map +7 -0
- package/dist/generated/node-types.js +1719 -0
- package/dist/generated/node-types.js.map +7 -0
- package/dist/generated/relationship-index.js +14552 -0
- package/dist/generated/relationship-index.js.map +7 -0
- package/dist/import/archimate-importer.js +166 -0
- package/dist/import/archimate-importer.js.map +7 -0
- package/dist/import/index.js +47 -0
- package/dist/import/index.js.map +7 -0
- package/dist/import/openapi-importer.js +143 -0
- package/dist/import/openapi-importer.js.map +7 -0
- package/dist/import/types.js +1 -0
- package/dist/import/types.js.map +7 -0
- package/dist/integrations/base-manager.js +311 -0
- package/dist/integrations/base-manager.js.map +7 -0
- package/dist/integrations/claude-manager.js +360 -0
- package/dist/integrations/claude-manager.js.map +7 -0
- package/dist/integrations/claude_code/agents/dr-advisor.md +253 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +278 -0
- package/dist/integrations/claude_code/agents/dr-extractor.md +286 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +657 -0
- package/dist/integrations/claude_code/commands/dr-design.md +373 -0
- package/dist/integrations/claude_code/commands/dr-info.md +132 -0
- package/dist/integrations/claude_code/commands/dr-init.md +251 -0
- package/dist/integrations/claude_code/commands/dr-map.md +936 -0
- package/dist/integrations/claude_code/commands/dr-model.md +493 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +814 -0
- package/dist/integrations/claude_code/commands/dr-sync.md +279 -0
- package/dist/integrations/claude_code/commands/dr-validate.md +654 -0
- package/dist/integrations/claude_code/commands/dr-verify.md +559 -0
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +903 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +271 -0
- package/dist/integrations/claude_code/docs/README.md +347 -0
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +550 -0
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +620 -0
- package/dist/integrations/claude_code/skills/dr_03_security_layer/SKILL.md +642 -0
- package/dist/integrations/claude_code/skills/dr_04_application_layer/SKILL.md +911 -0
- package/dist/integrations/claude_code/skills/dr_05_technology_layer/SKILL.md +836 -0
- package/dist/integrations/claude_code/skills/dr_06_api_layer/SKILL.md +933 -0
- package/dist/integrations/claude_code/skills/dr_07_data_model_layer/SKILL.md +210 -0
- package/dist/integrations/claude_code/skills/dr_08_dataset_layer/SKILL.md +319 -0
- package/dist/integrations/claude_code/skills/dr_09_ux_layer/SKILL.md +319 -0
- package/dist/integrations/claude_code/skills/dr_10_navigation_layer/SKILL.md +318 -0
- package/dist/integrations/claude_code/skills/dr_11_apm_layer/SKILL.md +273 -0
- package/dist/integrations/claude_code/skills/dr_12_testing_layer/SKILL.md +273 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +187 -0
- package/dist/integrations/claude_code/skills/dr_codebase_memory/SKILL.md +590 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +295 -0
- package/dist/integrations/copilot-manager.js +328 -0
- package/dist/integrations/copilot-manager.js.map +7 -0
- package/dist/integrations/github_copilot/agents/dr-advisor.md +243 -0
- package/dist/integrations/github_copilot/agents/dr-architect.md +1567 -0
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +491 -0
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +539 -0
- package/dist/integrations/github_copilot/skills/dr_03_security_layer/SKILL.md +667 -0
- package/dist/integrations/github_copilot/skills/dr_04_application_layer/SKILL.md +670 -0
- package/dist/integrations/github_copilot/skills/dr_05_technology_layer/SKILL.md +660 -0
- package/dist/integrations/github_copilot/skills/dr_06_api_layer/SKILL.md +855 -0
- package/dist/integrations/github_copilot/skills/dr_07_data_model_layer/SKILL.md +171 -0
- package/dist/integrations/github_copilot/skills/dr_08_dataset_layer/SKILL.md +216 -0
- package/dist/integrations/github_copilot/skills/dr_09_ux_layer/SKILL.md +230 -0
- package/dist/integrations/github_copilot/skills/dr_10_navigation_layer/SKILL.md +228 -0
- package/dist/integrations/github_copilot/skills/dr_11_apm_layer/SKILL.md +234 -0
- package/dist/integrations/github_copilot/skills/dr_12_testing_layer/SKILL.md +230 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +187 -0
- package/dist/integrations/hash-utils.js +51 -0
- package/dist/integrations/hash-utils.js.map +7 -0
- package/dist/integrations/types.js +16 -0
- package/dist/integrations/types.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +278 -0
- package/dist/reports/model-layer-report-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +57 -0
- package/dist/reports/model-report-data.js.map +7 -0
- package/dist/reports/model-report-orchestrator.js +103 -0
- package/dist/reports/model-report-orchestrator.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +595 -0
- package/dist/schemas/bundled/analyzers/manifest.json +9 -0
- package/dist/schemas/bundled/api.json +3411 -0
- package/dist/schemas/bundled/apm.json +2371 -0
- package/dist/schemas/bundled/application.json +1253 -0
- package/dist/schemas/bundled/base.json +1622 -0
- package/dist/schemas/bundled/business.json +1279 -0
- package/dist/schemas/bundled/data-model.json +1298 -0
- package/dist/schemas/bundled/data-store.json +2025 -0
- package/dist/schemas/bundled/manifest.json +93 -0
- package/dist/schemas/bundled/motivation.json +1360 -0
- package/dist/schemas/bundled/navigation.json +1874 -0
- package/dist/schemas/bundled/security.json +3488 -0
- package/dist/schemas/bundled/technology.json +2067 -0
- package/dist/schemas/bundled/testing.json +3120 -0
- package/dist/schemas/bundled/ux.json +3592 -0
- package/dist/server/schemas.js +202 -0
- package/dist/server/schemas.js.map +7 -0
- package/dist/server/server.js +1336 -252
- package/dist/server/server.js.map +3 -3
- package/dist/server/telemetry-middleware.js +22 -15
- package/dist/server/telemetry-middleware.js.map +2 -2
- package/dist/server-entry.js +65 -0
- package/dist/server-entry.js.map +7 -0
- package/dist/telemetry/config.js +89 -0
- package/dist/telemetry/config.js.map +7 -0
- package/dist/telemetry/console-interceptor.js.map +1 -1
- package/dist/telemetry/detect.js +24 -0
- package/dist/telemetry/detect.js.map +7 -0
- package/dist/telemetry/fetch-otlp-exporter.js +112 -0
- package/dist/telemetry/fetch-otlp-exporter.js.map +7 -0
- package/dist/telemetry/index.js +153 -3
- package/dist/telemetry/index.js.map +2 -2
- package/dist/telemetry/resilient-exporter.js +22 -8
- package/dist/telemetry/resilient-exporter.js.map +2 -2
- package/dist/telemetry/resilient-log-exporter.js +17 -8
- package/dist/telemetry/resilient-log-exporter.js.map +2 -2
- package/dist/telemetry/test-instrumentation.js.map +2 -2
- package/dist/types/index.js +84 -0
- package/dist/types/index.js.map +3 -3
- package/dist/types/source-reference.js +1 -0
- package/dist/types/source-reference.js.map +7 -0
- package/dist/utils/chat-logger.js +292 -0
- package/dist/utils/chat-logger.js.map +7 -0
- package/dist/utils/claude-stream.js +8 -0
- package/dist/utils/claude-stream.js.map +7 -0
- package/dist/utils/date-utils.js +7 -0
- package/dist/utils/date-utils.js.map +7 -0
- package/dist/utils/element-utils.js.map +1 -1
- package/dist/utils/errors.js +88 -15
- package/dist/utils/errors.js.map +3 -3
- package/dist/utils/export-python-annotations.js +82 -0
- package/dist/utils/export-python-annotations.js.map +7 -0
- package/dist/utils/file-io.js +7 -2
- package/dist/utils/file-io.js.map +2 -2
- package/dist/utils/file-lock.js +156 -0
- package/dist/utils/file-lock.js.map +7 -0
- package/dist/utils/id-generator.js +18 -0
- package/dist/utils/id-generator.js.map +7 -0
- package/dist/utils/layer-name-formatter.js +16 -0
- package/dist/utils/layer-name-formatter.js.map +7 -0
- package/dist/utils/markdown-anchor.js +7 -0
- package/dist/utils/markdown-anchor.js.map +7 -0
- package/dist/utils/markdown-link.js +12 -0
- package/dist/utils/markdown-link.js.map +7 -0
- package/dist/utils/markdown-table.js +17 -0
- package/dist/utils/markdown-table.js.map +7 -0
- package/dist/utils/mermaid-utils.js +11 -0
- package/dist/utils/mermaid-utils.js.map +7 -0
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +52 -0
- package/dist/utils/source-reference.js.map +7 -0
- package/dist/utils/spec-installer.js +40 -53
- package/dist/utils/spec-installer.js.map +2 -2
- package/dist/utils/spec-version.js +5 -1
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/utils/string-utils.js +12 -0
- package/dist/utils/string-utils.js.map +7 -0
- package/dist/utils/table-formatting.js +19 -0
- package/dist/utils/table-formatting.js.map +7 -0
- package/dist/validators/index.js +4 -0
- package/dist/validators/index.js.map +2 -2
- package/dist/validators/naming-validator.js +20 -23
- package/dist/validators/naming-validator.js.map +2 -2
- package/dist/validators/reference-validator.js +23 -38
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +243 -0
- package/dist/validators/relationship-schema-validator.js.map +7 -0
- package/dist/validators/schema-validator.js +168 -68
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/semantic-validator.js +36 -60
- package/dist/validators/semantic-validator.js.map +2 -2
- package/dist/validators/types.js.map +2 -2
- package/dist/validators/validation-formatter.js +159 -0
- package/dist/validators/validation-formatter.js.map +7 -0
- package/dist/validators/validator.js +24 -38
- package/dist/validators/validator.js.map +2 -2
- package/dist/viewer/README.md +139 -0
- package/dist/viewer/assets/main-CY0Xufuj.css +1 -0
- package/dist/viewer/assets/main-DtgErnwF.js +81 -0
- package/dist/viewer/assets/vendor-CxKvDp2x.js +17 -0
- package/dist/viewer/favicon.svg +11 -0
- package/dist/viewer/fonts/inter/Inter-Black.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Bold.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-ExtraBold.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Light.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Medium.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-Regular.woff2 +0 -0
- package/dist/viewer/fonts/inter/Inter-SemiBold.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
- package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
- package/dist/viewer/index.html +15 -0
- package/dist/viewer/manifest.json +99 -0
- package/dist/viewer/version.json +1 -0
- package/package.json +37 -13
- package/PYTHON_CLI_PARITY.md +0 -265
- package/PYTHON_CLI_STRUCTURE_UPDATE.md +0 -246
- package/biome.json +0 -42
- package/bunfig.toml +0 -3
- package/dist/ai/claude-client.js +0 -71
- package/dist/ai/claude-client.js.map +0 -7
- package/dist/ai/context-provider.js +0 -129
- package/dist/ai/context-provider.js.map +0 -7
- package/dist/ai/tools.js +0 -231
- package/dist/ai/tools.js.map +0 -7
- package/dist/commands/migrate.js +0 -36
- package/dist/commands/migrate.js.map +0 -7
- package/dist/commands/project.js +0 -74
- package/dist/commands/project.js.map +0 -7
- package/dist/core/active-changeset.js +0 -52
- package/dist/core/active-changeset.js.map +0 -7
- package/dist/core/projection-engine.js +0 -288
- package/dist/core/projection-engine.js.map +0 -7
- package/dist/schemas/bundled/01-motivation-layer.schema.json +0 -942
- package/dist/schemas/bundled/02-business-layer.schema.json +0 -1293
- package/dist/schemas/bundled/03-security-layer.schema.json +0 -2177
- package/dist/schemas/bundled/04-application-layer.schema.json +0 -1328
- package/dist/schemas/bundled/05-technology-layer.schema.json +0 -1465
- package/dist/schemas/bundled/06-api-layer.schema.json +0 -1743
- package/dist/schemas/bundled/07-data-model-layer.schema.json +0 -1253
- package/dist/schemas/bundled/08-datastore-layer.schema.json +0 -892
- package/dist/schemas/bundled/09-ux-layer.schema.json +0 -1506
- package/dist/schemas/bundled/10-navigation-layer.schema.json +0 -843
- package/dist/schemas/bundled/11-apm-observability-layer.schema.json +0 -1430
- package/dist/schemas/bundled/12-testing-layer.schema.json +0 -1091
- package/dist/schemas/bundled/common/layer-extensions.schema.json +0 -266
- package/dist/schemas/bundled/common/predicates.schema.json +0 -493
- package/dist/schemas/bundled/common/relationships.schema.json +0 -791
- package/dist/schemas/bundled/common/source-references.schema.json +0 -71
- package/dist/schemas/bundled/link-registry.json +0 -1839
- package/dist/schemas/bundled/link-registry.schema.json +0 -339
- package/dist/schemas/bundled/relationship-catalog.json +0 -1302
- package/dist/schemas/bundled/relationship-type.schema.json +0 -221
- package/dist/utils/model-path.js +0 -22
- package/dist/utils/model-path.js.map +0 -7
- package/docs/TEST-INSTRUMENTATION.md +0 -405
- package/docs/telemetry.md +0 -427
- package/test-debug.js +0 -33
- package/test-special.mjs +0 -46
- package/test_runner.ts +0 -7
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
async function findAnnotationsDirectory(modelRoot) {
|
|
6
|
+
const drPath = path.join(modelRoot, ".dr", "annotations");
|
|
7
|
+
try {
|
|
8
|
+
return await fs.access(drPath), drPath;
|
|
9
|
+
} catch {
|
|
10
|
+
const drAltPath = path.join(modelRoot, "documentation-robotics", "annotations");
|
|
11
|
+
try {
|
|
12
|
+
return await fs.access(drAltPath), drAltPath;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function loadAnnotations(annotationsDir) {
|
|
19
|
+
const threads = [];
|
|
20
|
+
try {
|
|
21
|
+
const files = await fs.readdir(annotationsDir);
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
if (!file.endsWith(".json"))
|
|
24
|
+
continue;
|
|
25
|
+
const filePath = path.join(annotationsDir, file), content = await fs.readFile(filePath, "utf-8"), data = JSON.parse(content), elementId = file.replace(".json", "");
|
|
26
|
+
threads.push({
|
|
27
|
+
elementId,
|
|
28
|
+
annotations: Array.isArray(data) ? data : [data]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Error loading annotations:", error);
|
|
33
|
+
}
|
|
34
|
+
return threads;
|
|
35
|
+
}
|
|
36
|
+
function exportToJSON(threads) {
|
|
37
|
+
return JSON.stringify(threads, null, 2);
|
|
38
|
+
}
|
|
39
|
+
function exportToMarkdown(threads) {
|
|
40
|
+
let markdown = `# Annotation Export
|
|
41
|
+
|
|
42
|
+
`;
|
|
43
|
+
markdown += `**Exported:** ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
44
|
+
|
|
45
|
+
`, markdown += `**Total Threads:** ${threads.length}
|
|
46
|
+
|
|
47
|
+
`, markdown += `---
|
|
48
|
+
|
|
49
|
+
`;
|
|
50
|
+
for (const thread of threads) {
|
|
51
|
+
markdown += `## Element: ${thread.elementId}
|
|
52
|
+
|
|
53
|
+
`;
|
|
54
|
+
for (const annotation of thread.annotations)
|
|
55
|
+
markdown += `### Annotation by ${annotation.author}
|
|
56
|
+
|
|
57
|
+
`, markdown += `**Date:** ${annotation.timestamp}
|
|
58
|
+
|
|
59
|
+
`, annotation.threadId && (markdown += `**Thread:** ${annotation.threadId}
|
|
60
|
+
|
|
61
|
+
`), markdown += `${annotation.content}
|
|
62
|
+
|
|
63
|
+
`, markdown += `---
|
|
64
|
+
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
return markdown;
|
|
68
|
+
}
|
|
69
|
+
async function main() {
|
|
70
|
+
const program = new Command();
|
|
71
|
+
program.name("export-python-annotations").description("Export annotations from Python CLI format").option("--model <path>", "Path to model root", process.cwd()).option("--output <file>", "Output file (required)").option("--format <format>", "Output format: json, markdown", "json").parse();
|
|
72
|
+
const options = program.opts();
|
|
73
|
+
options.output || (console.error("Error: --output is required"), process.exit(1)), console.log(`Searching for annotations in ${options.model}...`);
|
|
74
|
+
const annotationsDir = await findAnnotationsDirectory(options.model);
|
|
75
|
+
annotationsDir || (console.log("No annotations directory found."), console.log("This is normal if you never used the Python CLI annotation feature."), console.log(""), console.log("Annotation directories checked:"), console.log(" - .dr/annotations/"), console.log(" - documentation-robotics/annotations/"), process.exit(0)), console.log(`Found annotations directory: ${annotationsDir}`);
|
|
76
|
+
const threads = await loadAnnotations(annotationsDir);
|
|
77
|
+
threads.length === 0 && (console.log("No annotations found in directory."), process.exit(0)), console.log(`Loaded ${threads.length} annotation threads`);
|
|
78
|
+
let output;
|
|
79
|
+
options.format === "markdown" ? output = exportToMarkdown(threads) : output = exportToJSON(threads), await fs.writeFile(options.output, output, "utf-8"), console.log(`\u2713 Annotations exported to ${options.output}`), console.log(""), console.log("Next steps:"), console.log("1. Review the exported annotations"), console.log("2. Add important annotations to element descriptions:"), console.log(' dr update <element-id> --description "Annotation content"'), console.log("3. Consider using external tools for collaborative annotations:"), console.log(" - GitHub Issues"), console.log(" - Notion/Confluence"), console.log(" - Linear/Jira");
|
|
80
|
+
}
|
|
81
|
+
main().catch(console.error);
|
|
82
|
+
//# sourceMappingURL=export-python-annotations.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/export-python-annotations.ts"],
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n/**\n * Annotation Export Utility\n *\n * Exports annotations from Python CLI format to help users migrate.\n * Python CLI stores annotations in `.dr/annotations/` directory.\n * This tool exports them to JSON or Markdown for manual migration.\n *\n * Usage:\n * bun run src/utils/export-python-annotations.ts --output annotations.json\n * bun run src/utils/export-python-annotations.ts --format markdown --output annotations.md\n */\n\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport { Command } from \"commander\";\n\ninterface Annotation {\n id: string;\n elementId: string;\n author: string;\n timestamp: string;\n content: string;\n threadId?: string;\n parentId?: string;\n}\n\ninterface AnnotationThread {\n elementId: string;\n annotations: Annotation[];\n}\n\nasync function findAnnotationsDirectory(modelRoot: string): Promise<string | null> {\n // Try .dr/annotations/ first\n const drPath = path.join(modelRoot, \".dr\", \"annotations\");\n try {\n await fs.access(drPath);\n return drPath;\n } catch {\n // Try documentation-robotics/annotations/\n const drAltPath = path.join(modelRoot, \"documentation-robotics\", \"annotations\");\n try {\n await fs.access(drAltPath);\n return drAltPath;\n } catch {\n return null;\n }\n }\n}\n\nasync function loadAnnotations(annotationsDir: string): Promise<AnnotationThread[]> {\n const threads: AnnotationThread[] = [];\n\n try {\n const files = await fs.readdir(annotationsDir);\n\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n\n const filePath = path.join(annotationsDir, file);\n const content = await fs.readFile(filePath, \"utf-8\");\n const data = JSON.parse(content);\n\n // Extract element ID from filename (format: element-id.json)\n const elementId = file.replace(\".json\", \"\");\n\n threads.push({\n elementId,\n annotations: Array.isArray(data) ? data : [data],\n });\n }\n } catch (error) {\n console.error(\"Error loading annotations:\", error);\n }\n\n return threads;\n}\n\nfunction exportToJSON(threads: AnnotationThread[]): string {\n return JSON.stringify(threads, null, 2);\n}\n\nfunction exportToMarkdown(threads: AnnotationThread[]): string {\n let markdown = \"# Annotation Export\\n\\n\";\n markdown += `**Exported:** ${new Date().toISOString()}\\n\\n`;\n markdown += `**Total Threads:** ${threads.length}\\n\\n`;\n markdown += \"---\\n\\n\";\n\n for (const thread of threads) {\n markdown += `## Element: ${thread.elementId}\\n\\n`;\n\n for (const annotation of thread.annotations) {\n markdown += `### Annotation by ${annotation.author}\\n\\n`;\n markdown += `**Date:** ${annotation.timestamp}\\n\\n`;\n if (annotation.threadId) {\n markdown += `**Thread:** ${annotation.threadId}\\n\\n`;\n }\n markdown += `${annotation.content}\\n\\n`;\n markdown += \"---\\n\\n\";\n }\n }\n\n return markdown;\n}\n\nasync function main() {\n const program = new Command();\n\n program\n .name(\"export-python-annotations\")\n .description(\"Export annotations from Python CLI format\")\n .option(\"--model <path>\", \"Path to model root\", process.cwd())\n .option(\"--output <file>\", \"Output file (required)\")\n .option(\"--format <format>\", \"Output format: json, markdown\", \"json\")\n .parse();\n\n const options = program.opts();\n\n if (!options.output) {\n console.error(\"Error: --output is required\");\n process.exit(1);\n }\n\n console.log(`Searching for annotations in ${options.model}...`);\n\n const annotationsDir = await findAnnotationsDirectory(options.model);\n\n if (!annotationsDir) {\n console.log(\"No annotations directory found.\");\n console.log(\"This is normal if you never used the Python CLI annotation feature.\");\n console.log(\"\");\n console.log(\"Annotation directories checked:\");\n console.log(\" - .dr/annotations/\");\n console.log(\" - documentation-robotics/annotations/\");\n process.exit(0);\n }\n\n console.log(`Found annotations directory: ${annotationsDir}`);\n\n const threads = await loadAnnotations(annotationsDir);\n\n if (threads.length === 0) {\n console.log(\"No annotations found in directory.\");\n process.exit(0);\n }\n\n console.log(`Loaded ${threads.length} annotation threads`);\n\n let output: string;\n\n if (options.format === \"markdown\") {\n output = exportToMarkdown(threads);\n } else {\n output = exportToJSON(threads);\n }\n\n await fs.writeFile(options.output, output, \"utf-8\");\n\n console.log(`\u2713 Annotations exported to ${options.output}`);\n console.log(\"\");\n console.log(\"Next steps:\");\n console.log(\"1. Review the exported annotations\");\n console.log(\"2. Add important annotations to element descriptions:\");\n console.log(' dr update <element-id> --description \"Annotation content\"');\n console.log(\"3. Consider using external tools for collaborative annotations:\");\n console.log(\" - GitHub Issues\");\n console.log(\" - Notion/Confluence\");\n console.log(\" - Linear/Jira\");\n}\n\nmain().catch(console.error);\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/utils/file-io.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, rename, readFile as fsReadFile, writeFile as fsWriteFile, stat } from "node:fs/promises";
|
|
2
2
|
import { existsSync as fsExistsSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
3
4
|
import { startSpan, endSpan } from "../telemetry/index.js";
|
|
4
5
|
const isTelemetryEnabled = !1;
|
|
5
6
|
async function ensureDir(path) {
|
|
@@ -12,6 +13,8 @@ async function atomicWrite(path, content) {
|
|
|
12
13
|
"file.created": !fileExists2
|
|
13
14
|
});
|
|
14
15
|
try {
|
|
16
|
+
const dir = dirname(path);
|
|
17
|
+
await mkdir(dir, { recursive: !0 });
|
|
15
18
|
const tempPath = `${path}.tmp`;
|
|
16
19
|
await fsWriteFile(tempPath, content, "utf-8"), await rename(tempPath, path);
|
|
17
20
|
} finally {
|
|
@@ -44,7 +47,8 @@ async function writeFile(path, content) {
|
|
|
44
47
|
"file.created": !fileExists2
|
|
45
48
|
});
|
|
46
49
|
try {
|
|
47
|
-
|
|
50
|
+
const dir = dirname(path);
|
|
51
|
+
await mkdir(dir, { recursive: !0 }), await fsWriteFile(path, content, "utf-8");
|
|
48
52
|
} finally {
|
|
49
53
|
endSpan(span);
|
|
50
54
|
}
|
|
@@ -55,7 +59,8 @@ async function readJSON(path) {
|
|
|
55
59
|
}
|
|
56
60
|
async function writeJSON(path, data, pretty = !0) {
|
|
57
61
|
const content = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
58
|
-
await
|
|
62
|
+
await atomicWrite(path, content + `
|
|
63
|
+
`);
|
|
59
64
|
}
|
|
60
65
|
export {
|
|
61
66
|
atomicWrite,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/file-io.ts"],
|
|
4
|
-
"sourcesContent": ["import {
|
|
5
|
-
"mappings": "AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n mkdir,\n rename,\n readFile as fsReadFile,\n writeFile as fsWriteFile,\n stat,\n} from \"node:fs/promises\";\nimport { existsSync as fsExistsSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { startSpan, endSpan } from \"../telemetry/index.js\";\n\n// Fallback for runtime environments where TELEMETRY_ENABLED is not defined by esbuild\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\n/**\n * Ensure a directory exists, creating it if necessary\n */\nexport async function ensureDir(path: string): Promise<void> {\n await mkdir(path, { recursive: true });\n}\n\n/**\n * Write content to a file atomically using a temporary file\n */\nexport async function atomicWrite(path: string, content: string): Promise<void> {\n const fileExists = fsExistsSync(path);\n\n const span = startSpan(\"file.write\", {\n \"file.path\": path,\n \"file.size\": content.length,\n \"file.created\": !fileExists,\n });\n\n try {\n // Ensure parent directory exists\n const dir = dirname(path);\n await mkdir(dir, { recursive: true });\n\n const tempPath = `${path}.tmp`;\n await fsWriteFile(tempPath, content, \"utf-8\");\n // Rename temp file to target path (atomic operation)\n await rename(tempPath, path);\n } finally {\n endSpan(span);\n }\n}\n\n/**\n * Check if a file exists\n */\nexport async function fileExists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Read a file as text\n */\nexport async function readFile(path: string): Promise<string> {\n const span = startSpan(\"file.read\", {\n \"file.path\": path,\n \"file.exists\": fsExistsSync(path),\n });\n\n try {\n const content = await fsReadFile(path, \"utf-8\");\n\n if (isTelemetryEnabled && span) {\n span.setAttribute(\"file.size\", content.length);\n }\n\n return content;\n } finally {\n endSpan(span);\n }\n}\n\n/**\n * Write content to a file\n */\nexport async function writeFile(path: string, content: string): Promise<void> {\n const fileExists = fsExistsSync(path);\n\n const span = startSpan(\"file.write\", {\n \"file.path\": path,\n \"file.size\": content.length,\n \"file.created\": !fileExists,\n });\n\n try {\n // Ensure parent directory exists\n const dir = dirname(path);\n await mkdir(dir, { recursive: true });\n\n await fsWriteFile(path, content, \"utf-8\");\n } finally {\n endSpan(span);\n }\n}\n\n/**\n * Read a JSON file\n */\nexport async function readJSON<T>(path: string): Promise<T> {\n const content = await readFile(path);\n return JSON.parse(content) as T;\n}\n\n/**\n * Write a JSON object to a file\n * Uses atomic write to prevent corruption\n */\nexport async function writeJSON<T>(path: string, data: T, pretty: boolean = true): Promise<void> {\n const content = pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data);\n await atomicWrite(path, content + \"\\n\");\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SACE,OACA,QACA,YAAY,YACZ,aAAa,aACb,YACK;AACP,SAAS,cAAc,oBAAoB;AAC3C,SAAS,eAAe;AACxB,SAAS,WAAW,eAAe;AAInC,MAAM,qBAAgE;AAKtE,eAAsB,UAAU,MAAY;AAC1C,QAAM,MAAM,MAAM,EAAE,WAAW,GAAI,CAAE;AACvC;AAKA,eAAsB,YAAY,MAAc,SAAe;AAC7D,QAAMA,cAAa,aAAa,IAAI,GAE9B,OAAO,UAAU,cAAc;IACnC,aAAa;IACb,aAAa,QAAQ;IACrB,gBAAgB,CAACA;GAClB;AAED,MAAI;AAEF,UAAM,MAAM,QAAQ,IAAI;AACxB,UAAM,MAAM,KAAK,EAAE,WAAW,GAAI,CAAE;AAEpC,UAAM,WAAW,GAAG,IAAI;AACxB,UAAM,YAAY,UAAU,SAAS,OAAO,GAE5C,MAAM,OAAO,UAAU,IAAI;EAC7B;AACE,YAAQ,IAAI;EACd;AACF;AAKA,eAAsB,WAAW,MAAY;AAC3C,MAAI;AACF,iBAAM,KAAK,IAAI,GACR;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAKA,eAAsB,SAAS,MAAY;AACzC,QAAM,OAAO,UAAU,aAAa;IAClC,aAAa;IACb,eAAe,aAAa,IAAI;GACjC;AAED,MAAI;AACF,UAAM,UAAU,MAAM,WAAW,MAAM,OAAO;AAE9C,WAAI,sBAAsB,QACxB,KAAK,aAAa,aAAa,QAAQ,MAAM,GAGxC;EACT;AACE,YAAQ,IAAI;EACd;AACF;AAKA,eAAsB,UAAU,MAAc,SAAe;AAC3D,QAAMA,cAAa,aAAa,IAAI,GAE9B,OAAO,UAAU,cAAc;IACnC,aAAa;IACb,aAAa,QAAQ;IACrB,gBAAgB,CAACA;GAClB;AAED,MAAI;AAEF,UAAM,MAAM,QAAQ,IAAI;AACxB,UAAM,MAAM,KAAK,EAAE,WAAW,GAAI,CAAE,GAEpC,MAAM,YAAY,MAAM,SAAS,OAAO;EAC1C;AACE,YAAQ,IAAI;EACd;AACF;AAKA,eAAsB,SAAY,MAAY;AAC5C,QAAM,UAAU,MAAM,SAAS,IAAI;AACnC,SAAO,KAAK,MAAM,OAAO;AAC3B;AAMA,eAAsB,UAAa,MAAc,MAAS,SAAkB,IAAI;AAC9E,QAAM,UAAU,SAAS,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,KAAK,UAAU,IAAI;AAC5E,QAAM,YAAY,MAAM,UAAU;CAAI;AACxC;",
|
|
6
6
|
"names": ["fileExists"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { mkdir, rm, stat } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
const DEFAULT_STALE_LOCK_THRESHOLD = 3e4;
|
|
4
|
+
class FileLock {
|
|
5
|
+
lockPath;
|
|
6
|
+
acquired = !1;
|
|
7
|
+
constructor(resourcePath) {
|
|
8
|
+
this.lockPath = `${resourcePath}.lock`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Acquire exclusive lock with timeout and retry
|
|
12
|
+
*
|
|
13
|
+
* Uses atomic directory creation (mkdir with recursive: false) to ensure
|
|
14
|
+
* only one process can acquire the lock at a time.
|
|
15
|
+
*
|
|
16
|
+
* Before attempting lock acquisition, automatically checks for stale locks
|
|
17
|
+
* (locks older than the configured threshold) and removes them if detected.
|
|
18
|
+
* This prevents the CLI from becoming unusable after process crashes.
|
|
19
|
+
*/
|
|
20
|
+
async acquire(options = {}) {
|
|
21
|
+
const timeout = options.timeout || 5e3, retryInterval = options.retryInterval || 50, staleLockThreshold = options.staleLockThreshold || DEFAULT_STALE_LOCK_THRESHOLD;
|
|
22
|
+
if (options.detectStaleLocks !== !1)
|
|
23
|
+
try {
|
|
24
|
+
await FileLock.cleanupStaleLocks(this.lockPath.replace(/\.lock$/, ""), staleLockThreshold);
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
for (; ; )
|
|
29
|
+
try {
|
|
30
|
+
await mkdir(this.lockPath, { recursive: !1 }), this.acquired = !0;
|
|
31
|
+
return;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error.code === "EEXIST") {
|
|
34
|
+
if (Date.now() - startTime >= timeout)
|
|
35
|
+
throw new Error(`Failed to acquire lock on ${this.lockPath} within ${timeout}ms. Resource may be locked by another process or operation.`);
|
|
36
|
+
await this.sleep(retryInterval);
|
|
37
|
+
} else
|
|
38
|
+
throw new Error(`Failed to acquire lock on ${this.lockPath}: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Release the lock
|
|
43
|
+
*
|
|
44
|
+
* Removes the lock directory. Should always be called in a finally block.
|
|
45
|
+
* Throws errors on filesystem failures to propagate critical issues.
|
|
46
|
+
*
|
|
47
|
+
* @throws {Error} If lock removal fails (permission denied, disk full, etc)
|
|
48
|
+
*/
|
|
49
|
+
async release() {
|
|
50
|
+
if (this.acquired)
|
|
51
|
+
try {
|
|
52
|
+
await rm(this.lockPath, { recursive: !0, force: !0 }), this.acquired = !1;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const errorMsg = `Failed to release lock on ${this.lockPath} (acquired: ${this.acquired}): ${error.message}`;
|
|
55
|
+
throw console.error(`ERROR: ${errorMsg}`), new Error(errorMsg);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check if lock is currently held by this instance
|
|
60
|
+
*/
|
|
61
|
+
isAcquired() {
|
|
62
|
+
return this.acquired;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Execute function with lock protection
|
|
66
|
+
*
|
|
67
|
+
* Acquires lock, executes function, and releases lock in finally block.
|
|
68
|
+
* Ensures lock is always released even if function throws.
|
|
69
|
+
*
|
|
70
|
+
* Propagates both function errors and lock release errors to the caller.
|
|
71
|
+
* If the function throws and lock release also fails, the lock release error
|
|
72
|
+
* is thrown (with original error logged).
|
|
73
|
+
*
|
|
74
|
+
* @throws {Error} If function throws or if lock release fails
|
|
75
|
+
*/
|
|
76
|
+
async withLock(fn, options) {
|
|
77
|
+
await this.acquire(options);
|
|
78
|
+
let fnError;
|
|
79
|
+
try {
|
|
80
|
+
return await fn();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
throw fnError = error, error;
|
|
83
|
+
} finally {
|
|
84
|
+
try {
|
|
85
|
+
await this.release();
|
|
86
|
+
} catch (releaseError) {
|
|
87
|
+
throw fnError && console.error(`Function error suppressed due to lock release failure. Function error: ${fnError instanceof Error ? fnError.message : String(fnError)}`), releaseError;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a lock file exists (static utility)
|
|
93
|
+
*/
|
|
94
|
+
static exists(resourcePath) {
|
|
95
|
+
return existsSync(`${resourcePath}.lock`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if a lock is stale based on age
|
|
99
|
+
*
|
|
100
|
+
* A lock is considered stale if its modification time exceeds the threshold.
|
|
101
|
+
* This helps detect locks from crashed processes.
|
|
102
|
+
*
|
|
103
|
+
* @param resourcePath - Path to the locked resource
|
|
104
|
+
* @param threshold - Max age in ms before lock is considered stale (default: 30s)
|
|
105
|
+
* @returns true if lock exists and is stale, false otherwise
|
|
106
|
+
*/
|
|
107
|
+
static async isLockStale(resourcePath, threshold = DEFAULT_STALE_LOCK_THRESHOLD) {
|
|
108
|
+
const lockPath = `${resourcePath}.lock`;
|
|
109
|
+
try {
|
|
110
|
+
if (!existsSync(lockPath))
|
|
111
|
+
return !1;
|
|
112
|
+
const stats = await stat(lockPath);
|
|
113
|
+
return Date.now() - stats.mtime.getTime() > threshold;
|
|
114
|
+
} catch {
|
|
115
|
+
return !1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Cleanup stale locks automatically
|
|
120
|
+
*
|
|
121
|
+
* Removes lock directories that are older than the specified threshold.
|
|
122
|
+
* Logs the cleanup action for diagnostics. Safe to call repeatedly.
|
|
123
|
+
*
|
|
124
|
+
* @param resourcePath - Path to the locked resource
|
|
125
|
+
* @param threshold - Max age in ms before lock is considered stale (default: 30s)
|
|
126
|
+
*/
|
|
127
|
+
static async cleanupStaleLocks(resourcePath, threshold = DEFAULT_STALE_LOCK_THRESHOLD) {
|
|
128
|
+
if (await this.isLockStale(resourcePath, threshold)) {
|
|
129
|
+
const lockPath = `${resourcePath}.lock`;
|
|
130
|
+
try {
|
|
131
|
+
const stats = await stat(lockPath), lockAge = Date.now() - stats.mtime.getTime();
|
|
132
|
+
await rm(lockPath, { recursive: !0, force: !0 }), console.log(`Cleaned up stale lock at ${lockPath} (age: ${lockAge}ms, threshold: ${threshold}ms)`);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Force remove a stale lock (static utility)
|
|
139
|
+
*
|
|
140
|
+
* Use with caution - only for cleaning up locks from crashed processes.
|
|
141
|
+
*/
|
|
142
|
+
static async forceRemove(resourcePath) {
|
|
143
|
+
const lockPath = `${resourcePath}.lock`;
|
|
144
|
+
await rm(lockPath, { recursive: !0, force: !0 });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Sleep utility for retry delays
|
|
148
|
+
*/
|
|
149
|
+
sleep(ms) {
|
|
150
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
FileLock
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/file-lock.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * FileLock - Simple file-based locking mechanism for concurrent operations\n *\n * Uses atomic file creation to implement exclusive locks with timeout and retry.\n * Prevents race conditions in read-modify-write operations on changeset files.\n */\n\nimport { mkdir, rm, stat } from \"fs/promises\";\nimport { existsSync } from \"fs\";\n\nconst DEFAULT_STALE_LOCK_THRESHOLD = 30000; // 30 seconds\n\nexport interface LockOptions {\n timeout?: number; // Max time to wait for lock (ms)\n retryInterval?: number; // Time between retry attempts (ms)\n staleLockThreshold?: number; // Threshold in ms for detecting stale locks (default: 30000ms)\n detectStaleLocks?: boolean; // Whether to automatically detect and cleanup stale locks (default: true)\n}\n\n/**\n * FileLock provides exclusive access to a resource using lockfiles\n */\nexport class FileLock {\n private lockPath: string;\n private acquired: boolean = false;\n\n constructor(resourcePath: string) {\n // Create lock file path by appending .lock\n this.lockPath = `${resourcePath}.lock`;\n }\n\n /**\n * Acquire exclusive lock with timeout and retry\n *\n * Uses atomic directory creation (mkdir with recursive: false) to ensure\n * only one process can acquire the lock at a time.\n *\n * Before attempting lock acquisition, automatically checks for stale locks\n * (locks older than the configured threshold) and removes them if detected.\n * This prevents the CLI from becoming unusable after process crashes.\n */\n async acquire(options: LockOptions = {}): Promise<void> {\n const timeout = options.timeout || 5000; // 5 second default\n const retryInterval = options.retryInterval || 50; // 50ms default\n const staleLockThreshold = options.staleLockThreshold || DEFAULT_STALE_LOCK_THRESHOLD;\n const detectStaleLocks = options.detectStaleLocks !== false; // true by default\n\n // Attempt to clean up stale locks before acquiring\n if (detectStaleLocks) {\n try {\n await FileLock.cleanupStaleLocks(this.lockPath.replace(/\\.lock$/, \"\"), staleLockThreshold);\n } catch {\n // Silently ignore cleanup errors - we'll still attempt to acquire the lock\n }\n }\n\n const startTime = Date.now();\n\n while (true) {\n try {\n // Atomic operation: mkdir fails if directory exists\n await mkdir(this.lockPath, { recursive: false });\n this.acquired = true;\n return;\n } catch (error: any) {\n if (error.code === \"EEXIST\") {\n // Lock is held by another process\n if (Date.now() - startTime >= timeout) {\n throw new Error(\n `Failed to acquire lock on ${this.lockPath} within ${timeout}ms. ` +\n `Resource may be locked by another process or operation.`\n );\n }\n\n // Wait before retrying\n await this.sleep(retryInterval);\n } else {\n // Unexpected error\n throw new Error(`Failed to acquire lock on ${this.lockPath}: ${error.message}`);\n }\n }\n }\n }\n\n /**\n * Release the lock\n *\n * Removes the lock directory. Should always be called in a finally block.\n * Throws errors on filesystem failures to propagate critical issues.\n *\n * @throws {Error} If lock removal fails (permission denied, disk full, etc)\n */\n async release(): Promise<void> {\n if (!this.acquired) {\n return;\n }\n\n try {\n await rm(this.lockPath, { recursive: true, force: true });\n this.acquired = false;\n } catch (error: any) {\n // Construct detailed error message with lock state\n const errorMsg = `Failed to release lock on ${this.lockPath} (acquired: ${this.acquired}): ${error.message}`;\n\n // Log the error for visibility\n console.error(`ERROR: ${errorMsg}`);\n\n // Throw to propagate to callers - they may attempt force removal\n throw new Error(errorMsg);\n }\n }\n\n /**\n * Check if lock is currently held by this instance\n */\n isAcquired(): boolean {\n return this.acquired;\n }\n\n /**\n * Execute function with lock protection\n *\n * Acquires lock, executes function, and releases lock in finally block.\n * Ensures lock is always released even if function throws.\n *\n * Propagates both function errors and lock release errors to the caller.\n * If the function throws and lock release also fails, the lock release error\n * is thrown (with original error logged).\n *\n * @throws {Error} If function throws or if lock release fails\n */\n async withLock<T>(fn: () => Promise<T>, options?: LockOptions): Promise<T> {\n await this.acquire(options);\n let fnError: unknown;\n\n try {\n return await fn();\n } catch (error) {\n // Capture function error to re-throw after attempting lock release\n fnError = error;\n throw error;\n } finally {\n try {\n await this.release();\n } catch (releaseError) {\n // If function already threw, log both errors\n // Release error takes precedence since it indicates filesystem corruption\n if (fnError) {\n console.error(\n `Function error suppressed due to lock release failure. ` +\n `Function error: ${fnError instanceof Error ? fnError.message : String(fnError)}`\n );\n }\n throw releaseError;\n }\n }\n }\n\n /**\n * Check if a lock file exists (static utility)\n */\n static exists(resourcePath: string): boolean {\n return existsSync(`${resourcePath}.lock`);\n }\n\n /**\n * Check if a lock is stale based on age\n *\n * A lock is considered stale if its modification time exceeds the threshold.\n * This helps detect locks from crashed processes.\n *\n * @param resourcePath - Path to the locked resource\n * @param threshold - Max age in ms before lock is considered stale (default: 30s)\n * @returns true if lock exists and is stale, false otherwise\n */\n static async isLockStale(\n resourcePath: string,\n threshold: number = DEFAULT_STALE_LOCK_THRESHOLD\n ): Promise<boolean> {\n const lockPath = `${resourcePath}.lock`;\n\n try {\n if (!existsSync(lockPath)) {\n return false;\n }\n\n const stats = await stat(lockPath);\n const lockAge = Date.now() - stats.mtime.getTime();\n return lockAge > threshold;\n } catch {\n // If we can't determine lock age, treat as valid (not stale)\n // This ensures we don't accidentally remove locks we're unsure about\n return false;\n }\n }\n\n /**\n * Cleanup stale locks automatically\n *\n * Removes lock directories that are older than the specified threshold.\n * Logs the cleanup action for diagnostics. Safe to call repeatedly.\n *\n * @param resourcePath - Path to the locked resource\n * @param threshold - Max age in ms before lock is considered stale (default: 30s)\n */\n static async cleanupStaleLocks(\n resourcePath: string,\n threshold: number = DEFAULT_STALE_LOCK_THRESHOLD\n ): Promise<void> {\n const isStale = await this.isLockStale(resourcePath, threshold);\n\n if (isStale) {\n const lockPath = `${resourcePath}.lock`;\n try {\n const stats = await stat(lockPath);\n const lockAge = Date.now() - stats.mtime.getTime();\n await rm(lockPath, { recursive: true, force: true });\n console.log(\n `Cleaned up stale lock at ${lockPath} (age: ${lockAge}ms, threshold: ${threshold}ms)`\n );\n } catch (error) {\n // Silently ignore cleanup errors - lock may have been removed by another process\n // or other temporary filesystem issues\n }\n }\n }\n\n /**\n * Force remove a stale lock (static utility)\n *\n * Use with caution - only for cleaning up locks from crashed processes.\n */\n static async forceRemove(resourcePath: string): Promise<void> {\n const lockPath = `${resourcePath}.lock`;\n await rm(lockPath, { recursive: true, force: true });\n }\n\n /**\n * Sleep utility for retry delays\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
function toKebabCase(text) {
|
|
3
|
+
let result = text.replace(/[\s_]+/g, "-");
|
|
4
|
+
return result = result.replace(/([a-z])([A-Z])/g, "$1-$2"), result = result.toLowerCase(), result = result.replace(/\./g, ""), result = result.replace(/[^a-z0-9-]/g, ""), result = result.replace(/-+/g, "-"), result = result.replace(/^-+|-+$/g, ""), result;
|
|
5
|
+
}
|
|
6
|
+
function generateElementId(layer, elementType, name) {
|
|
7
|
+
const kebabName = toKebabCase(name);
|
|
8
|
+
return `${layer}.${elementType}.${kebabName}`;
|
|
9
|
+
}
|
|
10
|
+
function generateUUID() {
|
|
11
|
+
return crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
generateElementId,
|
|
15
|
+
generateUUID,
|
|
16
|
+
toKebabCase
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=id-generator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/id-generator.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Element ID generation utilities matching Python CLI implementation.\n */\n\nimport crypto from \"node:crypto\";\n\n/**\n * Convert text to kebab-case.\n * Matches Python implementation in utils/id_generator.py\n *\n * @example\n * toKebabCase(\"Customer Management\") // \"customer-management\"\n * toKebabCase(\"CustomerManagement\") // \"customer-management\"\n * toKebabCase(\"customer_management\") // \"customer-management\"\n */\nexport function toKebabCase(text: string): string {\n // Replace spaces and underscores with hyphens\n let result = text.replace(/[\\s_]+/g, \"-\");\n\n // Insert hyphen before capital letters (for camelCase/PascalCase)\n result = result.replace(/([a-z])([A-Z])/g, \"$1-$2\");\n\n // Convert to lowercase\n result = result.toLowerCase();\n\n // Remove dots (e.g. \"Node.js\" \u2192 \"nodejs\") to prevent extra ID segments\n result = result.replace(/\\./g, \"\");\n\n // Remove any characters not valid in a slug (keeps only a-z, 0-9, hyphens)\n result = result.replace(/[^a-z0-9-]/g, \"\");\n\n // Remove multiple consecutive hyphens\n result = result.replace(/-+/g, \"-\");\n\n // Remove leading/trailing hyphens\n result = result.replace(/^-+|-+$/g, \"\");\n\n return result;\n}\n\n/**\n * Generate element ID following Python CLI convention: {layer}.{type}.{kebab-name}\n * Matches Python implementation in utils/id_generator.py\n *\n * @example\n * generateElementId(\"business\", \"service\", \"Customer Management\")\n * // \"business.service.customer-management\"\n */\nexport function generateElementId(layer: string, elementType: string, name: string): string {\n const kebabName = toKebabCase(name);\n return `${layer}.${elementType}.${kebabName}`;\n}\n\n/**\n * Generate a new UUIDv4 using the Node.js built-in crypto module.\n *\n * @example\n * generateUUID() // \"550e8400-e29b-41d4-a716-446655440000\"\n */\nexport function generateUUID(): string {\n return crypto.randomUUID();\n}\n"],
|
|
5
|
+
"mappings": "AAIA,OAAO,YAAY;AAWb,SAAU,YAAY,MAAY;AAEtC,MAAI,SAAS,KAAK,QAAQ,WAAW,GAAG;AAGxC,kBAAS,OAAO,QAAQ,mBAAmB,OAAO,GAGlD,SAAS,OAAO,YAAW,GAG3B,SAAS,OAAO,QAAQ,OAAO,EAAE,GAGjC,SAAS,OAAO,QAAQ,eAAe,EAAE,GAGzC,SAAS,OAAO,QAAQ,OAAO,GAAG,GAGlC,SAAS,OAAO,QAAQ,YAAY,EAAE,GAE/B;AACT;AAUM,SAAU,kBAAkB,OAAe,aAAqB,MAAY;AAChF,QAAM,YAAY,YAAY,IAAI;AAClC,SAAO,GAAG,KAAK,IAAI,WAAW,IAAI,SAAS;AAC7C;AAQM,SAAU,eAAY;AAC1B,SAAO,OAAO,WAAU;AAC1B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function formatLayerName(layerName) {
|
|
2
|
+
const acronyms = {
|
|
3
|
+
api: "API",
|
|
4
|
+
ux: "UX",
|
|
5
|
+
apm: "APM"
|
|
6
|
+
};
|
|
7
|
+
return acronyms[layerName] ? acronyms[layerName] : layerName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
8
|
+
}
|
|
9
|
+
function formatLayerNameWithSuffix(layerName) {
|
|
10
|
+
return `${formatLayerName(layerName)} layer`;
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
formatLayerName,
|
|
14
|
+
formatLayerNameWithSuffix
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=layer-name-formatter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/layer-name-formatter.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Centralized layer name formatting\n * Ensures consistent layer name display across all CLI output\n */\n\n/**\n * Format layer name for display with title case\n * Examples: \"motivation\" \u2192 \"Motivation\", \"data-model\" \u2192 \"Data Model\"\n * Special cases: \"api\" \u2192 \"API\", \"ux\" \u2192 \"UX\", \"apm\" \u2192 \"APM\"\n */\nexport function formatLayerName(layerName: string): string {\n // Handle special acronym cases\n const acronyms: Record<string, string> = {\n api: \"API\",\n ux: \"UX\",\n apm: \"APM\",\n };\n\n if (acronyms[layerName]) {\n return acronyms[layerName];\n }\n\n return layerName\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/**\n * Format layer name with \" layer\" suffix\n * Examples: \"motivation\" \u2192 \"Motivation layer\", \"data-model\" \u2192 \"Data Model layer\"\n */\nexport function formatLayerNameWithSuffix(layerName: string): string {\n return `${formatLayerName(layerName)} layer`;\n}\n"],
|
|
5
|
+
"mappings": "AAUM,SAAU,gBAAgB,WAAiB;AAE/C,QAAM,WAAmC;IACvC,KAAK;IACL,IAAI;IACJ,KAAK;;AAGP,SAAI,SAAS,SAAS,IACb,SAAS,SAAS,IAGpB,UACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAW,IAAK,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AACb;AAMM,SAAU,0BAA0B,WAAiB;AACzD,SAAO,GAAG,gBAAgB,SAAS,CAAC;AACtC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/markdown-anchor.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Markdown anchor utilities for heading links\n */\n\n/**\n * Create a markdown anchor from text by converting to lowercase and replacing non-alphanumeric characters with hyphens\n * @param text The text to convert to an anchor\n * @returns Anchor string suitable for markdown heading links\n */\nexport function createAnchor(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n"],
|
|
5
|
+
"mappings": "AASM,SAAU,aAAa,MAAY;AACvC,SAAO,KACJ,YAAW,EACX,QAAQ,eAAe,GAAG,EAC1B,QAAQ,UAAU,EAAE;AACzB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function formatNodeTypeName(type) {
|
|
2
|
+
return type.split(/(?=[A-Z])/).join(" ").replace(/^./, (c) => c.toUpperCase());
|
|
3
|
+
}
|
|
4
|
+
function createDescriptiveLinkText(type) {
|
|
5
|
+
const prohibited = ["click here", "here", "link", "more"], formatted = formatNodeTypeName(type);
|
|
6
|
+
return prohibited.includes(formatted.toLowerCase()) ? `${formatted} node` : formatted;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
createDescriptiveLinkText,
|
|
10
|
+
formatNodeTypeName
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=markdown-link.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/markdown-link.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Markdown link text utilities\n */\n\n/**\n * Format node type name for display by splitting on capital letters and title-casing\n * @param type The node type string (e.g., \"CustomEndpoint\")\n * @returns Formatted display name (e.g., \"Custom Endpoint\")\n */\nexport function formatNodeTypeName(type: string): string {\n return type\n .split(/(?=[A-Z])/)\n .join(\" \")\n .replace(/^./, (c) => c.toUpperCase());\n}\n\n/**\n * Create descriptive link text that avoids markdownlint MD059 prohibited texts\n * Prohibited texts: \"click here\", \"here\", \"link\", \"more\"\n * @param type The node type to create link text for\n * @returns Descriptive link text safe for markdownlint MD059\n */\nexport function createDescriptiveLinkText(type: string): string {\n const prohibited = [\"click here\", \"here\", \"link\", \"more\"];\n const formatted = formatNodeTypeName(type);\n\n if (prohibited.includes(formatted.toLowerCase())) {\n // Add \"node\" suffix to make it descriptive\n return `${formatted} node`;\n }\n\n return formatted;\n}\n"],
|
|
5
|
+
"mappings": "AASM,SAAU,mBAAmB,MAAY;AAC7C,SAAO,KACJ,MAAM,WAAW,EACjB,KAAK,GAAG,EACR,QAAQ,MAAM,CAAC,MAAM,EAAE,YAAW,CAAE;AACzC;AAQM,SAAU,0BAA0B,MAAY;AACpD,QAAM,aAAa,CAAC,cAAc,QAAQ,QAAQ,MAAM,GAClD,YAAY,mBAAmB,IAAI;AAEzC,SAAI,WAAW,SAAS,UAAU,YAAW,CAAE,IAEtC,GAAG,SAAS,UAGd;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function formatMarkdownTable(headers, rows) {
|
|
2
|
+
const colWidths = headers.map((h, i) => {
|
|
3
|
+
let width = h.length;
|
|
4
|
+
for (const row of rows)
|
|
5
|
+
width = Math.max(width, (row[i] || "").length);
|
|
6
|
+
return width;
|
|
7
|
+
}), headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(" | "), sepRow = colWidths.map((w) => "-".repeat(w)).join(" | "), dataRows = rows.map((row) => row.map((cell, i) => cell.padEnd(colWidths[i])).join(" | "));
|
|
8
|
+
return `| ${headerRow} |
|
|
9
|
+
| ${sepRow} |
|
|
10
|
+
${dataRows.map((r) => `| ${r} |`).join(`
|
|
11
|
+
`)}
|
|
12
|
+
`;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
formatMarkdownTable
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=markdown-table.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/markdown-table.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Markdown table formatting utilities\n */\n\n/**\n * Format a markdown table with proper column alignment and padding\n * @param headers Array of header strings\n * @param rows Array of row arrays (each row is an array of cell strings)\n * @returns Formatted markdown table string with pipe delimiters\n */\nexport function formatMarkdownTable(headers: string[], rows: string[][]): string {\n // Calculate column widths based on content\n const colWidths = headers.map((h, i) => {\n let width = h.length;\n for (const row of rows) {\n width = Math.max(width, (row[i] || \"\").length);\n }\n return width;\n });\n\n // Build header row\n const headerRow = headers\n .map((h, i) => h.padEnd(colWidths[i]))\n .join(\" | \");\n\n // Build separator row\n const sepRow = colWidths.map((w) => \"-\".repeat(w)).join(\" | \");\n\n // Build data rows\n const dataRows = rows.map((row) =>\n row.map((cell, i) => cell.padEnd(colWidths[i])).join(\" | \")\n );\n\n return `| ${headerRow} |\\n| ${sepRow} |\\n${dataRows.map((r) => `| ${r} |`).join(\"\\n\")}\\n`;\n}\n"],
|
|
5
|
+
"mappings": "AAUM,SAAU,oBAAoB,SAAmB,MAAgB;AAErE,QAAM,YAAY,QAAQ,IAAI,CAAC,GAAG,MAAK;AACrC,QAAI,QAAQ,EAAE;AACd,eAAW,OAAO;AAChB,cAAQ,KAAK,IAAI,QAAQ,IAAI,CAAC,KAAK,IAAI,MAAM;AAE/C,WAAO;EACT,CAAC,GAGK,YAAY,QACf,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,UAAU,CAAC,CAAC,CAAC,EACpC,KAAK,KAAK,GAGP,SAAS,UAAU,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,KAAK,GAGvD,WAAW,KAAK,IAAI,CAAC,QACzB,IAAI,IAAI,CAAC,MAAM,MAAM,KAAK,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;AAG7D,SAAO,KAAK,SAAS;IAAS,MAAM;EAAO,SAAS,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK;CAAI,CAAC;;AACvF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function sanitizeMermaidId(text) {
|
|
2
|
+
return text.replace(/[^a-zA-Z0-9]/g, "_");
|
|
3
|
+
}
|
|
4
|
+
function escapeMermaidLabel(text) {
|
|
5
|
+
return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
escapeMermaidLabel,
|
|
9
|
+
sanitizeMermaidId
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=mermaid-utils.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/mermaid-utils.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Mermaid diagram utilities\n */\n\n/**\n * Sanitize text for use as a Mermaid diagram node ID\n * Replaces all non-alphanumeric characters with underscores\n * @param text The text to sanitize\n * @returns Sanitized ID safe for use in Mermaid diagrams\n */\nexport function sanitizeMermaidId(text: string): string {\n return text.replace(/[^a-zA-Z0-9]/g, \"_\");\n}\n\n/**\n * Escape special characters for use in Mermaid diagram labels\n *\n * Mermaid labels use double quotes as delimiters, so we escape:\n * - `\"` \u2192 `\\\"` (double quote: the label delimiter)\n * - `\\` \u2192 `\\\\` (backslash: prevents unintended escape sequences)\n *\n * Note: Unlike markdown, Mermaid renders special chars like |, *, [, ], {, } literally\n * (they don't need escaping), so we don't escape them. Using escapeMarkdown() on Mermaid\n * labels would produce visual artifacts like \\| and \\{ in the diagram.\n *\n * @param text The text to escape for use in a Mermaid label\n * @returns Escaped text safe for use in Mermaid diagram node/edge labels\n */\nexport function escapeMermaidLabel(text: string): string {\n return text\n .replace(/\\\\/g, \"\\\\\\\\\") // Escape backslash first to avoid double-escaping\n .replace(/\"/g, '\\\\\"'); // Escape double quotes (label delimiter)\n}\n"],
|
|
5
|
+
"mappings": "AAUM,SAAU,kBAAkB,MAAY;AAC5C,SAAO,KAAK,QAAQ,iBAAiB,GAAG;AAC1C;AAgBM,SAAU,mBAAmB,MAAY;AAC7C,SAAO,KACJ,QAAQ,OAAO,MAAM,EACrB,QAAQ,MAAM,KAAK;AACxB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/project-paths.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Project Path Utilities\n *\n * Handles finding project root and related paths in the DR project structure:\n *\n * my-project/\n * \u251C\u2500\u2500 .dr/ # Reference spec (ephemeral, git-ignored)\n * \u2502 \u251C\u2500\u2500 manifest.json # Installed spec version\n * \u2502 \u2514\u2500\u2500 schemas/\n * \u2514\u2500\u2500 documentation-robotics/ # Project model (permanent, git-committed)\n * \u251C\u2500\u2500 model/\n * \u2502 \u251C\u2500\u2500 manifest.yaml # Model spec version\n * \u2502 \u2514\u2500\u2500 layers/\n * \u251C\u2500\u2500 annotations/\n * \u2514\u2500\u2500 changesets/\n */\n\nimport { fileExists } from
|
|
5
|
-
"mappings": "AAiBA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,eAAe;AAE9B,MAAM,mBAAmB,GACnB,sBAAsB,0BACtB,mBAAmB;AAgBzB,eAAsB,gBAAgB,WAAkB;AACtD,MAAI,cAAc,aAAa,QAAQ,IAAG;AAE1C,WAAS,QAAQ,GAAG,QAAQ,kBAAkB,SAAS;AACrD,UAAM,cAAc,KAAK,aAAa,mBAAmB;AAEzD,QAAI,MAAM,WAAW,WAAW;AAC9B,aAAO;AAIT,UAAM,aAAa,QAAQ,WAAW;AAGtC,QAAI,eAAe;AACjB;AAGF,kBAAc;EAChB;AAEA,SAAO;AACT;AAQA,eAAsB,2BAA2B,WAAkB;AACjE,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,SAAO,OAAO,KAAK,MAAM,mBAAmB,IAAI;AAClD;AAQA,eAAsB,aAAa,WAAkB;AACnD,QAAM,SAAS,MAAM,2BAA2B,SAAS;AACzD,MAAI,CAAC;AAAQ,WAAO;AAEpB,QAAM,YAAY,KAAK,QAAQ,OAAO;AACtC,
|
|
4
|
+
"sourcesContent": ["/**\n * Project Path Utilities\n *\n * Handles finding project root and related paths in the DR project structure:\n *\n * my-project/\n * \u251C\u2500\u2500 .dr/ # Reference spec (ephemeral, git-ignored)\n * \u2502 \u251C\u2500\u2500 manifest.json # Installed spec version\n * \u2502 \u2514\u2500\u2500 schemas/\n * \u2514\u2500\u2500 documentation-robotics/ # Project model (permanent, git-committed)\n * \u251C\u2500\u2500 model/\n * \u2502 \u251C\u2500\u2500 manifest.yaml # Model spec version\n * \u2502 \u2514\u2500\u2500 layers/\n * \u251C\u2500\u2500 annotations/\n * \u2514\u2500\u2500 changesets/\n */\n\nimport { fileExists } from \"./file-io.js\";\nimport { join, dirname } from \"path\";\n\nconst MAX_SEARCH_DEPTH = 5;\nconst PROJECT_FOLDER_NAME = \"documentation-robotics\";\nconst SPEC_FOLDER_NAME = \".dr\";\n\n/**\n * Find the project root by searching for the documentation-robotics/ folder\n *\n * Searches upward from the current working directory up to MAX_SEARCH_DEPTH levels.\n * Returns the directory containing the documentation_robotics/ folder.\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to project root, or null if not found\n *\n * @example\n * // From /home/user/my-project/documentation-robotics/model/01_motivation\n * // Returns: /home/user/my-project\n * const root = await findProjectRoot();\n */\nexport async function findProjectRoot(startPath?: string): Promise<string | null> {\n let currentPath = startPath || process.cwd();\n\n for (let depth = 0; depth < MAX_SEARCH_DEPTH; depth++) {\n const projectPath = join(currentPath, PROJECT_FOLDER_NAME);\n\n if (await fileExists(projectPath)) {\n return currentPath;\n }\n\n // Move up one directory\n const parentPath = dirname(currentPath);\n\n // Check if we've reached the root\n if (parentPath === currentPath) {\n break;\n }\n\n currentPath = parentPath;\n }\n\n return null;\n}\n\n/**\n * Get the path to the documentation-robotics/ folder\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to documentation-robotics/ folder, or null if not found\n */\nexport async function getDocumentationRobotsPath(startPath?: string): Promise<string | null> {\n const root = await findProjectRoot(startPath);\n return root ? join(root, PROJECT_FOLDER_NAME) : null;\n}\n\n/**\n * Get the path to the model directory (documentation-robotics/model/)\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to model directory, or null if not found\n */\nexport async function getModelPath(startPath?: string): Promise<string | null> {\n const drPath = await getDocumentationRobotsPath(startPath);\n if (!drPath) return null;\n\n const modelPath = join(drPath, \"model\");\n return (await fileExists(modelPath)) ? modelPath : null;\n}\n\n/**\n * Get the path to the .dr/ folder (spec reference directory)\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns Path to .dr/ folder, or null if not found\n */\nexport async function getSpecReferencePath(startPath?: string): Promise<string | null> {\n const root = await findProjectRoot(startPath);\n if (!root) return null;\n\n const specPath = join(root, SPEC_FOLDER_NAME);\n return (await fileExists(specPath)) ? specPath : null;\n}\n\n/**\n * Check if currently in a DR project\n *\n * @param startPath - Optional starting path (defaults to process.cwd())\n * @returns True if documentation-robotics/ folder exists in hierarchy\n */\nexport async function isInDRProject(startPath?: string): Promise<boolean> {\n return (await findProjectRoot(startPath)) !== null;\n}\n"],
|
|
5
|
+
"mappings": "AAiBA,SAAS,kBAAkB;AAC3B,SAAS,MAAM,eAAe;AAE9B,MAAM,mBAAmB,GACnB,sBAAsB,0BACtB,mBAAmB;AAgBzB,eAAsB,gBAAgB,WAAkB;AACtD,MAAI,cAAc,aAAa,QAAQ,IAAG;AAE1C,WAAS,QAAQ,GAAG,QAAQ,kBAAkB,SAAS;AACrD,UAAM,cAAc,KAAK,aAAa,mBAAmB;AAEzD,QAAI,MAAM,WAAW,WAAW;AAC9B,aAAO;AAIT,UAAM,aAAa,QAAQ,WAAW;AAGtC,QAAI,eAAe;AACjB;AAGF,kBAAc;EAChB;AAEA,SAAO;AACT;AAQA,eAAsB,2BAA2B,WAAkB;AACjE,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,SAAO,OAAO,KAAK,MAAM,mBAAmB,IAAI;AAClD;AAQA,eAAsB,aAAa,WAAkB;AACnD,QAAM,SAAS,MAAM,2BAA2B,SAAS;AACzD,MAAI,CAAC;AAAQ,WAAO;AAEpB,QAAM,YAAY,KAAK,QAAQ,OAAO;AACtC,SAAQ,MAAM,WAAW,SAAS,IAAK,YAAY;AACrD;AAQA,eAAsB,qBAAqB,WAAkB;AAC3D,QAAM,OAAO,MAAM,gBAAgB,SAAS;AAC5C,MAAI,CAAC;AAAM,WAAO;AAElB,QAAM,WAAW,KAAK,MAAM,gBAAgB;AAC5C,SAAQ,MAAM,WAAW,QAAQ,IAAK,WAAW;AACnD;AAQA,eAAsB,cAAc,WAAkB;AACpD,SAAQ,MAAM,gBAAgB,SAAS,MAAO;AAChD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CLIError } from "./errors.js";
|
|
2
|
+
function validateSourceReferenceOptions(options) {
|
|
3
|
+
if (!(options.sourceFile || options.sourceSymbol || options.sourceProvenance || options.sourceRepoRemote || options.sourceRepoCommit)) {
|
|
4
|
+
process.stderr.isTTY && console.warn("Warning: no source provenance specified. Add --source-file and --source-provenance to maintain traceability for drift detection and /dr-sync workflows.");
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if (!options.sourceProvenance)
|
|
8
|
+
throw new CLIError("--source-provenance is required when specifying source reference", 1, [
|
|
9
|
+
"Specify provenance type: extracted, manual, inferred, or generated"
|
|
10
|
+
]);
|
|
11
|
+
const fileOptionalProvenances = ["inferred", "generated"];
|
|
12
|
+
if (!options.sourceFile && !fileOptionalProvenances.includes(options.sourceProvenance))
|
|
13
|
+
throw new CLIError("--source-file is required when specifying source reference", 1, [
|
|
14
|
+
"Provide source file path relative to repository root",
|
|
15
|
+
'Example: --source-file "src/services/auth.ts"',
|
|
16
|
+
"Note: --source-file is optional when --source-provenance is 'inferred' or 'generated'"
|
|
17
|
+
]);
|
|
18
|
+
const validProvenance = ["extracted", "manual", "inferred", "generated"];
|
|
19
|
+
if (!validProvenance.includes(options.sourceProvenance))
|
|
20
|
+
throw new CLIError(`Invalid --source-provenance value: "${options.sourceProvenance}"`, 1, [
|
|
21
|
+
`Must be one of: ${validProvenance.join(", ")}`
|
|
22
|
+
]);
|
|
23
|
+
if (options.sourceRepoCommit && !/^[0-9a-f]{40}$/.test(options.sourceRepoCommit))
|
|
24
|
+
throw new CLIError(`Invalid --source-repo-commit value: "${options.sourceRepoCommit}"`, 1, [
|
|
25
|
+
"Must be exactly 40 hexadecimal characters (full Git commit SHA)"
|
|
26
|
+
]);
|
|
27
|
+
if (options.sourceRepoRemote && !options.sourceRepoCommit)
|
|
28
|
+
throw new CLIError("--source-repo-commit is required when --source-repo-remote is provided", 1, ["Provide both remote URL and commit SHA for repository context"]);
|
|
29
|
+
if (options.sourceRepoCommit && !options.sourceRepoRemote)
|
|
30
|
+
throw new CLIError("--source-repo-remote is required when --source-repo-commit is provided", 1, ["Provide both remote URL and commit SHA for repository context"]);
|
|
31
|
+
}
|
|
32
|
+
function buildSourceReference(options) {
|
|
33
|
+
if (!options.sourceProvenance)
|
|
34
|
+
return;
|
|
35
|
+
const reference = {
|
|
36
|
+
provenance: options.sourceProvenance
|
|
37
|
+
};
|
|
38
|
+
return options.sourceFile && (reference.locations = [
|
|
39
|
+
{
|
|
40
|
+
file: options.sourceFile,
|
|
41
|
+
...options.sourceSymbol && { symbol: options.sourceSymbol }
|
|
42
|
+
}
|
|
43
|
+
]), options.sourceRepoRemote && options.sourceRepoCommit && (reference.repository = {
|
|
44
|
+
url: options.sourceRepoRemote,
|
|
45
|
+
commit: options.sourceRepoCommit
|
|
46
|
+
}), reference;
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
buildSourceReference,
|
|
50
|
+
validateSourceReferenceOptions
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=source-reference.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/source-reference.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Source Reference Utilities\n * Shared validation and builder functions for source reference options\n */\n\nimport type { SourceReference, ProvenanceType } from \"../types/index.js\";\nimport { CLIError } from \"./errors.js\";\n\nexport interface SourceReferenceOptions {\n sourceFile?: string;\n sourceSymbol?: string;\n sourceProvenance?: string;\n sourceRepoRemote?: string;\n sourceRepoCommit?: string;\n}\n\n/**\n * Validate source reference options\n */\nexport function validateSourceReferenceOptions(options: SourceReferenceOptions): void {\n const hasSourceOptions =\n options.sourceFile ||\n options.sourceSymbol ||\n options.sourceProvenance ||\n options.sourceRepoRemote ||\n options.sourceRepoCommit;\n\n if (!hasSourceOptions) {\n if (process.stderr.isTTY) {\n console.warn(\n \"Warning: no source provenance specified. Add --source-file and --source-provenance to maintain traceability for drift detection and /dr-sync workflows.\"\n );\n }\n return;\n }\n\n // Require provenance first so we can use it to determine file requirements\n if (!options.sourceProvenance) {\n throw new CLIError(\"--source-provenance is required when specifying source reference\", 1, [\n \"Specify provenance type: extracted, manual, inferred, or generated\",\n ]);\n }\n\n // For inferred and generated provenance, source file is optional (no code exists yet)\n const fileOptionalProvenances = [\"inferred\", \"generated\"];\n if (!options.sourceFile && !fileOptionalProvenances.includes(options.sourceProvenance)) {\n throw new CLIError(\"--source-file is required when specifying source reference\", 1, [\n \"Provide source file path relative to repository root\",\n 'Example: --source-file \"src/services/auth.ts\"',\n \"Note: --source-file is optional when --source-provenance is 'inferred' or 'generated'\",\n ]);\n }\n\n // Validate provenance enum\n const validProvenance = [\"extracted\", \"manual\", \"inferred\", \"generated\"];\n if (!validProvenance.includes(options.sourceProvenance)) {\n throw new CLIError(`Invalid --source-provenance value: \"${options.sourceProvenance}\"`, 1, [\n `Must be one of: ${validProvenance.join(\", \")}`,\n ]);\n }\n\n // Validate commit SHA format if provided\n if (options.sourceRepoCommit && !/^[0-9a-f]{40}$/.test(options.sourceRepoCommit)) {\n throw new CLIError(`Invalid --source-repo-commit value: \"${options.sourceRepoCommit}\"`, 1, [\n \"Must be exactly 40 hexadecimal characters (full Git commit SHA)\",\n ]);\n }\n\n // Require both or neither repo context fields\n if (options.sourceRepoRemote && !options.sourceRepoCommit) {\n throw new CLIError(\n \"--source-repo-commit is required when --source-repo-remote is provided\",\n 1,\n [\"Provide both remote URL and commit SHA for repository context\"]\n );\n }\n\n if (options.sourceRepoCommit && !options.sourceRepoRemote) {\n throw new CLIError(\n \"--source-repo-remote is required when --source-repo-commit is provided\",\n 1,\n [\"Provide both remote URL and commit SHA for repository context\"]\n );\n }\n}\n\n/**\n * Build SourceReference object from options\n */\nexport function buildSourceReference(options: SourceReferenceOptions): SourceReference | undefined {\n if (!options.sourceProvenance) {\n return undefined;\n }\n\n const reference: SourceReference = {\n provenance: options.sourceProvenance as ProvenanceType,\n };\n\n if (options.sourceFile) {\n reference.locations = [\n {\n file: options.sourceFile,\n ...(options.sourceSymbol && { symbol: options.sourceSymbol }),\n },\n ];\n }\n\n if (options.sourceRepoRemote && options.sourceRepoCommit) {\n reference.repository = {\n url: options.sourceRepoRemote,\n commit: options.sourceRepoCommit,\n };\n }\n\n return reference;\n}\n"],
|
|
5
|
+
"mappings": "AAMA,SAAS,gBAAgB;AAanB,SAAU,+BAA+B,SAA+B;AAQ5E,MAAI,EANF,QAAQ,cACR,QAAQ,gBACR,QAAQ,oBACR,QAAQ,oBACR,QAAQ,mBAEa;AACrB,IAAI,QAAQ,OAAO,SACjB,QAAQ,KACN,yJAAyJ;AAG7J;EACF;AAGA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,SAAS,oEAAoE,GAAG;MACxF;KACD;AAIH,QAAM,0BAA0B,CAAC,YAAY,WAAW;AACxD,MAAI,CAAC,QAAQ,cAAc,CAAC,wBAAwB,SAAS,QAAQ,gBAAgB;AACnF,UAAM,IAAI,SAAS,8DAA8D,GAAG;MAClF;MACA;MACA;KACD;AAIH,QAAM,kBAAkB,CAAC,aAAa,UAAU,YAAY,WAAW;AACvE,MAAI,CAAC,gBAAgB,SAAS,QAAQ,gBAAgB;AACpD,UAAM,IAAI,SAAS,uCAAuC,QAAQ,gBAAgB,KAAK,GAAG;MACxF,mBAAmB,gBAAgB,KAAK,IAAI,CAAC;KAC9C;AAIH,MAAI,QAAQ,oBAAoB,CAAC,iBAAiB,KAAK,QAAQ,gBAAgB;AAC7E,UAAM,IAAI,SAAS,wCAAwC,QAAQ,gBAAgB,KAAK,GAAG;MACzF;KACD;AAIH,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,0EACA,GACA,CAAC,+DAA+D,CAAC;AAIrE,MAAI,QAAQ,oBAAoB,CAAC,QAAQ;AACvC,UAAM,IAAI,SACR,0EACA,GACA,CAAC,+DAA+D,CAAC;AAGvE;AAKM,SAAU,qBAAqB,SAA+B;AAClE,MAAI,CAAC,QAAQ;AACX;AAGF,QAAM,YAA6B;IACjC,YAAY,QAAQ;;AAGtB,SAAI,QAAQ,eACV,UAAU,YAAY;IACpB;MACE,MAAM,QAAQ;MACd,GAAI,QAAQ,gBAAgB,EAAE,QAAQ,QAAQ,aAAY;;MAK5D,QAAQ,oBAAoB,QAAQ,qBACtC,UAAU,aAAa;IACrB,KAAK,QAAQ;IACb,QAAQ,QAAQ;MAIb;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|