@documentation-robotics/cli 0.1.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +171 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/analyzers/verify-engine.js +22 -19
- package/dist/analyzers/verify-engine.js.map +2 -2
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +112 -28
- package/dist/cli.js.map +3 -3
- package/dist/coding-agents/agents/claude-code.js +3 -1
- package/dist/coding-agents/agents/claude-code.js.map +2 -2
- package/dist/coding-agents/claude-code-client.js +2 -1
- package/dist/coding-agents/claude-code-client.js.map +2 -2
- package/dist/coding-agents/copilot-client.js +17 -12
- package/dist/coding-agents/copilot-client.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +152 -0
- package/dist/coding-agents/default-permissions.js.map +7 -0
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +12 -7
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +134 -23
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +261 -147
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +52 -19
- package/dist/commands/delete.js.map +3 -3
- package/dist/commands/element.js +3 -2
- package/dist/commands/element.js.map +2 -2
- package/dist/commands/farm.js +773 -0
- package/dist/commands/farm.js.map +7 -0
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +122 -0
- package/dist/commands/mcp.js.map +7 -0
- package/dist/commands/relationship.js +251 -47
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +1 -1
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +56 -24
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +41 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +2 -2
- package/dist/commands/version.js.map +2 -2
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/config.js +83 -0
- package/dist/config.js.map +7 -0
- package/dist/core/annotation-store.js +138 -0
- package/dist/core/annotation-store.js.map +7 -0
- package/dist/core/catalog-migration.js +33 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +153 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +518 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/farm-manifest.js +140 -0
- package/dist/core/farm-manifest.js.map +7 -0
- package/dist/core/farm-sync-engine.js +346 -0
- package/dist/core/farm-sync-engine.js.map +7 -0
- package/dist/core/farm-sync-state.js +167 -0
- package/dist/core/farm-sync-state.js.map +7 -0
- package/dist/core/manifest.js +6 -3
- package/dist/core/manifest.js.map +2 -2
- package/dist/core/migration-registry.js +84 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +257 -44
- package/dist/core/model.js.map +3 -3
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +68 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +64 -17
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +14 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2209 -7727
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +79 -0
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +52 -1
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +111 -0
- package/dist/mcp/api-key-manager.js.map +7 -0
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/http-transport.js +176 -0
- package/dist/mcp/http-transport.js.map +7 -0
- package/dist/mcp/resource-registry.js +97 -0
- package/dist/mcp/resource-registry.js.map +7 -0
- package/dist/mcp/tool-registry.js +60 -0
- package/dist/mcp/tool-registry.js.map +7 -0
- package/dist/mcp/tools/annotation-create.js +39 -0
- package/dist/mcp/tools/annotation-create.js.map +7 -0
- package/dist/mcp/tools/annotation-delete.js +29 -0
- package/dist/mcp/tools/annotation-delete.js.map +7 -0
- package/dist/mcp/tools/annotation-get.js +29 -0
- package/dist/mcp/tools/annotation-get.js.map +7 -0
- package/dist/mcp/tools/annotation-list.js +24 -0
- package/dist/mcp/tools/annotation-list.js.map +7 -0
- package/dist/mcp/tools/annotation-reply.js +34 -0
- package/dist/mcp/tools/annotation-reply.js.map +7 -0
- package/dist/mcp/tools/annotation-shared.js +10 -0
- package/dist/mcp/tools/annotation-shared.js.map +7 -0
- package/dist/mcp/tools/annotation-update.js +39 -0
- package/dist/mcp/tools/annotation-update.js.map +7 -0
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-list.js +33 -0
- package/dist/mcp/tools/changeset-list.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +55 -0
- package/dist/mcp/tools/changeset-show.js.map +7 -0
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/chat-launch.js +50 -0
- package/dist/mcp/tools/chat-launch.js.map +7 -0
- package/dist/mcp/tools/chat-status.js +26 -0
- package/dist/mcp/tools/chat-status.js.map +7 -0
- package/dist/mcp/tools/index.js +65 -0
- package/dist/mcp/tools/index.js.map +7 -0
- package/dist/mcp/tools/model-add.js +107 -0
- package/dist/mcp/tools/model-add.js.map +7 -0
- package/dist/mcp/tools/model-delete.js +99 -0
- package/dist/mcp/tools/model-delete.js.map +7 -0
- package/dist/mcp/tools/model-export.js +72 -0
- package/dist/mcp/tools/model-export.js.map +7 -0
- package/dist/mcp/tools/model-info.js +51 -0
- package/dist/mcp/tools/model-info.js.map +7 -0
- package/dist/mcp/tools/model-list.js +53 -0
- package/dist/mcp/tools/model-list.js.map +7 -0
- package/dist/mcp/tools/model-reload.js +34 -0
- package/dist/mcp/tools/model-reload.js.map +7 -0
- package/dist/mcp/tools/model-search.js +102 -0
- package/dist/mcp/tools/model-search.js.map +7 -0
- package/dist/mcp/tools/model-show.js +68 -0
- package/dist/mcp/tools/model-show.js.map +7 -0
- package/dist/mcp/tools/model-stats.js +22 -0
- package/dist/mcp/tools/model-stats.js.map +7 -0
- package/dist/mcp/tools/model-trace.js +118 -0
- package/dist/mcp/tools/model-trace.js.map +7 -0
- package/dist/mcp/tools/model-update.js +121 -0
- package/dist/mcp/tools/model-update.js.map +7 -0
- package/dist/mcp/tools/model-validate.js +78 -0
- package/dist/mcp/tools/model-validate.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +126 -0
- package/dist/mcp/tools/shared.js.map +7 -0
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/reports/model-layer-report-generator.js +5 -3
- package/dist/reports/model-layer-report-generator.js.map +2 -2
- package/dist/reports/model-readme-generator.js +116 -0
- package/dist/reports/model-readme-generator.js.map +7 -0
- package/dist/reports/model-report-data.js +37 -1
- package/dist/reports/model-report-data.js.map +2 -2
- package/dist/reports/model-report-orchestrator.js +29 -6
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/reports/model-report-utils.js +9 -0
- package/dist/reports/model-report-utils.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +745 -370
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +403 -383
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +718 -1643
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +234 -31
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/config.js +2 -40
- package/dist/telemetry/config.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/types/index.js.map +2 -2
- package/dist/utils/ansi-suppressor.js +23 -0
- package/dist/utils/ansi-suppressor.js.map +7 -0
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/color-utils.js +34 -0
- package/dist/utils/color-utils.js.map +7 -0
- package/dist/utils/element-reference-shape.js +15 -1
- package/dist/utils/element-reference-shape.js.map +3 -3
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +75 -10
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/globals.js +17 -1
- package/dist/utils/globals.js.map +2 -2
- package/dist/utils/project-paths.js +50 -3
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/reference-path-parser.js +49 -0
- package/dist/utils/reference-path-parser.js.map +7 -0
- package/dist/utils/source-reference.js +17 -10
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +10 -2
- package/dist/utils/spec-version.js.map +2 -2
- package/dist/validators/composed-reference-validator.js +240 -0
- package/dist/validators/composed-reference-validator.js.map +7 -0
- package/dist/validators/reference-validator.js +57 -13
- package/dist/validators/reference-validator.js.map +3 -3
- package/dist/validators/relationship-schema-validator.js +33 -5
- package/dist/validators/relationship-schema-validator.js.map +3 -3
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +7 -3
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/coding-agents/copilot-client.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * GitHub Copilot CLI Client\n *\n * Implements chat functionality using GitHub Copilot CLI subprocess.\n * Supports both `gh copilot` (GitHub CLI extension) and `@github/copilot` (npm package).\n *\n * Key features:\n * - Availability detection for both CLI variants\n * - Plain text/markdown output parsing (heuristic)\n * - Session management using command-line flags\n * - Code block detection\n * - System message detection\n */\n\nimport { BaseChatClient, ChatOptions } from \"./base-chat-client.js\";\nimport { spawnSync, spawn, ChildProcess } from \"child_process\";\nimport ansis from \"ansis\";\nimport { getChatLogger } from \"../utils/chat-logger.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport {\n isTelemetryEnabled,\n startSpan,\n endSpan,\n emitLog,\n SeverityNumber,\n} from \"../telemetry/index.js\";\n\n/**\n * Output parser for GitHub Copilot plain text responses\n * Uses heuristics to detect code blocks, system messages, and tool invocations\n */\nclass CopilotOutputParser {\n private inCodeBlock = false;\n private codeBlockLanguage = \"\";\n\n /**\n * Parse a line of output from Copilot\n * @param line The line to parse\n * @returns Formatted output string or null if line should be skipped\n */\n parseLine(line: string): string | null {\n // Detect code block start/end (markdown fences)\n const codeBlockMatch = line.match(/^```(\\w*)$/);\n if (codeBlockMatch) {\n this.inCodeBlock = !this.inCodeBlock;\n if (this.inCodeBlock) {\n this.codeBlockLanguage = codeBlockMatch[1] || \"\";\n return ansis.dim(`\\n[Code block: ${this.codeBlockLanguage || \"text\"}]\\n`);\n } else {\n this.codeBlockLanguage = \"\";\n return ansis.dim(\"\\n[End code block]\\n\");\n }\n }\n\n // Detect system messages (lines starting with special markers)\n if (line.match(/^(Note:|Warning:|Error:|Info:)/i)) {\n return ansis.yellow(line);\n }\n\n // Detect potential tool invocation patterns\n if (line.match(/^(Running|Executing|Searching|Analyzing):/i)) {\n return ansis.dim(line);\n }\n\n // Return line as-is\n return line;\n }\n\n /**\n * Reset parser state\n */\n reset(): void {\n this.inCodeBlock = false;\n this.codeBlockLanguage = \"\";\n }\n}\n\n/**\n * GitHub Copilot CLI Client\n */\nexport class CopilotClient extends BaseChatClient {\n private parser = new CopilotOutputParser();\n private copilotCommand: \"copilot\" | null = null;\n private isFirstMessage = true;\n\n /**\n * Check if GitHub Copilot CLI is available\n * Only supports standalone `copilot` npm package (@github/copilot)\n */\n async isAvailable(): Promise<boolean> {\n const span = isTelemetryEnabled ? startSpan(\"copilot.availability-check\") : null;\n\n try {\n // Return cached result if already detected\n if (this.copilotCommand !== null) {\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.available\", true);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setAttribute(\"client.cached\", true);\n (span as any).setAttribute(\"client.variant\", this.copilotCommand);\n (span as any).setStatus({ code: 0 });\n }\n return true;\n }\n\n // Check for standalone copilot command (npm package)\n try {\n const copilotResult = spawnSync(\"copilot\", [\"--version\"], {\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n if (copilotResult.status === 0) {\n this.copilotCommand = \"copilot\";\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.available\", true);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setAttribute(\"client.cached\", false);\n (span as any).setAttribute(\"client.variant\", \"copilot\");\n (span as any).setStatus({ code: 0 });\n }\n return true;\n }\n } catch {\n // standalone copilot not available\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.available\", false);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setStatus({ code: 0 });\n }\n\n return false;\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setAttribute(\"client.available\", false);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n return false;\n } finally {\n endSpan(span);\n }\n }\n\n /**\n * Send a message to GitHub Copilot and stream the response\n * @param message The user's message\n * @param options Chat options\n */\n async sendMessage(message: string, options?: ChatOptions): Promise<string> {\n const span = isTelemetryEnabled\n ? startSpan(\"copilot.send-message\", {\n \"message.length\": message.length,\n \"message.content\": message.substring(0, 500), // First 500 chars for context\n \"client.name\": \"GitHub Copilot\",\n \"client.agent\": options?.agent,\n \"client.withDanger\": options?.withDanger === true,\n \"options.workingDirectory\": options?.workingDirectory,\n \"options.sessionId\": options?.sessionId,\n \"client.workingDirectory\": options?.workingDirectory,\n \"client.sessionId\": options?.sessionId,\n \"client.isFirstMessage\": this.isFirstMessage,\n })\n : null;\n\n // Ensure we have determined which command to use\n if (!this.copilotCommand) {\n const available = await this.isAvailable();\n if (!available) {\n throw new Error(\"GitHub Copilot CLI is not available\");\n }\n }\n\n // Log the user message\n const logger = getChatLogger();\n if (logger) {\n await logger.logUserMessage(message, {\n client: \"GitHub Copilot\",\n agent: options?.agent,\n });\n }\n\n // Create or continue session\n const hadExistingSession = !!this.currentSession;\n if (!this.currentSession) {\n this.createSession();\n } else {\n this.updateSessionTimestamp();\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.hadExistingSession\", hadExistingSession);\n (span as any).setAttribute(\n \"client.internalSessionId\",\n this.getCurrentSession()?.id ?? \"none\"\n );\n (span as any).setAttribute(\"client.variant\", this.copilotCommand);\n }\n\n return new Promise((resolve, reject) => {\n const proc = this.spawnCopilotProcess(message, options);\n\n let buffer = \"\";\n let assistantOutput = \"\";\n let lineCount = 0;\n this.parser.reset();\n\n // Handle stdout\n proc.stdout?.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n buffer += chunk;\n assistantOutput += chunk;\n\n // Process complete lines\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n lineCount++;\n if (!line.trim()) {\n process.stdout.write(\"\\n\");\n continue;\n }\n\n const parsed = this.parser.parseLine(line);\n if (parsed !== null) {\n process.stdout.write(parsed + \"\\n\");\n }\n }\n });\n\n // Handle stderr (usually authentication prompts or errors)\n proc.stderr?.on(\"data\", (data: Buffer) => {\n const errorText = data.toString();\n // Only show errors that are relevant to the user\n if (\n errorText.includes(\"error\") ||\n errorText.includes(\"Error\") ||\n errorText.includes(\"not authenticated\")\n ) {\n process.stderr.write(ansis.red(errorText));\n\n if (isTelemetryEnabled && span) {\n emitLog(SeverityNumber.ERROR, \"Copilot stderr error\", {\n \"error.source\": \"stderr\",\n \"error.text\": errorText.substring(0, 500),\n \"error.isAuthError\": errorText.includes(\"not authenticated\"),\n });\n }\n\n // Log error\n if (logger) {\n void logger.logError(errorText, {\n source: \"stderr\",\n client: \"GitHub Copilot\",\n });\n }\n }\n });\n\n // Handle errors\n proc.on(\"error\", (error) => {\n if (isTelemetryEnabled && span) {\n emitLog(SeverityNumber.ERROR, \"Copilot process error\", {\n \"error.message\": error.message,\n \"error.stack\": error.stack,\n \"error.code\": (error as any).code, // ENOENT, EACCES, etc.\n \"process.command\": this.copilotCommand === \"copilot\" ? \"copilot\" : \"gh copilot\",\n \"message.length\": message.length,\n \"message.preview\": message.substring(0, 200),\n });\n (span as any).recordException(error);\n (span as any).setStatus({ code: 2, message: error.message });\n }\n if (logger) {\n void logger.logError(error.message, {\n source: \"process\",\n client: \"GitHub Copilot\",\n stack: error.stack,\n errorCode: (error as any).code,\n });\n }\n endSpan(span);\n reject(error);\n });\n\n // Handle process exit\n proc.on(\"close\", (exitCode) => {\n // Print any remaining buffer\n if (buffer.trim()) {\n const parsed = this.parser.parseLine(buffer);\n if (parsed !== null) {\n process.stdout.write(parsed);\n }\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.lineCount\", lineCount);\n (span as any).setAttribute(\"client.outputLength\", assistantOutput.length);\n (span as any).setAttribute(\"client.exitCode\", exitCode || 0);\n (span as any).setAttribute(\"response.content\", assistantOutput.substring(0, 1000)); // First 1000 chars\n (span as any).setAttribute(\"response.preview\", assistantOutput.substring(0, 200)); // Short preview\n }\n\n if (exitCode === 0) {\n // Log assistant message if we got output\n if (logger && assistantOutput.trim()) {\n void logger.logAssistantMessage(assistantOutput, {\n client: \"GitHub Copilot\",\n });\n }\n if (isTelemetryEnabled && span) {\n (span as any).setStatus({ code: 0 });\n }\n endSpan(span);\n resolve(assistantOutput);\n } else {\n const errorMsg = `Copilot process exited with code ${exitCode}`;\n if (isTelemetryEnabled && span) {\n emitLog(SeverityNumber.ERROR, \"Copilot process exited with error\", {\n \"process.exitCode\": exitCode,\n });\n (span as any).setStatus({ code: 2, message: errorMsg });\n }\n if (logger) {\n void logger.logError(errorMsg, {\n exitCode,\n client: \"GitHub Copilot\",\n });\n }\n endSpan(span);\n reject(new Error(errorMsg));\n }\n });\n });\n }\n\n /**\n * Spawn the GitHub Copilot CLI process\n * @param message The message to send\n * @param options Chat options\n * @returns The spawned child process\n */\n private spawnCopilotProcess(message: string, options?: ChatOptions): ChildProcess {\n const span = isTelemetryEnabled ? startSpan(\"copilot.spawn-process\") : null;\n\n try {\n const args: string[] = [];\n const cwd = options?.workingDirectory || process.cwd();\n\n const isFirst = this.isFirstMessage;\n\n // For first message: copilot --prompt \"message\" --agent \"dr-architect\" --allow-all-tools\n // For subsequent messages: copilot --continue --prompt \"message\" --allow-all-tools\n if (isFirst) {\n args.push(\"--prompt\", message);\n this.isFirstMessage = false;\n } else {\n args.push(\"--continue\", \"--prompt\", message);\n }\n\n // Add agent if specified (only on first message, agent persists in session)\n if (isFirst && options?.agent) {\n args.push(\"--agent\", options.agent);\n }\n\n // Add allow-all-tools flag if withDanger is enabled (required for non-interactive)\n // Note: This flag may not be available in all copilot versions\n if (options?.withDanger) {\n try {\n // Check if flag is supported by testing with --help\n const helpResult = spawnSync(\n this.copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n this.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 args.push(\"--allow-all-tools\");\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.allowAllToolsSupported\", true);\n }\n } else {\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.allowAllToolsSupported\", false);\n emitLog(SeverityNumber.WARN, \"GitHub Copilot --allow-all-tools flag not available\", {\n \"copilot.variant\": this.copilotCommand,\n withDanger: true,\n });\n }\n console.warn(\n ansis.yellow(\"Warning: --allow-all-tools flag not supported by your copilot version\")\n );\n }\n } catch (error) {\n // If check fails, try adding the flag anyway\n args.push(\"--allow-all-tools\");\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.allowAllToolsCheckFailed\", true);\n }\n }\n }\n\n const command = \"copilot\";\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.command\", command);\n (span as any).setAttribute(\"process.args\", args.join(\" \"));\n (span as any).setAttribute(\"process.argCount\", args.length);\n (span as any).setAttribute(\"process.isFirstMessage\", isFirst);\n (span as any).setAttribute(\"process.hasContinue\", args.includes(\"--continue\"));\n (span as any).setAttribute(\"process.hasAgent\", !!options?.agent);\n (span as any).setAttribute(\"process.agent\", options?.agent);\n (span as any).setAttribute(\"process.withDanger\", options?.withDanger === true);\n (span as any).setAttribute(\"process.workingDirectory\", cwd);\n (span as any).setAttribute(\"process.messageLength\", message.length);\n (span as any).setAttribute(\"process.variant\", this.copilotCommand);\n }\n\n // Log the command that's being executed\n const logger = getChatLogger();\n if (logger) {\n void logger.logCommand(command, args, {\n client: \"GitHub Copilot\",\n workingDirectory: cwd,\n withDanger: options?.withDanger || false,\n copilotVariant: this.copilotCommand,\n messageLength: message.length,\n hasAgent: !!options?.agent,\n isFirstMessage: isFirst,\n });\n }\n\n const proc = spawn(command, args, {\n cwd,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.spawned\", true);\n (span as any).setAttribute(\"process.pid\", proc.pid);\n (span as any).setStatus({ code: 0 });\n }\n\n return proc;\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n throw error;\n } finally {\n endSpan(span);\n }\n }\n\n /**\n * Get the name of this client\n */\n getClientName(): string {\n return \"GitHub Copilot\";\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * GitHub Copilot CLI Client\n *\n * Implements chat functionality using GitHub Copilot CLI subprocess.\n * Supports both `gh copilot` (GitHub CLI extension) and `@github/copilot` (npm package).\n *\n * Key features:\n * - Availability detection for both CLI variants\n * - Plain text/markdown output parsing (heuristic)\n * - Session management using command-line flags\n * - Code block detection\n * - System message detection\n */\n\nimport { BaseChatClient, ChatOptions } from \"./base-chat-client.js\";\nimport { spawnSync, spawn, ChildProcess } from \"child_process\";\nimport ansis from \"ansis\";\nimport { getChatLogger } from \"../utils/chat-logger.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { applyCopilotPermissions } from \"./default-permissions.js\";\nimport {\n isTelemetryEnabled,\n startSpan,\n endSpan,\n emitLog,\n SeverityNumber,\n} from \"../telemetry/index.js\";\n\n/**\n * Output parser for GitHub Copilot plain text responses\n * Uses heuristics to detect code blocks, system messages, and tool invocations\n */\nclass CopilotOutputParser {\n private inCodeBlock = false;\n private codeBlockLanguage = \"\";\n\n /**\n * Parse a line of output from Copilot\n * @param line The line to parse\n * @returns Formatted output string or null if line should be skipped\n */\n parseLine(line: string): string | null {\n // Detect code block start/end (markdown fences)\n const codeBlockMatch = line.match(/^```(\\w*)$/);\n if (codeBlockMatch) {\n this.inCodeBlock = !this.inCodeBlock;\n if (this.inCodeBlock) {\n this.codeBlockLanguage = codeBlockMatch[1] || \"\";\n return ansis.dim(`\\n[Code block: ${this.codeBlockLanguage || \"text\"}]\\n`);\n } else {\n this.codeBlockLanguage = \"\";\n return ansis.dim(\"\\n[End code block]\\n\");\n }\n }\n\n // Detect system messages (lines starting with special markers)\n if (line.match(/^(Note:|Warning:|Error:|Info:)/i)) {\n return ansis.yellow(line);\n }\n\n // Detect potential tool invocation patterns\n if (line.match(/^(Running|Executing|Searching|Analyzing):/i)) {\n return ansis.dim(line);\n }\n\n // Return line as-is\n return line;\n }\n\n /**\n * Reset parser state\n */\n reset(): void {\n this.inCodeBlock = false;\n this.codeBlockLanguage = \"\";\n }\n}\n\n/**\n * GitHub Copilot CLI Client\n */\nexport class CopilotClient extends BaseChatClient {\n private parser = new CopilotOutputParser();\n private copilotCommand: \"copilot\" | null = null;\n private isFirstMessage = true;\n\n /**\n * Check if GitHub Copilot CLI is available\n * Only supports standalone `copilot` npm package (@github/copilot)\n */\n async isAvailable(): Promise<boolean> {\n const span = isTelemetryEnabled ? startSpan(\"copilot.availability-check\") : null;\n\n try {\n // Return cached result if already detected\n if (this.copilotCommand !== null) {\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.available\", true);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setAttribute(\"client.cached\", true);\n (span as any).setAttribute(\"client.variant\", this.copilotCommand);\n (span as any).setStatus({ code: 0 });\n }\n return true;\n }\n\n // Check for standalone copilot command (npm package)\n try {\n const copilotResult = spawnSync(\"copilot\", [\"--version\"], {\n stdio: \"pipe\",\n encoding: \"utf-8\",\n });\n if (copilotResult.status === 0) {\n this.copilotCommand = \"copilot\";\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.available\", true);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setAttribute(\"client.cached\", false);\n (span as any).setAttribute(\"client.variant\", \"copilot\");\n (span as any).setStatus({ code: 0 });\n }\n return true;\n }\n } catch {\n // standalone copilot not available\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.available\", false);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setStatus({ code: 0 });\n }\n\n return false;\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setAttribute(\"client.available\", false);\n (span as any).setAttribute(\"client.name\", \"GitHub Copilot\");\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n return false;\n } finally {\n endSpan(span);\n }\n }\n\n /**\n * Send a message to GitHub Copilot and stream the response\n * @param message The user's message\n * @param options Chat options\n */\n async sendMessage(message: string, options?: ChatOptions): Promise<string> {\n const span = isTelemetryEnabled\n ? startSpan(\"copilot.send-message\", {\n \"message.length\": message.length,\n \"message.content\": message.substring(0, 500), // First 500 chars for context\n \"client.name\": \"GitHub Copilot\",\n \"client.agent\": options?.agent,\n \"client.withDanger\": options?.withDanger === true,\n \"options.workingDirectory\": options?.workingDirectory,\n \"options.sessionId\": options?.sessionId,\n \"client.workingDirectory\": options?.workingDirectory,\n \"client.sessionId\": options?.sessionId,\n \"client.isFirstMessage\": this.isFirstMessage,\n })\n : null;\n\n // Ensure we have determined which command to use\n if (!this.copilotCommand) {\n const available = await this.isAvailable();\n if (!available) {\n throw new Error(\"GitHub Copilot CLI is not available\");\n }\n }\n\n // Log the user message\n const logger = getChatLogger();\n if (logger) {\n await logger.logUserMessage(message, {\n client: \"GitHub Copilot\",\n agent: options?.agent,\n });\n }\n\n // Create or continue session\n const hadExistingSession = !!this.currentSession;\n if (!this.currentSession) {\n this.createSession();\n } else {\n this.updateSessionTimestamp();\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.hadExistingSession\", hadExistingSession);\n (span as any).setAttribute(\n \"client.internalSessionId\",\n this.getCurrentSession()?.id ?? \"none\"\n );\n (span as any).setAttribute(\"client.variant\", this.copilotCommand);\n }\n\n return new Promise((resolve, reject) => {\n const proc = this.spawnCopilotProcess(message, options);\n\n let buffer = \"\";\n let assistantOutput = \"\";\n let lineCount = 0;\n this.parser.reset();\n\n // Handle stdout\n proc.stdout?.on(\"data\", (data: Buffer) => {\n const chunk = data.toString();\n buffer += chunk;\n assistantOutput += chunk;\n\n // Process complete lines\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n lineCount++;\n if (!line.trim()) {\n process.stdout.write(\"\\n\");\n continue;\n }\n\n const parsed = this.parser.parseLine(line);\n if (parsed !== null) {\n process.stdout.write(parsed + \"\\n\");\n }\n }\n });\n\n // Handle stderr (usually authentication prompts or errors)\n proc.stderr?.on(\"data\", (data: Buffer) => {\n const errorText = data.toString();\n // Only show errors that are relevant to the user\n if (\n errorText.includes(\"error\") ||\n errorText.includes(\"Error\") ||\n errorText.includes(\"not authenticated\")\n ) {\n process.stderr.write(ansis.red(errorText));\n\n if (isTelemetryEnabled && span) {\n emitLog(SeverityNumber.ERROR, \"Copilot stderr error\", {\n \"error.source\": \"stderr\",\n \"error.text\": errorText.substring(0, 500),\n \"error.isAuthError\": errorText.includes(\"not authenticated\"),\n });\n }\n\n // Log error\n if (logger) {\n void logger.logError(errorText, {\n source: \"stderr\",\n client: \"GitHub Copilot\",\n });\n }\n }\n });\n\n // Handle errors\n proc.on(\"error\", (error) => {\n if (isTelemetryEnabled && span) {\n emitLog(SeverityNumber.ERROR, \"Copilot process error\", {\n \"error.message\": error.message,\n \"error.stack\": error.stack,\n \"error.code\": (error as any).code, // ENOENT, EACCES, etc.\n \"process.command\": this.copilotCommand === \"copilot\" ? \"copilot\" : \"gh copilot\",\n \"message.length\": message.length,\n \"message.preview\": message.substring(0, 200),\n });\n (span as any).recordException(error);\n (span as any).setStatus({ code: 2, message: error.message });\n }\n if (logger) {\n void logger.logError(error.message, {\n source: \"process\",\n client: \"GitHub Copilot\",\n stack: error.stack,\n errorCode: (error as any).code,\n });\n }\n endSpan(span);\n reject(error);\n });\n\n // Handle process exit\n proc.on(\"close\", (exitCode) => {\n // Print any remaining buffer\n if (buffer.trim()) {\n const parsed = this.parser.parseLine(buffer);\n if (parsed !== null) {\n process.stdout.write(parsed);\n }\n }\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"client.lineCount\", lineCount);\n (span as any).setAttribute(\"client.outputLength\", assistantOutput.length);\n (span as any).setAttribute(\"client.exitCode\", exitCode || 0);\n (span as any).setAttribute(\"response.content\", assistantOutput.substring(0, 1000)); // First 1000 chars\n (span as any).setAttribute(\"response.preview\", assistantOutput.substring(0, 200)); // Short preview\n }\n\n if (exitCode === 0) {\n // Log assistant message if we got output\n if (logger && assistantOutput.trim()) {\n void logger.logAssistantMessage(assistantOutput, {\n client: \"GitHub Copilot\",\n });\n }\n if (isTelemetryEnabled && span) {\n (span as any).setStatus({ code: 0 });\n }\n endSpan(span);\n resolve(assistantOutput);\n } else {\n const errorMsg = `Copilot process exited with code ${exitCode}`;\n if (isTelemetryEnabled && span) {\n emitLog(SeverityNumber.ERROR, \"Copilot process exited with error\", {\n \"process.exitCode\": exitCode,\n });\n (span as any).setStatus({ code: 2, message: errorMsg });\n }\n if (logger) {\n void logger.logError(errorMsg, {\n exitCode,\n client: \"GitHub Copilot\",\n });\n }\n endSpan(span);\n reject(new Error(errorMsg));\n }\n });\n });\n }\n\n /**\n * Spawn the GitHub Copilot CLI process\n * @param message The message to send\n * @param options Chat options\n * @returns The spawned child process\n */\n private spawnCopilotProcess(message: string, options?: ChatOptions): ChildProcess {\n const span = isTelemetryEnabled ? startSpan(\"copilot.spawn-process\") : null;\n\n try {\n const args: string[] = [];\n const cwd = options?.workingDirectory || process.cwd();\n\n const isFirst = this.isFirstMessage;\n\n // For first message: copilot --prompt \"message\" --agent \"dr-architect\" [--allowedTools ...]\n // For subsequent messages: copilot --continue --prompt \"message\" [--allowedTools ...]\n if (isFirst) {\n args.push(\"--prompt\", message);\n this.isFirstMessage = false;\n } else {\n args.push(\"--continue\", \"--prompt\", message);\n }\n\n // Add agent if specified (only on first message, agent persists in session)\n if (isFirst && options?.agent) {\n args.push(\"--agent\", options.agent);\n }\n\n // Handle permissions based on withDanger flag\n const withDanger = options?.withDanger === true;\n const variant = this.copilotCommand === \"copilot\" ? \"copilot\" : \"gh copilot\";\n\n applyCopilotPermissions(args, variant, this.copilotCommand || \"copilot\", withDanger, (attr, value) => {\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(attr, value);\n if (attr === \"process.allowAllToolsSupported\" && value === false && withDanger) {\n emitLog(SeverityNumber.WARN, \"GitHub Copilot --allow-all-tools flag not available\", {\n \"copilot.variant\": this.copilotCommand,\n withDanger: true,\n });\n } else if (attr === \"process.readSafePermissionsApplied\" && value === false) {\n emitLog(SeverityNumber.WARN, \"Copilot --allowedTools flag not supported, using default permissions\", {\n \"copilot.variant\": this.copilotCommand,\n \"copilot.note\": \"Installed Copilot CLI version may be outdated. Consider upgrading for permission controls.\",\n });\n } else if (attr === \"process.readSafePermissionCheckFailed\") {\n emitLog(SeverityNumber.WARN, \"Failed to check Copilot permission support\", {\n \"error.message\": \"\",\n \"copilot.variant\": this.copilotCommand,\n });\n }\n }\n });\n\n const command = \"copilot\";\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.command\", command);\n (span as any).setAttribute(\"process.args\", args.join(\" \"));\n (span as any).setAttribute(\"process.argCount\", args.length);\n (span as any).setAttribute(\"process.isFirstMessage\", isFirst);\n (span as any).setAttribute(\"process.hasContinue\", args.includes(\"--continue\"));\n (span as any).setAttribute(\"process.hasAgent\", !!options?.agent);\n (span as any).setAttribute(\"process.agent\", options?.agent);\n (span as any).setAttribute(\"process.withDanger\", withDanger);\n (span as any).setAttribute(\"process.workingDirectory\", cwd);\n (span as any).setAttribute(\"process.messageLength\", message.length);\n (span as any).setAttribute(\"process.variant\", this.copilotCommand);\n }\n\n // Log the command that's being executed\n const logger = getChatLogger();\n if (logger) {\n void logger.logCommand(command, args, {\n client: \"GitHub Copilot\",\n workingDirectory: cwd,\n withDanger: withDanger,\n copilotVariant: this.copilotCommand,\n messageLength: message.length,\n hasAgent: !!options?.agent,\n isFirstMessage: isFirst,\n });\n }\n\n const proc = spawn(command, args, {\n cwd,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n\n if (isTelemetryEnabled && span) {\n (span as any).setAttribute(\"process.spawned\", true);\n (span as any).setAttribute(\"process.pid\", proc.pid);\n (span as any).setStatus({ code: 0 });\n }\n\n return proc;\n } catch (error) {\n if (isTelemetryEnabled && span) {\n (span as any).recordException(error as Error);\n (span as any).setStatus({\n code: 2,\n message: getErrorMessage(error),\n });\n }\n throw error;\n } finally {\n endSpan(span);\n }\n }\n\n\n /**\n * Get the name of this client\n */\n getClientName(): string {\n return \"GitHub Copilot\";\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import ansis from "ansis";
|
|
3
|
+
const PERMISSIONS_DEFINITION = [
|
|
4
|
+
{
|
|
5
|
+
name: "Bash",
|
|
6
|
+
description: "Execute the dr CLI tool for read-safe model queries",
|
|
7
|
+
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",
|
|
8
|
+
allowsWrite: !1
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "Read",
|
|
12
|
+
description: "Read files from the codebase",
|
|
13
|
+
scope: ".",
|
|
14
|
+
allowsWrite: !1
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "Read",
|
|
18
|
+
description: "Read from the documentation-robotics model folder",
|
|
19
|
+
scope: "documentation-robotics",
|
|
20
|
+
allowsWrite: !1
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "Read",
|
|
24
|
+
description: "Read from the dr configuration folder",
|
|
25
|
+
scope: ".dr",
|
|
26
|
+
allowsWrite: !1
|
|
27
|
+
}
|
|
28
|
+
], DEFAULT_READ_SAFE_PERMISSIONS = Object.freeze(PERMISSIONS_DEFINITION.map((p) => Object.freeze({ ...p })));
|
|
29
|
+
function formatForClaudeCode() {
|
|
30
|
+
return DEFAULT_READ_SAFE_PERMISSIONS.flatMap((p) => p.scope ? p.name === "Bash" ? bashCommands(p.scope).map((command) => `Bash(${command}:*)`) : [`${p.name}(${p.scope})`] : [p.name]).join(",");
|
|
31
|
+
}
|
|
32
|
+
const CHANGESET_WRITE_COMMANDS = Object.freeze([
|
|
33
|
+
"dr changeset create",
|
|
34
|
+
"dr changeset activate",
|
|
35
|
+
"dr changeset deactivate",
|
|
36
|
+
"dr changeset status",
|
|
37
|
+
"dr changeset list",
|
|
38
|
+
"dr changeset show",
|
|
39
|
+
"dr changeset staged",
|
|
40
|
+
"dr changeset stage",
|
|
41
|
+
"dr changeset unstage",
|
|
42
|
+
"dr changeset preview",
|
|
43
|
+
"dr changeset diff",
|
|
44
|
+
"dr add",
|
|
45
|
+
"dr update",
|
|
46
|
+
"dr relationship add",
|
|
47
|
+
"dr relationship list",
|
|
48
|
+
"dr relationship show",
|
|
49
|
+
"dr schema"
|
|
50
|
+
]);
|
|
51
|
+
import { CHANGESET_ONLY_ENV } from "../core/changeset-only-guard.js";
|
|
52
|
+
function formatForClaudeCodeWithChangesets() {
|
|
53
|
+
return [formatForClaudeCode(), ...CHANGESET_WRITE_COMMANDS.map((command) => `Bash(${command}:*)`)].join(",");
|
|
54
|
+
}
|
|
55
|
+
function bashCommands(scope) {
|
|
56
|
+
return scope.split("|").map((command) => command.trim()).filter((command) => command.length > 0);
|
|
57
|
+
}
|
|
58
|
+
function formatForCopilot() {
|
|
59
|
+
return formatForClaudeCode();
|
|
60
|
+
}
|
|
61
|
+
function validatePermissions(permissions) {
|
|
62
|
+
if (!Array.isArray(permissions) || permissions.length === 0)
|
|
63
|
+
throw new Error("Permissions must be a non-empty array");
|
|
64
|
+
for (let i = 0; i < permissions.length; i++) {
|
|
65
|
+
const perm = permissions[i];
|
|
66
|
+
if (typeof perm.name != "string" || perm.name.length === 0)
|
|
67
|
+
throw new Error(`Permission[${i}].name must be a non-empty string, got: ${typeof perm.name}`);
|
|
68
|
+
if (typeof perm.description != "string" || perm.description.length === 0)
|
|
69
|
+
throw new Error(`Permission[${i}].description must be a non-empty string, got: ${typeof perm.description}`);
|
|
70
|
+
if (typeof perm.allowsWrite != "boolean")
|
|
71
|
+
throw new Error(`Permission[${i}].allowsWrite must be a boolean, got: ${typeof perm.allowsWrite}`);
|
|
72
|
+
}
|
|
73
|
+
const writePerms = permissions.filter((p) => p.allowsWrite);
|
|
74
|
+
if (writePerms.length > 0)
|
|
75
|
+
throw new Error(`Read-safe permissions must not allow write operations. Found ${writePerms.length} permission(s) with allowsWrite=true`);
|
|
76
|
+
const allowedToolNames = /* @__PURE__ */ new Set(["Bash", "Read"]), disallowedTools = permissions.filter((p) => !allowedToolNames.has(p.name));
|
|
77
|
+
if (disallowedTools.length > 0)
|
|
78
|
+
throw new Error(`Read-safe permissions must only include Bash and Read tools. Found disallowed tool(s): ${disallowedTools.map((p) => p.name).join(", ")}`);
|
|
79
|
+
const bashPerms = permissions.filter((p) => p.name === "Bash");
|
|
80
|
+
if (bashPerms.length > 0) {
|
|
81
|
+
const invalidBash = bashPerms.filter((p) => {
|
|
82
|
+
const hasValidDescription = p.description.toLowerCase().includes("dr") && p.description.toLowerCase().includes("cli"), hasValidScope = typeof p.scope == "string" && p.scope.length > 0;
|
|
83
|
+
let hasValidScopeContent = !0;
|
|
84
|
+
if (hasValidScope) {
|
|
85
|
+
const scope = p.scope;
|
|
86
|
+
(scope === "*" || scope === "." || scope === "dr *" || scope.startsWith("dr *")) && (hasValidScopeContent = !1), scope.startsWith("dr ") || (hasValidScopeContent = !1);
|
|
87
|
+
}
|
|
88
|
+
return !hasValidDescription || !hasValidScope || !hasValidScopeContent;
|
|
89
|
+
});
|
|
90
|
+
if (invalidBash.length > 0) {
|
|
91
|
+
const errors = invalidBash.map((p) => {
|
|
92
|
+
const issues = [];
|
|
93
|
+
if ((!p.description.toLowerCase().includes("dr") || !p.description.toLowerCase().includes("cli")) && issues.push("invalid description (must include 'dr' and 'cli')"), typeof p.scope != "string" || p.scope.length === 0)
|
|
94
|
+
issues.push("missing or empty scope");
|
|
95
|
+
else {
|
|
96
|
+
const scope = p.scope;
|
|
97
|
+
(scope === "*" || scope === "." || scope === "dr *" || scope.startsWith("dr *")) && issues.push("scope uses dangerous wildcard pattern (must specify read-safe commands)"), scope.startsWith("dr ") || issues.push("scope must start with 'dr ' for Bash tool");
|
|
98
|
+
}
|
|
99
|
+
return `${p.description} (${issues.join(", ")})`;
|
|
100
|
+
});
|
|
101
|
+
throw new Error(`Bash permission must be for dr CLI only with properly restricted scope. Found invalid Bash permission(s): ${errors.join("; ")}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!permissions.find((p) => p.name === "Read" && p.description.toLowerCase().includes("codebase")))
|
|
105
|
+
throw new Error("Read-safe permissions must include Read permission for codebase");
|
|
106
|
+
if (!permissions.find((p) => p.name === "Read" && p.description.includes("documentation-robotics")))
|
|
107
|
+
throw new Error("Read-safe permissions must include Read permission for documentation-robotics folder");
|
|
108
|
+
if (!permissions.find((p) => p.name === "Read" && (p.scope === ".dr" || p.description.includes(".dr"))))
|
|
109
|
+
throw new Error("Read-safe permissions must include Read permission for .dr folder");
|
|
110
|
+
}
|
|
111
|
+
function validateReadSafeConstraints() {
|
|
112
|
+
validatePermissions(DEFAULT_READ_SAFE_PERMISSIONS);
|
|
113
|
+
}
|
|
114
|
+
function applyCopilotPermissions(cmd, variant, copilotCommand, withDanger, onTelemetry, testSpawnSync) {
|
|
115
|
+
const spawnSyncFn = testSpawnSync || spawnSync;
|
|
116
|
+
if (withDanger)
|
|
117
|
+
try {
|
|
118
|
+
const helpResult = spawnSyncFn(copilotCommand === "copilot" ? "copilot" : "gh", copilotCommand === "copilot" ? ["--help"] : ["copilot", "--help"], { stdio: "pipe", encoding: "utf-8", timeout: 1e3 });
|
|
119
|
+
helpResult.stdout?.includes("--allow-all-tools") || helpResult.stdout?.includes("allow-all-tools") ? (cmd.push("--allow-all-tools"), onTelemetry?.("process.allowAllToolsSupported", !0)) : (onTelemetry?.("process.allowAllToolsSupported", !1), console.warn(ansis.yellow(`Note: --allow-all-tools flag not supported by your ${variant} version`)));
|
|
120
|
+
} catch {
|
|
121
|
+
onTelemetry?.("process.allowAllToolsCheckFailed", !0), console.warn(ansis.yellow(`WARNING: Could not verify --allow-all-tools support for ${variant}. Launching WITHOUT permission escalation. If you need full tool access, please ensure your ${variant} CLI is up-to-date.`));
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
try {
|
|
125
|
+
const helpText = spawnSyncFn(copilotCommand === "copilot" ? "copilot" : "gh", copilotCommand === "copilot" ? ["--help"] : ["copilot", "--help"], { stdio: "pipe", encoding: "utf-8", timeout: 1e3 }).stdout || "";
|
|
126
|
+
if (helpText.includes("--allowedTools") || helpText.includes("allowedTools") || helpText.includes("--allowed-tools") || helpText.includes("allowed-tools")) {
|
|
127
|
+
const allowedToolsValue = formatForCopilot();
|
|
128
|
+
cmd.push("--allowedTools", allowedToolsValue), onTelemetry?.("process.readSafePermissionsApplied", !0), onTelemetry?.("process.allowedTools", allowedToolsValue);
|
|
129
|
+
} else {
|
|
130
|
+
const allowedToolsValue = formatForCopilot();
|
|
131
|
+
cmd.push("--allowedTools", allowedToolsValue), onTelemetry?.("process.readSafePermissionsApplied", !0), onTelemetry?.("process.allowedTools", allowedToolsValue), console.warn(ansis.yellow(`Note: Your ${variant} version doesn't advertise --allowedTools in help, but attempting to apply it. If not supported, please upgrade @github/copilot for granular permission controls.`));
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
onTelemetry?.("process.readSafePermissionCheckFailed", !0);
|
|
135
|
+
const allowedToolsValue = formatForCopilot();
|
|
136
|
+
cmd.push("--allowedTools", allowedToolsValue), onTelemetry?.("process.readSafePermissionsApplied", !0), onTelemetry?.("process.allowedTools", allowedToolsValue), console.warn(ansis.yellow(`WARNING: Could not verify read-safe permission support for ${variant}. Applying read-safe restrictions defensively. If this is not supported by your ${variant} version, please upgrade @github/copilot or copilot CLI.`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
validateReadSafeConstraints();
|
|
140
|
+
export {
|
|
141
|
+
CHANGESET_ONLY_ENV,
|
|
142
|
+
CHANGESET_WRITE_COMMANDS,
|
|
143
|
+
DEFAULT_READ_SAFE_PERMISSIONS,
|
|
144
|
+
applyCopilotPermissions,
|
|
145
|
+
bashCommands,
|
|
146
|
+
formatForClaudeCode,
|
|
147
|
+
formatForClaudeCodeWithChangesets,
|
|
148
|
+
formatForCopilot,
|
|
149
|
+
validatePermissions,
|
|
150
|
+
validateReadSafeConstraints
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=default-permissions.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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:*),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
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { ApiKeyManager } from "../mcp/api-key-manager.js";
|
|
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
|
+
]), DR_MCP_CHANGESET_TOOLS = Object.freeze([
|
|
28
|
+
"changeset_create",
|
|
29
|
+
"changeset_activate",
|
|
30
|
+
"changeset_deactivate",
|
|
31
|
+
"model_add",
|
|
32
|
+
"model_update",
|
|
33
|
+
"model_delete",
|
|
34
|
+
"relationship_add",
|
|
35
|
+
"relationship_delete",
|
|
36
|
+
"changeset_update",
|
|
37
|
+
"changeset_unstage"
|
|
38
|
+
]);
|
|
39
|
+
function drMcpToolRule(tool) {
|
|
40
|
+
return `mcp__${DR_MCP_SERVER_NAME}__${tool}`;
|
|
41
|
+
}
|
|
42
|
+
function drMcpAllowedTools(mode) {
|
|
43
|
+
return mode === "danger" ? [] : (mode === "changesets" ? [...DR_MCP_READ_TOOLS, ...DR_MCP_CHANGESET_TOOLS] : [...DR_MCP_READ_TOOLS]).map(drMcpToolRule);
|
|
44
|
+
}
|
|
45
|
+
function resolveDrCommand(env = process.env, exists = existsSync) {
|
|
46
|
+
const override = env[DR_CLI_ENTRY_ENV];
|
|
47
|
+
if (override && exists(override))
|
|
48
|
+
return { command: process.execPath, args: [override] };
|
|
49
|
+
const bundled = fileURLToPath(new URL("../cli.js", import.meta.url));
|
|
50
|
+
return exists(bundled) ? { command: process.execPath, args: [bundled] } : { command: "dr", args: [] };
|
|
51
|
+
}
|
|
52
|
+
function buildDrMcpConfig(options) {
|
|
53
|
+
const env = {
|
|
54
|
+
DR_MCP_API_KEY: options.apiKey,
|
|
55
|
+
[DR_MCP_ROOT_PATH_ENV]: options.rootPath,
|
|
56
|
+
// The key is already in this private config; don't print it into the client's MCP logs.
|
|
57
|
+
DR_MCP_QUIET_KEY: "1"
|
|
58
|
+
};
|
|
59
|
+
return options.changesetOnly && (env[CHANGESET_ONLY_ENV] = "true"), {
|
|
60
|
+
mcpServers: {
|
|
61
|
+
[DR_MCP_SERVER_NAME]: {
|
|
62
|
+
type: "stdio",
|
|
63
|
+
command: options.drCommand.command,
|
|
64
|
+
args: [...options.drCommand.args, "mcp"],
|
|
65
|
+
env
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function prepareDrMcpLaunch(options) {
|
|
71
|
+
const keyManager = options.keyManager ?? new ApiKeyManager(), { key } = await keyManager.ensureKey(), config = buildDrMcpConfig({
|
|
72
|
+
drCommand: options.drCommand ?? resolveDrCommand(),
|
|
73
|
+
apiKey: key,
|
|
74
|
+
rootPath: options.rootPath,
|
|
75
|
+
changesetOnly: options.changesetOnly
|
|
76
|
+
}), dir = await mkdtemp(join(options.tempRoot ?? tmpdir(), "dr-drbot-mcp-"));
|
|
77
|
+
let removed = !1;
|
|
78
|
+
const cleanup = async () => {
|
|
79
|
+
if (!removed) {
|
|
80
|
+
removed = !0;
|
|
81
|
+
try {
|
|
82
|
+
await rm(dir, { recursive: !0, force: !0 });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.warn(`[DrBot] Could not remove MCP config directory ${dir}: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
try {
|
|
89
|
+
await chmod(dir, 448);
|
|
90
|
+
const configPath = join(dir, "mcp.json");
|
|
91
|
+
return await writeFile(configPath, JSON.stringify(config, null, 2), {
|
|
92
|
+
mode: 384
|
|
93
|
+
}), { configPath, cleanup };
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw await cleanup(), error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function drBotMcpPrompt(mode) {
|
|
99
|
+
const lines = [
|
|
100
|
+
`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.`
|
|
101
|
+
];
|
|
102
|
+
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(`
|
|
103
|
+
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
106
|
+
const DR_ARCHITECT_AGENT = "dr-architect";
|
|
107
|
+
function parseAgentFrontmatterTools(markdown) {
|
|
108
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(markdown);
|
|
109
|
+
if (!match)
|
|
110
|
+
return null;
|
|
111
|
+
const lines = match[1].split(/\r?\n/), index = lines.findIndex((line) => /^tools\s*:/.test(line));
|
|
112
|
+
if (index === -1)
|
|
113
|
+
return null;
|
|
114
|
+
const unquote = (value) => value.trim().replace(/^["']|["']$/g, "").trim(), inline = lines[index].replace(/^tools\s*:/, "").trim();
|
|
115
|
+
if (inline)
|
|
116
|
+
return inline.replace(/^\[|\]$/g, "").split(",").map(unquote).filter(Boolean);
|
|
117
|
+
const tools = [];
|
|
118
|
+
for (const line of lines.slice(index + 1)) {
|
|
119
|
+
const item = /^\s+-\s*(.+)$/.exec(line);
|
|
120
|
+
if (!item)
|
|
121
|
+
break;
|
|
122
|
+
tools.push(unquote(item[1]));
|
|
123
|
+
}
|
|
124
|
+
return tools;
|
|
125
|
+
}
|
|
126
|
+
function findInstalledAgentFile(rootPath, homeDir = homedir(), exists = existsSync) {
|
|
127
|
+
for (const base of [rootPath, homeDir]) {
|
|
128
|
+
const candidate = join(base, ".claude", "agents", `${DR_ARCHITECT_AGENT}.md`);
|
|
129
|
+
if (exists(candidate))
|
|
130
|
+
return candidate;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function drMcpToolsForMode(mode) {
|
|
135
|
+
return mode === "danger" ? [...DR_MCP_READ_TOOLS, ...DR_MCP_CHANGESET_TOOLS].map(drMcpToolRule) : drMcpAllowedTools(mode);
|
|
136
|
+
}
|
|
137
|
+
async function checkInstalledAgentMcpTools(rootPath, mode, homeDir = homedir()) {
|
|
138
|
+
const path = findInstalledAgentFile(rootPath, homeDir);
|
|
139
|
+
if (!path)
|
|
140
|
+
return null;
|
|
141
|
+
const tools = parseAgentFrontmatterTools(await readFile(path, "utf8"));
|
|
142
|
+
if (tools === null)
|
|
143
|
+
return null;
|
|
144
|
+
const listed = new Set(tools);
|
|
145
|
+
if (listed.has(`mcp__${DR_MCP_SERVER_NAME}`) || listed.has(`mcp__${DR_MCP_SERVER_NAME}__*`))
|
|
146
|
+
return null;
|
|
147
|
+
const required = drMcpToolsForMode(mode), missing = required.filter((rule) => !listed.has(rule));
|
|
148
|
+
return missing.length === 0 ? null : { path, missing, noneVisible: missing.length === required.length };
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
DR_ARCHITECT_AGENT,
|
|
152
|
+
DR_CLI_ENTRY_ENV,
|
|
153
|
+
DR_MCP_CHANGESET_TOOLS,
|
|
154
|
+
DR_MCP_READ_TOOLS,
|
|
155
|
+
DR_MCP_ROOT_PATH_ENV,
|
|
156
|
+
DR_MCP_SERVER_NAME,
|
|
157
|
+
buildDrMcpConfig,
|
|
158
|
+
checkInstalledAgentMcpTools,
|
|
159
|
+
drBotMcpPrompt,
|
|
160
|
+
drMcpAllowedTools,
|
|
161
|
+
drMcpToolRule,
|
|
162
|
+
findInstalledAgentFile,
|
|
163
|
+
parseAgentFrontmatterTools,
|
|
164
|
+
prepareDrMcpLaunch,
|
|
165
|
+
resolveDrCommand
|
|
166
|
+
};
|
|
167
|
+
//# 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 * Authentication: `dr mcp` over stdio requires DR_MCP_API_KEY to match the installation's\n * stored key (mcp/api-key-manager.ts). Over stdio the client supplies both the process and its\n * environment, so a key minted per launch would only be checked against itself; the launch\n * instead resolves the installation key (creating it on first use, as `dr mcp` does) and puts\n * it only in a private temp config file (0600, in a 0700 directory under the OS temp dir),\n * never in the repo, the model directory or the process arguments. The file is removed when the\n * agent process exits.\n *\n * Tolerance: if anything here fails (no key, 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 } from \"node:fs\";\nimport { chmod, mkdtemp, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { ApiKeyManager } from \"../mcp/api-key-manager.js\";\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]);\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. Commit and apply are not MCP tools at all, and changeset_discard (which deletes the\n * staged changes) stays out: abandoning a changeset is the user's call.\n * chat_launch (an agent launching another agent) and the annotation writers stay out.\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 apiKey: string;\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_API_KEY: options.apiKey,\n [DR_MCP_ROOT_PATH_ENV]: options.rootPath,\n // The key is already in this private config; don't print it into the client's MCP logs.\n DR_MCP_QUIET_KEY: \"1\",\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 keyManager?: Pick<ApiKeyManager, \"ensureKey\">;\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 keyManager = options.keyManager ?? new ApiKeyManager();\n // No prompt: the server is non-interactive, so a first-time key goes to the default path.\n const { key } = await keyManager.ensureKey();\n const config = buildDrMcpConfig({\n drCommand: options.drCommand ?? resolveDrCommand(),\n apiKey: key,\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 * The dr-architect agent file Claude Code loads for a session run in `rootPath`: the project's\n * .claude/agents copy wins over the user's ~/.claude/agents copy. Null when neither exists.\n */\nexport function findInstalledAgentFile(\n rootPath: string,\n homeDir: string = homedir(),\n exists: (path: string) => boolean = existsSync\n): string | null {\n for (const base of [rootPath, homeDir]) {\n const candidate = join(base, \".claude\", \"agents\", `${DR_ARCHITECT_AGENT}.md`);\n if (exists(candidate)) return candidate;\n }\n return null;\n}\n\n/** The dr MCP tools a mode uses that the installed dr-architect agent can't see. */\nexport interface AgentMcpToolCheck {\n /** The agent file that was checked. */\n path: string;\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\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 the installed dr-architect agent lists the dr MCP tools DrBot's mode uses. An\n * agent's `tools:` field limits what it can call, so a copy installed before DrBot moved to MCP\n * can't see the server at all, whatever --allowedTools says. Null when there's nothing to report:\n * no agent file found, no `tools:` field, a server-wide `mcp__dr` entry, or every tool listed.\n */\nexport async function checkInstalledAgentMcpTools(\n rootPath: string,\n mode: DrBotMode,\n homeDir: string = homedir()\n): Promise<AgentMcpToolCheck | null> {\n const path = findInstalledAgentFile(rootPath, homeDir);\n if (!path) return null;\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, missing, noneVisible: missing.length === required.length };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/commands/add.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Layer } from "../core/layer.js";
|
|
|
4
4
|
import { Element } from "../core/element.js";
|
|
5
5
|
import { MutationHandler } from "../core/mutation-handler.js";
|
|
6
6
|
import { validateSourceReferenceOptions, 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";
|
|
@@ -36,7 +37,7 @@ async function addCommand(layer, type, name, options) {
|
|
|
36
37
|
span?.setAttribute("layer.name", layer), span?.setAttribute("element.type", resolvedType), span?.setAttribute("element.id", elementPath), validateSourceReferenceOptions(options);
|
|
37
38
|
let model;
|
|
38
39
|
try {
|
|
39
|
-
model = await Model.load(
|
|
40
|
+
model = await Model.load(options.model, { layers: [layer] });
|
|
40
41
|
} catch (error) {
|
|
41
42
|
const message = getErrorMessage(error);
|
|
42
43
|
throw message.includes("No DR project") || message.includes("Model not found") ? new ModelNotFoundError() : error;
|
|
@@ -78,7 +79,7 @@ async function addCommand(layer, type, name, options) {
|
|
|
78
79
|
const errorMessages = attrValidation.errors.map((e) => ` ${e.message}`).join(`
|
|
79
80
|
`);
|
|
80
81
|
throw new CLIError(`Element ${elementPath} has invalid attributes:
|
|
81
|
-
${errorMessages}`, ErrorCategory.USER, [
|
|
82
|
+
${errorMessages}`, ErrorCategory.USER, [schemaLookupHint("cli", layer, resolvedType)]);
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
if (layerObj.getElement(elementPath))
|
|
@@ -93,15 +94,19 @@ ${errorMessages}`, ErrorCategory.USER, [`Run "dr schema ${layer} ${type}" to see
|
|
|
93
94
|
}), handler.getAfterState()) {
|
|
94
95
|
const activeChangeset = await handler.getStagingManager().getActive();
|
|
95
96
|
activeChangeset && activeChangeset.status === "staged" ? handleSuccess(`Staged element ${ansis.bold(elementPath)} to ${ansis.bold(activeChangeset.name)}`, {
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
elementId: elementPath,
|
|
98
|
+
layer,
|
|
98
99
|
type: resolvedType,
|
|
99
|
-
name: options.name || name
|
|
100
|
-
|
|
100
|
+
name: options.name || name,
|
|
101
|
+
changesetStatus: "staged",
|
|
102
|
+
changeset: activeChangeset.name
|
|
103
|
+
}, { verbose: options.verbose }) : handleSuccess(`Added element ${ansis.bold(elementPath)} to ${ansis.bold(layer)} layer`, {
|
|
104
|
+
elementId: elementPath,
|
|
105
|
+
layer,
|
|
101
106
|
type: resolvedType,
|
|
102
107
|
name: options.name || name,
|
|
103
108
|
description: options.description || "(none)"
|
|
104
|
-
});
|
|
109
|
+
}, { verbose: !0 });
|
|
105
110
|
}
|
|
106
111
|
} catch (error) {
|
|
107
112
|
handleError(error);
|