@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/changeset-graph.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * ChangesetGraph \u2014 the model as a changeset would leave it, elements and relationships both.\n *\n * The per-layer projection (`VirtualProjectionEngine`) merges staged element changes but keeps the\n * base model's relationships, so a traversal over it cannot see the connections a change adds or\n * removes. This view applies every staged change, relationship changes included, in sequence order,\n * and keeps a status on each element and relationship so callers can tell what the change touches.\n *\n * It reads the base model through `getBaseLayer()` and `getBaseRelationships()`, so it works for\n * any changeset (active or not) and is never affected by the model's own active-changeset\n * projection.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { Model } from \"./model.js\";\nimport type { Changeset, StagedChange, UnstagedChange } from \"./changeset.js\";\nimport { compactChange, nextSequenceNumber, sequenceOf } from \"./changeset.js\";\nimport type { SourceReference } from \"../types/source-reference.js\";\nimport { stagedSourceReference } from \"./staged-source-reference.js\";\n\nexport type ChangeStatus = \"added\" | \"updated\" | \"deleted\" | \"unchanged\";\n\nexport interface GraphElement {\n /** Dotted path (`layer.type.name`), the id relationships and changes use. */\n id: string;\n uuid?: string;\n layer: string;\n type: string;\n name: string;\n description?: string;\n attributes: Record<string, unknown>;\n sourceReference?: SourceReference;\n status: ChangeStatus;\n /** Element state before the change, for updated and deleted elements. */\n before?: Record<string, unknown>;\n /**\n * The changeset stages an add of this element, but the base model already has it (e.g. an as-built\n * changeset that created it was committed after the plan was staged). Commit skips such an add, so\n * the element keeps its base content and status; it is reported apart from the changeset's adds.\n */\n alreadyInBase?: true;\n}\n\nexport interface GraphRelationship {\n source: string;\n predicate: string;\n target: string;\n layer: string;\n targetLayer?: string;\n properties?: Record<string, unknown>;\n status: Exclude<ChangeStatus, \"updated\">;\n /** Endpoints as the relationship row names them (a UUID or a path), when they differ from the resolved ones. */\n raw?: { source: string; target: string };\n /** The changeset stages an add of this relationship, but the base model already has it. */\n alreadyInBase?: true;\n}\n\nexport interface Neighbor {\n relationship: GraphRelationship;\n /** \"out\" when the element is the relationship's source, \"in\" when it is the target. */\n direction: \"out\" | \"in\";\n other: string;\n}\n\n/**\n * A changeset's changes in sequence order, each with its sequence number (its index when the change\n * carries none).\n */\nexport function sequencedChanges(changeset: Changeset): Array<{ change: StagedChange; sequence: number }> {\n return (changeset.changes as StagedChange[])\n .map((change, index) => ({ change, sequence: sequenceOf(change, index) }))\n .sort((a, b) => a.sequence - b.sequence);\n}\n\n/**\n * The changes that made up the plan at `revision`, in sequence order: the staged changes numbered\n * at or below it, plus the unstaged ones (tombstones) that were staged by then and unstaged after.\n * Without a revision, the changes staged now.\n */\nexport function changesAtRevision(\n changeset: Changeset,\n revision?: number\n): Array<{ change: StagedChange; sequence: number; unstagedAt?: number }> {\n const live = sequencedChanges(changeset);\n if (revision === undefined) return live;\n // Each sequence number at or below the revision, as it stood then: the staged change, unless a\n // tombstone says it was unstaged or rewritten after the revision; then the tombstone's version.\n const bySequence = new Map<number, { change: StagedChange; sequence: number; unstagedAt?: number }>();\n for (const entry of live) if (entry.sequence <= revision) bySequence.set(entry.sequence, entry);\n for (const t of tombstonesLiveAt(changeset, revision)) {\n bySequence.set(t.sequence, {\n change: { ...t.change, sequenceNumber: t.sequence } as StagedChange,\n sequence: t.sequence,\n ...(t.rewritten ? {} : { unstagedAt: t.removedAt }),\n });\n }\n return [...bySequence.values()].sort((a, b) => a.sequence - b.sequence);\n}\n\n/**\n * For each sequence number at or below `revision` that was unstaged or rewritten after it, the\n * tombstone holding the version it had at `revision`: the earliest one removed after it.\n */\nfunction tombstonesLiveAt(changeset: Changeset, revision: number): UnstagedChange[] {\n const earliest = new Map<number, UnstagedChange>();\n for (const t of changeset.unstaged ?? []) {\n if (t.sequence > revision || t.removedAt <= revision) continue;\n const kept = earliest.get(t.sequence);\n if (!kept || t.removedAt < kept.removedAt) earliest.set(t.sequence, t);\n }\n return [...earliest.values()].sort((a, b) => a.sequence - b.sequence);\n}\n\n/**\n * Tombstones of changes that were staged at `revision` and have been unstaged (not merely\n * rewritten) since: for each such sequence number, its unstage tombstone. A change rewritten and\n * then unstaged has both; the unstage one is reported.\n */\nexport function unstagedSince(changeset: Changeset, revision: number): UnstagedChange[] {\n const live = new Set(sequencedChanges(changeset).map(({ sequence }) => sequence));\n const result: UnstagedChange[] = [];\n for (const { sequence } of tombstonesLiveAt(changeset, revision)) {\n if (live.has(sequence)) continue;\n const unstage = (changeset.unstaged ?? [])\n .filter((t) => t.sequence === sequence && !t.rewritten && t.removedAt > revision)\n .sort((x, y) => y.removedAt - x.removedAt)[0];\n if (unstage) result.push(unstage);\n }\n return result;\n}\n\n/**\n * The changeset's revision: the last sequence number it has used, a stage or an unstage (each\n * takes one), or -1 when nothing was ever staged. For a changeset never unstaged from, its last\n * change's number.\n */\nexport function changesetRevision(changeset: Changeset): number {\n return nextSequenceNumber(changeset) - 1;\n}\n\n/**\n * A token naming the plan at a revision, not just its number: `<revision>:<hash>`, where the hash\n * covers every change that made up the plan at `throughSequence` (now, by default): its type,\n * element, timestamp and content, unstaged changes included while they were staged. An update can\n * rewrite a staged change in place, so the plan behind a revision number can change; the token\n * then differs. Nothing is stored for it: the token is recomputed from what is staged and the\n * unstage tombstones.\n */\nexport function changesetRevisionToken(changeset: Changeset, throughSequence?: number): string {\n const revision = throughSequence ?? changesetRevision(changeset);\n const hash = createHash(\"sha256\");\n for (const { change, sequence } of changesAtRevision(changeset, revision)) {\n const compact = compactChange(change);\n hash.update(\n JSON.stringify([sequence, compact.type, compact.elementId, compact.timestamp ?? null, compact.before ?? null, compact.after ?? null])\n );\n hash.update(\"\\n\");\n }\n return `${revision}:${hash.digest(\"hex\").slice(0, 12)}`;\n}\n\n/** Splits a revision token into its revision and hash, or returns undefined when it is not one. */\nexport function parseRevisionToken(token: string): { revision: number; hash: string } | undefined {\n const match = /^(-1|\\d+):([0-9a-f]+)$/.exec(token);\n return match ? { revision: Number(match[1]), hash: match[2] } : undefined;\n}\n\n/** Relationship key used by staged changes: `source::predicate::target`. */\nexport function relationshipKey(rel: { source: string; predicate: string; target: string }): string {\n return `${rel.source}::${rel.predicate}::${rel.target}`;\n}\n\nexport class ChangesetGraph {\n readonly elements = new Map<string, GraphElement>();\n private readonly relationshipsByKey = new Map<string, GraphRelationship>();\n /** UUID \u2192 dotted path, so relationship rows that name elements by UUID still resolve. */\n private readonly uuidToPath = new Map<string, string>();\n private adjacency: Map<string, Neighbor[]> | null = null;\n /** The staged changes this projection applied, in sequence order. */\n private appliedChanges: StagedChange[] = [];\n private _revision = -1;\n\n private constructor(readonly changeset: Changeset) {}\n\n /** The revision this projection shows (see `changesetRevision`); -1 when nothing was staged by then. */\n get revision(): number {\n return this._revision;\n }\n\n /**\n * Builds the projected graph of `changeset` over the base model. With `throughSequence`, the plan\n * as it stood at that revision, over today's base: the changes numbered at most that, including\n * ones unstaged since (rebuilt from their tombstones).\n */\n static async build(\n model: Model,\n changeset: Changeset,\n options: { throughSequence?: number } = {}\n ): Promise<ChangesetGraph> {\n const graph = new ChangesetGraph(changeset);\n\n for (const layerName of model.getLayerNames()) {\n const layer = await model.getBaseLayer(layerName);\n if (!layer) continue;\n for (const element of layer.listElements()) {\n const record = element.toJSON() as unknown as Record<string, unknown>;\n graph.putElement(fromRecord(record, layerName, \"unchanged\"));\n }\n }\n\n // The committed relationships: a model loaded with this changeset active has already merged\n // its staged relationship changes into `model.relationships`, and they are applied below.\n const baseRelationships =\n typeof model.getBaseRelationships === \"function\"\n ? model.getBaseRelationships()\n : model.relationships.getAll();\n for (const rel of baseRelationships) {\n const resolved = {\n source: graph.resolve(rel.source),\n predicate: rel.predicate,\n target: graph.resolve(rel.target),\n };\n graph.relationshipsByKey.set(relationshipKey(resolved), {\n ...resolved,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n ...(rel.properties ? { properties: rel.properties } : {}),\n status: \"unchanged\",\n ...(resolved.source !== rel.source || resolved.target !== rel.target\n ? { raw: { source: rel.source, target: rel.target } }\n : {}),\n });\n }\n\n // Up to a revision, changes unstaged after it count again (from their tombstones).\n const through = options.throughSequence;\n const applied = changesAtRevision(changeset, through);\n graph.appliedChanges = applied.map(({ change }) => change);\n const current = changesetRevision(changeset);\n graph._revision = through === undefined ? current : Math.min(through, current);\n for (const change of graph.appliedChanges) {\n graph.apply(change);\n }\n\n return graph;\n }\n\n /** Maps a UUID to its dotted path; returns any other identifier unchanged. */\n resolve(id: string): string {\n return this.uuidToPath.get(id) ?? id;\n }\n\n getElement(id: string): GraphElement | undefined {\n return this.elements.get(this.resolve(id));\n }\n\n /** Every relationship in the projection, including the ones the changeset deletes. */\n relationships(): GraphRelationship[] {\n return [...this.relationshipsByKey.values()];\n }\n\n /** Whether the projection holds a relationship that is not deleted. */\n hasLiveRelationship(source: string, predicate: string, target: string): boolean {\n const rel = this.relationshipsByKey.get(\n relationshipKey({ source: this.resolve(source), predicate, target: this.resolve(target) })\n );\n return !!rel && rel.status !== \"deleted\";\n }\n\n getRelationship(source: string, predicate: string, target: string): GraphRelationship | undefined {\n return this.relationshipsByKey.get(\n relationshipKey({ source: this.resolve(source), predicate, target: this.resolve(target) })\n );\n }\n\n /** Elements the changeset adds, updates or deletes, in the order of their first change. */\n changedElements(): GraphElement[] {\n const seen = new Set<string>();\n const result: GraphElement[] = [];\n for (const change of this.appliedChanges) {\n if (change.type.startsWith(\"relationship-\")) continue;\n const element = this.getElement(change.elementId);\n if (!element || element.status === \"unchanged\" || seen.has(element.id)) continue;\n seen.add(element.id);\n result.push(element);\n }\n return result;\n }\n\n /** Relationships the changeset adds or deletes. */\n changedRelationships(): GraphRelationship[] {\n return this.relationships().filter((rel) => rel.status !== \"unchanged\");\n }\n\n /**\n * Staged adds the base model already has: elements and relationships the changeset adds whose id\n * (or source, predicate and target) now exists in the base. They are not counted as adds, since\n * commit skips them; a later staged update or delete of the same element still applies.\n */\n alreadyInBase(): { elements: GraphElement[]; relationships: GraphRelationship[] } {\n return {\n elements: [...this.elements.values()].filter((e) => e.alreadyInBase && e.status !== \"deleted\"),\n relationships: this.relationships().filter((r) => r.alreadyInBase && r.status === \"unchanged\"),\n };\n }\n\n /** Relationships touching an element, with the element's side of each. */\n neighbors(id: string, options: { includeDeleted?: boolean } = {}): Neighbor[] {\n const all = this.getAdjacency().get(this.resolve(id)) ?? [];\n return options.includeDeleted ? all : all.filter((n) => n.relationship.status !== \"deleted\");\n }\n\n private getAdjacency(): Map<string, Neighbor[]> {\n if (this.adjacency) return this.adjacency;\n const adjacency = new Map<string, Neighbor[]>();\n const push = (id: string, neighbor: Neighbor) => {\n const list = adjacency.get(id);\n if (list) list.push(neighbor);\n else adjacency.set(id, [neighbor]);\n };\n for (const rel of this.relationshipsByKey.values()) {\n push(rel.source, { relationship: rel, direction: \"out\", other: rel.target });\n if (rel.target !== rel.source) {\n push(rel.target, { relationship: rel, direction: \"in\", other: rel.source });\n }\n }\n this.adjacency = adjacency;\n return adjacency;\n }\n\n private putElement(element: GraphElement): void {\n this.elements.set(element.id, element);\n if (element.uuid && element.uuid !== element.id) {\n this.uuidToPath.set(element.uuid, element.id);\n }\n }\n\n /**\n * A committed changeset's changes are already in the base model, so a base element or\n * relationship matching one of its adds is that add, not an earlier copy of it.\n */\n private isCommitted(): boolean {\n return this.changeset.status === \"committed\";\n }\n\n private apply(change: StagedChange): void {\n switch (change.type) {\n case \"add\": {\n if (!change.after) return;\n const element = fromRecord(change.after, change.layerName, \"added\", change.elementId);\n const existing = this.getElement(element.id);\n if (existing && existing.status === \"unchanged\" && !this.isCommitted()) {\n // The base already has it: commit would skip this add, so the base element stands.\n // (A committed changeset's adds are in the base because it landed them; they stay adds.)\n existing.alreadyInBase = true;\n return;\n }\n this.putElement(element);\n return;\n }\n case \"update\": {\n if (!change.after) return;\n const existing = this.getElement(change.elementId);\n const status: ChangeStatus = existing?.status === \"added\" ? \"added\" : \"updated\";\n const element = fromRecord(change.after, change.layerName, status, change.elementId);\n element.before = existing?.before ?? change.before;\n this.putElement(element);\n return;\n }\n case \"delete\": {\n const existing = this.getElement(change.elementId);\n if (!existing) return;\n if (existing.status === \"added\") {\n this.elements.delete(existing.id);\n } else {\n existing.before = existing.before ?? change.before;\n existing.status = \"deleted\";\n }\n return;\n }\n case \"relationship-add\": {\n const data = change.after as Partial<GraphRelationship> | undefined;\n if (!data?.source || !data.target || !data.predicate) return;\n const rel = {\n source: this.resolve(data.source),\n predicate: data.predicate,\n target: this.resolve(data.target),\n };\n const key = relationshipKey(rel);\n const existing = this.relationshipsByKey.get(key);\n if (existing && existing.status === \"unchanged\" && !this.isCommitted()) {\n // The base already has it (a staged add made before it was committed there).\n existing.alreadyInBase = true;\n return;\n }\n this.relationshipsByKey.set(key, {\n ...rel,\n layer: data.layer ?? change.layerName,\n ...(data.targetLayer ? { targetLayer: data.targetLayer } : {}),\n ...(data.properties ? { properties: data.properties } : {}),\n // Re-adding a relationship this changeset deleted restores it. A committed changeset's add\n // is in the base because the changeset landed it, so it stays an add.\n status: existing?.status === \"deleted\" ? \"unchanged\" : \"added\",\n ...(existing?.raw ? { raw: existing.raw } : {}),\n });\n return;\n }\n case \"relationship-delete\": {\n const data = (change.before ?? change.after) as Partial<GraphRelationship> | undefined;\n const [source, predicate, target] = data?.source\n ? [data.source, data.predicate, data.target]\n : change.elementId.split(\"::\");\n if (!source || !predicate || !target) return;\n const key = relationshipKey({\n source: this.resolve(source),\n predicate,\n target: this.resolve(target),\n });\n const existing = this.relationshipsByKey.get(key);\n if (!existing) return;\n if (existing.status === \"added\") this.relationshipsByKey.delete(key);\n else existing.status = \"deleted\";\n return;\n }\n }\n }\n}\n\nfunction fromRecord(\n record: Record<string, unknown>,\n layerName: string,\n status: ChangeStatus,\n fallbackId?: string\n): GraphElement {\n const str = (value: unknown): string | undefined =>\n typeof value === \"string\" && value.length > 0 ? value : undefined;\n const id = str(record.path) ?? fallbackId ?? str(record.id) ?? \"\";\n return {\n id,\n uuid: str(record.id),\n layer: str(record.layer_id) ?? str(record.layer) ?? layerName,\n type: str(record.type) ?? \"\",\n name: str(record.name) ?? id,\n description: str(record.description),\n attributes: (record.attributes as Record<string, unknown> | undefined) ?? {},\n sourceReference: stagedSourceReference(record),\n status,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { CLIError, ErrorCategory } from "../utils/errors.js";
|
|
2
|
+
const CHANGESET_ONLY_ENV = "DR_CHANGESET_ONLY", ON_VALUES = /* @__PURE__ */ new Set(["true", "1", "yes", "on"]), OFF_VALUES = /* @__PURE__ */ new Set(["false", "0", "no", "off"]), warnedValues = /* @__PURE__ */ new Set();
|
|
3
|
+
function isChangesetOnly(env = process.env) {
|
|
4
|
+
const raw = env[CHANGESET_ONLY_ENV];
|
|
5
|
+
if (raw === void 0)
|
|
6
|
+
return !1;
|
|
7
|
+
const value = raw.trim().toLowerCase();
|
|
8
|
+
return value === "" || OFF_VALUES.has(value) ? !1 : (ON_VALUES.has(value) || warnedValues.has(raw) || (warnedValues.add(raw), console.error(`[dr] ${CHANGESET_ONLY_ENV}=${JSON.stringify(raw)} is not a recognised value (use true/1/yes/on or false/0/no/off); treating it as on, so base-model writes are refused.`)), !0);
|
|
9
|
+
}
|
|
10
|
+
function resetChangesetOnlyWarnings() {
|
|
11
|
+
warnedValues.clear();
|
|
12
|
+
}
|
|
13
|
+
class ChangesetOnlyError extends CLIError {
|
|
14
|
+
constructor(...args) {
|
|
15
|
+
super(...args), this.name = "ChangesetOnlyError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertBaseModelWritable(env = process.env) {
|
|
19
|
+
if (isChangesetOnly(env))
|
|
20
|
+
throw new ChangesetOnlyError("This session can only change the model through a changeset, and no changeset is active.", ErrorCategory.USER, [
|
|
21
|
+
'Run "dr changeset create <name>" (it activates the new changeset), or "dr changeset activate <name>"',
|
|
22
|
+
"Then re-run the command: it will be staged in the changeset for the user to review and commit"
|
|
23
|
+
], { operation: "save model", context: `${CHANGESET_ONLY_ENV}=${env[CHANGESET_ONLY_ENV]}` });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
CHANGESET_ONLY_ENV,
|
|
27
|
+
ChangesetOnlyError,
|
|
28
|
+
assertBaseModelWritable,
|
|
29
|
+
isChangesetOnly,
|
|
30
|
+
resetChangesetOnlyWarnings
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=changeset-only-guard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-only-guard.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Changeset-only guard for chat agents.\n *\n * `dr visualize --with-changesets` lets its chat agent run `dr add`, `dr update` and\n * `dr relationship add`. Those commands stage their change when a changeset is active,\n * but write straight to the model when none is, and Claude Code's permission rules can't\n * tell the two apart. So the agent process runs with `CHANGESET_ONLY_ENV` set, and every\n * method that saves the base model calls `assertBaseModelWritable()` first: with the\n * variable set, the write fails and nothing on disk changes. Staging writes go to the\n * changeset store, not through the model, so they are unaffected.\n */\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\n\nexport const CHANGESET_ONLY_ENV = \"DR_CHANGESET_ONLY\";\n\nconst ON_VALUES = new Set([\"true\", \"1\", \"yes\", \"on\"]);\nconst OFF_VALUES = new Set([\"false\", \"0\", \"no\", \"off\"]);\nconst warnedValues = new Set<string>();\n\n/**\n * Whether this process may only change the model through a changeset.\n *\n * Unset or empty means off. true/1/yes/on (any case) mean on, false/0/no/off mean off. Any\n * other value fails safe: the guard is on, and a warning naming the value goes to stderr once,\n * so a typo never silently lets an agent write the base model.\n */\nexport function isChangesetOnly(env: NodeJS.ProcessEnv = process.env): boolean {\n const raw = env[CHANGESET_ONLY_ENV];\n if (raw === undefined) return false;\n const value = raw.trim().toLowerCase();\n if (value === \"\" || OFF_VALUES.has(value)) return false;\n if (ON_VALUES.has(value)) return true;\n if (!warnedValues.has(raw)) {\n warnedValues.add(raw);\n console.error(\n `[dr] ${CHANGESET_ONLY_ENV}=${JSON.stringify(raw)} is not a recognised value ` +\n \"(use true/1/yes/on or false/0/no/off); treating it as on, so base-model writes are refused.\"\n );\n }\n return true;\n}\n\n/** Test hook: forget which unrecognised values have already been warned about. */\nexport function resetChangesetOnlyWarnings(): void {\n warnedValues.clear();\n}\n\n/** Thrown by `assertBaseModelWritable()`, so callers (e.g. the MCP server) can recognise the refusal. */\nexport class ChangesetOnlyError extends CLIError {\n constructor(...args: ConstructorParameters<typeof CLIError>) {\n super(...args);\n this.name = \"ChangesetOnlyError\";\n }\n}\n\n/** Throws when this process may only change the model through a changeset. */\nexport function assertBaseModelWritable(env: NodeJS.ProcessEnv = process.env): void {\n if (!isChangesetOnly(env)) return;\n throw new ChangesetOnlyError(\n \"This session can only change the model through a changeset, and no changeset is active.\",\n ErrorCategory.USER,\n [\n 'Run \"dr changeset create <name>\" (it activates the new changeset), or \"dr changeset activate <name>\"',\n \"Then re-run the command: it will be staged in the changeset for the user to review and commit\",\n ],\n { operation: \"save model\", context: `${CHANGESET_ONLY_ENV}=${env[CHANGESET_ONLY_ENV]}` }\n );\n}\n"],
|
|
5
|
+
"mappings": "AAWA,SAAS,UAAU,qBAAqB;AAEjC,MAAM,qBAAqB,qBAE5B,YAAY,oBAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,GAC9C,aAAa,oBAAI,IAAI,CAAC,SAAS,KAAK,MAAM,KAAK,CAAC,GAChD,eAAe,oBAAI,IAAG;AAStB,SAAU,gBAAgB,MAAyB,QAAQ,KAAG;AAClE,QAAM,MAAM,IAAI,kBAAkB;AAClC,MAAI,QAAQ;AAAW,WAAO;AAC9B,QAAM,QAAQ,IAAI,KAAI,EAAG,YAAW;AACpC,SAAI,UAAU,MAAM,WAAW,IAAI,KAAK,IAAU,MAC9C,UAAU,IAAI,KAAK,KAClB,aAAa,IAAI,GAAG,MACvB,aAAa,IAAI,GAAG,GACpB,QAAQ,MACN,QAAQ,kBAAkB,IAAI,KAAK,UAAU,GAAG,CAAC,wHAC8C,IAG5F;AACT;AAGM,SAAU,6BAA0B;AACxC,eAAa,MAAK;AACpB;AAGM,MAAO,2BAA2B,SAAQ;EAC9C,eAAe,MAA4C;AACzD,UAAM,GAAG,IAAI,GACb,KAAK,OAAO;EACd;;AAII,SAAU,wBAAwB,MAAyB,QAAQ,KAAG;AAC1E,MAAK,gBAAgB,GAAG;AACxB,UAAM,IAAI,mBACR,2FACA,cAAc,MACd;MACE;MACA;OAEF,EAAE,WAAW,cAAc,SAAS,GAAG,kBAAkB,IAAI,IAAI,kBAAkB,CAAC,GAAE,CAAE;AAE5F;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { stagedRelationshipTriple } from "./relationship-staging.js";
|
|
2
|
+
const FIELD_ALIASES = { sourceReference: "source_reference" }, NESTED_FIELDS = /* @__PURE__ */ new Set(["attributes", "metadata"]);
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return !!value && typeof value == "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function canonical(value) {
|
|
7
|
+
return value === void 0 ? "undefined" : Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : isRecord(value) ? `{${Object.keys(value).filter((k) => value[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(",")}}` : JSON.stringify(value);
|
|
8
|
+
}
|
|
9
|
+
function isUnset(value) {
|
|
10
|
+
return value == null;
|
|
11
|
+
}
|
|
12
|
+
function sameValue(a, b) {
|
|
13
|
+
return isUnset(a) && isUnset(b) ? !0 : canonical(a) === canonical(b);
|
|
14
|
+
}
|
|
15
|
+
function normalizeSnapshot(snapshot) {
|
|
16
|
+
const out = {};
|
|
17
|
+
if (!snapshot)
|
|
18
|
+
return out;
|
|
19
|
+
for (const [key, value] of Object.entries(snapshot)) {
|
|
20
|
+
const name = FIELD_ALIASES[key] ?? key;
|
|
21
|
+
(name !== key || !(name in out)) && (out[name] = value);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function changedFields(before, after) {
|
|
26
|
+
const a = normalizeSnapshot(before), b = normalizeSnapshot(after), fields = [];
|
|
27
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]))
|
|
28
|
+
if (!sameValue(a[key], b[key])) {
|
|
29
|
+
if (NESTED_FIELDS.has(key) && (isRecord(a[key]) || isUnset(a[key])) && (isRecord(b[key]) || isUnset(b[key]))) {
|
|
30
|
+
const inner = { before: a[key] ?? {}, after: b[key] ?? {} };
|
|
31
|
+
for (const sub of /* @__PURE__ */ new Set([...Object.keys(inner.before), ...Object.keys(inner.after)]))
|
|
32
|
+
sameValue(inner.before[sub], inner.after[sub]) || fields.push(`${key}.${sub}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
fields.push(key);
|
|
36
|
+
}
|
|
37
|
+
return fields.sort();
|
|
38
|
+
}
|
|
39
|
+
function elementType(change) {
|
|
40
|
+
const type = (change.after ?? change.before)?.type;
|
|
41
|
+
return typeof type == "string" ? type : void 0;
|
|
42
|
+
}
|
|
43
|
+
function summarizeChange(change, index) {
|
|
44
|
+
const base = { sequence: change.sequenceNumber ?? index, operation: change.type, layer: change.layerName };
|
|
45
|
+
if (change.type === "relationship-add" || change.type === "relationship-delete") {
|
|
46
|
+
const triple = stagedRelationshipTriple(change);
|
|
47
|
+
return triple ? { ...base, ...triple } : { ...base, elementId: change.elementId };
|
|
48
|
+
}
|
|
49
|
+
const entry = { ...base, elementId: change.elementId }, type = elementType(change);
|
|
50
|
+
return type && (entry.elementType = type), change.type === "update" && (entry.changedFields = changedFields(change.before, change.after)), entry;
|
|
51
|
+
}
|
|
52
|
+
function summarizeChangeset(changes) {
|
|
53
|
+
const totals = {
|
|
54
|
+
add: 0,
|
|
55
|
+
update: 0,
|
|
56
|
+
delete: 0,
|
|
57
|
+
"relationship-add": 0,
|
|
58
|
+
"relationship-delete": 0,
|
|
59
|
+
total: changes.length
|
|
60
|
+
}, entries = changes.map((change, index) => (totals[change.type] = (totals[change.type] ?? 0) + 1, summarizeChange(change, index)));
|
|
61
|
+
return entries.sort((x, y) => x.sequence - y.sequence), { totals, changes: entries };
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
changedFields,
|
|
65
|
+
summarizeChange,
|
|
66
|
+
summarizeChangeset
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=changeset-summary.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/changeset-summary.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * A compact view of a changeset's staged changes: one entry per change with its operation and what\n * it names, and for updates the fields it changed, without the before/after snapshots.\n *\n * Used by the MCP `changeset_show` tool's `detail: \"summary\"` mode, where an agent iterating on a\n * plan wants to see the plan's shape each pass without paying for every element snapshot.\n * See docs/CHANGE_MODELING.md (\"Iterating on a plan\").\n */\n\nimport type { Change, StagedChange } from \"./changeset.js\";\nimport { stagedRelationshipTriple } from \"./relationship-staging.js\";\n\nexport type ChangeOperation = Change[\"type\"];\n\nexport interface ChangeSummaryEntry {\n sequence: number;\n operation: ChangeOperation;\n layer: string;\n /** Element changes: the element's id. */\n elementId?: string;\n /** Element changes: the element's type (from its snapshot). */\n elementType?: string;\n /** Updates: the fields that differ between before and after (`name`, `attributes.summary`, ...). */\n changedFields?: string[];\n /** Relationship changes: the relationship's ends and predicate. */\n source?: string;\n predicate?: string;\n target?: string;\n}\n\nexport interface ChangesetSummary {\n totals: Record<ChangeOperation, number> & { total: number };\n changes: ChangeSummaryEntry[];\n}\n\n/** Snapshot keys that name the same field. The legacy camelCase key is reported as `source_reference`. */\nconst FIELD_ALIASES: Record<string, string> = { sourceReference: \"source_reference\" };\n\n/** Keys whose nested keys are reported one level down (`attributes.summary`). */\nconst NESTED_FIELDS = new Set([\"attributes\", \"metadata\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** Stable JSON for comparing snapshot values regardless of key order. */\nfunction canonical(value: unknown): string {\n if (value === undefined) return \"undefined\";\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (isRecord(value)) {\n const keys = Object.keys(value)\n .filter((k) => value[k] !== undefined)\n .sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(\",\")}}`;\n }\n return JSON.stringify(value);\n}\n\n/** `null` and absent both mean \"not set\" in a snapshot (a cleared source reference is written as null). */\nfunction isUnset(value: unknown): boolean {\n return value === undefined || value === null;\n}\n\nfunction sameValue(a: unknown, b: unknown): boolean {\n if (isUnset(a) && isUnset(b)) return true;\n return canonical(a) === canonical(b);\n}\n\nfunction normalizeSnapshot(snapshot: Record<string, unknown> | undefined): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (!snapshot) return out;\n for (const [key, value] of Object.entries(snapshot)) {\n const name = FIELD_ALIASES[key] ?? key;\n // The legacy key carries the intended value; it wins over a stale snake_case copy.\n if (name !== key || !(name in out)) out[name] = value;\n }\n return out;\n}\n\n/**\n * The fields an update changes: top-level snapshot keys whose values differ, with `attributes`\n * and `metadata` broken down by key. Sorted for a stable result.\n */\nexport function changedFields(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined\n): string[] {\n const a = normalizeSnapshot(before);\n const b = normalizeSnapshot(after);\n const fields: string[] = [];\n for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {\n if (sameValue(a[key], b[key])) continue;\n if (NESTED_FIELDS.has(key) && (isRecord(a[key]) || isUnset(a[key])) && (isRecord(b[key]) || isUnset(b[key]))) {\n const inner = { before: (a[key] ?? {}) as Record<string, unknown>, after: (b[key] ?? {}) as Record<string, unknown> };\n for (const sub of new Set([...Object.keys(inner.before), ...Object.keys(inner.after)])) {\n if (!sameValue(inner.before[sub], inner.after[sub])) fields.push(`${key}.${sub}`);\n }\n continue;\n }\n fields.push(key);\n }\n return fields.sort();\n}\n\nfunction elementType(change: Change): string | undefined {\n const snapshot = (change.after ?? change.before) as Record<string, unknown> | undefined;\n const type = snapshot?.type;\n return typeof type === \"string\" ? type : undefined;\n}\n\nexport function summarizeChange(change: Change | StagedChange, index: number): ChangeSummaryEntry {\n const sequence = (change as StagedChange).sequenceNumber ?? index;\n const base = { sequence, operation: change.type, layer: change.layerName };\n if (change.type === \"relationship-add\" || change.type === \"relationship-delete\") {\n const triple = stagedRelationshipTriple(change as StagedChange);\n // A malformed row still gets an entry, named by its key, rather than vanishing from the view.\n return triple ? { ...base, ...triple } : { ...base, elementId: change.elementId };\n }\n const entry: ChangeSummaryEntry = { ...base, elementId: change.elementId };\n const type = elementType(change);\n if (type) entry.elementType = type;\n if (change.type === \"update\") {\n entry.changedFields = changedFields(\n change.before as Record<string, unknown> | undefined,\n change.after as Record<string, unknown> | undefined\n );\n }\n return entry;\n}\n\nexport function summarizeChangeset(changes: ReadonlyArray<Change | StagedChange>): ChangesetSummary {\n const totals = {\n add: 0,\n update: 0,\n delete: 0,\n \"relationship-add\": 0,\n \"relationship-delete\": 0,\n total: changes.length,\n } as ChangesetSummary[\"totals\"];\n const entries = changes.map((change, index) => {\n totals[change.type] = (totals[change.type] ?? 0) + 1;\n return summarizeChange(change, index);\n });\n entries.sort((x, y) => x.sequence - y.sequence);\n return { totals, changes: entries };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,6 +2,11 @@ import { VirtualProjectionEngine } from "./virtual-projection.js";
|
|
|
2
2
|
import { StagedChangesetStorage } from "./staged-changeset-storage.js";
|
|
3
3
|
import { Validator } from "../validators/validator.js";
|
|
4
4
|
import { ValidationResult } from "../validators/types.js";
|
|
5
|
+
import { Relationships, elementIdentifiers } from "./relationships.js";
|
|
6
|
+
import { getErrorMessage } from "../utils/errors.js";
|
|
7
|
+
function errorKey(issue) {
|
|
8
|
+
return `${issue.layer}\0${issue.elementId ?? ""}\0${issue.message}`;
|
|
9
|
+
}
|
|
5
10
|
class ChangesetValidator {
|
|
6
11
|
projectionEngine;
|
|
7
12
|
storage;
|
|
@@ -10,29 +15,70 @@ class ChangesetValidator {
|
|
|
10
15
|
this.projectionEngine = new VirtualProjectionEngine(rootPath), this.storage = new StagedChangesetStorage(rootPath), this.validator = new Validator();
|
|
11
16
|
}
|
|
12
17
|
/**
|
|
13
|
-
* Validate a changeset by running the full
|
|
14
|
-
*
|
|
15
|
-
* Only errors affecting elements touched by the changeset (add/update) block the
|
|
16
|
-
* commit. Pre-existing errors in unchanged elements are demoted to warnings so that
|
|
17
|
-
* incremental fixes can be committed without requiring the entire model to be valid
|
|
18
|
-
* first.
|
|
18
|
+
* Validate a changeset by running the full validation pipeline on its projected model.
|
|
19
19
|
*
|
|
20
|
-
* @
|
|
21
|
-
*
|
|
22
|
-
* @returns ValidationResult from all 4 stages
|
|
20
|
+
* See {@link validateChangesetReport}: only errors the changeset introduces are
|
|
21
|
+
* returned as errors; errors the base model already had are demoted to warnings.
|
|
23
22
|
*/
|
|
24
23
|
async validateChangeset(baseModel, changesetId) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
return (await this.validateChangesetReport(baseModel, changesetId)).result;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Judge the change, not the base: validate the committed base model and the
|
|
28
|
+
* projected model (base + staged changes), and block only on errors the
|
|
29
|
+
* projection has beyond the base's. An error is identified by its layer,
|
|
30
|
+
* element and message, and counted, so a changeset that adds another copy of
|
|
31
|
+
* an existing problem still blocks.
|
|
32
|
+
*
|
|
33
|
+
* A model with pre-existing errors (a relationship the catalog has since
|
|
34
|
+
* retired, say) can therefore take a change to the same element; those errors
|
|
35
|
+
* come back as warnings and in `preExistingErrors`.
|
|
36
|
+
*/
|
|
37
|
+
async validateChangesetReport(baseModel, changesetId) {
|
|
38
|
+
const changeset = await this.storage.load(changesetId), projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId), baseRelationships = baseModel.getBaseRelationships(), projectedRelationships = Relationships.fromArray([...baseRelationships]), deletedIdentifiers = /* @__PURE__ */ new Set();
|
|
39
|
+
for (const change of changeset?.changes ?? [])
|
|
40
|
+
if (change.type === "relationship-add" && change.after) {
|
|
41
|
+
const rel = change.after;
|
|
42
|
+
projectedRelationships.find(rel.source, rel.target, rel.predicate).length === 0 && projectedRelationships.add({ ...rel, category: rel.category ?? "structural" });
|
|
43
|
+
} else if (change.type === "relationship-delete" && change.before) {
|
|
44
|
+
const rel = change.before;
|
|
45
|
+
projectedRelationships.delete(rel.source, rel.target, rel.predicate);
|
|
46
|
+
} else if (change.type === "delete") {
|
|
47
|
+
const before = change.before ?? {};
|
|
48
|
+
for (const id of elementIdentifiers(before, change.elementId))
|
|
49
|
+
deletedIdentifiers.add(id);
|
|
50
|
+
}
|
|
51
|
+
projectedModel.relationships = deletedIdentifiers.size > 0 ? Relationships.fromArray(projectedRelationships.getAll().filter((rel) => !deletedIdentifiers.has(rel.source) && !deletedIdentifiers.has(rel.target))) : projectedRelationships;
|
|
52
|
+
const projectedResult = await this.validator.validateModel(projectedModel);
|
|
53
|
+
let baseErrors = [], baseValidationFailure;
|
|
54
|
+
try {
|
|
55
|
+
const baseLayers = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const name of baseModel.getLayerNames()) {
|
|
57
|
+
const layer = await baseModel.getBaseLayer(name);
|
|
58
|
+
layer && baseLayers.set(name, layer);
|
|
59
|
+
}
|
|
60
|
+
const baseView = {
|
|
61
|
+
manifest: baseModel.manifest,
|
|
62
|
+
layers: baseLayers,
|
|
63
|
+
relationships: Relationships.fromArray([...baseRelationships]),
|
|
64
|
+
loadedLayerFilter: baseModel.loadedLayerFilter,
|
|
65
|
+
isProjection: !0
|
|
66
|
+
};
|
|
67
|
+
baseErrors = (await this.validator.validateModel(baseView)).errors;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
baseValidationFailure = getErrorMessage(error);
|
|
70
|
+
}
|
|
71
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const issue of baseErrors) {
|
|
73
|
+
const key = errorKey(issue);
|
|
74
|
+
remaining.set(key, (remaining.get(key) ?? 0) + 1);
|
|
75
|
+
}
|
|
76
|
+
const result = new ValidationResult(), preExistingErrors = [];
|
|
77
|
+
for (const issue of projectedResult.errors) {
|
|
78
|
+
const key = errorKey(issue), count = remaining.get(key) ?? 0;
|
|
79
|
+
count > 0 ? (remaining.set(key, count - 1), preExistingErrors.push(issue), result.warnings.push({ ...issue, severity: "warning", message: `(pre-existing) ${issue.message}` })) : result.errors.push(issue);
|
|
80
|
+
}
|
|
81
|
+
return result.warnings.push(...projectedResult.warnings), { result, preExistingErrors, ...baseValidationFailure ? { baseValidationFailure } : {} };
|
|
36
82
|
}
|
|
37
83
|
}
|
|
38
84
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/core/changeset-validator.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * ChangesetValidator - Validates staged changes against projected model\n *\n * Runs the
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * ChangesetValidator - Validates staged changes against projected model\n *\n * Runs the validation pipeline against a merged view of the base model with\n * staged changes applied, and against the base model itself, so a commit is\n * judged by the errors the changeset introduces rather than by the base's.\n */\n\nimport { VirtualProjectionEngine, type ProjectedModel } from \"./virtual-projection.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { Validator } from \"../validators/validator.js\";\nimport { ValidationResult, type ValidationIssue } from \"../validators/types.js\";\nimport type { Model } from \"./model.js\";\nimport type { Layer } from \"./layer.js\";\nimport type { StagedChange } from \"./changeset.js\";\nimport { Relationships, elementIdentifiers, type Relationship } from \"./relationships.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * The outcome of judging a changeset: what it introduces, and what was already wrong.\n */\nexport interface ChangesetValidationReport {\n /**\n * Errors that block the commit: those the projected model has beyond the base\n * model's. Pre-existing errors are carried as warnings.\n */\n result: ValidationResult;\n /** Errors the base model already had that the projected model still has. */\n preExistingErrors: ValidationIssue[];\n /** Set when the base model could not be validated, so every projected error blocks. */\n baseValidationFailure?: string;\n}\n\n/** Identity of an error, for comparing the base model's errors with the projection's. */\nfunction errorKey(issue: ValidationIssue): string {\n return `${issue.layer}\\u0000${issue.elementId ?? \"\"}\\u0000${issue.message}`;\n}\n\n/**\n * Validates changesets by running validation pipeline on projected models\n */\nexport class ChangesetValidator {\n private projectionEngine: VirtualProjectionEngine;\n private storage: StagedChangesetStorage;\n private validator: Validator;\n\n constructor(rootPath: string) {\n this.projectionEngine = new VirtualProjectionEngine(rootPath);\n this.storage = new StagedChangesetStorage(rootPath);\n this.validator = new Validator();\n }\n\n /**\n * Validate a changeset by running the full validation pipeline on its projected model.\n *\n * See {@link validateChangesetReport}: only errors the changeset introduces are\n * returned as errors; errors the base model already had are demoted to warnings.\n */\n async validateChangeset(baseModel: Model, changesetId: string): Promise<ValidationResult> {\n return (await this.validateChangesetReport(baseModel, changesetId)).result;\n }\n\n /**\n * Judge the change, not the base: validate the committed base model and the\n * projected model (base + staged changes), and block only on errors the\n * projection has beyond the base's. An error is identified by its layer,\n * element and message, and counted, so a changeset that adds another copy of\n * an existing problem still blocks.\n *\n * A model with pre-existing errors (a relationship the catalog has since\n * retired, say) can therefore take a change to the same element; those errors\n * come back as warnings and in `preExistingErrors`.\n */\n async validateChangesetReport(baseModel: Model, changesetId: string): Promise<ChangesetValidationReport> {\n const changeset = await this.storage.load(changesetId);\n\n // Project the model with staged changes applied\n const projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId);\n\n // Relationships as committed, without the active changeset's staged changes that\n // Model.load() merges in, so the result does not depend on which changeset is active.\n const baseRelationships = baseModel.getBaseRelationships();\n\n // Relationships as the changeset leaves them. Those naming an element this changeset\n // deletes are removed at commit (by UUID or by path), so they are left out here too.\n const projectedRelationships = Relationships.fromArray([...baseRelationships]);\n const deletedIdentifiers = new Set<string>();\n for (const change of (changeset?.changes ?? []) as StagedChange[]) {\n if (change.type === \"relationship-add\" && change.after) {\n const rel = change.after as unknown as Relationship;\n if (projectedRelationships.find(rel.source, rel.target, rel.predicate).length === 0) {\n projectedRelationships.add({ ...rel, category: rel.category ?? \"structural\" });\n }\n } else if (change.type === \"relationship-delete\" && change.before) {\n const rel = change.before as { source: string; target: string; predicate?: string };\n projectedRelationships.delete(rel.source, rel.target, rel.predicate);\n } else if (change.type === \"delete\") {\n const before = (change.before ?? {}) as { id?: unknown; path?: unknown };\n for (const id of elementIdentifiers(before, change.elementId)) {\n deletedIdentifiers.add(id);\n }\n }\n }\n projectedModel.relationships =\n deletedIdentifiers.size > 0\n ? Relationships.fromArray(\n projectedRelationships\n .getAll()\n .filter((rel) => !deletedIdentifiers.has(rel.source) && !deletedIdentifiers.has(rel.target))\n )\n : projectedRelationships;\n\n const projectedResult = await this.validator.validateModel(projectedModel as unknown as Model);\n\n // The base model, validated the same way.\n let baseErrors: ValidationIssue[] = [];\n let baseValidationFailure: string | undefined;\n try {\n const baseLayers = new Map<string, Layer>();\n for (const name of baseModel.getLayerNames()) {\n const layer = await baseModel.getBaseLayer(name);\n if (layer) baseLayers.set(name, layer);\n }\n const baseView: ProjectedModel = {\n manifest: baseModel.manifest,\n layers: baseLayers,\n relationships: Relationships.fromArray([...baseRelationships]),\n loadedLayerFilter: baseModel.loadedLayerFilter,\n isProjection: true,\n };\n baseErrors = (await this.validator.validateModel(baseView as unknown as Model)).errors;\n } catch (error) {\n // Without a base to compare against, every projected error blocks (the strict\n // behaviour), and the report says why.\n baseValidationFailure = getErrorMessage(error);\n }\n\n const remaining = new Map<string, number>();\n for (const issue of baseErrors) {\n const key = errorKey(issue);\n remaining.set(key, (remaining.get(key) ?? 0) + 1);\n }\n\n const result = new ValidationResult();\n const preExistingErrors: ValidationIssue[] = [];\n for (const issue of projectedResult.errors) {\n const key = errorKey(issue);\n const count = remaining.get(key) ?? 0;\n if (count > 0) {\n remaining.set(key, count - 1);\n preExistingErrors.push(issue);\n result.warnings.push({ ...issue, severity: \"warning\", message: `(pre-existing) ${issue.message}` });\n } else {\n result.errors.push(issue);\n }\n }\n result.warnings.push(...projectedResult.warnings);\n\n return { result, preExistingErrors, ...(baseValidationFailure ? { baseValidationFailure } : {}) };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|