@documentation-robotics/cli 0.2.0 → 0.4.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 +46 -16
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- 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 +191 -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/extract.js +138 -0
- package/dist/analyzers/cards/extract.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/routes.js +96 -0
- package/dist/analyzers/cards/routes.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 +270 -174
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +342 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +49 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/extraction-plan.js +541 -0
- package/dist/analyzers/extraction-plan.js.map +7 -0
- package/dist/analyzers/index-job.js +229 -0
- package/dist/analyzers/index-job.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/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 +23 -11
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +31 -1
- package/dist/coding-agents/default-permissions.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +269 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +7 -4
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +143 -40
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +63 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +80 -29
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +10 -4
- package/dist/commands/delete.js.map +2 -2
- package/dist/commands/farm.js +176 -60
- package/dist/commands/farm.js.map +2 -2
- package/dist/commands/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +40 -21
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +208 -37
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +52 -32
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -103
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +19 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +34 -15
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/annotation-store.js +15 -5
- package/dist/core/annotation-store.js.map +2 -2
- package/dist/core/catalog-migration.js +34 -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 +160 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.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 +848 -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/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.js.map +7 -0
- package/dist/core/farm-manifest.js +12 -6
- package/dist/core/farm-manifest.js.map +2 -2
- package/dist/core/farm-sync-engine.js +1 -1
- package/dist/core/farm-sync-engine.js.map +2 -2
- package/dist/core/farm-sync-state.js +10 -2
- package/dist/core/farm-sync-state.js.map +2 -2
- package/dist/core/migration-registry.js +88 -47
- 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 +82 -30
- package/dist/core/model.js.map +2 -2
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/project-init.js +53 -0
- package/dist/core/project-init.js.map +7 -0
- 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 +161 -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 +145 -18
- 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 +166 -24
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/upgrade-plan.js +117 -0
- package/dist/core/upgrade-plan.js.map +7 -0
- package/dist/core/virtual-projection.js +13 -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 +2468 -7913
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +30 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +8 -2
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +20 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +66 -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 +47 -26
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +68 -2
- package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
- package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
- 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 +95 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +178 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +8 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +6 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +3 -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 +225 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +177 -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 +14 -1
- package/dist/mcp/api-key-manager.js.map +2 -2
- package/dist/mcp/changeset-only.js +45 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/project-root.js +85 -0
- package/dist/mcp/project-root.js.map +7 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/server-instructions.js.map +7 -0
- package/dist/mcp/strict-schema.js +52 -0
- package/dist/mcp/strict-schema.js.map +7 -0
- package/dist/mcp/tool-registry.js +55 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +98 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-commit.js +224 -0
- package/dist/mcp/tools/changeset-commit.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +260 -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-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +17 -1
- package/dist/mcp/tools/changeset-show.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +49 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/codebase-extract.js +836 -0
- package/dist/mcp/tools/codebase-extract.js.map +7 -0
- package/dist/mcp/tools/codebase-index.js +125 -0
- package/dist/mcp/tools/codebase-index.js.map +7 -0
- package/dist/mcp/tools/codebase-shared.js +265 -0
- package/dist/mcp/tools/codebase-shared.js.map +7 -0
- package/dist/mcp/tools/codebase-verify.js +48 -0
- package/dist/mcp/tools/codebase-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +34 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +191 -74
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-delete.js +2 -2
- package/dist/mcp/tools/model-delete.js.map +2 -2
- package/dist/mcp/tools/model-search.js +14 -0
- package/dist/mcp/tools/model-search.js.map +2 -2
- package/dist/mcp/tools/model-show.js +29 -2
- package/dist/mcp/tools/model-show.js.map +2 -2
- package/dist/mcp/tools/model-trace.js +93 -30
- package/dist/mcp/tools/model-trace.js.map +2 -2
- package/dist/mcp/tools/model-update.js +46 -26
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/model-validate.js +46 -4
- package/dist/mcp/tools/model-validate.js.map +2 -2
- package/dist/mcp/tools/project-status-collectors.js +381 -0
- package/dist/mcp/tools/project-status-collectors.js.map +7 -0
- package/dist/mcp/tools/project-status.js +237 -0
- package/dist/mcp/tools/project-status.js.map +7 -0
- package/dist/mcp/tools/project-tools.js +505 -0
- package/dist/mcp/tools/project-tools.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +167 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/review-open.js +210 -0
- package/dist/mcp/tools/review-open.js.map +7 -0
- package/dist/mcp/tools/shared.js +35 -4
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/reports/model-report-orchestrator.js +31 -2
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/schemas/bundled/analyzers/cbm.json +1200 -70
- 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 +735 -371
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +395 -411
- 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 +865 -1646
- 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 +686 -231
- package/dist/server/server.js.map +3 -3
- package/dist/server/viewer-registry.js +264 -0
- package/dist/server/viewer-registry.js.map +7 -0
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/ansi-suppressor.js +6 -8
- package/dist/utils/ansi-suppressor.js.map +2 -2
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +9 -3
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/id-generator.js +5 -2
- package/dist/utils/id-generator.js.map +2 -2
- package/dist/utils/project-paths.js +26 -1
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +64 -31
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/composed-reference-validator.js +5 -27
- package/dist/validators/composed-reference-validator.js.map +2 -2
- package/dist/validators/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- package/dist/validators/reference-validator.js +0 -7
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +3 -3
- package/dist/validators/relationship-schema-validator.js.map +2 -2
- 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-CUOQGGRA.css +1 -0
- package/dist/viewer/assets/main-DvH3gYrI.js +84 -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 +2 -1
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/coding-agents/default-permissions.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Default Read-Safe Permissions Module\n *\n * Defines the read-safe permission allowlist for AI agents (Claude Code and GitHub Copilot)\n * when launched in default (non-danger) mode. This module provides a centralized,\n * client-agnostic definition of permitted tools and operations that both integration\n * managers can consume identically, despite their different CLI permission models.\n *\n * Permission Model:\n * - Read-Safe (Default): Limited to read operations and CLI execution\n * - Unrestricted (--with-danger): Full tool access\n *\n * The read-safe allowlist covers exactly four capabilities:\n * 1. Running the `dr` CLI\n * 2. Reading the codebase\n * 3. Reading the `documentation-robotics/` folder\n * 4. Reading the `.dr/` folder\n */\n\nimport { spawnSync } from \"child_process\";\nimport ansis from \"ansis\";\n\n/**\n * Represents a single tool permission with optional scoping\n */\nexport interface ToolPermission {\n /** Name of the tool (e.g., 'Bash', 'Read') */\n name: string;\n\n /** Human-readable description of what this permission allows */\n description: string;\n\n /** Optional directory/file path scope restriction (relative to project root) */\n scope?: string;\n\n /** Whether this tool allows write/modify operations (should be false for read-safe) */\n allowsWrite: boolean;\n}\n\n/**\n * Read-safe permission allowlist for default (non-danger) mode\n *\n * This is the core configuration that defines which tools are available\n * when launched without the --with-danger flag. Both Claude Code and\n * GitHub Copilot integrations consume this identically.\n *\n * IMPORTANT: This array is deeply immutable via Object.freeze() and validated at module load time.\n * Mutating or adding entries after module initialization will not re-validate constraints.\n */\nconst PERMISSIONS_DEFINITION: ToolPermission[] = [\n {\n name: \"Bash\",\n description: \"Execute the dr CLI tool for read-safe model queries\",\n scope: \"dr query|dr show|dr list|dr search|dr export|dr validate|dr info|dr stats|dr report|dr audit|dr chat|dr version|dr conformance\",\n allowsWrite: false,\n },\n {\n name: \"Read\",\n description: \"Read files from the codebase\",\n scope: \".\",\n allowsWrite: false,\n },\n {\n name: \"Read\",\n description: \"Read from the documentation-robotics model folder\",\n scope: \"documentation-robotics\",\n allowsWrite: false,\n },\n {\n name: \"Read\",\n description: \"Read from the dr configuration folder\",\n scope: \".dr\",\n allowsWrite: false,\n },\n];\n\nexport const DEFAULT_READ_SAFE_PERMISSIONS: readonly ToolPermission[] = Object.freeze(\n PERMISSIONS_DEFINITION.map((p) => Object.freeze({ ...p }))\n);\n\n/**\n * Convert read-safe permissions to Claude Code CLI format\n *\n * Claude Code uses the --allowedTools flag with scoped tool permissions.\n * This function formats the permission list as expected by Claude Code's --allowedTools parameter,\n * including scope constraints for each tool (e.g., \"Bash(dr query|dr show|dr list|...),Read(.),Read(documentation-robotics)\").\n *\n * @returns String formatted for Claude Code --allowedTools flag\n */\nexport function formatForClaudeCode(): string {\n // Format each permission as \"ToolName\" or \"ToolName(scope)\" with scope constraints\n const formatted = DEFAULT_READ_SAFE_PERMISSIONS.map((p) => {\n if (p.scope) {\n return `${p.name}(${p.scope})`;\n }\n return p.name;\n });\n return formatted.join(\",\");\n}\n\n/**\n * Convert read-safe permissions to GitHub Copilot CLI format\n *\n * GitHub Copilot CLI may support a --allowedTools flag (or equivalent) for granular permission control.\n * This function formats the permission list similarly to Claude Code for potential future Copilot versions\n * that support granular scoped permissions. If the installed Copilot version doesn't support this flag,\n * the caller should gracefully degrade (e.g., launch without the flag, with a warning).\n *\n * Format: \"Bash(dr query|dr show|dr list|...),Read(.),Read(documentation-robotics),Read(.dr)\"\n *\n * @returns String formatted for Copilot --allowedTools flag (if supported)\n */\nexport function formatForCopilot(): string {\n return formatForClaudeCode();\n}\n\n/**\n * Validate a permission array against read-safe constraints\n *\n * Internal function that validates any permission array. Used by tests to verify\n * that invalid configurations are properly rejected.\n *\n * @param permissions The permission array to validate\n * @throws {Error} If any constraint is violated\n */\nexport function validatePermissions(permissions: readonly ToolPermission[]): void {\n // Constraint 1: Non-empty allowlist\n if (!Array.isArray(permissions) || permissions.length === 0) {\n throw new Error(\n \"Permissions must be a non-empty array\"\n );\n }\n\n // Constraint 2: All permissions must have required fields with correct types\n for (let i = 0; i < permissions.length; i++) {\n const perm = permissions[i];\n\n if (typeof perm.name !== \"string\" || perm.name.length === 0) {\n throw new Error(\n `Permission[${i}].name must be a non-empty string, got: ${typeof perm.name}`\n );\n }\n\n if (typeof perm.description !== \"string\" || perm.description.length === 0) {\n throw new Error(\n `Permission[${i}].description must be a non-empty string, got: ${typeof perm.description}`\n );\n }\n\n if (typeof perm.allowsWrite !== \"boolean\") {\n throw new Error(\n `Permission[${i}].allowsWrite must be a boolean, got: ${typeof perm.allowsWrite}`\n );\n }\n }\n\n // Constraint 3: No permission should allow write operations (read-safe invariant)\n const writePerms = permissions.filter((p) => p.allowsWrite);\n if (writePerms.length > 0) {\n throw new Error(\n `Read-safe permissions must not allow write operations. Found ${writePerms.length} permission(s) with allowsWrite=true`\n );\n }\n\n // Constraint 4: Only Bash and Read tools allowed (allowlist validation, not denylist)\n const allowedToolNames = new Set([\"Bash\", \"Read\"]);\n const disallowedTools = permissions.filter(\n (p) => !allowedToolNames.has(p.name)\n );\n if (disallowedTools.length > 0) {\n throw new Error(\n `Read-safe permissions must only include Bash and Read tools. Found disallowed tool(s): ${disallowedTools.map((p) => p.name).join(\", \")}`\n );\n }\n\n // Constraint 5: Bash must be for dr CLI only with properly restricted scope (no wildcards or bare patterns)\n const bashPerms = permissions.filter((p) => p.name === \"Bash\");\n if (bashPerms.length > 0) {\n const invalidBash = bashPerms.filter((p) => {\n const hasValidDescription = p.description.toLowerCase().includes(\"dr\") && p.description.toLowerCase().includes(\"cli\");\n const hasValidScope = typeof p.scope === \"string\" && p.scope.length > 0;\n\n // Validate scope content: reject bare wildcards and unsafe patterns\n let hasValidScopeContent = true;\n if (hasValidScope) {\n const scope = p.scope;\n // Reject dangerous scope patterns: bare \"*\", \".\", or \"dr *\"\n if (scope === \"*\" || scope === \".\" || scope === \"dr *\" || scope.startsWith(\"dr *\")) {\n hasValidScopeContent = false;\n }\n // Ensure scope starts with \"dr \" for Bash tool\n if (!scope.startsWith(\"dr \")) {\n hasValidScopeContent = false;\n }\n }\n\n return !hasValidDescription || !hasValidScope || !hasValidScopeContent;\n });\n if (invalidBash.length > 0) {\n const errors = invalidBash.map((p) => {\n const issues = [];\n if (!p.description.toLowerCase().includes(\"dr\") || !p.description.toLowerCase().includes(\"cli\")) {\n issues.push(\"invalid description (must include 'dr' and 'cli')\");\n }\n if (typeof p.scope !== \"string\" || p.scope.length === 0) {\n issues.push(\"missing or empty scope\");\n } else {\n const scope = p.scope;\n if (scope === \"*\" || scope === \".\" || scope === \"dr *\" || scope.startsWith(\"dr *\")) {\n issues.push(\"scope uses dangerous wildcard pattern (must specify read-safe commands)\");\n }\n if (!scope.startsWith(\"dr \")) {\n issues.push(\"scope must start with 'dr ' for Bash tool\");\n }\n }\n return `${p.description} (${issues.join(\", \")})`;\n });\n throw new Error(\n `Bash permission must be for dr CLI only with properly restricted scope. Found invalid Bash permission(s): ${errors.join(\"; \")}`\n );\n }\n }\n\n // Constraint 6: Must have Read permission for codebase\n const codebaseRead = permissions.find(\n (p) => p.name === \"Read\" && p.description.toLowerCase().includes(\"codebase\")\n );\n if (!codebaseRead) {\n throw new Error(\"Read-safe permissions must include Read permission for codebase\");\n }\n\n // Constraint 7: Must have Read permission for documentation-robotics folder\n const docRoboticsRead = permissions.find(\n (p) => p.name === \"Read\" && p.description.includes(\"documentation-robotics\")\n );\n if (!docRoboticsRead) {\n throw new Error(\"Read-safe permissions must include Read permission for documentation-robotics folder\");\n }\n\n // Constraint 8: Must have Read permission for .dr folder\n const drFolderRead = permissions.find(\n (p) => p.name === \"Read\" && (p.scope === \".dr\" || p.description.includes(\".dr\"))\n );\n if (!drFolderRead) {\n throw new Error(\"Read-safe permissions must include Read permission for .dr folder\");\n }\n\n // All constraints passed\n}\n\n/**\n * Validate read-safe permission constraints\n *\n * Called during module initialization to verify the permission allowlist maintains\n * its safety invariants: all permissions are read-only, required tools are present,\n * and no dangerous tools are included.\n *\n * Throws an error if constraints are violated, preventing runtime with an invalid configuration.\n *\n * @throws {Error} If any constraint is violated\n */\nexport function validateReadSafeConstraints(): void {\n validatePermissions(DEFAULT_READ_SAFE_PERMISSIONS);\n}\n\n/**\n * Apply Copilot permissions to command arguments with graceful degradation\n *\n * Shared utility for both CopilotClient and VisualizationServer to probe\n * Copilot CLI capability and apply appropriate permission flags.\n * Handles both default mode (read-safe --allowedTools) and danger mode (--allow-all-tools).\n *\n * @param cmd The command array to modify (mutated in place)\n * @param variant Display name for error messages (e.g., \"gh copilot\" or \"copilot\")\n * @param copilotCommand The copilot CLI name (\"copilot\" or \"gh\")\n * @param withDanger Whether to use danger mode (--allow-all-tools)\n * @param onTelemetry Optional callback for telemetry logging\n * @param testSpawnSync Optional injected spawnSync for testing (allows mocking CLI probe)\n */\nexport function applyCopilotPermissions(\n cmd: string[],\n variant: string,\n copilotCommand: string,\n withDanger: boolean,\n onTelemetry?: (attr: string, value: any) => void,\n testSpawnSync?: typeof spawnSync\n): void {\n const spawnSyncFn = testSpawnSync || spawnSync;\n\n if (withDanger) {\n try {\n const helpResult = spawnSyncFn(\n copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n copilotCommand === \"copilot\" ? [\"--help\"] : [\"copilot\", \"--help\"],\n { stdio: \"pipe\", encoding: \"utf-8\", timeout: 1000 }\n );\n if (\n helpResult.stdout?.includes(\"--allow-all-tools\") ||\n helpResult.stdout?.includes(\"allow-all-tools\")\n ) {\n cmd.push(\"--allow-all-tools\");\n onTelemetry?.(\"process.allowAllToolsSupported\", true);\n } else {\n onTelemetry?.(\"process.allowAllToolsSupported\", false);\n console.warn(\n ansis.yellow(`Note: --allow-all-tools flag not supported by your ${variant} version`)\n );\n }\n } catch {\n onTelemetry?.(\"process.allowAllToolsCheckFailed\", true);\n console.warn(\n ansis.yellow(\n `WARNING: Could not verify --allow-all-tools support for ${variant}. ` +\n `Launching WITHOUT permission escalation. If you need full tool access, ` +\n `please ensure your ${variant} CLI is up-to-date.`\n )\n );\n }\n } else {\n try {\n const helpResult = spawnSyncFn(\n copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n copilotCommand === \"copilot\" ? [\"--help\"] : [\"copilot\", \"--help\"],\n { stdio: \"pipe\", encoding: \"utf-8\", timeout: 1000 }\n );\n\n const helpText = helpResult.stdout || \"\";\n const supportsAllowedTools =\n helpText.includes(\"--allowedTools\") ||\n helpText.includes(\"allowedTools\") ||\n helpText.includes(\"--allowed-tools\") ||\n helpText.includes(\"allowed-tools\");\n\n if (supportsAllowedTools) {\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n } else {\n // Flag not advertised in help, but still try to apply it optimistically\n // (fail-safe: try to restrict access even if we're not 100% sure the flag exists)\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n console.warn(\n ansis.yellow(\n `Note: Your ${variant} version doesn't advertise --allowedTools in help, ` +\n `but attempting to apply it. If not supported, ` +\n `please upgrade @github/copilot for granular permission controls.`\n )\n );\n }\n } catch {\n onTelemetry?.(\"process.readSafePermissionCheckFailed\", true);\n // Fail-closed approach: unable to verify support, so apply read-safe restrictions defensively\n // to maintain the read-safe guarantee even when the CLI probe fails.\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n console.warn(\n ansis.yellow(\n `WARNING: Could not verify read-safe permission support for ${variant}. ` +\n `Applying read-safe restrictions defensively. ` +\n `If this is not supported by your ${variant} version, please upgrade @github/copilot or copilot CLI.`\n )\n );\n }\n }\n}\n\n// Validate read-safe constraints during module initialization\nvalidateReadSafeConstraints();\n"],
|
|
5
|
-
"mappings": "AAmBA,SAAS,iBAAiB;AAC1B,OAAO,WAAW;AA6BlB,MAAM,yBAA2C;EAC/C;IACE,MAAM;IACN,aAAa;IACb,OAAO;IACP,aAAa;;EAEf;IACE,MAAM;IACN,aAAa;IACb,OAAO;IACP,aAAa;;EAEf;IACE,MAAM;IACN,aAAa;IACb,OAAO;IACP,aAAa;;EAEf;IACE,MAAM;IACN,aAAa;IACb,OAAO;IACP,aAAa;;GAIJ,gCAA2D,OAAO,OAC7E,uBAAuB,IAAI,CAAC,MAAM,OAAO,OAAO,EAAE,GAAG,EAAC,CAAE,CAAC,CAAC;
|
|
4
|
+
"sourcesContent": ["/**\n * Default Read-Safe Permissions Module\n *\n * Defines the read-safe permission allowlist for AI agents (Claude Code and GitHub Copilot)\n * when launched in default (non-danger) mode. This module provides a centralized,\n * client-agnostic definition of permitted tools and operations that both integration\n * managers can consume identically, despite their different CLI permission models.\n *\n * Permission Model:\n * - Read-Safe (Default): Limited to read operations and CLI execution\n * - Unrestricted (--with-danger): Full tool access\n *\n * The read-safe allowlist covers exactly four capabilities:\n * 1. Running the `dr` CLI\n * 2. Reading the codebase\n * 3. Reading the `documentation-robotics/` folder\n * 4. Reading the `.dr/` folder\n */\n\nimport { spawnSync } from \"child_process\";\nimport ansis from \"ansis\";\n\n/**\n * Represents a single tool permission with optional scoping\n */\nexport interface ToolPermission {\n /** Name of the tool (e.g., 'Bash', 'Read') */\n name: string;\n\n /** Human-readable description of what this permission allows */\n description: string;\n\n /** Optional directory/file path scope restriction (relative to project root) */\n scope?: string;\n\n /** Whether this tool allows write/modify operations (should be false for read-safe) */\n allowsWrite: boolean;\n}\n\n/**\n * Read-safe permission allowlist for default (non-danger) mode\n *\n * This is the core configuration that defines which tools are available\n * when launched without the --with-danger flag. Both Claude Code and\n * GitHub Copilot integrations consume this identically.\n *\n * IMPORTANT: This array is deeply immutable via Object.freeze() and validated at module load time.\n * Mutating or adding entries after module initialization will not re-validate constraints.\n */\nconst PERMISSIONS_DEFINITION: ToolPermission[] = [\n {\n name: \"Bash\",\n description: \"Execute the dr CLI tool for read-safe model queries\",\n scope: \"dr query|dr show|dr list|dr search|dr export|dr validate|dr info|dr stats|dr report|dr audit|dr chat|dr version|dr conformance\",\n allowsWrite: false,\n },\n {\n name: \"Read\",\n description: \"Read files from the codebase\",\n scope: \".\",\n allowsWrite: false,\n },\n {\n name: \"Read\",\n description: \"Read from the documentation-robotics model folder\",\n scope: \"documentation-robotics\",\n allowsWrite: false,\n },\n {\n name: \"Read\",\n description: \"Read from the dr configuration folder\",\n scope: \".dr\",\n allowsWrite: false,\n },\n];\n\nexport const DEFAULT_READ_SAFE_PERMISSIONS: readonly ToolPermission[] = Object.freeze(\n PERMISSIONS_DEFINITION.map((p) => Object.freeze({ ...p }))\n);\n\n/**\n * Convert read-safe permissions to Claude Code CLI format\n *\n * Claude Code uses the --allowedTools flag with scoped tool permissions.\n * This function formats the permission list as expected by Claude Code's --allowedTools parameter,\n * including scope constraints for each tool (e.g., \"Bash(dr query:*),Bash(dr show:*),...,Read(.),Read(documentation-robotics)\").\n *\n * A Bash scope lists several commands separated by \"|\". Claude Code's rule syntax has no \"or\":\n * `Bash(dr query|dr show)` matches no command at all, so each command becomes its own prefix\n * rule, `Bash(dr show:*)`. Other tools keep their scope as-is, e.g. `Read(.dr)`.\n *\n * @returns String formatted for Claude Code --allowedTools flag\n */\nexport function formatForClaudeCode(): string {\n const formatted = DEFAULT_READ_SAFE_PERMISSIONS.flatMap((p) => {\n if (!p.scope) {\n return [p.name];\n }\n if (p.name === \"Bash\") {\n return bashCommands(p.scope).map((command) => `Bash(${command}:*)`);\n }\n return [`${p.name}(${p.scope})`];\n });\n return formatted.join(\",\");\n}\n\n/**\n * Extra dr commands for the changeset-only write mode (`dr visualize --with-changesets`).\n *\n * On top of the read-safe list, the agent can open and work in a changeset and stage new\n * elements, element updates and new relationships, and unstage one element it staged by\n * mistake. Committing, applying, reverting, discarding (which throws away every staged change,\n * the user's included) or deleting a changeset, and deleting elements or relationships, stay\n * with the user.\n *\n * `dr add`, `dr update` and `dr relationship add` write straight to the model when no\n * changeset is active, so the permissions alone don't keep writes in a changeset: the\n * agent also runs with `CHANGESET_ONLY_ENV` set, which makes the model refuse to save\n * (see core/changeset-only-guard.ts).\n */\nexport const CHANGESET_WRITE_COMMANDS: readonly string[] = Object.freeze([\n \"dr changeset create\",\n \"dr changeset activate\",\n \"dr changeset deactivate\",\n \"dr changeset status\",\n \"dr changeset list\",\n \"dr changeset show\",\n \"dr changeset staged\",\n \"dr changeset stage\",\n \"dr changeset unstage\",\n \"dr changeset preview\",\n \"dr changeset diff\",\n \"dr add\",\n \"dr update\",\n \"dr relationship add\",\n \"dr relationship list\",\n \"dr relationship show\",\n \"dr schema\",\n]);\n\nexport { CHANGESET_ONLY_ENV } from \"../core/changeset-only-guard.js\";\n\n/**\n * Claude Code --allowedTools value for changeset-only mode: the read-safe list plus one\n * `Bash(<cmd>:*)` rule per `CHANGESET_WRITE_COMMANDS` entry.\n */\nexport function formatForClaudeCodeWithChangesets(): string {\n return [formatForClaudeCode(), ...CHANGESET_WRITE_COMMANDS.map((command) => `Bash(${command}:*)`)].join(\",\");\n}\n\n/** The individual commands in a \"|\"-separated Bash scope, e.g. \"dr query|dr show\". */\nexport function bashCommands(scope: string): string[] {\n return scope\n .split(\"|\")\n .map((command) => command.trim())\n .filter((command) => command.length > 0);\n}\n\n/**\n * Convert read-safe permissions to GitHub Copilot CLI format\n *\n * GitHub Copilot CLI may support a --allowedTools flag (or equivalent) for granular permission control.\n * This function formats the permission list similarly to Claude Code for potential future Copilot versions\n * that support granular scoped permissions. If the installed Copilot version doesn't support this flag,\n * the caller should gracefully degrade (e.g., launch without the flag, with a warning).\n *\n * Format: \"Bash(dr query|dr show|dr list|...),Read(.),Read(documentation-robotics),Read(.dr)\"\n *\n * @returns String formatted for Copilot --allowedTools flag (if supported)\n */\nexport function formatForCopilot(): string {\n return formatForClaudeCode();\n}\n\n/**\n * Validate a permission array against read-safe constraints\n *\n * Internal function that validates any permission array. Used by tests to verify\n * that invalid configurations are properly rejected.\n *\n * @param permissions The permission array to validate\n * @throws {Error} If any constraint is violated\n */\nexport function validatePermissions(permissions: readonly ToolPermission[]): void {\n // Constraint 1: Non-empty allowlist\n if (!Array.isArray(permissions) || permissions.length === 0) {\n throw new Error(\n \"Permissions must be a non-empty array\"\n );\n }\n\n // Constraint 2: All permissions must have required fields with correct types\n for (let i = 0; i < permissions.length; i++) {\n const perm = permissions[i];\n\n if (typeof perm.name !== \"string\" || perm.name.length === 0) {\n throw new Error(\n `Permission[${i}].name must be a non-empty string, got: ${typeof perm.name}`\n );\n }\n\n if (typeof perm.description !== \"string\" || perm.description.length === 0) {\n throw new Error(\n `Permission[${i}].description must be a non-empty string, got: ${typeof perm.description}`\n );\n }\n\n if (typeof perm.allowsWrite !== \"boolean\") {\n throw new Error(\n `Permission[${i}].allowsWrite must be a boolean, got: ${typeof perm.allowsWrite}`\n );\n }\n }\n\n // Constraint 3: No permission should allow write operations (read-safe invariant)\n const writePerms = permissions.filter((p) => p.allowsWrite);\n if (writePerms.length > 0) {\n throw new Error(\n `Read-safe permissions must not allow write operations. Found ${writePerms.length} permission(s) with allowsWrite=true`\n );\n }\n\n // Constraint 4: Only Bash and Read tools allowed (allowlist validation, not denylist)\n const allowedToolNames = new Set([\"Bash\", \"Read\"]);\n const disallowedTools = permissions.filter(\n (p) => !allowedToolNames.has(p.name)\n );\n if (disallowedTools.length > 0) {\n throw new Error(\n `Read-safe permissions must only include Bash and Read tools. Found disallowed tool(s): ${disallowedTools.map((p) => p.name).join(\", \")}`\n );\n }\n\n // Constraint 5: Bash must be for dr CLI only with properly restricted scope (no wildcards or bare patterns)\n const bashPerms = permissions.filter((p) => p.name === \"Bash\");\n if (bashPerms.length > 0) {\n const invalidBash = bashPerms.filter((p) => {\n const hasValidDescription = p.description.toLowerCase().includes(\"dr\") && p.description.toLowerCase().includes(\"cli\");\n const hasValidScope = typeof p.scope === \"string\" && p.scope.length > 0;\n\n // Validate scope content: reject bare wildcards and unsafe patterns\n let hasValidScopeContent = true;\n if (hasValidScope) {\n const scope = p.scope;\n // Reject dangerous scope patterns: bare \"*\", \".\", or \"dr *\"\n if (scope === \"*\" || scope === \".\" || scope === \"dr *\" || scope.startsWith(\"dr *\")) {\n hasValidScopeContent = false;\n }\n // Ensure scope starts with \"dr \" for Bash tool\n if (!scope.startsWith(\"dr \")) {\n hasValidScopeContent = false;\n }\n }\n\n return !hasValidDescription || !hasValidScope || !hasValidScopeContent;\n });\n if (invalidBash.length > 0) {\n const errors = invalidBash.map((p) => {\n const issues = [];\n if (!p.description.toLowerCase().includes(\"dr\") || !p.description.toLowerCase().includes(\"cli\")) {\n issues.push(\"invalid description (must include 'dr' and 'cli')\");\n }\n if (typeof p.scope !== \"string\" || p.scope.length === 0) {\n issues.push(\"missing or empty scope\");\n } else {\n const scope = p.scope;\n if (scope === \"*\" || scope === \".\" || scope === \"dr *\" || scope.startsWith(\"dr *\")) {\n issues.push(\"scope uses dangerous wildcard pattern (must specify read-safe commands)\");\n }\n if (!scope.startsWith(\"dr \")) {\n issues.push(\"scope must start with 'dr ' for Bash tool\");\n }\n }\n return `${p.description} (${issues.join(\", \")})`;\n });\n throw new Error(\n `Bash permission must be for dr CLI only with properly restricted scope. Found invalid Bash permission(s): ${errors.join(\"; \")}`\n );\n }\n }\n\n // Constraint 6: Must have Read permission for codebase\n const codebaseRead = permissions.find(\n (p) => p.name === \"Read\" && p.description.toLowerCase().includes(\"codebase\")\n );\n if (!codebaseRead) {\n throw new Error(\"Read-safe permissions must include Read permission for codebase\");\n }\n\n // Constraint 7: Must have Read permission for documentation-robotics folder\n const docRoboticsRead = permissions.find(\n (p) => p.name === \"Read\" && p.description.includes(\"documentation-robotics\")\n );\n if (!docRoboticsRead) {\n throw new Error(\"Read-safe permissions must include Read permission for documentation-robotics folder\");\n }\n\n // Constraint 8: Must have Read permission for .dr folder\n const drFolderRead = permissions.find(\n (p) => p.name === \"Read\" && (p.scope === \".dr\" || p.description.includes(\".dr\"))\n );\n if (!drFolderRead) {\n throw new Error(\"Read-safe permissions must include Read permission for .dr folder\");\n }\n\n // All constraints passed\n}\n\n/**\n * Validate read-safe permission constraints\n *\n * Called during module initialization to verify the permission allowlist maintains\n * its safety invariants: all permissions are read-only, required tools are present,\n * and no dangerous tools are included.\n *\n * Throws an error if constraints are violated, preventing runtime with an invalid configuration.\n *\n * @throws {Error} If any constraint is violated\n */\nexport function validateReadSafeConstraints(): void {\n validatePermissions(DEFAULT_READ_SAFE_PERMISSIONS);\n}\n\n/**\n * Apply Copilot permissions to command arguments with graceful degradation\n *\n * Shared utility for both CopilotClient and VisualizationServer to probe\n * Copilot CLI capability and apply appropriate permission flags.\n * Handles both default mode (read-safe --allowedTools) and danger mode (--allow-all-tools).\n *\n * @param cmd The command array to modify (mutated in place)\n * @param variant Display name for error messages (e.g., \"gh copilot\" or \"copilot\")\n * @param copilotCommand The copilot CLI name (\"copilot\" or \"gh\")\n * @param withDanger Whether to use danger mode (--allow-all-tools)\n * @param onTelemetry Optional callback for telemetry logging\n * @param testSpawnSync Optional injected spawnSync for testing (allows mocking CLI probe)\n */\nexport function applyCopilotPermissions(\n cmd: string[],\n variant: string,\n copilotCommand: string,\n withDanger: boolean,\n onTelemetry?: (attr: string, value: any) => void,\n testSpawnSync?: typeof spawnSync\n): void {\n const spawnSyncFn = testSpawnSync || spawnSync;\n\n if (withDanger) {\n try {\n const helpResult = spawnSyncFn(\n copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n copilotCommand === \"copilot\" ? [\"--help\"] : [\"copilot\", \"--help\"],\n { stdio: \"pipe\", encoding: \"utf-8\", timeout: 1000 }\n );\n if (\n helpResult.stdout?.includes(\"--allow-all-tools\") ||\n helpResult.stdout?.includes(\"allow-all-tools\")\n ) {\n cmd.push(\"--allow-all-tools\");\n onTelemetry?.(\"process.allowAllToolsSupported\", true);\n } else {\n onTelemetry?.(\"process.allowAllToolsSupported\", false);\n console.warn(\n ansis.yellow(`Note: --allow-all-tools flag not supported by your ${variant} version`)\n );\n }\n } catch {\n onTelemetry?.(\"process.allowAllToolsCheckFailed\", true);\n console.warn(\n ansis.yellow(\n `WARNING: Could not verify --allow-all-tools support for ${variant}. ` +\n `Launching WITHOUT permission escalation. If you need full tool access, ` +\n `please ensure your ${variant} CLI is up-to-date.`\n )\n );\n }\n } else {\n try {\n const helpResult = spawnSyncFn(\n copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n copilotCommand === \"copilot\" ? [\"--help\"] : [\"copilot\", \"--help\"],\n { stdio: \"pipe\", encoding: \"utf-8\", timeout: 1000 }\n );\n\n const helpText = helpResult.stdout || \"\";\n const supportsAllowedTools =\n helpText.includes(\"--allowedTools\") ||\n helpText.includes(\"allowedTools\") ||\n helpText.includes(\"--allowed-tools\") ||\n helpText.includes(\"allowed-tools\");\n\n if (supportsAllowedTools) {\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n } else {\n // Flag not advertised in help, but still try to apply it optimistically\n // (fail-safe: try to restrict access even if we're not 100% sure the flag exists)\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n console.warn(\n ansis.yellow(\n `Note: Your ${variant} version doesn't advertise --allowedTools in help, ` +\n `but attempting to apply it. If not supported, ` +\n `please upgrade @github/copilot for granular permission controls.`\n )\n );\n }\n } catch {\n onTelemetry?.(\"process.readSafePermissionCheckFailed\", true);\n // Fail-closed approach: unable to verify support, so apply read-safe restrictions defensively\n // to maintain the read-safe guarantee even when the CLI probe fails.\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n console.warn(\n ansis.yellow(\n `WARNING: Could not verify read-safe permission support for ${variant}. ` +\n `Applying read-safe restrictions defensively. ` +\n `If this is not supported by your ${variant} version, please upgrade @github/copilot or copilot CLI.`\n )\n );\n }\n }\n}\n\n// Validate read-safe constraints during module initialization\nvalidateReadSafeConstraints();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parse as parseYaml } from "yaml";
|
|
7
|
+
import { CHANGESET_ONLY_ENV } from "../core/changeset-only-guard.js";
|
|
8
|
+
const DR_MCP_SERVER_NAME = "dr", DR_MCP_ROOT_PATH_ENV = "DR_MCP_ROOT_PATH", DR_CLI_ENTRY_ENV = "DR_CLI_ENTRY", DR_MCP_READ_TOOLS = Object.freeze([
|
|
9
|
+
"model_list",
|
|
10
|
+
"model_show",
|
|
11
|
+
"model_search",
|
|
12
|
+
"model_stats",
|
|
13
|
+
"model_info",
|
|
14
|
+
"model_trace",
|
|
15
|
+
"model_validate",
|
|
16
|
+
"model_export",
|
|
17
|
+
"model_reload",
|
|
18
|
+
"changeset_list",
|
|
19
|
+
"changeset_show",
|
|
20
|
+
"changeset_impact",
|
|
21
|
+
"changeset_brief",
|
|
22
|
+
"changeset_verify",
|
|
23
|
+
"spec_type",
|
|
24
|
+
"annotation_list",
|
|
25
|
+
"annotation_get",
|
|
26
|
+
"chat_status",
|
|
27
|
+
"project_status"
|
|
28
|
+
]), DR_MCP_CHANGESET_TOOLS = Object.freeze([
|
|
29
|
+
"changeset_create",
|
|
30
|
+
"changeset_activate",
|
|
31
|
+
"changeset_deactivate",
|
|
32
|
+
"model_add",
|
|
33
|
+
"model_update",
|
|
34
|
+
"model_delete",
|
|
35
|
+
"relationship_add",
|
|
36
|
+
"relationship_delete",
|
|
37
|
+
"changeset_update",
|
|
38
|
+
"changeset_unstage"
|
|
39
|
+
]);
|
|
40
|
+
function drMcpToolRule(tool) {
|
|
41
|
+
return `mcp__${DR_MCP_SERVER_NAME}__${tool}`;
|
|
42
|
+
}
|
|
43
|
+
function drMcpAllowedTools(mode) {
|
|
44
|
+
return mode === "danger" ? [] : (mode === "changesets" ? [...DR_MCP_READ_TOOLS, ...DR_MCP_CHANGESET_TOOLS] : [...DR_MCP_READ_TOOLS]).map(drMcpToolRule);
|
|
45
|
+
}
|
|
46
|
+
function resolveDrCommand(env = process.env, exists = existsSync) {
|
|
47
|
+
const override = env[DR_CLI_ENTRY_ENV];
|
|
48
|
+
if (override && exists(override))
|
|
49
|
+
return { command: process.execPath, args: [override] };
|
|
50
|
+
const bundled = fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
51
|
+
return exists(bundled) ? { command: process.execPath, args: [bundled] } : { command: "dr", args: [] };
|
|
52
|
+
}
|
|
53
|
+
function buildDrMcpConfig(options) {
|
|
54
|
+
const env = {
|
|
55
|
+
[DR_MCP_ROOT_PATH_ENV]: options.rootPath
|
|
56
|
+
};
|
|
57
|
+
return options.changesetOnly && (env[CHANGESET_ONLY_ENV] = "true"), {
|
|
58
|
+
mcpServers: {
|
|
59
|
+
[DR_MCP_SERVER_NAME]: {
|
|
60
|
+
type: "stdio",
|
|
61
|
+
command: options.drCommand.command,
|
|
62
|
+
args: [...options.drCommand.args, "mcp"],
|
|
63
|
+
env
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function prepareDrMcpLaunch(options) {
|
|
69
|
+
const config = buildDrMcpConfig({
|
|
70
|
+
drCommand: options.drCommand ?? resolveDrCommand(),
|
|
71
|
+
rootPath: options.rootPath,
|
|
72
|
+
changesetOnly: options.changesetOnly
|
|
73
|
+
}), dir = await mkdtemp(join(options.tempRoot ?? tmpdir(), "dr-drbot-mcp-"));
|
|
74
|
+
let removed = !1;
|
|
75
|
+
const cleanup = async () => {
|
|
76
|
+
if (!removed) {
|
|
77
|
+
removed = !0;
|
|
78
|
+
try {
|
|
79
|
+
await rm(dir, { recursive: !0, force: !0 });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn(`[DrBot] Could not remove MCP config directory ${dir}: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
await chmod(dir, 448);
|
|
87
|
+
const configPath = join(dir, "mcp.json");
|
|
88
|
+
return await writeFile(configPath, JSON.stringify(config, null, 2), {
|
|
89
|
+
mode: 384
|
|
90
|
+
}), { configPath, cleanup };
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw await cleanup(), error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function drBotMcpPrompt(mode) {
|
|
96
|
+
const lines = [
|
|
97
|
+
`The Documentation Robotics MCP server "${DR_MCP_SERVER_NAME}" is connected for the model this session serves (tools named mcp__${DR_MCP_SERVER_NAME}__*). Prefer these tools to running dr through Bash: model_search/model_show/model_list to look things up, spec_type to check what a type takes and which relationships the catalog allows, changeset_impact and changeset_brief to pressure-test a change.`
|
|
98
|
+
];
|
|
99
|
+
return mode === "changesets" ? lines.push("This session may change the model only through a changeset. Call changeset_create first (it activates the new changeset), then stage changes with model_add, model_update, model_delete, relationship_add and relationship_delete, and review them with changeset_brief. Writes with no active changeset are refused. Never commit or apply a changeset: the user reviews and commits it.") : mode === "read-safe" && lines.push("This session is read-only: answer from the model, and do not try to change it."), lines.join(`
|
|
100
|
+
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
const DR_ARCHITECT_AGENT = "dr-architect";
|
|
104
|
+
function parseAgentFrontmatterTools(markdown) {
|
|
105
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
|
|
106
|
+
if (!match)
|
|
107
|
+
return null;
|
|
108
|
+
const lines = match[1].split(/\r?\n/), index = lines.findIndex((line) => /^tools\s*:/.test(line));
|
|
109
|
+
if (index === -1)
|
|
110
|
+
return null;
|
|
111
|
+
const unquote = (value) => value.trim().replace(/^["']|["']$/g, "").trim(), inline = lines[index].replace(/^tools\s*:/, "").trim();
|
|
112
|
+
if (inline)
|
|
113
|
+
return inline.replace(/^\[|\]$/g, "").split(",").map(unquote).filter(Boolean);
|
|
114
|
+
const tools = [];
|
|
115
|
+
for (const line of lines.slice(index + 1)) {
|
|
116
|
+
const item = /^\s+-\s*(.+)$/.exec(line);
|
|
117
|
+
if (!item)
|
|
118
|
+
break;
|
|
119
|
+
tools.push(unquote(item[1]));
|
|
120
|
+
}
|
|
121
|
+
return tools;
|
|
122
|
+
}
|
|
123
|
+
function agentDirectories(rootPath, options = {}) {
|
|
124
|
+
const configDir = (options.env ?? process.env).CLAUDE_CONFIG_DIR, userDir = configDir ? resolve(configDir, "agents") : join(options.homeDir ?? homedir(), ".claude", "agents"), dirs = [];
|
|
125
|
+
let dir = resolve(rootPath);
|
|
126
|
+
for (; ; ) {
|
|
127
|
+
const agents = join(dir, ".claude", "agents");
|
|
128
|
+
if (agents !== userDir && dirs.push({ dir: agents, source: "project" }), existsSync(join(dir, ".git")))
|
|
129
|
+
break;
|
|
130
|
+
const parent = dirname(dir);
|
|
131
|
+
if (parent === dir)
|
|
132
|
+
break;
|
|
133
|
+
dir = parent;
|
|
134
|
+
}
|
|
135
|
+
return dirs.push({ dir: userDir, source: "user" }), dirs;
|
|
136
|
+
}
|
|
137
|
+
function agentFileName(path) {
|
|
138
|
+
try {
|
|
139
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(readFileSync(path, "utf8")), frontmatter = match ? parseYaml(match[1]) : null;
|
|
140
|
+
if (typeof frontmatter?.name == "string" && frontmatter.name.trim())
|
|
141
|
+
return frontmatter.name.trim();
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
return basename(path, ".md");
|
|
145
|
+
}
|
|
146
|
+
function markdownFiles(dir) {
|
|
147
|
+
let entries;
|
|
148
|
+
try {
|
|
149
|
+
entries = readdirSync(dir, { withFileTypes: !0 });
|
|
150
|
+
} catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const files = [];
|
|
154
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
155
|
+
const path = join(dir, entry.name);
|
|
156
|
+
entry.isDirectory() ? files.push(...markdownFiles(path)) : entry.name.endsWith(".md") && files.push(path);
|
|
157
|
+
}
|
|
158
|
+
return files;
|
|
159
|
+
}
|
|
160
|
+
function findInstalledAgentFile(rootPath, options = {}) {
|
|
161
|
+
for (const { dir, source } of agentDirectories(rootPath, options)) {
|
|
162
|
+
const path = markdownFiles(dir).find((file) => agentFileName(file) === DR_ARCHITECT_AGENT);
|
|
163
|
+
if (path)
|
|
164
|
+
return { path, source };
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function bundledAgentFile(exists = existsSync) {
|
|
169
|
+
const relativePaths = [
|
|
170
|
+
`../integrations/claude_code/agents/${DR_ARCHITECT_AGENT}.md`,
|
|
171
|
+
`../../../integrations/claude_code/agents/${DR_ARCHITECT_AGENT}.md`
|
|
172
|
+
];
|
|
173
|
+
for (const relativePath of relativePaths) {
|
|
174
|
+
const candidate = fileURLToPath(new URL(relativePath, import.meta.url));
|
|
175
|
+
if (exists(candidate))
|
|
176
|
+
return candidate;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
function resolveDrBotAgent(rootPath, options = {}) {
|
|
181
|
+
const installed = findInstalledAgentFile(rootPath, options);
|
|
182
|
+
if (installed)
|
|
183
|
+
return installed;
|
|
184
|
+
const bundledPath = options.bundledPath === void 0 ? bundledAgentFile() : options.bundledPath;
|
|
185
|
+
return bundledPath ? { path: bundledPath, source: "bundled" } : null;
|
|
186
|
+
}
|
|
187
|
+
function agentDefinitionFromMarkdown(markdown) {
|
|
188
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(markdown);
|
|
189
|
+
if (!match)
|
|
190
|
+
throw new Error("agent file has no frontmatter");
|
|
191
|
+
const frontmatter = parseYaml(match[1]) ?? {};
|
|
192
|
+
if (typeof frontmatter.description != "string" || !frontmatter.description.trim())
|
|
193
|
+
throw new Error("agent file has no description");
|
|
194
|
+
const definition = {
|
|
195
|
+
description: frontmatter.description,
|
|
196
|
+
prompt: markdown.slice(match[0].length).trim()
|
|
197
|
+
}, tools = parseAgentFrontmatterTools(markdown);
|
|
198
|
+
return tools !== null && (definition.tools = tools), typeof frontmatter.model == "string" && (definition.model = frontmatter.model), definition;
|
|
199
|
+
}
|
|
200
|
+
async function drBotAgentArgs(agent) {
|
|
201
|
+
if (agent?.source !== "bundled")
|
|
202
|
+
return [];
|
|
203
|
+
const definition = agentDefinitionFromMarkdown(await readFile(agent.path, "utf8"));
|
|
204
|
+
return ["--agents", JSON.stringify({ [DR_ARCHITECT_AGENT]: definition })];
|
|
205
|
+
}
|
|
206
|
+
function drMcpToolsForMode(mode) {
|
|
207
|
+
return mode === "danger" ? [...DR_MCP_READ_TOOLS, ...DR_MCP_CHANGESET_TOOLS].map(drMcpToolRule) : drMcpAllowedTools(mode);
|
|
208
|
+
}
|
|
209
|
+
async function checkAgentMcpTools(agent, mode) {
|
|
210
|
+
if (!agent)
|
|
211
|
+
return null;
|
|
212
|
+
const { path, source } = agent, tools = parseAgentFrontmatterTools(await readFile(path, "utf8"));
|
|
213
|
+
if (tools === null)
|
|
214
|
+
return null;
|
|
215
|
+
const listed = new Set(tools);
|
|
216
|
+
if (listed.has(`mcp__${DR_MCP_SERVER_NAME}`) || listed.has(`mcp__${DR_MCP_SERVER_NAME}__*`))
|
|
217
|
+
return null;
|
|
218
|
+
const required = drMcpToolsForMode(mode), missing = required.filter((rule) => !listed.has(rule));
|
|
219
|
+
return missing.length === 0 ? null : { path, source, missing, noneVisible: missing.length === required.length };
|
|
220
|
+
}
|
|
221
|
+
async function checkInstalledAgentMcpTools(rootPath, mode, options = {}) {
|
|
222
|
+
return checkAgentMcpTools(resolveDrBotAgent(rootPath, options), mode);
|
|
223
|
+
}
|
|
224
|
+
async function prepareDrBotAgent(rootPath, mode, options = {}) {
|
|
225
|
+
const agent = resolveDrBotAgent(rootPath, options);
|
|
226
|
+
let args;
|
|
227
|
+
try {
|
|
228
|
+
args = await drBotAgentArgs(agent);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
const missing = drMcpToolsForMode(mode);
|
|
231
|
+
return {
|
|
232
|
+
agent,
|
|
233
|
+
args: [],
|
|
234
|
+
check: {
|
|
235
|
+
path: agent.path,
|
|
236
|
+
source: "bundled",
|
|
237
|
+
missing,
|
|
238
|
+
noneVisible: !0,
|
|
239
|
+
error: error instanceof Error ? error.message : String(error)
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return { agent, args, check: await checkAgentMcpTools(agent, mode) };
|
|
244
|
+
}
|
|
245
|
+
export {
|
|
246
|
+
DR_ARCHITECT_AGENT,
|
|
247
|
+
DR_CLI_ENTRY_ENV,
|
|
248
|
+
DR_MCP_CHANGESET_TOOLS,
|
|
249
|
+
DR_MCP_READ_TOOLS,
|
|
250
|
+
DR_MCP_ROOT_PATH_ENV,
|
|
251
|
+
DR_MCP_SERVER_NAME,
|
|
252
|
+
agentDefinitionFromMarkdown,
|
|
253
|
+
agentDirectories,
|
|
254
|
+
buildDrMcpConfig,
|
|
255
|
+
bundledAgentFile,
|
|
256
|
+
checkAgentMcpTools,
|
|
257
|
+
checkInstalledAgentMcpTools,
|
|
258
|
+
drBotAgentArgs,
|
|
259
|
+
drBotMcpPrompt,
|
|
260
|
+
drMcpAllowedTools,
|
|
261
|
+
drMcpToolRule,
|
|
262
|
+
findInstalledAgentFile,
|
|
263
|
+
parseAgentFrontmatterTools,
|
|
264
|
+
prepareDrBotAgent,
|
|
265
|
+
prepareDrMcpLaunch,
|
|
266
|
+
resolveDrBotAgent,
|
|
267
|
+
resolveDrCommand
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=drbot-mcp.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/coding-agents/drbot-mcp.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * DrBot on MCP: connect the viewer's chat agent to the dr MCP server.\n *\n * `dr visualize` launches Claude Code as DrBot (server/server.ts). Rather than having the\n * agent shell out to `dr` through Bash, the launch hands Claude Code an MCP config that runs\n * this same dr installation's `dr mcp` over stdio, pointed at the served model, so DrBot plans\n * changes with the change-modeling tools (changeset_create, spec_type, model_add,\n * relationship_add, changeset_brief) the way any other agent does.\n *\n * `dr mcp` over stdio needs no API key (the client spawns it as the same user). The config goes\n * in a private temp file (0600, in a 0700 directory under the OS temp dir), never in the repo or\n * the model directory, and is removed when the agent process exits.\n *\n * Tolerance: if anything here fails (no temp dir), the caller launches DrBot without\n * MCP and it falls back to the read-safe Bash rules; the failure is logged, not swallowed.\n */\n\nimport { existsSync, readdirSync, readFileSync, type Dirent } from \"node:fs\";\nimport { chmod, mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { parse as parseYaml } from \"yaml\";\nimport { CHANGESET_ONLY_ENV } from \"../core/changeset-only-guard.js\";\n\n/** Name the dr MCP server is registered under; Claude Code exposes its tools as mcp__dr__<tool>. */\nexport const DR_MCP_SERVER_NAME = \"dr\";\n\n/** Env var `dr mcp` honours to serve a model from a directory other than its cwd. */\nexport const DR_MCP_ROOT_PATH_ENV = \"DR_MCP_ROOT_PATH\";\n\n/** Env var that overrides the dr CLI entry script DrBot's MCP server runs (tests, custom installs). */\nexport const DR_CLI_ENTRY_ENV = \"DR_CLI_ENTRY\";\n\n/** Tools that only read: allowed in every DrBot mode. */\nexport const DR_MCP_READ_TOOLS: readonly string[] = Object.freeze([\n \"model_list\",\n \"model_show\",\n \"model_search\",\n \"model_stats\",\n \"model_info\",\n \"model_trace\",\n \"model_validate\",\n \"model_export\",\n \"model_reload\",\n \"changeset_list\",\n \"changeset_show\",\n \"changeset_impact\",\n \"changeset_brief\",\n \"changeset_verify\",\n \"spec_type\",\n \"annotation_list\",\n \"annotation_get\",\n \"chat_status\",\n \"project_status\",\n]);\n\n/**\n * Tools added in changeset-only mode (`dr visualize --with-changesets`): open and work in a\n * changeset and stage element and relationship changes. The MCP server runs with\n * DR_CHANGESET_ONLY set, so any of these that would write the base model (no active changeset)\n * is refused there (mcp/changeset-only.ts). changeset_update and changeset_unstage revise the\n * plan. Left out on purpose:\n * - changeset_commit: landing needs a person to confirm it, and the server refuses it in\n * changeset-only mode anyway. Apply is not an MCP tool.\n * - changeset_discard: it deletes the staged changes, and abandoning a changeset is the user's call.\n * - project_init and project_upgrade: they create or migrate the model outside any changeset.\n * - review_open: DrBot already lives in the viewer it would open.\n * - codebase_extract: it writes a changeset from the code, a run the user starts from their own\n * session. The server still allows it in changeset-only mode, since it only writes a changeset.\n * - chat_launch (an agent launching another agent) and the annotation writers.\n */\nexport const DR_MCP_CHANGESET_TOOLS: readonly string[] = Object.freeze([\n \"changeset_create\",\n \"changeset_activate\",\n \"changeset_deactivate\",\n \"model_add\",\n \"model_update\",\n \"model_delete\",\n \"relationship_add\",\n \"relationship_delete\",\n \"changeset_update\",\n \"changeset_unstage\",\n]);\n\nexport type DrBotMode = \"read-safe\" | \"changesets\" | \"danger\";\n\n/** Claude Code permission rule for one dr MCP tool. */\nexport function drMcpToolRule(tool: string): string {\n return `mcp__${DR_MCP_SERVER_NAME}__${tool}`;\n}\n\n/** The mcp__dr__* rules DrBot may use without prompting in a mode (danger mode skips permissions). */\nexport function drMcpAllowedTools(mode: DrBotMode): string[] {\n if (mode === \"danger\") return [];\n const tools = mode === \"changesets\" ? [...DR_MCP_READ_TOOLS, ...DR_MCP_CHANGESET_TOOLS] : [...DR_MCP_READ_TOOLS];\n return tools.map(drMcpToolRule);\n}\n\n/** How to run this dr installation: a runtime plus the CLI entry script, or `dr` from PATH. */\nexport interface DrCommand {\n command: string;\n args: string[];\n}\n\n/**\n * Resolves the command that runs this same dr installation. The built CLI lives next to this\n * module's parent (dist/coding-agents/ \u2192 dist/cli.js) and runs under whichever runtime is\n * running now; DR_CLI_ENTRY overrides the script. Falls back to `dr` on PATH.\n */\nexport function resolveDrCommand(\n env: NodeJS.ProcessEnv = process.env,\n exists: (path: string) => boolean = existsSync\n): DrCommand {\n const override = env[DR_CLI_ENTRY_ENV];\n if (override && exists(override)) {\n return { command: process.execPath, args: [override] };\n }\n const bundled = fileURLToPath(new URL(\"../cli.js\", import.meta.url));\n if (exists(bundled)) {\n return { command: process.execPath, args: [bundled] };\n }\n return { command: \"dr\", args: [] };\n}\n\nexport interface DrMcpConfigOptions {\n drCommand: DrCommand;\n /** Project root of the served model. */\n rootPath: string;\n /** Run the server in changeset-only mode (DR_CHANGESET_ONLY=true). */\n changesetOnly: boolean;\n}\n\n/** The Claude Code MCP config (the --mcp-config file's content) for DrBot's dr server. */\nexport function buildDrMcpConfig(options: DrMcpConfigOptions): {\n mcpServers: Record<\n string,\n {\n type: \"stdio\";\n command: string;\n args: string[];\n env: Record<string, string>;\n }\n >;\n} {\n const env: Record<string, string> = {\n [DR_MCP_ROOT_PATH_ENV]: options.rootPath,\n };\n if (options.changesetOnly) {\n env[CHANGESET_ONLY_ENV] = \"true\";\n }\n return {\n mcpServers: {\n [DR_MCP_SERVER_NAME]: {\n type: \"stdio\",\n command: options.drCommand.command,\n args: [...options.drCommand.args, \"mcp\"],\n env,\n },\n },\n };\n}\n\n/** A written MCP config and the function that removes it again (safe to call more than once). */\nexport interface DrMcpLaunch {\n configPath: string;\n cleanup: () => Promise<void>;\n}\n\nexport interface PrepareDrMcpLaunchOptions {\n rootPath: string;\n changesetOnly: boolean;\n drCommand?: DrCommand;\n /** Parent directory for the private config directory; defaults to the OS temp dir. */\n tempRoot?: string;\n}\n\n/**\n * Writes DrBot's MCP config to a private temp file. Throws on failure; the caller decides to\n * launch without MCP.\n */\nexport async function prepareDrMcpLaunch(options: PrepareDrMcpLaunchOptions): Promise<DrMcpLaunch> {\n const config = buildDrMcpConfig({\n drCommand: options.drCommand ?? resolveDrCommand(),\n rootPath: options.rootPath,\n changesetOnly: options.changesetOnly,\n });\n\n const dir = await mkdtemp(join(options.tempRoot ?? tmpdir(), \"dr-drbot-mcp-\"));\n let removed = false;\n const cleanup = async () => {\n if (removed) return;\n removed = true;\n try {\n await rm(dir, { recursive: true, force: true });\n } catch (error) {\n console.warn(`[DrBot] Could not remove MCP config directory ${dir}: ${(error as Error).message}`);\n }\n };\n\n try {\n await chmod(dir, 0o700);\n const configPath = join(dir, \"mcp.json\");\n await writeFile(configPath, JSON.stringify(config, null, 2), {\n mode: 0o600,\n });\n return { configPath, cleanup };\n } catch (error) {\n await cleanup();\n throw error;\n }\n}\n\n/** Appended to DrBot's system prompt when the dr MCP server is connected. */\nexport function drBotMcpPrompt(mode: DrBotMode): string {\n const lines = [\n `The Documentation Robotics MCP server \"${DR_MCP_SERVER_NAME}\" is connected for the model this session serves ` +\n `(tools named mcp__${DR_MCP_SERVER_NAME}__*). Prefer these tools to running dr through Bash: ` +\n \"model_search/model_show/model_list to look things up, spec_type to check what a type takes and which \" +\n \"relationships the catalog allows, changeset_impact and changeset_brief to pressure-test a change.\",\n ];\n if (mode === \"changesets\") {\n lines.push(\n \"This session may change the model only through a changeset. Call changeset_create first (it activates the \" +\n \"new changeset), then stage changes with model_add, model_update, model_delete, relationship_add and \" +\n \"relationship_delete, and review them with changeset_brief. Writes with no active changeset are refused. \" +\n \"Never commit or apply a changeset: the user reviews and commits it.\"\n );\n } else if (mode === \"read-safe\") {\n lines.push(\"This session is read-only: answer from the model, and do not try to change it.\");\n }\n return lines.join(\"\\n\\n\");\n}\n\n/** The Claude Code agent DrBot runs as (`claude --agent dr-architect`). */\nexport const DR_ARCHITECT_AGENT = \"dr-architect\";\n\n/**\n * The tool names in an agent file's frontmatter `tools:` field, or null when the file has no\n * `tools:` field (Claude Code then gives the agent every tool, MCP tools included). Accepts the\n * comma-separated form, a flow list (`[a, b]`) and a block list.\n */\nexport function parseAgentFrontmatterTools(markdown: string): string[] | null {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(markdown);\n if (!match) return null;\n const lines = match[1].split(/\\r?\\n/);\n const index = lines.findIndex((line) => /^tools\\s*:/.test(line));\n if (index === -1) return null;\n const unquote = (value: string) =>\n value\n .trim()\n .replace(/^[\"']|[\"']$/g, \"\")\n .trim();\n const inline = lines[index].replace(/^tools\\s*:/, \"\").trim();\n if (inline) {\n return inline\n .replace(/^\\[|\\]$/g, \"\")\n .split(\",\")\n .map(unquote)\n .filter(Boolean);\n }\n const tools: string[] = [];\n for (const line of lines.slice(index + 1)) {\n const item = /^\\s+-\\s*(.+)$/.exec(line);\n if (!item) break;\n tools.push(unquote(item[1]));\n }\n return tools;\n}\n\n/**\n * Where Claude Code looks for a session's agents, and what DrBot passes it.\n *\n * Checked against claude 2.1.321 (#1044): a session started in a directory loads every `.md` under\n * `.claude/agents/` (subfolders included) of that directory and each parent, up to the git root\n * (or the filesystem root outside a repo), then the user folder (`$CLAUDE_CONFIG_DIR/agents`, or\n * `~/.claude/agents`), then plugins. An agent's name is its frontmatter `name:`, or the file name\n * without one. For `--agent dr-architect` the nearest folder wins; the dr plugin's own copy,\n * `dr:dr-architect`, comes last and names the plugin's server's tools (mcp__plugin_dr_dr__*),\n * which DrBot's MCP server (named `dr`) doesn't serve.\n */\nexport interface AgentLookupOptions {\n /** Home directory for `~/.claude/agents`; defaults to the OS home. */\n homeDir?: string;\n /** Environment for CLAUDE_CONFIG_DIR; defaults to process.env. */\n env?: NodeJS.ProcessEnv;\n /** The bundled dr-architect source; defaults to bundledAgentFile(). Null: there is none. */\n bundledPath?: string | null;\n}\n\n/** An agents folder Claude Code reads, and whether it counts as the project's or the user's. */\nexport interface AgentDirectory {\n dir: string;\n source: \"project\" | \"user\";\n}\n\n/** The agents folders a session started in `rootPath` reads, highest priority first. */\nexport function agentDirectories(rootPath: string, options: AgentLookupOptions = {}): AgentDirectory[] {\n const env = options.env ?? process.env;\n const configDir = env.CLAUDE_CONFIG_DIR;\n const userDir = configDir\n ? resolve(configDir, \"agents\")\n : join(options.homeDir ?? homedir(), \".claude\", \"agents\");\n const dirs: AgentDirectory[] = [];\n let dir = resolve(rootPath);\n for (;;) {\n const agents = join(dir, \".claude\", \"agents\");\n if (agents !== userDir) dirs.push({ dir: agents, source: \"project\" });\n if (existsSync(join(dir, \".git\"))) break;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n dirs.push({ dir: userDir, source: \"user\" });\n return dirs;\n}\n\n/** The name Claude Code gives an agent file: frontmatter `name:`, else the file name. */\nfunction agentFileName(path: string): string {\n try {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(readFileSync(path, \"utf8\"));\n const frontmatter = match ? (parseYaml(match[1]) as Record<string, unknown> | null) : null;\n if (typeof frontmatter?.name === \"string\" && frontmatter.name.trim()) return frontmatter.name.trim();\n } catch {\n // Unreadable or malformed frontmatter: Claude Code falls back to the file name too.\n }\n return basename(path, \".md\");\n}\n\n/** Every .md file under a folder, subfolders included, in a stable order. Empty when it doesn't exist. */\nfunction markdownFiles(dir: string): string[] {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const files: string[] = [];\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const path = join(dir, entry.name);\n if (entry.isDirectory()) files.push(...markdownFiles(path));\n else if (entry.name.endsWith(\".md\")) files.push(path);\n }\n return files;\n}\n\n/** An installed dr-architect Claude Code would load before the plugin's, or null. */\nexport function findInstalledAgentFile(\n rootPath: string,\n options: AgentLookupOptions = {}\n): { path: string; source: \"project\" | \"user\" } | null {\n for (const { dir, source } of agentDirectories(rootPath, options)) {\n const path = markdownFiles(dir).find((file) => agentFileName(file) === DR_ARCHITECT_AGENT);\n if (path) return { path, source };\n }\n return null;\n}\n\n/**\n * The dr-architect source shipped with this CLI (the file `dr claude install` copies), with its\n * mcp__dr__* tool names. Built CLI: dist/coding-agents/ \u2192 dist/integrations/; from source:\n * cli/src/coding-agents/ \u2192 integrations/. Null when neither exists.\n */\nexport function bundledAgentFile(exists: (path: string) => boolean = existsSync): string | null {\n const relativePaths = [\n `../integrations/claude_code/agents/${DR_ARCHITECT_AGENT}.md`,\n `../../../integrations/claude_code/agents/${DR_ARCHITECT_AGENT}.md`,\n ];\n for (const relativePath of relativePaths) {\n const candidate = fileURLToPath(new URL(relativePath, import.meta.url));\n if (exists(candidate)) return candidate;\n }\n return null;\n}\n\n/** The dr-architect copy DrBot runs, and where it comes from. */\nexport interface DrBotAgent {\n path: string;\n source: \"project\" | \"user\" | \"bundled\";\n}\n\n/**\n * The dr-architect copy DrBot runs in `rootPath`: an installed project or user copy, else the\n * bundled source (passed with --agents, which outranks the plugin's copy). Null when there is\n * none at all.\n */\nexport function resolveDrBotAgent(rootPath: string, options: AgentLookupOptions = {}): DrBotAgent | null {\n const installed = findInstalledAgentFile(rootPath, options);\n if (installed) return installed;\n const bundledPath = options.bundledPath === undefined ? bundledAgentFile() : options.bundledPath;\n return bundledPath ? { path: bundledPath, source: \"bundled\" } : null;\n}\n\n/** A Claude Code --agents entry: what an agent file's frontmatter and body say. */\nexport interface AgentDefinition {\n description: string;\n prompt: string;\n tools?: string[];\n model?: string;\n}\n\n/** Turns an agent markdown file into a --agents definition. Throws without frontmatter or a description. */\nexport function agentDefinitionFromMarkdown(markdown: string): AgentDefinition {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/.exec(markdown);\n if (!match) throw new Error(\"agent file has no frontmatter\");\n const frontmatter = (parseYaml(match[1]) ?? {}) as Record<string, unknown>;\n if (typeof frontmatter.description !== \"string\" || !frontmatter.description.trim()) {\n throw new Error(\"agent file has no description\");\n }\n const definition: AgentDefinition = {\n description: frontmatter.description,\n prompt: markdown.slice(match[0].length).trim(),\n };\n const tools = parseAgentFrontmatterTools(markdown);\n if (tools !== null) definition.tools = tools;\n if (typeof frontmatter.model === \"string\") definition.model = frontmatter.model;\n return definition;\n}\n\n/**\n * Extra `claude` arguments that make `--agent dr-architect` run `agent`. Only the bundled copy\n * needs them: `--agents` outranks every installed agent, the plugin's included. Throws when the\n * bundled file can't be read or parsed.\n */\nexport async function drBotAgentArgs(agent: DrBotAgent | null): Promise<string[]> {\n if (agent?.source !== \"bundled\") return [];\n const definition = agentDefinitionFromMarkdown(await readFile(agent.path, \"utf8\"));\n return [\"--agents\", JSON.stringify({ [DR_ARCHITECT_AGENT]: definition })];\n}\n\n/** The dr MCP tools a mode uses that the dr-architect DrBot runs can't see. */\nexport interface AgentMcpToolCheck {\n /** The agent file that was checked. */\n path: string;\n /** Where that copy comes from. */\n source: DrBotAgent[\"source\"];\n /** The mcp__dr__* rules the mode uses that the agent's `tools:` field leaves out. */\n missing: string[];\n /** True when the agent can see none of them (a copy installed before DrBot used MCP). */\n noneVisible: boolean;\n /**\n * Set when the bundled copy couldn't be passed with --agents. Claude Code then runs whatever it\n * finds (the plugin's copy, say), so DrBot can't count on any dr tool: noneVisible is true.\n */\n error?: string;\n}\n\n/** Every dr MCP tool DrBot can use in a mode (danger mode may use them all). */\nfunction drMcpToolsForMode(mode: DrBotMode): string[] {\n return mode === \"danger\"\n ? [...DR_MCP_READ_TOOLS, ...DR_MCP_CHANGESET_TOOLS].map(drMcpToolRule)\n : drMcpAllowedTools(mode);\n}\n\n/**\n * Checks that an agent file lists the dr MCP tools DrBot's mode uses. An agent's `tools:` field\n * limits what it can call, so a copy installed before DrBot moved to MCP can't see the server at\n * all, whatever --allowedTools says. Null when there's nothing to report: no agent, no `tools:`\n * field, a server-wide `mcp__dr` entry, or every tool listed.\n */\nexport async function checkAgentMcpTools(\n agent: DrBotAgent | null,\n mode: DrBotMode\n): Promise<AgentMcpToolCheck | null> {\n if (!agent) return null;\n const { path, source } = agent;\n const tools = parseAgentFrontmatterTools(await readFile(path, \"utf8\"));\n if (tools === null) return null;\n const listed = new Set(tools);\n if (listed.has(`mcp__${DR_MCP_SERVER_NAME}`) || listed.has(`mcp__${DR_MCP_SERVER_NAME}__*`)) return null;\n const required = drMcpToolsForMode(mode);\n const missing = required.filter((rule) => !listed.has(rule));\n if (missing.length === 0) return null;\n return { path, source, missing, noneVisible: missing.length === required.length };\n}\n\n/** checkAgentMcpTools for the copy resolveDrBotAgent picks in `rootPath`. */\nexport async function checkInstalledAgentMcpTools(\n rootPath: string,\n mode: DrBotMode,\n options: AgentLookupOptions = {}\n): Promise<AgentMcpToolCheck | null> {\n return checkAgentMcpTools(resolveDrBotAgent(rootPath, options), mode);\n}\n\n/** The dr-architect DrBot runs: the extra claude arguments and the tool check, from one lookup. */\nexport interface DrBotAgentLaunch {\n agent: DrBotAgent | null;\n /** `--agents <json>` for the bundled copy, else empty. */\n args: string[];\n check: AgentMcpToolCheck | null;\n}\n\n/**\n * Resolves DrBot's dr-architect once and derives both the launch arguments and the tool check\n * from it. When the bundled copy can't be passed, the check reports that no dr tool is visible\n * (with `error`) rather than passing against a file claude won't run. Throws if an installed\n * copy can't be read.\n */\nexport async function prepareDrBotAgent(\n rootPath: string,\n mode: DrBotMode,\n options: AgentLookupOptions = {}\n): Promise<DrBotAgentLaunch> {\n const agent = resolveDrBotAgent(rootPath, options);\n let args: string[];\n try {\n args = await drBotAgentArgs(agent);\n } catch (error) {\n const missing = drMcpToolsForMode(mode);\n return {\n agent,\n args: [],\n check: {\n path: agent!.path,\n source: \"bundled\",\n missing,\n noneVisible: true,\n error: error instanceof Error ? error.message : String(error),\n },\n };\n }\n return { agent, args, check: await checkAgentMcpTools(agent, mode) };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/commands/add.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Model } from "../core/model.js";
|
|
|
3
3
|
import { Layer } from "../core/layer.js";
|
|
4
4
|
import { Element } from "../core/element.js";
|
|
5
5
|
import { MutationHandler } from "../core/mutation-handler.js";
|
|
6
|
-
import { validateSourceReferenceOptions, buildSourceReference } from "../utils/source-reference.js";
|
|
6
|
+
import { validateSourceReferenceOptions, resolveSourceReferenceOptions, buildSourceReference } from "../utils/source-reference.js";
|
|
7
|
+
import { schemaLookupHint } from "../utils/error-hints.js";
|
|
7
8
|
import { SchemaValidator } from "../validators/schema-validator.js";
|
|
8
9
|
import { getActiveSpan } from "../telemetry/index.js";
|
|
9
10
|
import { generateElementId, generateUUID } from "../utils/id-generator.js";
|
|
@@ -34,9 +35,11 @@ async function addCommand(layer, type, name, options) {
|
|
|
34
35
|
]);
|
|
35
36
|
const span = getActiveSpan();
|
|
36
37
|
span?.setAttribute("layer.name", layer), span?.setAttribute("element.type", resolvedType), span?.setAttribute("element.id", elementPath), validateSourceReferenceOptions(options);
|
|
38
|
+
const source = resolveSourceReferenceOptions(options);
|
|
39
|
+
source.warnings.forEach((warning) => console.warn(ansis.yellow(`Warning: ${warning}`)));
|
|
37
40
|
let model;
|
|
38
41
|
try {
|
|
39
|
-
model = await Model.load(options.model
|
|
42
|
+
model = await Model.load(options.model, { layers: [layer] });
|
|
40
43
|
} catch (error) {
|
|
41
44
|
const message = getErrorMessage(error);
|
|
42
45
|
throw message.includes("No DR project") || message.includes("Model not found") ? new ModelNotFoundError() : error;
|
|
@@ -68,7 +71,7 @@ async function addCommand(layer, type, name, options) {
|
|
|
68
71
|
},
|
|
69
72
|
layer
|
|
70
73
|
// Set layer so setSourceReference can use it
|
|
71
|
-
}), sourceRef = buildSourceReference(options);
|
|
74
|
+
}), sourceRef = buildSourceReference(source.options);
|
|
72
75
|
sourceRef && element.setSourceReference(sourceRef);
|
|
73
76
|
{
|
|
74
77
|
const schemaValidator = new SchemaValidator(), tempLayer = new Layer(layer);
|
|
@@ -78,7 +81,7 @@ async function addCommand(layer, type, name, options) {
|
|
|
78
81
|
const errorMessages = attrValidation.errors.map((e) => ` ${e.message}`).join(`
|
|
79
82
|
`);
|
|
80
83
|
throw new CLIError(`Element ${elementPath} has invalid attributes:
|
|
81
|
-
${errorMessages}`, ErrorCategory.USER, [
|
|
84
|
+
${errorMessages}`, ErrorCategory.USER, [schemaLookupHint("cli", layer, resolvedType)]);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
if (layerObj.getElement(elementPath))
|
package/dist/commands/add.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/add.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Add an element to a layer\n */\n\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { Layer } from \"../core/layer.js\";\nimport { Element } from \"../core/element.js\";\nimport { MutationHandler } from \"../core/mutation-handler.js\";\nimport {
|
|
5
|
-
"mappings": "AAIA,OAAO,WAAW;AAClB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,
|
|
4
|
+
"sourcesContent": ["/**\n * Add an element to a layer\n */\n\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { Layer } from \"../core/layer.js\";\nimport { Element } from \"../core/element.js\";\nimport { MutationHandler } from \"../core/mutation-handler.js\";\nimport {\n validateSourceReferenceOptions,\n resolveSourceReferenceOptions,\n buildSourceReference,\n} from \"../utils/source-reference.js\";\nimport { schemaLookupHint } from \"../utils/error-hints.js\";\nimport { SchemaValidator } from \"../validators/schema-validator.js\";\nimport { getActiveSpan } from \"../telemetry/index.js\";\nimport { generateElementId, generateUUID } from \"../utils/id-generator.js\";\nimport { getAllLayerIds, isValidLayer } from \"../generated/layer-registry.js\";\nimport { isValidNodeType, getNodeTypesForLayer, normalizeNodeType } from \"../generated/node-types.js\";\nimport {\n CLIError,\n ErrorCategory,\n InvalidJSONError,\n ModelNotFoundError,\n findSimilar,\n formatValidOptions,\n getErrorMessage,\n handleError,\n handleSuccess,\n} from \"../utils/errors.js\";\n\n\nexport interface AddOptions {\n model?: string;\n name?: string;\n description?: string;\n attributes?: string;\n sourceFile?: string;\n sourceSymbol?: string;\n sourceProvenance?: string;\n sourceRepoRemote?: string;\n sourceRepoCommit?: string;\n verbose?: boolean;\n debug?: boolean;\n}\n\nexport async function addCommand(\n layer: string,\n type: string,\n name: string,\n options: AddOptions\n): Promise<void> {\n try {\n // Validate layer name\n if (!isValidLayer(layer)) {\n const validLayers = getAllLayerIds();\n const similar = findSimilar(layer, validLayers, 3);\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n if (similar.length > 0) {\n suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n }\n throw new CLIError(`Unknown layer \"${layer}\"`, ErrorCategory.USER, suggestions, {\n operation: \"add\",\n context: `Layer: ${layer}, Type: ${type}, Name: ${name}`,\n });\n }\n\n // Validate element type for this layer\n if (!isValidNodeType(layer, type)) {\n const validNodeTypes = getNodeTypesForLayer(layer);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(type, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${layer}: ${formatValidOptions(typeNames)}`];\n if (similar.length > 0) {\n suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n }\n throw new CLIError(\n `Invalid element type \"${type}\" for layer \"${layer}\"`,\n ErrorCategory.USER,\n suggestions,\n {\n operation: \"add\",\n context: `Layer: ${layer}, Type: ${type}, Name: ${name}`,\n }\n );\n }\n\n // Resolve abbreviated type names to their canonical spec names\n // e.g., \"service\" \u2192 \"applicationservice\" for the application layer\n const resolvedType = normalizeNodeType(layer, type);\n\n // Generate slug path: {layer}.{resolvedType}.{kebab-name}\n const elementPath = generateElementId(layer, resolvedType, name); // slug\n const elementUUID = generateUUID(); // UUIDv4\n\n // Validate the generated slug is non-empty (name may collapse to empty after stripping special chars)\n const slugPart = elementPath.split(\".\")[2];\n if (!slugPart) {\n throw new CLIError(\n `Cannot generate a valid element ID from name \"${name}\"`,\n ErrorCategory.USER,\n [\n \"Element names must contain at least one letter or digit\",\n `Generated ID would be: ${elementPath}`,\n ]\n );\n }\n\n const span = getActiveSpan();\n span?.setAttribute(\"layer.name\", layer);\n span?.setAttribute(\"element.type\", resolvedType);\n span?.setAttribute(\"element.id\", elementPath);\n\n // Validate source reference options: a file without a provenance gets 'manual'; an\n // 'extracted' or 'manual' provenance without a file is ignored with a warning.\n validateSourceReferenceOptions(options);\n const source = resolveSourceReferenceOptions(options);\n source.warnings.forEach((warning) => console.warn(ansis.yellow(`Warning: ${warning}`)));\n\n // Load model (with error handling for missing models)\n let model: Model;\n try {\n model = await Model.load(options.model, { layers: [layer] });\n } catch (error) {\n const message = getErrorMessage(error);\n if (message.includes(\"No DR project\") || message.includes(\"Model not found\")) {\n throw new ModelNotFoundError();\n }\n throw error;\n }\n\n // Get or create layer\n let layerObj = await model.getLayer(layer);\n if (!layerObj) {\n layerObj = new Layer(layer);\n model.addLayer(layerObj);\n }\n\n // Parse attributes if provided\n let attributes: Record<string, unknown> = {};\n if (options.attributes) {\n try {\n attributes = JSON.parse(options.attributes);\n } catch (e) {\n throw new InvalidJSONError(options.attributes, \"--attributes\");\n }\n }\n\n // Create element in spec-node format\n // id = UUIDv4 for schema compliance; path = slug for human-readable identifier\n const element = new Element({\n id: elementUUID, // UUIDv4\n path: elementPath, // slug: {layer}.{resolvedType}.{kebab-name}\n spec_node_id: `${layer}.${resolvedType}`,\n layer_id: layer,\n type: resolvedType,\n name: options.name || name,\n description: options.description,\n attributes: attributes,\n metadata: {\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n version: 1,\n },\n layer, // Set layer so setSourceReference can use it\n });\n\n\n // Add source reference if provided\n const sourceRef = buildSourceReference(source.options);\n if (sourceRef) {\n element.setSourceReference(sourceRef);\n }\n\n // Validate element attributes against spec schema before persisting\n {\n const schemaValidator = new SchemaValidator();\n const tempLayer = new Layer(layer);\n tempLayer.addElement(element);\n const attrValidation = await schemaValidator.validateLayer(tempLayer);\n if (!attrValidation.isValid()) {\n const errorMessages = attrValidation.errors\n .map((e) => ` ${e.message}`)\n .join(\"\\n\");\n throw new CLIError(\n `Element ${elementPath} has invalid attributes:\\n${errorMessages}`,\n ErrorCategory.USER,\n [schemaLookupHint(\"cli\", layer, resolvedType)]\n );\n }\n }\n\n // Check if element already exists (look up by path)\n if (layerObj.getElement(elementPath)) {\n throw new CLIError(\n `Element ${elementPath} already exists in ${layer} layer`,\n ErrorCategory.USER,\n [\n `Use \"dr show ${elementPath}\" to view the existing element`,\n `Use \"dr update ${elementPath}\" to modify it`,\n `Use \"dr delete ${elementPath}\" to remove it first if you want to recreate it`,\n ],\n { operation: \"add\", context: `Duplicate element path` }\n );\n }\n\n // Unified mutation handler for add operation\n const handler = new MutationHandler(model, elementPath, layer);\n\n // Execute add through unified path (handles staging and base model consistently)\n await handler.executeAdd(element, (elem) => {\n // This mutator is called by executeAdd for base model path only\n layerObj.addElement(elem);\n });\n\n // Determine if operation was staged or applied to base model\n if (handler.getAfterState()) {\n // Check if we went through staging path\n const stagingManager = handler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n if (activeChangeset && activeChangeset.status === \"staged\") {\n // Staging path\n handleSuccess(\n `Staged element ${ansis.bold(elementPath)} to ${ansis.bold(activeChangeset.name)}`,\n {\n elementId: elementPath,\n layer,\n type: resolvedType,\n name: options.name || name,\n changesetStatus: \"staged\",\n changeset: activeChangeset.name,\n },\n { verbose: options.verbose }\n );\n } else {\n // Base model path\n handleSuccess(`Added element ${ansis.bold(elementPath)} to ${ansis.bold(layer)} layer`, {\n elementId: elementPath,\n layer,\n type: resolvedType,\n name: options.name || name,\n description: options.description || \"(none)\",\n }, { verbose: true });\n }\n }\n } catch (error) {\n handleError(error);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|