@documentation-robotics/cli 0.1.13 → 0.3.0
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/README.md +74 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +171 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/analyzers/verify-engine.js +22 -19
- package/dist/analyzers/verify-engine.js.map +2 -2
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +112 -28
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +3 -1
- package/dist/coding-agents/agents/claude-code.js.map +2 -2
- package/dist/coding-agents/claude-code-client.js +2 -1
- package/dist/coding-agents/claude-code-client.js.map +2 -2
- package/dist/coding-agents/copilot-client.js +17 -12
- package/dist/coding-agents/copilot-client.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +152 -0
- package/dist/coding-agents/default-permissions.js.map +7 -0
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +12 -7
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +134 -23
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +261 -147
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +52 -19
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/element.js +3 -2
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/farm.js +773 -0
- package/dist/commands/farm.js.map +7 -0
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +122 -0
- package/dist/commands/mcp.js.map +7 -0
- package/dist/commands/relationship.js +251 -47
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +56 -24
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +41 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +2 -2
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/config.js +83 -0
- package/dist/config.js.map +7 -0
- package/dist/core/annotation-store.js +138 -0
- package/dist/core/annotation-store.js.map +7 -0
- package/dist/core/catalog-migration.js +33 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +153 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +518 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/farm-manifest.js +140 -0
- package/dist/core/farm-manifest.js.map +7 -0
- package/dist/core/farm-sync-engine.js +346 -0
- package/dist/core/farm-sync-engine.js.map +7 -0
- package/dist/core/farm-sync-state.js +167 -0
- package/dist/core/farm-sync-state.js.map +7 -0
- package/dist/core/manifest.js +6 -3
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +84 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +257 -44
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +68 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +64 -17
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +14 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2209 -7727
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +79 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +52 -1
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +111 -0
- package/dist/mcp/api-key-manager.js.map +7 -0
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/http-transport.js +176 -0
- package/dist/mcp/http-transport.js.map +7 -0
- package/dist/mcp/resource-registry.js +97 -0
- package/dist/mcp/resource-registry.js.map +7 -0
- package/dist/mcp/tool-registry.js +60 -0
- package/dist/mcp/tool-registry.js.map +7 -0
- package/dist/mcp/tools/annotation-create.js +39 -0
- package/dist/mcp/tools/annotation-create.js.map +7 -0
- package/dist/mcp/tools/annotation-delete.js +29 -0
- package/dist/mcp/tools/annotation-delete.js.map +7 -0
- package/dist/mcp/tools/annotation-get.js +29 -0
- package/dist/mcp/tools/annotation-get.js.map +7 -0
- package/dist/mcp/tools/annotation-list.js +24 -0
- package/dist/mcp/tools/annotation-list.js.map +7 -0
- package/dist/mcp/tools/annotation-reply.js +34 -0
- package/dist/mcp/tools/annotation-reply.js.map +7 -0
- package/dist/mcp/tools/annotation-shared.js +10 -0
- package/dist/mcp/tools/annotation-shared.js.map +7 -0
- package/dist/mcp/tools/annotation-update.js +39 -0
- package/dist/mcp/tools/annotation-update.js.map +7 -0
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-list.js +33 -0
- package/dist/mcp/tools/changeset-list.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +55 -0
- package/dist/mcp/tools/changeset-show.js.map +7 -0
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/chat-launch.js +50 -0
- package/dist/mcp/tools/chat-launch.js.map +7 -0
- package/dist/mcp/tools/chat-status.js +26 -0
- package/dist/mcp/tools/chat-status.js.map +7 -0
- package/dist/mcp/tools/index.js +65 -0
- package/dist/mcp/tools/index.js.map +7 -0
- package/dist/mcp/tools/model-add.js +107 -0
- package/dist/mcp/tools/model-add.js.map +7 -0
- package/dist/mcp/tools/model-delete.js +99 -0
- package/dist/mcp/tools/model-delete.js.map +7 -0
- package/dist/mcp/tools/model-export.js +72 -0
- package/dist/mcp/tools/model-export.js.map +7 -0
- package/dist/mcp/tools/model-info.js +51 -0
- package/dist/mcp/tools/model-info.js.map +7 -0
- package/dist/mcp/tools/model-list.js +53 -0
- package/dist/mcp/tools/model-list.js.map +7 -0
- package/dist/mcp/tools/model-reload.js +34 -0
- package/dist/mcp/tools/model-reload.js.map +7 -0
- package/dist/mcp/tools/model-search.js +102 -0
- package/dist/mcp/tools/model-search.js.map +7 -0
- package/dist/mcp/tools/model-show.js +68 -0
- package/dist/mcp/tools/model-show.js.map +7 -0
- package/dist/mcp/tools/model-stats.js +22 -0
- package/dist/mcp/tools/model-stats.js.map +7 -0
- package/dist/mcp/tools/model-trace.js +118 -0
- package/dist/mcp/tools/model-trace.js.map +7 -0
- package/dist/mcp/tools/model-update.js +121 -0
- package/dist/mcp/tools/model-update.js.map +7 -0
- package/dist/mcp/tools/model-validate.js +78 -0
- package/dist/mcp/tools/model-validate.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +126 -0
- package/dist/mcp/tools/shared.js.map +7 -0
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +5 -3
- package/dist/reports/model-layer-report-generator.js.map +2 -2
- package/dist/reports/model-readme-generator.js +116 -0
- package/dist/reports/model-readme-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +37 -1
- package/dist/reports/model-report-data.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +29 -6
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/reports/model-report-utils.js +9 -0
- package/dist/reports/model-report-utils.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +745 -370
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +403 -383
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +718 -1643
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +234 -31
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/config.js +2 -40
- package/dist/telemetry/config.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/types/index.js.map +2 -2
- package/dist/utils/ansi-suppressor.js +23 -0
- package/dist/utils/ansi-suppressor.js.map +7 -0
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/color-utils.js +34 -0
- package/dist/utils/color-utils.js.map +7 -0
- package/dist/utils/element-reference-shape.js +15 -1
- package/dist/utils/element-reference-shape.js.map +3 -3
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +75 -10
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/globals.js +17 -1
- package/dist/utils/globals.js.map +2 -2
- package/dist/utils/project-paths.js +50 -3
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/reference-path-parser.js +49 -0
- package/dist/utils/reference-path-parser.js.map +7 -0
- package/dist/utils/source-reference.js +17 -10
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +10 -2
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/validators/composed-reference-validator.js +240 -0
- package/dist/validators/composed-reference-validator.js.map +7 -0
- package/dist/validators/reference-validator.js +57 -13
- package/dist/validators/reference-validator.js.map +3 -3
- package/dist/validators/relationship-schema-validator.js +33 -5
- package/dist/validators/relationship-schema-validator.js.map +3 -3
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +7 -3
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/model-upgrade.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Model upgrade - moves a model to a newer spec version and keeps its staged changesets\n * committable.\n *\n * A staged changeset records a hash of the model it was created on (its base snapshot), and\n * committing it fails while the model no longer has that hash. An upgrade always changes the\n * hash: it writes a new spec version into the manifest, and migrations may rewrite element\n * files. So the upgrade re-baselines each staged changeset that was in step with the model\n * before the upgrade onto the upgraded model. A changeset that had drifted before the upgrade\n * keeps its base snapshot, so that drift still blocks its commit.\n */\n\nimport { readdir } from \"fs/promises\";\nimport { Model } from \"./model.js\";\nimport { MigrationRegistry, type ApplyMigrationsResult } from \"./migration-registry.js\";\nimport { applyCatalogMigrations } from \"./catalog-migration.js\";\nimport { BaseSnapshotManager } from \"./base-snapshot-manager.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { FileLock } from \"../utils/file-lock.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\nexport interface ModelUpgradeOptions {\n fromVersion: string;\n toVersion: string;\n /** No migration path exists between the versions: only write the new spec version */\n versionBumpOnly?: boolean;\n /**\n * The model is at the target version but still names retired types or relationships: apply\n * the catalog migration (element type renames and relationship rewrites) on its own\n */\n catalogOnly?: boolean;\n /** Passed to the migrations (default true) */\n validate?: boolean;\n}\n\n/** A staged changeset, by directory id and name */\nexport interface ChangesetRef {\n id: string;\n name: string;\n}\n\nexport interface ChangesetRebaselineResult {\n /** Staged changesets now based on the upgraded model */\n rebaselined: ChangesetRef[];\n /** Staged changesets that had drifted before the upgrade; they keep their base snapshot */\n drifted: ChangesetRef[];\n /** Changesets that could not be read or re-baselined */\n errors: string[];\n}\n\nexport interface ModelUpgradeResult {\n /** The migrations applied; absent for a version bump */\n migration?: ApplyMigrationsResult;\n changesets: ChangesetRebaselineResult;\n}\n\n/** Staged changesets sorted by whether they match the model before an upgrade */\nexport interface ChangesetBaselines {\n /** Snapshot of the model before the upgrade; null when there is no staged changeset */\n snapshot: string | null;\n inStep: ChangesetRef[];\n drifted: ChangesetRef[];\n errors: string[];\n}\n\n/**\n * Upgrade the model at startPath to options.toVersion and save it, then re-baseline its\n * staged changesets on the upgraded model.\n */\nexport async function upgradeModel(\n startPath: string,\n options: ModelUpgradeOptions\n): Promise<ModelUpgradeResult> {\n // Load every layer, as `dr changeset commit` does before it checks for drift\n const model = await Model.load(startPath, { lazyLoad: false });\n const baselines = await captureChangesetBaselines(model);\n\n let migration: ApplyMigrationsResult | undefined;\n if (options.versionBumpOnly) {\n model.manifest.specVersion = options.toVersion;\n await model.saveManifest();\n } else if (options.catalogOnly) {\n const catalog = await applyCatalogMigrations(model);\n if (catalog.errors.length > 0) {\n throw new Error(`Catalog migration failed: ${catalog.errors.join(\"; \")}`);\n }\n migration = {\n applied: [\n {\n from: options.fromVersion,\n to: options.toVersion,\n description: `Catalog changes applied (${catalog.summaries.join(\"; \")})`,\n changes: {\n migrationsApplied: 1,\n filesModified: catalog.filesModified,\n description: catalog.summaries.join(\"; \"),\n details: catalog.details,\n },\n },\n ],\n currentVersion: options.fromVersion,\n targetVersion: options.toVersion,\n totalChanges: 1,\n };\n await model.saveDirtyLayers();\n } else {\n migration = await new MigrationRegistry().applyMigrations(model, {\n fromVersion: options.fromVersion,\n toVersion: options.toVersion,\n dryRun: false,\n validate: options.validate ?? true,\n });\n await model.saveManifest();\n await model.saveDirtyLayers();\n }\n\n return { migration, changesets: await rebaselineChangesets(startPath, baselines) };\n}\n\n/**\n * Hash the model and sort its staged changesets into those whose base snapshot matches it\n * and those that had drifted from it. Call before the model changes.\n *\n * @param model - loaded with lazyLoad: false, as `dr changeset commit` loads it, because the\n * snapshot hashes every loaded layer\n */\nexport async function captureChangesetBaselines(model: Model): Promise<ChangesetBaselines> {\n if (model.lazyLoad) {\n throw new Error(\"captureChangesetBaselines needs a model loaded with lazyLoad: false\");\n }\n\n const { changesets, errors } = await listStagedChangesets(model.rootPath);\n const baselines: ChangesetBaselines = { snapshot: null, inStep: [], drifted: [], errors };\n if (changesets.length === 0) {\n return baselines;\n }\n\n const snapshot = await new BaseSnapshotManager().captureSnapshot(model);\n baselines.snapshot = snapshot;\n for (const { ref, baseSnapshot } of changesets) {\n // A changeset without a base snapshot never reports drift, so it needs no re-baseline\n if (!baseSnapshot) {\n continue;\n }\n (baseSnapshot === snapshot ? baselines.inStep : baselines.drifted).push(ref);\n }\n return baselines;\n}\n\n/**\n * Point the changesets that were in step before the upgrade at the upgraded model. Call after\n * the upgraded model is saved.\n *\n * @param startPath - the path the model was loaded from before the upgrade\n */\nexport async function rebaselineChangesets(\n startPath: string,\n baselines: ChangesetBaselines\n): Promise<ChangesetRebaselineResult> {\n const result: ChangesetRebaselineResult = {\n rebaselined: [],\n drifted: baselines.drifted,\n errors: [...baselines.errors],\n };\n if (baselines.inStep.length === 0) {\n return result;\n }\n\n // Reload rather than hash the model in memory: migrations may rewrite files on disk\n // without updating it, and the commit hashes what it loads from disk\n const upgraded = await Model.load(startPath, { lazyLoad: false });\n const snapshot = await new BaseSnapshotManager().captureSnapshot(upgraded);\n const storage = new StagedChangesetStorage(upgraded.rootPath);\n\n for (const ref of baselines.inStep) {\n try {\n await new FileLock(storage.getChangesetPath(ref.id)).withLock(async () => {\n const changeset = await storage.load(ref.id);\n // Skip a changeset whose status or base changed while the upgrade ran\n if (\n !changeset ||\n changeset.status !== \"staged\" ||\n changeset.baseSnapshot !== baselines.snapshot\n ) {\n return;\n }\n changeset.baseSnapshot = snapshot;\n changeset.updateModified();\n await storage.save(changeset);\n result.rebaselined.push(ref);\n });\n } catch (error) {\n result.errors.push(\n `Could not re-baseline changeset '${ref.name}': ${getErrorMessage(error)}; committing it needs --force`\n );\n }\n }\n return result;\n}\n\nasync function listStagedChangesets(rootPath: string): Promise<{\n changesets: Array<{ ref: ChangesetRef; baseSnapshot: string | undefined }>;\n errors: string[];\n}> {\n const storage = new StagedChangesetStorage(rootPath);\n const changesets: Array<{ ref: ChangesetRef; baseSnapshot: string | undefined }> = [];\n const errors: string[] = [];\n\n let ids: string[];\n try {\n ids = (await readdir(storage.getChangesetsDir(), { withFileTypes: true }))\n // FileLock keeps \"<changeset>.lock\" directories beside the changesets\n .filter((entry) => entry.isDirectory() && !entry.name.endsWith(\".lock\"))\n .map((entry) => entry.name)\n .sort();\n } catch (error) {\n if ((error as { code?: string }).code !== \"ENOENT\") {\n errors.push(`Could not list changesets: ${getErrorMessage(error)}`);\n }\n return { changesets, errors };\n }\n\n for (const id of ids) {\n try {\n const changeset = await storage.load(id);\n if (changeset?.status === \"staged\") {\n changesets.push({\n ref: { id, name: changeset.name || id },\n baseSnapshot: changeset.baseSnapshot,\n });\n }\n } catch (error) {\n errors.push(`Could not read changeset '${id}': ${getErrorMessage(error)}`);\n }\n }\n return { changesets, errors };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/core/model.js
CHANGED
|
@@ -10,6 +10,9 @@ import { ensureDir, writeFile } from "../utils/file-io.js";
|
|
|
10
10
|
import { getCliVersion } from "../utils/spec-version.js";
|
|
11
11
|
import { startSpan, endSpan, startActiveSpan } from "../telemetry/index.js";
|
|
12
12
|
import { findProjectRoot } from "../utils/project-paths.js";
|
|
13
|
+
import { getCodebasePath } from "../utils/globals.js";
|
|
14
|
+
import { getErrorMessage } from "../utils/errors.js";
|
|
15
|
+
import { assertBaseModelWritable } from "./changeset-only-guard.js";
|
|
13
16
|
import { getNodeType } from "../generated/node-types.js";
|
|
14
17
|
import path from "node:path";
|
|
15
18
|
import fs from "node:fs/promises";
|
|
@@ -49,19 +52,26 @@ function isPersistableAttribute(specNodeId, key) {
|
|
|
49
52
|
const isTelemetryEnabled = !1;
|
|
50
53
|
class Model {
|
|
51
54
|
rootPath;
|
|
55
|
+
codebaseRoot;
|
|
52
56
|
manifest;
|
|
53
57
|
graph;
|
|
54
58
|
layers;
|
|
55
59
|
relationships;
|
|
56
60
|
lazyLoad;
|
|
61
|
+
loadedLayerFilter;
|
|
57
62
|
loadedLayers;
|
|
58
63
|
virtualProjectionEngine;
|
|
59
64
|
/** ID of the currently active changeset, or null if none is active. Set during load(). */
|
|
60
65
|
_activeChangesetId = null;
|
|
61
66
|
/** Layer names that exist only in staged changes (not yet committed to base model). */
|
|
62
67
|
_stagedLayerNames = /* @__PURE__ */ new Set();
|
|
68
|
+
/**
|
|
69
|
+
* relationships.yaml as committed, captured at load time before the active changeset's staged
|
|
70
|
+
* relationship changes are merged into `relationships`. Null when no changeset was active.
|
|
71
|
+
*/
|
|
72
|
+
_baseRelationships = null;
|
|
63
73
|
constructor(rootPath, manifest, options = {}) {
|
|
64
|
-
this.rootPath = rootPath, this.manifest = manifest, this.graph = new GraphModel(), this.layers = /* @__PURE__ */ new Map(), this.relationships = new Relationships(), this.lazyLoad = options.lazyLoad ?? !1, this.loadedLayers = /* @__PURE__ */ new Set();
|
|
74
|
+
this.rootPath = rootPath, this.codebaseRoot = options.codebaseRoot ?? rootPath, this.manifest = manifest, this.graph = new GraphModel(), this.layers = /* @__PURE__ */ new Map(), this.relationships = new Relationships(), this.lazyLoad = options.lazyLoad ?? !1, this.loadedLayerFilter = options.layers, this.loadedLayers = /* @__PURE__ */ new Set();
|
|
65
75
|
}
|
|
66
76
|
/**
|
|
67
77
|
* Get the committed base layer (no staging projection).
|
|
@@ -79,8 +89,11 @@ class Model {
|
|
|
79
89
|
if (this.lazyLoad && !this.loadedLayers.has(name) && await this.loadLayer(name), this._activeChangesetId)
|
|
80
90
|
try {
|
|
81
91
|
return await this.getVirtualProjectionEngine().projectLayer(this, this._activeChangesetId, name);
|
|
82
|
-
} catch {
|
|
83
|
-
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const errorMessage = err instanceof Error ? err.message : String(err), isChangesetNotFound = errorMessage.includes("Changeset") && errorMessage.includes("not found"), isCorrupted = errorMessage.includes("corrupted");
|
|
94
|
+
if (isChangesetNotFound || isCorrupted)
|
|
95
|
+
return this.layers.get(name);
|
|
96
|
+
throw err;
|
|
84
97
|
}
|
|
85
98
|
return this.layers.get(name);
|
|
86
99
|
}
|
|
@@ -108,18 +121,34 @@ class Model {
|
|
|
108
121
|
getActiveChangesetId() {
|
|
109
122
|
return this._activeChangesetId;
|
|
110
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The committed relationships, without the active changeset's staged additions and removals
|
|
126
|
+
* that load() merges into `relationships` for read commands. Equal to `relationships.getAll()`
|
|
127
|
+
* when no changeset was active at load time.
|
|
128
|
+
*/
|
|
129
|
+
getBaseRelationships() {
|
|
130
|
+
return this._baseRelationships ? [...this._baseRelationships] : this.relationships.getAll();
|
|
131
|
+
}
|
|
111
132
|
/**
|
|
112
133
|
* Load a layer from disk (model/XX_layername/*.yaml files)
|
|
113
134
|
*/
|
|
114
135
|
async loadLayer(name) {
|
|
115
136
|
await startActiveSpan("layer.load", async (layerSpan) => {
|
|
116
137
|
const fs2 = await import("fs/promises"), yaml = await import("yaml");
|
|
117
|
-
let layerPath = null
|
|
118
|
-
const modelDir = `${this.rootPath}/documentation-robotics/model`;
|
|
138
|
+
let layerPath = null, modelDir = `${this.rootPath}/documentation-robotics/model`;
|
|
119
139
|
try {
|
|
120
140
|
const layerDir = (await fs2.readdir(modelDir, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
|
|
121
141
|
layerDir && (layerPath = `${modelDir}/${layerDir.name}`);
|
|
122
|
-
} catch {
|
|
142
|
+
} catch (err) {
|
|
143
|
+
if (!(err?.code === "ENOENT"))
|
|
144
|
+
throw err;
|
|
145
|
+
try {
|
|
146
|
+
const layerDir = (await fs2.readdir(this.rootPath, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
|
|
147
|
+
layerDir && (layerPath = `${this.rootPath}/${layerDir.name}`);
|
|
148
|
+
} catch (err2) {
|
|
149
|
+
if (!(err2?.code === "ENOENT"))
|
|
150
|
+
throw err2;
|
|
151
|
+
}
|
|
123
152
|
}
|
|
124
153
|
if (!layerPath)
|
|
125
154
|
return;
|
|
@@ -211,10 +240,19 @@ class Model {
|
|
|
211
240
|
const staged = Array.from(this._stagedLayerNames).filter((l) => !this.layers.has(l));
|
|
212
241
|
return [...base, ...staged];
|
|
213
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Records that the active changeset now stages an element change in `layerName`, so
|
|
245
|
+
* getLayerNames() (and findElementLayer()) discover a layer the base model does not have without
|
|
246
|
+
* a reload. StagingAreaManager calls this when it stages into this model's active changeset.
|
|
247
|
+
*/
|
|
248
|
+
noteStagedLayer(changesetId, layerName) {
|
|
249
|
+
this._activeChangesetId && this._activeChangesetId === changesetId && layerName && this._stagedLayerNames.add(layerName);
|
|
250
|
+
}
|
|
214
251
|
/**
|
|
215
252
|
* Save a layer to disk (model/XX_layername/*.yaml)
|
|
216
253
|
*/
|
|
217
254
|
async saveLayer(name) {
|
|
255
|
+
assertBaseModelWritable();
|
|
218
256
|
const layer = this.layers.get(name);
|
|
219
257
|
if (!layer)
|
|
220
258
|
throw new Error(`Layer ${name} not found`);
|
|
@@ -224,7 +262,16 @@ class Model {
|
|
|
224
262
|
try {
|
|
225
263
|
const layerDir = (await fs2.readdir(modelDir, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
|
|
226
264
|
layerDir && (layerPath = `${modelDir}/${layerDir.name}`);
|
|
227
|
-
} catch {
|
|
265
|
+
} catch (err) {
|
|
266
|
+
if (!(err?.code === "ENOENT"))
|
|
267
|
+
throw err;
|
|
268
|
+
try {
|
|
269
|
+
const layerDir = (await fs2.readdir(this.rootPath, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
|
|
270
|
+
layerDir && (layerPath = `${this.rootPath}/${layerDir.name}`);
|
|
271
|
+
} catch (err2) {
|
|
272
|
+
if (!(err2?.code === "ENOENT"))
|
|
273
|
+
throw err2;
|
|
274
|
+
}
|
|
228
275
|
}
|
|
229
276
|
if (!layerPath) {
|
|
230
277
|
const layerNameWithoutPrefix = name.replace(/^\d{2}-/, ""), index = CANONICAL_LAYER_NAMES.indexOf(layerNameWithoutPrefix);
|
|
@@ -302,7 +349,7 @@ class Model {
|
|
|
302
349
|
* Save the manifest to disk (documentation-robotics/model/manifest.yaml)
|
|
303
350
|
*/
|
|
304
351
|
async saveManifest() {
|
|
305
|
-
this.manifest.updateModified();
|
|
352
|
+
assertBaseModelWritable(), this.manifest.updateModified();
|
|
306
353
|
const yaml = await import("yaml"), manifestPath = `${this.rootPath}/documentation-robotics/model/manifest.yaml`, yamlData = {
|
|
307
354
|
version: this.manifest.version,
|
|
308
355
|
schema: "documentation-robotics-v1",
|
|
@@ -328,7 +375,7 @@ class Model {
|
|
|
328
375
|
...layer && { elements: this.getLayerElementCounts(layer) }
|
|
329
376
|
};
|
|
330
377
|
}
|
|
331
|
-
this.manifest.changeset_history && this.manifest.changeset_history.length > 0 && (yamlData.changeset_history = this.manifest.changeset_history), await ensureDir(`${this.rootPath}/documentation-robotics/model`), await writeFile(manifestPath, yaml.stringify(yamlData));
|
|
378
|
+
this.manifest.codebase_path && (yamlData.codebase_path = this.manifest.codebase_path), this.manifest.changeset_history && this.manifest.changeset_history.length > 0 && (yamlData.changeset_history = this.manifest.changeset_history), this.manifest.models && Object.keys(this.manifest.models).length > 0 && (yamlData.models = this.manifest.models), await ensureDir(`${this.rootPath}/documentation-robotics/model`), await writeFile(manifestPath, yaml.stringify(yamlData));
|
|
332
379
|
}
|
|
333
380
|
getLayerElementCounts(layer) {
|
|
334
381
|
const counts = {};
|
|
@@ -350,8 +397,8 @@ class Model {
|
|
|
350
397
|
throw new Error(`Failed to load relationships.yaml: ${err instanceof Error ? err.message : String(err)}`);
|
|
351
398
|
return;
|
|
352
399
|
}
|
|
353
|
-
if (!Array.isArray(data)) {
|
|
354
|
-
console.warn(`Warning: relationships.yaml contains non-array content (received ${
|
|
400
|
+
if (data === null && (data = []), !Array.isArray(data)) {
|
|
401
|
+
console.warn(`Warning: relationships.yaml contains non-array content (received ${typeof data}). Expected an array of relationships. Skipping relationship loading.`);
|
|
355
402
|
return;
|
|
356
403
|
}
|
|
357
404
|
this.relationships = Relationships.fromArray(data);
|
|
@@ -372,17 +419,86 @@ class Model {
|
|
|
372
419
|
}
|
|
373
420
|
/**
|
|
374
421
|
* Save relationships to relationships.yaml
|
|
422
|
+
*
|
|
423
|
+
* @param options.preserveHeader - Keep the file's existing leading comment block
|
|
424
|
+
* (refreshing its `# Last updated:` line) instead of writing the standard header.
|
|
375
425
|
*/
|
|
376
|
-
async saveRelationships() {
|
|
426
|
+
async saveRelationships(options = {}) {
|
|
427
|
+
assertBaseModelWritable();
|
|
377
428
|
const fs2 = await import("fs/promises"), yaml = await import("yaml"), relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;
|
|
378
429
|
await ensureDir(`${this.rootPath}/documentation-robotics/model`);
|
|
379
|
-
const data = this.relationships.toArray(),
|
|
430
|
+
const data = this.relationships.toArray(), timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
431
|
+
let header = `# Intra-layer relationships
|
|
380
432
|
# Format: source_id -> predicate -> target_id
|
|
381
|
-
# Last updated: ${
|
|
433
|
+
# Last updated: ${timestamp}
|
|
382
434
|
|
|
383
|
-
|
|
435
|
+
`;
|
|
436
|
+
if (options.preserveHeader)
|
|
437
|
+
try {
|
|
438
|
+
const { extractRelationshipsHeader } = await import("./relationship-prune.js"), existing = extractRelationshipsHeader(await fs2.readFile(relationshipsPath, "utf-8"));
|
|
439
|
+
existing && (header = existing.replace(/^(# Last updated:).*$/m, `$1 ${timestamp}`) + `
|
|
440
|
+
`);
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
const yamlContent = header + yaml.stringify(data);
|
|
384
444
|
await fs2.writeFile(relationshipsPath, yamlContent, "utf-8"), this.relationships.markClean();
|
|
385
445
|
}
|
|
446
|
+
/**
|
|
447
|
+
* Auto-resolve codebaseRoot from farm manifest if inside a farm
|
|
448
|
+
*
|
|
449
|
+
* @param modelRoot - The model's project root (documentation_robotics parent)
|
|
450
|
+
* @returns Resolved codebase root path, or null if not in farm or resolution fails
|
|
451
|
+
*/
|
|
452
|
+
static async resolveFarmCodebaseRoot(modelRoot) {
|
|
453
|
+
const { findFarmRoot } = await import("../utils/project-paths.js"), { FarmManifest } = await import("./farm-manifest.js"), farmRoot = await findFarmRoot(modelRoot);
|
|
454
|
+
if (!farmRoot)
|
|
455
|
+
return null;
|
|
456
|
+
const farmYamlPath = path.join(farmRoot, "farm.yaml");
|
|
457
|
+
let manifest;
|
|
458
|
+
try {
|
|
459
|
+
manifest = await FarmManifest.load(farmYamlPath);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
const errorMsg = getErrorMessage(error);
|
|
462
|
+
if (errorMsg.includes("ENOENT") || errorMsg.includes("not found"))
|
|
463
|
+
return null;
|
|
464
|
+
throw new Error(`Failed to load farm manifest at ${farmYamlPath}: ${errorMsg}`);
|
|
465
|
+
}
|
|
466
|
+
const modelName = path.basename(modelRoot);
|
|
467
|
+
for (const project of manifest.getAllProjects())
|
|
468
|
+
if (path.basename(project.model) === modelName)
|
|
469
|
+
return path.resolve(farmRoot, project.source);
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Walk upward from startPath looking for documentation-robotics/model/manifest.yaml,
|
|
474
|
+
* stopping at the nearest .git directory (repo boundary) so this can never cross into
|
|
475
|
+
* an unrelated parent project or a sibling farm/detached model directory.
|
|
476
|
+
*
|
|
477
|
+
* @param startPath - Directory to start the walk from
|
|
478
|
+
* @returns Manifest path if found within the repo boundary, or null
|
|
479
|
+
*/
|
|
480
|
+
static async findManifestUpwardBounded(startPath) {
|
|
481
|
+
let currentPath = startPath;
|
|
482
|
+
for (; ; ) {
|
|
483
|
+
const candidateManifestPath = path.join(currentPath, "documentation-robotics", "model", "manifest.yaml");
|
|
484
|
+
try {
|
|
485
|
+
return await fs.access(candidateManifestPath), candidateManifestPath;
|
|
486
|
+
} catch (err) {
|
|
487
|
+
if (!(err?.code === "ENOENT"))
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
return await fs.access(path.join(currentPath, ".git")), null;
|
|
492
|
+
} catch (err) {
|
|
493
|
+
if (!(err?.code === "ENOENT"))
|
|
494
|
+
throw err;
|
|
495
|
+
}
|
|
496
|
+
const parentPath = path.dirname(currentPath);
|
|
497
|
+
if (parentPath === currentPath)
|
|
498
|
+
return null;
|
|
499
|
+
currentPath = parentPath;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
386
502
|
/**
|
|
387
503
|
* Resolve model paths (private helper)
|
|
388
504
|
*
|
|
@@ -390,18 +506,52 @@ class Model {
|
|
|
390
506
|
* @returns Project root and manifest path
|
|
391
507
|
*/
|
|
392
508
|
static async resolveModelPaths(startPath) {
|
|
393
|
-
|
|
509
|
+
if (startPath) {
|
|
510
|
+
const resolvedPath = path.resolve(startPath);
|
|
511
|
+
let manifestPath2;
|
|
512
|
+
resolvedPath.endsWith("manifest.yaml") ? manifestPath2 = resolvedPath : resolvedPath.endsWith("model") ? manifestPath2 = path.join(resolvedPath, "manifest.yaml") : manifestPath2 = path.join(resolvedPath, "documentation-robotics", "model", "manifest.yaml");
|
|
513
|
+
try {
|
|
514
|
+
await fs.access(manifestPath2);
|
|
515
|
+
let projectRoot2;
|
|
516
|
+
const manifestParentDir = path.dirname(manifestPath2), standardProjectRoot = path.dirname(path.dirname(path.dirname(manifestPath2))), standardDocRobotsPath = path.join(standardProjectRoot, "documentation-robotics", "model");
|
|
517
|
+
try {
|
|
518
|
+
await fs.access(standardDocRobotsPath), projectRoot2 = standardProjectRoot;
|
|
519
|
+
} catch (err) {
|
|
520
|
+
if (!(err?.code === "ENOENT"))
|
|
521
|
+
throw err;
|
|
522
|
+
projectRoot2 = manifestParentDir;
|
|
523
|
+
}
|
|
524
|
+
return { projectRoot: projectRoot2, manifestPath: path.normalize(manifestPath2) };
|
|
525
|
+
} catch (err) {
|
|
526
|
+
if (!(err?.code === "ENOENT"))
|
|
527
|
+
throw err;
|
|
528
|
+
const fallbackManifestPath = await Model.findManifestUpwardBounded(resolvedPath);
|
|
529
|
+
if (fallbackManifestPath)
|
|
530
|
+
return { projectRoot: path.dirname(path.dirname(path.dirname(fallbackManifestPath))), manifestPath: path.normalize(fallbackManifestPath) };
|
|
531
|
+
throw new Error(`Model not found at ${startPath}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
394
534
|
if (process.env.DR_MODEL_PATH) {
|
|
395
535
|
const envPath = path.resolve(process.env.DR_MODEL_PATH);
|
|
396
536
|
let manifestPath2;
|
|
397
537
|
envPath.endsWith("manifest.yaml") ? manifestPath2 = envPath : envPath.endsWith("model") ? manifestPath2 = path.join(envPath, "manifest.yaml") : manifestPath2 = path.join(envPath, "documentation-robotics", "model", "manifest.yaml");
|
|
398
538
|
try {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
539
|
+
await fs.access(manifestPath2);
|
|
540
|
+
let projectRoot2;
|
|
541
|
+
const manifestParentDir = path.dirname(manifestPath2), standardProjectRoot = path.dirname(path.dirname(path.dirname(manifestPath2))), standardDocRobotsPath = path.join(standardProjectRoot, "documentation-robotics", "model");
|
|
542
|
+
try {
|
|
543
|
+
await fs.access(standardDocRobotsPath), projectRoot2 = standardProjectRoot;
|
|
544
|
+
} catch (err) {
|
|
545
|
+
if (!(err?.code === "ENOENT"))
|
|
546
|
+
throw err;
|
|
547
|
+
projectRoot2 = manifestParentDir;
|
|
548
|
+
}
|
|
549
|
+
return { projectRoot: projectRoot2, manifestPath: path.normalize(manifestPath2) };
|
|
550
|
+
} catch (err) {
|
|
551
|
+
throw err?.code === "ENOENT" ? new Error(`Model not found at DR_MODEL_PATH: ${process.env.DR_MODEL_PATH}`) : err;
|
|
402
552
|
}
|
|
403
553
|
}
|
|
404
|
-
const projectRoot = await findProjectRoot(searchPath);
|
|
554
|
+
const searchPath = process.cwd(), projectRoot = await findProjectRoot(searchPath);
|
|
405
555
|
if (!projectRoot)
|
|
406
556
|
throw new Error(`No DR project found. Could not find documentation_robotics/ folder.
|
|
407
557
|
Run "dr init" to create a new project, or navigate to a directory containing a DR project.`);
|
|
@@ -413,6 +563,21 @@ Run "dr init" to create a new project, or navigate to a directory containing a D
|
|
|
413
563
|
Run "dr init" to create a model.`);
|
|
414
564
|
}
|
|
415
565
|
}
|
|
566
|
+
/**
|
|
567
|
+
* Validate that the manifest's models field is a plain object.
|
|
568
|
+
*
|
|
569
|
+
* @param models - The raw value from manifest YAML
|
|
570
|
+
* @returns Validation result with valid flag and error message if invalid
|
|
571
|
+
*/
|
|
572
|
+
static validateModelsField(models) {
|
|
573
|
+
return models == null ? { valid: !0 } : typeof models != "object" || Array.isArray(models) ? {
|
|
574
|
+
valid: !1,
|
|
575
|
+
error: `Invalid manifest 'models' field: expected an object with model names as keys (e.g., { "auth-service": { } }), but got ${Array.isArray(models) ? "an array" : `a ${typeof models}`}. This often happens when YAML syntax is incorrect, like writing 'models: auth-service' instead of 'models: { auth-service: {} }' or 'models:\\n auth-service: {}'.`
|
|
576
|
+
} : Object.getPrototypeOf(models) !== Object.prototype ? {
|
|
577
|
+
valid: !1,
|
|
578
|
+
error: `Invalid manifest 'models' field: expected a plain object, but got an instance of ${models.constructor?.name || "unknown"}. Please ensure 'models' is a simple YAML object mapping model names to declarations.`
|
|
579
|
+
} : { valid: !0 };
|
|
580
|
+
}
|
|
416
581
|
/**
|
|
417
582
|
* Load a model from disk
|
|
418
583
|
*
|
|
@@ -432,10 +597,34 @@ Run "dr init" to create a model.`);
|
|
|
432
597
|
name: manifestYaml.project?.name || "Unnamed Model",
|
|
433
598
|
description: manifestYaml.project?.description || "",
|
|
434
599
|
version: manifestYaml.project?.version || manifestYaml.version || "0.1.0",
|
|
600
|
+
cliVersion: manifestYaml.cli_version,
|
|
435
601
|
specVersion: manifestYaml.spec_version || "0.6.0",
|
|
436
602
|
created: manifestYaml.created || (/* @__PURE__ */ new Date()).toISOString(),
|
|
437
603
|
modified: manifestYaml.updated || (/* @__PURE__ */ new Date()).toISOString()
|
|
438
|
-
}
|
|
604
|
+
};
|
|
605
|
+
if (manifestYaml.models) {
|
|
606
|
+
const modelsValidation = Model.validateModelsField(manifestYaml.models);
|
|
607
|
+
if (!modelsValidation.valid)
|
|
608
|
+
throw new Error(modelsValidation.error);
|
|
609
|
+
manifestData.models = manifestYaml.models;
|
|
610
|
+
}
|
|
611
|
+
manifestYaml.codebase_path && (manifestData.codebase_path = manifestYaml.codebase_path);
|
|
612
|
+
const manifest = new Manifest(manifestData);
|
|
613
|
+
let codebaseRoot = projectRoot;
|
|
614
|
+
if (options.codebaseRoot)
|
|
615
|
+
codebaseRoot = options.codebaseRoot;
|
|
616
|
+
else {
|
|
617
|
+
const globalCodebasePath = getCodebasePath();
|
|
618
|
+
if (globalCodebasePath)
|
|
619
|
+
codebaseRoot = path.resolve(globalCodebasePath);
|
|
620
|
+
else if (manifest.codebase_path)
|
|
621
|
+
codebaseRoot = path.resolve(projectRoot, manifest.codebase_path);
|
|
622
|
+
else {
|
|
623
|
+
const farmCodebaseRoot = await Model.resolveFarmCodebaseRoot(projectRoot);
|
|
624
|
+
farmCodebaseRoot && (codebaseRoot = farmCodebaseRoot);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const model = new Model(projectRoot, manifest, { ...options, codebaseRoot });
|
|
439
628
|
if (!options.lazyLoad)
|
|
440
629
|
try {
|
|
441
630
|
if (manifestYaml.layers)
|
|
@@ -458,29 +647,37 @@ Run "dr init" to create a model.`);
|
|
|
458
647
|
try {
|
|
459
648
|
const activeId = (await fs.readFile(activePath, "utf-8")).trim();
|
|
460
649
|
if (activeId) {
|
|
461
|
-
model._activeChangesetId = activeId;
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
650
|
+
model._activeChangesetId = activeId, model._baseRelationships = model.relationships.getAll();
|
|
651
|
+
const storage = new StagedChangesetStorage(projectRoot);
|
|
652
|
+
try {
|
|
653
|
+
const changeset = await storage.load(activeId);
|
|
654
|
+
if (changeset)
|
|
655
|
+
for (const change of changeset.changes) {
|
|
656
|
+
if ((change.type === "add" || change.type === "update" || change.type === "delete") && model._stagedLayerNames.add(change.layerName), change.type === "relationship-add" && change.after) {
|
|
657
|
+
const rel = change.after;
|
|
658
|
+
model.relationships.find(rel.source, rel.target, rel.predicate).length === 0 && model.relationships.add({
|
|
659
|
+
source: rel.source,
|
|
660
|
+
target: rel.target,
|
|
661
|
+
predicate: rel.predicate,
|
|
662
|
+
layer: rel.layer,
|
|
663
|
+
...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
|
|
664
|
+
category: rel.category ?? "structural",
|
|
665
|
+
...rel.properties ? { properties: rel.properties } : {}
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (change.type === "relationship-delete" && change.before) {
|
|
669
|
+
const rel = change.before;
|
|
670
|
+
model.relationships.delete(rel.source, rel.target, rel.predicate);
|
|
671
|
+
}
|
|
480
672
|
}
|
|
481
|
-
|
|
673
|
+
} catch (err) {
|
|
674
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
675
|
+
throw new Error(`Failed to load active changeset '${activeId}': ${errorMessage}`, { cause: err });
|
|
676
|
+
}
|
|
482
677
|
}
|
|
483
|
-
} catch {
|
|
678
|
+
} catch (err) {
|
|
679
|
+
if (!(err?.code === "ENOENT"))
|
|
680
|
+
throw err;
|
|
484
681
|
model._activeChangesetId = null;
|
|
485
682
|
}
|
|
486
683
|
let entityCount = 0;
|
|
@@ -496,13 +693,28 @@ Run "dr init" to create a model.`);
|
|
|
496
693
|
* Initialize a new model in a directory
|
|
497
694
|
*/
|
|
498
695
|
static async init(rootPath, manifestData, options = {}) {
|
|
696
|
+
const yaml = await import("yaml");
|
|
499
697
|
await ensureDir(`${rootPath}/documentation-robotics/model`);
|
|
500
698
|
for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {
|
|
501
699
|
const orderNum = String(i + 1).padStart(2, "0"), layerName = CANONICAL_LAYER_NAMES[i];
|
|
502
700
|
await ensureDir(`${rootPath}/documentation-robotics/model/${orderNum}_${layerName}`);
|
|
503
701
|
}
|
|
504
|
-
const manifest = new Manifest(manifestData)
|
|
505
|
-
|
|
702
|
+
const manifest = new Manifest(manifestData);
|
|
703
|
+
let codebaseRoot = rootPath;
|
|
704
|
+
if (options.codebaseRoot)
|
|
705
|
+
codebaseRoot = options.codebaseRoot;
|
|
706
|
+
else {
|
|
707
|
+
const globalCodebasePath = getCodebasePath();
|
|
708
|
+
globalCodebasePath ? codebaseRoot = path.resolve(globalCodebasePath) : manifest.codebase_path && (codebaseRoot = path.resolve(rootPath, manifest.codebase_path));
|
|
709
|
+
}
|
|
710
|
+
const model = new Model(rootPath, manifest, { ...options, codebaseRoot });
|
|
711
|
+
await model.saveManifest();
|
|
712
|
+
const relationshipsPath = `${rootPath}/documentation-robotics/model/relationships.yaml`, yamlContent = `# Intra-layer relationships
|
|
713
|
+
# Format: source_id -> predicate -> target_id
|
|
714
|
+
# Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
715
|
+
|
|
716
|
+
` + yaml.stringify([]);
|
|
717
|
+
return await writeFile(relationshipsPath, yamlContent), model;
|
|
506
718
|
}
|
|
507
719
|
/**
|
|
508
720
|
* Graph-based API methods for querying the model
|
|
@@ -592,6 +804,7 @@ Run "dr init" to create a model.`);
|
|
|
592
804
|
* @throws Error if atomic write fails
|
|
593
805
|
*/
|
|
594
806
|
async saveDirtyLayersAtomic() {
|
|
807
|
+
assertBaseModelWritable();
|
|
595
808
|
const tempLayerWrites = [];
|
|
596
809
|
try {
|
|
597
810
|
for (const layer of this.layers.values())
|