@documentation-robotics/cli 0.1.13 → 0.2.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 +38 -0
- package/dist/analyzers/verify-engine.js +22 -19
- package/dist/analyzers/verify-engine.js.map +2 -2
- package/dist/cli.js +101 -27
- 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 +122 -0
- package/dist/coding-agents/default-permissions.js.map +7 -0
- package/dist/commands/add.js +10 -6
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +18 -10
- 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.js +238 -145
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +45 -18
- 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 +657 -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 +107 -0
- package/dist/commands/mcp.js.map +7 -0
- package/dist/commands/relationship.js +53 -30
- 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 +19 -6
- package/dist/commands/update.js.map +2 -2
- 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/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/farm-manifest.js +134 -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 +159 -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 +55 -1
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model.js +177 -16
- package/dist/core/model.js.map +3 -3
- package/dist/core/virtual-projection.js +1 -0
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/generated/node-types.js +1 -1
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +45 -9
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +55 -0
- package/dist/integrations/claude_code/docs/README.md +4 -0
- package/dist/mcp/api-key-manager.js +98 -0
- package/dist/mcp/api-key-manager.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 +44 -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-list.js +33 -0
- package/dist/mcp/tools/changeset-list.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +39 -0
- package/dist/mcp/tools/changeset-show.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 +47 -0
- package/dist/mcp/tools/index.js.map +7 -0
- package/dist/mcp/tools/model-add.js +106 -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 +88 -0
- package/dist/mcp/tools/model-search.js.map +7 -0
- package/dist/mcp/tools/model-show.js +41 -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 +55 -0
- package/dist/mcp/tools/model-trace.js.map +7 -0
- package/dist/mcp/tools/model-update.js +105 -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/shared.js +103 -0
- package/dist/mcp/tools/shared.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 +2 -2
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1 -1
- package/dist/schemas/bundled/apm.json +1 -1
- package/dist/schemas/bundled/application.json +12 -1
- package/dist/schemas/bundled/base.json +1 -1
- package/dist/schemas/bundled/business.json +1 -1
- package/dist/schemas/bundled/data-model.json +38 -2
- package/dist/schemas/bundled/data-store.json +1 -1
- package/dist/schemas/bundled/manifest.json +4 -4
- package/dist/schemas/bundled/motivation.json +1 -1
- package/dist/schemas/bundled/navigation.json +1 -1
- package/dist/schemas/bundled/product.json +1 -1
- package/dist/schemas/bundled/security.json +1 -1
- package/dist/schemas/bundled/technology.json +1 -1
- package/dist/schemas/bundled/testing.json +1 -1
- package/dist/schemas/bundled/ux.json +1 -1
- package/dist/server/server.js +22 -3
- package/dist/server/server.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 +25 -0
- package/dist/utils/ansi-suppressor.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/errors.js +69 -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 +24 -2
- 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 +5 -0
- 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 +262 -0
- package/dist/validators/composed-reference-validator.js.map +7 -0
- package/dist/validators/reference-validator.js +59 -8
- package/dist/validators/reference-validator.js.map +3 -3
- package/dist/validators/relationship-schema-validator.js +30 -2
- package/dist/validators/relationship-schema-validator.js.map +3 -3
- package/package.json +6 -3
|
@@ -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,122 @@
|
|
|
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.map((p) => p.scope ? `${p.name}(${p.scope})` : p.name).join(",");
|
|
31
|
+
}
|
|
32
|
+
function formatForCopilot() {
|
|
33
|
+
return formatForClaudeCode();
|
|
34
|
+
}
|
|
35
|
+
function validatePermissions(permissions) {
|
|
36
|
+
if (!Array.isArray(permissions) || permissions.length === 0)
|
|
37
|
+
throw new Error("Permissions must be a non-empty array");
|
|
38
|
+
for (let i = 0; i < permissions.length; i++) {
|
|
39
|
+
const perm = permissions[i];
|
|
40
|
+
if (typeof perm.name != "string" || perm.name.length === 0)
|
|
41
|
+
throw new Error(`Permission[${i}].name must be a non-empty string, got: ${typeof perm.name}`);
|
|
42
|
+
if (typeof perm.description != "string" || perm.description.length === 0)
|
|
43
|
+
throw new Error(`Permission[${i}].description must be a non-empty string, got: ${typeof perm.description}`);
|
|
44
|
+
if (typeof perm.allowsWrite != "boolean")
|
|
45
|
+
throw new Error(`Permission[${i}].allowsWrite must be a boolean, got: ${typeof perm.allowsWrite}`);
|
|
46
|
+
}
|
|
47
|
+
const writePerms = permissions.filter((p) => p.allowsWrite);
|
|
48
|
+
if (writePerms.length > 0)
|
|
49
|
+
throw new Error(`Read-safe permissions must not allow write operations. Found ${writePerms.length} permission(s) with allowsWrite=true`);
|
|
50
|
+
const allowedToolNames = /* @__PURE__ */ new Set(["Bash", "Read"]), disallowedTools = permissions.filter((p) => !allowedToolNames.has(p.name));
|
|
51
|
+
if (disallowedTools.length > 0)
|
|
52
|
+
throw new Error(`Read-safe permissions must only include Bash and Read tools. Found disallowed tool(s): ${disallowedTools.map((p) => p.name).join(", ")}`);
|
|
53
|
+
const bashPerms = permissions.filter((p) => p.name === "Bash");
|
|
54
|
+
if (bashPerms.length > 0) {
|
|
55
|
+
const invalidBash = bashPerms.filter((p) => {
|
|
56
|
+
const hasValidDescription = p.description.toLowerCase().includes("dr") && p.description.toLowerCase().includes("cli"), hasValidScope = typeof p.scope == "string" && p.scope.length > 0;
|
|
57
|
+
let hasValidScopeContent = !0;
|
|
58
|
+
if (hasValidScope) {
|
|
59
|
+
const scope = p.scope;
|
|
60
|
+
(scope === "*" || scope === "." || scope === "dr *" || scope.startsWith("dr *")) && (hasValidScopeContent = !1), scope.startsWith("dr ") || (hasValidScopeContent = !1);
|
|
61
|
+
}
|
|
62
|
+
return !hasValidDescription || !hasValidScope || !hasValidScopeContent;
|
|
63
|
+
});
|
|
64
|
+
if (invalidBash.length > 0) {
|
|
65
|
+
const errors = invalidBash.map((p) => {
|
|
66
|
+
const issues = [];
|
|
67
|
+
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)
|
|
68
|
+
issues.push("missing or empty scope");
|
|
69
|
+
else {
|
|
70
|
+
const scope = p.scope;
|
|
71
|
+
(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");
|
|
72
|
+
}
|
|
73
|
+
return `${p.description} (${issues.join(", ")})`;
|
|
74
|
+
});
|
|
75
|
+
throw new Error(`Bash permission must be for dr CLI only with properly restricted scope. Found invalid Bash permission(s): ${errors.join("; ")}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!permissions.find((p) => p.name === "Read" && p.description.toLowerCase().includes("codebase")))
|
|
79
|
+
throw new Error("Read-safe permissions must include Read permission for codebase");
|
|
80
|
+
if (!permissions.find((p) => p.name === "Read" && p.description.includes("documentation-robotics")))
|
|
81
|
+
throw new Error("Read-safe permissions must include Read permission for documentation-robotics folder");
|
|
82
|
+
if (!permissions.find((p) => p.name === "Read" && (p.scope === ".dr" || p.description.includes(".dr"))))
|
|
83
|
+
throw new Error("Read-safe permissions must include Read permission for .dr folder");
|
|
84
|
+
}
|
|
85
|
+
function validateReadSafeConstraints() {
|
|
86
|
+
validatePermissions(DEFAULT_READ_SAFE_PERMISSIONS);
|
|
87
|
+
}
|
|
88
|
+
function applyCopilotPermissions(cmd, variant, copilotCommand, withDanger, onTelemetry, testSpawnSync) {
|
|
89
|
+
const spawnSyncFn = testSpawnSync || spawnSync;
|
|
90
|
+
if (withDanger)
|
|
91
|
+
try {
|
|
92
|
+
const helpResult = spawnSyncFn(copilotCommand === "copilot" ? "copilot" : "gh", copilotCommand === "copilot" ? ["--help"] : ["copilot", "--help"], { stdio: "pipe", encoding: "utf-8", timeout: 1e3 });
|
|
93
|
+
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`)));
|
|
94
|
+
} catch {
|
|
95
|
+
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.`));
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
try {
|
|
99
|
+
const helpText = spawnSyncFn(copilotCommand === "copilot" ? "copilot" : "gh", copilotCommand === "copilot" ? ["--help"] : ["copilot", "--help"], { stdio: "pipe", encoding: "utf-8", timeout: 1e3 }).stdout || "";
|
|
100
|
+
if (helpText.includes("--allowedTools") || helpText.includes("allowedTools") || helpText.includes("--allowed-tools") || helpText.includes("allowed-tools")) {
|
|
101
|
+
const allowedToolsValue = formatForCopilot();
|
|
102
|
+
cmd.push("--allowedTools", allowedToolsValue), onTelemetry?.("process.readSafePermissionsApplied", !0), onTelemetry?.("process.allowedTools", allowedToolsValue);
|
|
103
|
+
} else {
|
|
104
|
+
const allowedToolsValue = formatForCopilot();
|
|
105
|
+
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.`));
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
onTelemetry?.("process.readSafePermissionCheckFailed", !0);
|
|
109
|
+
const allowedToolsValue = formatForCopilot();
|
|
110
|
+
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.`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
validateReadSafeConstraints();
|
|
114
|
+
export {
|
|
115
|
+
DEFAULT_READ_SAFE_PERMISSIONS,
|
|
116
|
+
applyCopilotPermissions,
|
|
117
|
+
formatForClaudeCode,
|
|
118
|
+
formatForCopilot,
|
|
119
|
+
validatePermissions,
|
|
120
|
+
validateReadSafeConstraints
|
|
121
|
+
};
|
|
122
|
+
//# 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|dr show|dr list|...),Read(.),Read(documentation-robotics)\").\n *\n * @returns String formatted for Claude Code --allowedTools flag\n */\nexport function formatForClaudeCode(): string {\n // Format each permission as \"ToolName\" or \"ToolName(scope)\" with scope constraints\n const formatted = DEFAULT_READ_SAFE_PERMISSIONS.map((p) => {\n if (p.scope) {\n return `${p.name}(${p.scope})`;\n }\n return p.name;\n });\n return formatted.join(\",\");\n}\n\n/**\n * Convert read-safe permissions to GitHub Copilot CLI format\n *\n * GitHub Copilot CLI may support a --allowedTools flag (or equivalent) for granular permission control.\n * This function formats the permission list similarly to Claude Code for potential future Copilot versions\n * that support granular scoped permissions. If the installed Copilot version doesn't support this flag,\n * the caller should gracefully degrade (e.g., launch without the flag, with a warning).\n *\n * Format: \"Bash(dr query|dr show|dr list|...),Read(.),Read(documentation-robotics),Read(.dr)\"\n *\n * @returns String formatted for Copilot --allowedTools flag (if supported)\n */\nexport function formatForCopilot(): string {\n return formatForClaudeCode();\n}\n\n/**\n * Validate a permission array against read-safe constraints\n *\n * Internal function that validates any permission array. Used by tests to verify\n * that invalid configurations are properly rejected.\n *\n * @param permissions The permission array to validate\n * @throws {Error} If any constraint is violated\n */\nexport function validatePermissions(permissions: readonly ToolPermission[]): void {\n // Constraint 1: Non-empty allowlist\n if (!Array.isArray(permissions) || permissions.length === 0) {\n throw new Error(\n \"Permissions must be a non-empty array\"\n );\n }\n\n // Constraint 2: All permissions must have required fields with correct types\n for (let i = 0; i < permissions.length; i++) {\n const perm = permissions[i];\n\n if (typeof perm.name !== \"string\" || perm.name.length === 0) {\n throw new Error(\n `Permission[${i}].name must be a non-empty string, got: ${typeof perm.name}`\n );\n }\n\n if (typeof perm.description !== \"string\" || perm.description.length === 0) {\n throw new Error(\n `Permission[${i}].description must be a non-empty string, got: ${typeof perm.description}`\n );\n }\n\n if (typeof perm.allowsWrite !== \"boolean\") {\n throw new Error(\n `Permission[${i}].allowsWrite must be a boolean, got: ${typeof perm.allowsWrite}`\n );\n }\n }\n\n // Constraint 3: No permission should allow write operations (read-safe invariant)\n const writePerms = permissions.filter((p) => p.allowsWrite);\n if (writePerms.length > 0) {\n throw new Error(\n `Read-safe permissions must not allow write operations. Found ${writePerms.length} permission(s) with allowsWrite=true`\n );\n }\n\n // Constraint 4: Only Bash and Read tools allowed (allowlist validation, not denylist)\n const allowedToolNames = new Set([\"Bash\", \"Read\"]);\n const disallowedTools = permissions.filter(\n (p) => !allowedToolNames.has(p.name)\n );\n if (disallowedTools.length > 0) {\n throw new Error(\n `Read-safe permissions must only include Bash and Read tools. Found disallowed tool(s): ${disallowedTools.map((p) => p.name).join(\", \")}`\n );\n }\n\n // Constraint 5: Bash must be for dr CLI only with properly restricted scope (no wildcards or bare patterns)\n const bashPerms = permissions.filter((p) => p.name === \"Bash\");\n if (bashPerms.length > 0) {\n const invalidBash = bashPerms.filter((p) => {\n const hasValidDescription = p.description.toLowerCase().includes(\"dr\") && p.description.toLowerCase().includes(\"cli\");\n const hasValidScope = typeof p.scope === \"string\" && p.scope.length > 0;\n\n // Validate scope content: reject bare wildcards and unsafe patterns\n let hasValidScopeContent = true;\n if (hasValidScope) {\n const scope = p.scope;\n // Reject dangerous scope patterns: bare \"*\", \".\", or \"dr *\"\n if (scope === \"*\" || scope === \".\" || scope === \"dr *\" || scope.startsWith(\"dr *\")) {\n hasValidScopeContent = false;\n }\n // Ensure scope starts with \"dr \" for Bash tool\n if (!scope.startsWith(\"dr \")) {\n hasValidScopeContent = false;\n }\n }\n\n return !hasValidDescription || !hasValidScope || !hasValidScopeContent;\n });\n if (invalidBash.length > 0) {\n const errors = invalidBash.map((p) => {\n const issues = [];\n if (!p.description.toLowerCase().includes(\"dr\") || !p.description.toLowerCase().includes(\"cli\")) {\n issues.push(\"invalid description (must include 'dr' and 'cli')\");\n }\n if (typeof p.scope !== \"string\" || p.scope.length === 0) {\n issues.push(\"missing or empty scope\");\n } else {\n const scope = p.scope;\n if (scope === \"*\" || scope === \".\" || scope === \"dr *\" || scope.startsWith(\"dr *\")) {\n issues.push(\"scope uses dangerous wildcard pattern (must specify read-safe commands)\");\n }\n if (!scope.startsWith(\"dr \")) {\n issues.push(\"scope must start with 'dr ' for Bash tool\");\n }\n }\n return `${p.description} (${issues.join(\", \")})`;\n });\n throw new Error(\n `Bash permission must be for dr CLI only with properly restricted scope. Found invalid Bash permission(s): ${errors.join(\"; \")}`\n );\n }\n }\n\n // Constraint 6: Must have Read permission for codebase\n const codebaseRead = permissions.find(\n (p) => p.name === \"Read\" && p.description.toLowerCase().includes(\"codebase\")\n );\n if (!codebaseRead) {\n throw new Error(\"Read-safe permissions must include Read permission for codebase\");\n }\n\n // Constraint 7: Must have Read permission for documentation-robotics folder\n const docRoboticsRead = permissions.find(\n (p) => p.name === \"Read\" && p.description.includes(\"documentation-robotics\")\n );\n if (!docRoboticsRead) {\n throw new Error(\"Read-safe permissions must include Read permission for documentation-robotics folder\");\n }\n\n // Constraint 8: Must have Read permission for .dr folder\n const drFolderRead = permissions.find(\n (p) => p.name === \"Read\" && (p.scope === \".dr\" || p.description.includes(\".dr\"))\n );\n if (!drFolderRead) {\n throw new Error(\"Read-safe permissions must include Read permission for .dr folder\");\n }\n\n // All constraints passed\n}\n\n/**\n * Validate read-safe permission constraints\n *\n * Called during module initialization to verify the permission allowlist maintains\n * its safety invariants: all permissions are read-only, required tools are present,\n * and no dangerous tools are included.\n *\n * Throws an error if constraints are violated, preventing runtime with an invalid configuration.\n *\n * @throws {Error} If any constraint is violated\n */\nexport function validateReadSafeConstraints(): void {\n validatePermissions(DEFAULT_READ_SAFE_PERMISSIONS);\n}\n\n/**\n * Apply Copilot permissions to command arguments with graceful degradation\n *\n * Shared utility for both CopilotClient and VisualizationServer to probe\n * Copilot CLI capability and apply appropriate permission flags.\n * Handles both default mode (read-safe --allowedTools) and danger mode (--allow-all-tools).\n *\n * @param cmd The command array to modify (mutated in place)\n * @param variant Display name for error messages (e.g., \"gh copilot\" or \"copilot\")\n * @param copilotCommand The copilot CLI name (\"copilot\" or \"gh\")\n * @param withDanger Whether to use danger mode (--allow-all-tools)\n * @param onTelemetry Optional callback for telemetry logging\n * @param testSpawnSync Optional injected spawnSync for testing (allows mocking CLI probe)\n */\nexport function applyCopilotPermissions(\n cmd: string[],\n variant: string,\n copilotCommand: string,\n withDanger: boolean,\n onTelemetry?: (attr: string, value: any) => void,\n testSpawnSync?: typeof spawnSync\n): void {\n const spawnSyncFn = testSpawnSync || spawnSync;\n\n if (withDanger) {\n try {\n const helpResult = spawnSyncFn(\n copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n copilotCommand === \"copilot\" ? [\"--help\"] : [\"copilot\", \"--help\"],\n { stdio: \"pipe\", encoding: \"utf-8\", timeout: 1000 }\n );\n if (\n helpResult.stdout?.includes(\"--allow-all-tools\") ||\n helpResult.stdout?.includes(\"allow-all-tools\")\n ) {\n cmd.push(\"--allow-all-tools\");\n onTelemetry?.(\"process.allowAllToolsSupported\", true);\n } else {\n onTelemetry?.(\"process.allowAllToolsSupported\", false);\n console.warn(\n ansis.yellow(`Note: --allow-all-tools flag not supported by your ${variant} version`)\n );\n }\n } catch {\n onTelemetry?.(\"process.allowAllToolsCheckFailed\", true);\n console.warn(\n ansis.yellow(\n `WARNING: Could not verify --allow-all-tools support for ${variant}. ` +\n `Launching WITHOUT permission escalation. If you need full tool access, ` +\n `please ensure your ${variant} CLI is up-to-date.`\n )\n );\n }\n } else {\n try {\n const helpResult = spawnSyncFn(\n copilotCommand === \"copilot\" ? \"copilot\" : \"gh\",\n copilotCommand === \"copilot\" ? [\"--help\"] : [\"copilot\", \"--help\"],\n { stdio: \"pipe\", encoding: \"utf-8\", timeout: 1000 }\n );\n\n const helpText = helpResult.stdout || \"\";\n const supportsAllowedTools =\n helpText.includes(\"--allowedTools\") ||\n helpText.includes(\"allowedTools\") ||\n helpText.includes(\"--allowed-tools\") ||\n helpText.includes(\"allowed-tools\");\n\n if (supportsAllowedTools) {\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n } else {\n // Flag not advertised in help, but still try to apply it optimistically\n // (fail-safe: try to restrict access even if we're not 100% sure the flag exists)\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n console.warn(\n ansis.yellow(\n `Note: Your ${variant} version doesn't advertise --allowedTools in help, ` +\n `but attempting to apply it. If not supported, ` +\n `please upgrade @github/copilot for granular permission controls.`\n )\n );\n }\n } catch {\n onTelemetry?.(\"process.readSafePermissionCheckFailed\", true);\n // Fail-closed approach: unable to verify support, so apply read-safe restrictions defensively\n // to maintain the read-safe guarantee even when the CLI probe fails.\n const allowedToolsValue = formatForCopilot();\n cmd.push(\"--allowedTools\", allowedToolsValue);\n onTelemetry?.(\"process.readSafePermissionsApplied\", true);\n onTelemetry?.(\"process.allowedTools\", allowedToolsValue);\n console.warn(\n ansis.yellow(\n `WARNING: Could not verify read-safe permission support for ${variant}. ` +\n `Applying read-safe restrictions defensively. ` +\n `If this is not supported by your ${variant} version, please upgrade @github/copilot or copilot CLI.`\n )\n );\n }\n }\n}\n\n// Validate read-safe constraints during module initialization\nvalidateReadSafeConstraints();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/commands/add.js
CHANGED
|
@@ -36,7 +36,7 @@ async function addCommand(layer, type, name, options) {
|
|
|
36
36
|
span?.setAttribute("layer.name", layer), span?.setAttribute("element.type", resolvedType), span?.setAttribute("element.id", elementPath), validateSourceReferenceOptions(options);
|
|
37
37
|
let model;
|
|
38
38
|
try {
|
|
39
|
-
model = await Model.load(
|
|
39
|
+
model = await Model.load(options.model || process.cwd(), { layers: [layer] });
|
|
40
40
|
} catch (error) {
|
|
41
41
|
const message = getErrorMessage(error);
|
|
42
42
|
throw message.includes("No DR project") || message.includes("Model not found") ? new ModelNotFoundError() : error;
|
|
@@ -93,15 +93,19 @@ ${errorMessages}`, ErrorCategory.USER, [`Run "dr schema ${layer} ${type}" to see
|
|
|
93
93
|
}), handler.getAfterState()) {
|
|
94
94
|
const activeChangeset = await handler.getStagingManager().getActive();
|
|
95
95
|
activeChangeset && activeChangeset.status === "staged" ? handleSuccess(`Staged element ${ansis.bold(elementPath)} to ${ansis.bold(activeChangeset.name)}`, {
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
elementId: elementPath,
|
|
97
|
+
layer,
|
|
98
98
|
type: resolvedType,
|
|
99
|
-
name: options.name || name
|
|
100
|
-
|
|
99
|
+
name: options.name || name,
|
|
100
|
+
changesetStatus: "staged",
|
|
101
|
+
changeset: activeChangeset.name
|
|
102
|
+
}, { verbose: options.verbose }) : handleSuccess(`Added element ${ansis.bold(elementPath)} to ${ansis.bold(layer)} layer`, {
|
|
103
|
+
elementId: elementPath,
|
|
104
|
+
layer,
|
|
101
105
|
type: resolvedType,
|
|
102
106
|
name: options.name || name,
|
|
103
107
|
description: options.description || "(none)"
|
|
104
|
-
});
|
|
108
|
+
}, { verbose: !0 });
|
|
105
109
|
}
|
|
106
110
|
} catch (error) {
|
|
107
111
|
handleError(error);
|
package/dist/commands/add.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/commands/add.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Add an element to a layer\n */\n\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { Layer } from \"../core/layer.js\";\nimport { Element } from \"../core/element.js\";\nimport { MutationHandler } from \"../core/mutation-handler.js\";\nimport { validateSourceReferenceOptions, buildSourceReference } from \"../utils/source-reference.js\";\nimport { SchemaValidator } from \"../validators/schema-validator.js\";\nimport { getActiveSpan } from \"../telemetry/index.js\";\nimport { generateElementId, generateUUID } from \"../utils/id-generator.js\";\nimport { getAllLayerIds, isValidLayer } from \"../generated/layer-registry.js\";\nimport { isValidNodeType, getNodeTypesForLayer, normalizeNodeType } from \"../generated/node-types.js\";\nimport {\n CLIError,\n ErrorCategory,\n InvalidJSONError,\n ModelNotFoundError,\n findSimilar,\n formatValidOptions,\n getErrorMessage,\n handleError,\n handleSuccess,\n} from \"../utils/errors.js\";\n\n\nexport interface AddOptions {\n name?: string;\n description?: string;\n attributes?: string;\n sourceFile?: string;\n sourceSymbol?: string;\n sourceProvenance?: string;\n sourceRepoRemote?: string;\n sourceRepoCommit?: string;\n verbose?: boolean;\n debug?: boolean;\n}\n\nexport async function addCommand(\n layer: string,\n type: string,\n name: string,\n options: AddOptions\n): Promise<void> {\n try {\n // Validate layer name\n if (!isValidLayer(layer)) {\n const validLayers = getAllLayerIds();\n const similar = findSimilar(layer, validLayers, 3);\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n if (similar.length > 0) {\n suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n }\n throw new CLIError(`Unknown layer \"${layer}\"`, ErrorCategory.USER, suggestions, {\n operation: \"add\",\n context: `Layer: ${layer}, Type: ${type}, Name: ${name}`,\n });\n }\n\n // Validate element type for this layer\n if (!isValidNodeType(layer, type)) {\n const validNodeTypes = getNodeTypesForLayer(layer);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(type, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${layer}: ${formatValidOptions(typeNames)}`];\n if (similar.length > 0) {\n suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n }\n throw new CLIError(\n `Invalid element type \"${type}\" for layer \"${layer}\"`,\n ErrorCategory.USER,\n suggestions,\n {\n operation: \"add\",\n context: `Layer: ${layer}, Type: ${type}, Name: ${name}`,\n }\n );\n }\n\n // Resolve abbreviated type names to their canonical spec names\n // e.g., \"service\" \u2192 \"applicationservice\" for the application layer\n const resolvedType = normalizeNodeType(layer, type);\n\n // Generate slug path: {layer}.{resolvedType}.{kebab-name}\n const elementPath = generateElementId(layer, resolvedType, name); // slug\n const elementUUID = generateUUID(); // UUIDv4\n\n // Validate the generated slug is non-empty (name may collapse to empty after stripping special chars)\n const slugPart = elementPath.split(\".\")[2];\n if (!slugPart) {\n throw new CLIError(\n `Cannot generate a valid element ID from name \"${name}\"`,\n ErrorCategory.USER,\n [\n \"Element names must contain at least one letter or digit\",\n `Generated ID would be: ${elementPath}`,\n ]\n );\n }\n\n const span = getActiveSpan();\n span?.setAttribute(\"layer.name\", layer);\n span?.setAttribute(\"element.type\", resolvedType);\n span?.setAttribute(\"element.id\", elementPath);\n\n // Validate source reference options\n validateSourceReferenceOptions(options);\n\n // Load model (with error handling for missing models)\n let model: Model;\n try {\n model = await Model.load(
|
|
5
|
-
"mappings": "AAIA,OAAO,WAAW;AAClB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,gCAAgC,4BAA4B;AACrE,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB,oBAAoB;AAChD,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,iBAAiB,sBAAsB,yBAAyB;AACzE,SACE,UACA,eACA,kBACA,oBACA,aACA,oBACA,iBACA,aACA,qBACK;
|
|
4
|
+
"sourcesContent": ["/**\n * Add an element to a layer\n */\n\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { Layer } from \"../core/layer.js\";\nimport { Element } from \"../core/element.js\";\nimport { MutationHandler } from \"../core/mutation-handler.js\";\nimport { validateSourceReferenceOptions, buildSourceReference } from \"../utils/source-reference.js\";\nimport { SchemaValidator } from \"../validators/schema-validator.js\";\nimport { getActiveSpan } from \"../telemetry/index.js\";\nimport { generateElementId, generateUUID } from \"../utils/id-generator.js\";\nimport { getAllLayerIds, isValidLayer } from \"../generated/layer-registry.js\";\nimport { isValidNodeType, getNodeTypesForLayer, normalizeNodeType } from \"../generated/node-types.js\";\nimport {\n CLIError,\n ErrorCategory,\n InvalidJSONError,\n ModelNotFoundError,\n findSimilar,\n formatValidOptions,\n getErrorMessage,\n handleError,\n handleSuccess,\n} from \"../utils/errors.js\";\n\n\nexport interface AddOptions {\n model?: string;\n name?: string;\n description?: string;\n attributes?: string;\n sourceFile?: string;\n sourceSymbol?: string;\n sourceProvenance?: string;\n sourceRepoRemote?: string;\n sourceRepoCommit?: string;\n verbose?: boolean;\n debug?: boolean;\n}\n\nexport async function addCommand(\n layer: string,\n type: string,\n name: string,\n options: AddOptions\n): Promise<void> {\n try {\n // Validate layer name\n if (!isValidLayer(layer)) {\n const validLayers = getAllLayerIds();\n const similar = findSimilar(layer, validLayers, 3);\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n if (similar.length > 0) {\n suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n }\n throw new CLIError(`Unknown layer \"${layer}\"`, ErrorCategory.USER, suggestions, {\n operation: \"add\",\n context: `Layer: ${layer}, Type: ${type}, Name: ${name}`,\n });\n }\n\n // Validate element type for this layer\n if (!isValidNodeType(layer, type)) {\n const validNodeTypes = getNodeTypesForLayer(layer);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(type, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${layer}: ${formatValidOptions(typeNames)}`];\n if (similar.length > 0) {\n suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n }\n throw new CLIError(\n `Invalid element type \"${type}\" for layer \"${layer}\"`,\n ErrorCategory.USER,\n suggestions,\n {\n operation: \"add\",\n context: `Layer: ${layer}, Type: ${type}, Name: ${name}`,\n }\n );\n }\n\n // Resolve abbreviated type names to their canonical spec names\n // e.g., \"service\" \u2192 \"applicationservice\" for the application layer\n const resolvedType = normalizeNodeType(layer, type);\n\n // Generate slug path: {layer}.{resolvedType}.{kebab-name}\n const elementPath = generateElementId(layer, resolvedType, name); // slug\n const elementUUID = generateUUID(); // UUIDv4\n\n // Validate the generated slug is non-empty (name may collapse to empty after stripping special chars)\n const slugPart = elementPath.split(\".\")[2];\n if (!slugPart) {\n throw new CLIError(\n `Cannot generate a valid element ID from name \"${name}\"`,\n ErrorCategory.USER,\n [\n \"Element names must contain at least one letter or digit\",\n `Generated ID would be: ${elementPath}`,\n ]\n );\n }\n\n const span = getActiveSpan();\n span?.setAttribute(\"layer.name\", layer);\n span?.setAttribute(\"element.type\", resolvedType);\n span?.setAttribute(\"element.id\", elementPath);\n\n // Validate source reference options\n validateSourceReferenceOptions(options);\n\n // Load model (with error handling for missing models)\n let model: Model;\n try {\n model = await Model.load(options.model || process.cwd(), { layers: [layer] });\n } catch (error) {\n const message = getErrorMessage(error);\n if (message.includes(\"No DR project\") || message.includes(\"Model not found\")) {\n throw new ModelNotFoundError();\n }\n throw error;\n }\n\n // Get or create layer\n let layerObj = await model.getLayer(layer);\n if (!layerObj) {\n layerObj = new Layer(layer);\n model.addLayer(layerObj);\n }\n\n // Parse attributes if provided\n let attributes: Record<string, unknown> = {};\n if (options.attributes) {\n try {\n attributes = JSON.parse(options.attributes);\n } catch (e) {\n throw new InvalidJSONError(options.attributes, \"--attributes\");\n }\n }\n\n // Create element in spec-node format\n // id = UUIDv4 for schema compliance; path = slug for human-readable identifier\n const element = new Element({\n id: elementUUID, // UUIDv4\n path: elementPath, // slug: {layer}.{resolvedType}.{kebab-name}\n spec_node_id: `${layer}.${resolvedType}`,\n layer_id: layer,\n type: resolvedType,\n name: options.name || name,\n description: options.description,\n attributes: attributes,\n metadata: {\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n version: 1,\n },\n layer, // Set layer so setSourceReference can use it\n });\n\n\n // Add source reference if provided\n const sourceRef = buildSourceReference(options);\n if (sourceRef) {\n element.setSourceReference(sourceRef);\n }\n\n // Validate element attributes against spec schema before persisting\n {\n const schemaValidator = new SchemaValidator();\n const tempLayer = new Layer(layer);\n tempLayer.addElement(element);\n const attrValidation = await schemaValidator.validateLayer(tempLayer);\n if (!attrValidation.isValid()) {\n const errorMessages = attrValidation.errors\n .map((e) => ` ${e.message}`)\n .join(\"\\n\");\n throw new CLIError(\n `Element ${elementPath} has invalid attributes:\\n${errorMessages}`,\n ErrorCategory.USER,\n [`Run \"dr schema ${layer} ${type}\" to see the required attributes`]\n );\n }\n }\n\n // Check if element already exists (look up by path)\n if (layerObj.getElement(elementPath)) {\n throw new CLIError(\n `Element ${elementPath} already exists in ${layer} layer`,\n ErrorCategory.USER,\n [\n `Use \"dr show ${elementPath}\" to view the existing element`,\n `Use \"dr update ${elementPath}\" to modify it`,\n `Use \"dr delete ${elementPath}\" to remove it first if you want to recreate it`,\n ],\n { operation: \"add\", context: `Duplicate element path` }\n );\n }\n\n // Unified mutation handler for add operation\n const handler = new MutationHandler(model, elementPath, layer);\n\n // Execute add through unified path (handles staging and base model consistently)\n await handler.executeAdd(element, (elem) => {\n // This mutator is called by executeAdd for base model path only\n layerObj.addElement(elem);\n });\n\n // Determine if operation was staged or applied to base model\n if (handler.getAfterState()) {\n // Check if we went through staging path\n const stagingManager = handler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n if (activeChangeset && activeChangeset.status === \"staged\") {\n // Staging path\n handleSuccess(\n `Staged element ${ansis.bold(elementPath)} to ${ansis.bold(activeChangeset.name)}`,\n {\n elementId: elementPath,\n layer,\n type: resolvedType,\n name: options.name || name,\n changesetStatus: \"staged\",\n changeset: activeChangeset.name,\n },\n { verbose: options.verbose }\n );\n } else {\n // Base model path\n handleSuccess(`Added element ${ansis.bold(elementPath)} to ${ansis.bold(layer)} layer`, {\n elementId: elementPath,\n layer,\n type: resolvedType,\n name: options.name || name,\n description: options.description || \"(none)\",\n }, { verbose: true });\n }\n }\n } catch (error) {\n handleError(error);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,12 +16,13 @@ Examples:
|
|
|
16
16
|
$ dr analyzer discover # Scan and select analyzer
|
|
17
17
|
$ dr analyzer discover --reselect # Force re-selection
|
|
18
18
|
$ dr analyzer discover --json # Output discovery results as JSON`).action(async (options) => {
|
|
19
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
19
20
|
try {
|
|
20
21
|
const projectRoot = await findProjectRoot() || process.cwd(), registry = AnalyzerRegistry.getInstance();
|
|
21
22
|
if (await registry.initialize(), !options.reselect) {
|
|
22
23
|
const session = await readSession(projectRoot);
|
|
23
24
|
if (session)
|
|
24
|
-
if (
|
|
25
|
+
if (jsonMode) {
|
|
25
26
|
const analyzerNames = await registry.getAnalyzerNames(), analyzerOptions2 = [];
|
|
26
27
|
for (const name of analyzerNames)
|
|
27
28
|
try {
|
|
@@ -53,11 +54,11 @@ Examples:
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
const discoverResult = await performDiscover(registry, {
|
|
56
|
-
json:
|
|
57
|
+
json: jsonMode,
|
|
57
58
|
reselect: options.reselect,
|
|
58
59
|
isTTY: process.stdin.isTTY
|
|
59
60
|
}), { discoveryResult, installed, analyzerOptions, selectedAnalyzer, shouldWriteSession } = discoverResult;
|
|
60
|
-
if (
|
|
61
|
+
if (jsonMode) {
|
|
61
62
|
if (selectedAnalyzer && shouldWriteSession) {
|
|
62
63
|
const state = {
|
|
63
64
|
active_analyzer: selectedAnalyzer,
|
|
@@ -113,6 +114,7 @@ Examples:
|
|
|
113
114
|
$ dr analyzer status # Show status of active analyzer
|
|
114
115
|
$ dr analyzer status --name cbm # Show status of specific analyzer
|
|
115
116
|
$ dr analyzer status --json # Output as JSON`).action(async (options) => {
|
|
117
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
116
118
|
try {
|
|
117
119
|
const projectRoot = await findProjectRoot() || process.cwd();
|
|
118
120
|
let analyzerName = options.name;
|
|
@@ -133,7 +135,7 @@ Examples:
|
|
|
133
135
|
detected: await backend.detect(),
|
|
134
136
|
indexed: !1,
|
|
135
137
|
fresh: !1
|
|
136
|
-
},
|
|
138
|
+
}, jsonMode) {
|
|
137
139
|
console.log(JSON.stringify(status, null, 2)), actualProjectRoot && await writeStatus(status, actualProjectRoot, analyzerName);
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
@@ -186,6 +188,7 @@ Examples:
|
|
|
186
188
|
$ dr analyzer endpoints # List endpoints from active analyzer
|
|
187
189
|
$ dr analyzer endpoints --json # Output as JSON
|
|
188
190
|
$ dr analyzer endpoints --name cbm # List endpoints from specific analyzer`).action(async (options) => {
|
|
191
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
189
192
|
try {
|
|
190
193
|
const projectRoot = await findProjectRoot();
|
|
191
194
|
if (!projectRoot)
|
|
@@ -205,7 +208,7 @@ Examples:
|
|
|
205
208
|
if (!(await backend.status(projectRoot)).indexed)
|
|
206
209
|
throw new CLIError("Project not indexed", ErrorCategory.USER, ["Run `dr analyzer index` to index the project first"]);
|
|
207
210
|
const endpoints = await backend.endpoints(projectRoot);
|
|
208
|
-
if (
|
|
211
|
+
if (jsonMode) {
|
|
209
212
|
console.log(JSON.stringify(endpoints, null, 2));
|
|
210
213
|
return;
|
|
211
214
|
}
|
|
@@ -232,6 +235,7 @@ Examples:
|
|
|
232
235
|
$ dr analyzer services # List all services
|
|
233
236
|
$ dr analyzer services --layer application
|
|
234
237
|
$ dr analyzer services --json # Output as JSON`).action(async (options) => {
|
|
238
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
235
239
|
try {
|
|
236
240
|
const projectRoot = await findProjectRoot();
|
|
237
241
|
if (!projectRoot)
|
|
@@ -251,7 +255,7 @@ Examples:
|
|
|
251
255
|
if (!await readIndexMeta(projectRoot, analyzerName))
|
|
252
256
|
throw new CLIError("Project not indexed", ErrorCategory.USER, ["Run `dr analyzer index` first"]);
|
|
253
257
|
let services = await backend.services(projectRoot);
|
|
254
|
-
if (options.layer && (services = services.filter((s) => s.suggested_layer === options.layer)),
|
|
258
|
+
if (options.layer && (services = services.filter((s) => s.suggested_layer === options.layer)), jsonMode) {
|
|
255
259
|
console.log(JSON.stringify(services, null, 2));
|
|
256
260
|
return;
|
|
257
261
|
}
|
|
@@ -277,6 +281,7 @@ Examples:
|
|
|
277
281
|
Examples:
|
|
278
282
|
$ dr analyzer datastores # List all inferred datastores
|
|
279
283
|
$ dr analyzer datastores --json # Output as JSON`).action(async (options) => {
|
|
284
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
280
285
|
try {
|
|
281
286
|
const projectRoot = await findProjectRoot();
|
|
282
287
|
if (!projectRoot)
|
|
@@ -296,7 +301,7 @@ Examples:
|
|
|
296
301
|
if (!await readIndexMeta(projectRoot, analyzerName))
|
|
297
302
|
throw new CLIError("Project not indexed", ErrorCategory.USER, ["Run `dr analyzer index` first"]);
|
|
298
303
|
const datastores = await backend.datastores(projectRoot);
|
|
299
|
-
if (
|
|
304
|
+
if (jsonMode) {
|
|
300
305
|
console.log(JSON.stringify(datastores, null, 2));
|
|
301
306
|
return;
|
|
302
307
|
}
|
|
@@ -323,6 +328,7 @@ Examples:
|
|
|
323
328
|
$ dr analyzer callers com.example.Service.handleRequest
|
|
324
329
|
$ dr analyzer callers com.example.Service.handleRequest --depth 5
|
|
325
330
|
$ dr analyzer callers com.example.Service.handleRequest --json`).action(async (qualifiedName, options) => {
|
|
331
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
326
332
|
try {
|
|
327
333
|
const depth = parseInt(options.depth, 10);
|
|
328
334
|
if (isNaN(depth) || depth < 1)
|
|
@@ -345,7 +351,7 @@ Examples:
|
|
|
345
351
|
if (!await readIndexMeta(projectRoot, analyzerName))
|
|
346
352
|
throw new CLIError("Project not indexed", ErrorCategory.USER, ["Run `dr analyzer index` first"]);
|
|
347
353
|
const callers = await backend.callers(projectRoot, qualifiedName, depth);
|
|
348
|
-
if (
|
|
354
|
+
if (jsonMode) {
|
|
349
355
|
console.log(JSON.stringify(callers, null, 2));
|
|
350
356
|
return;
|
|
351
357
|
}
|
|
@@ -368,6 +374,7 @@ Examples:
|
|
|
368
374
|
$ dr analyzer callees com.example.Service.handleRequest
|
|
369
375
|
$ dr analyzer callees com.example.Service.handleRequest --depth 5
|
|
370
376
|
$ dr analyzer callees com.example.Service.handleRequest --json`).action(async (qualifiedName, options) => {
|
|
377
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
371
378
|
try {
|
|
372
379
|
const depth = parseInt(options.depth, 10);
|
|
373
380
|
if (isNaN(depth) || depth < 1)
|
|
@@ -390,7 +397,7 @@ Examples:
|
|
|
390
397
|
if (!await readIndexMeta(projectRoot, analyzerName))
|
|
391
398
|
throw new CLIError("Project not indexed", ErrorCategory.USER, ["Run `dr analyzer index` first"]);
|
|
392
399
|
const callees = await backend.callees(projectRoot, qualifiedName, depth);
|
|
393
|
-
if (
|
|
400
|
+
if (jsonMode) {
|
|
394
401
|
console.log(JSON.stringify(callees, null, 2));
|
|
395
402
|
return;
|
|
396
403
|
}
|
|
@@ -452,9 +459,10 @@ Examples:
|
|
|
452
459
|
$ dr analyzer verify --format markdown # Output as Markdown to stdout
|
|
453
460
|
$ dr analyzer verify --output report.json # Save report to file (format inferred as JSON)
|
|
454
461
|
$ dr analyzer verify --output report.md # Save report as Markdown`).action(async (options) => {
|
|
462
|
+
const jsonMode = options.json ?? program.opts().json;
|
|
455
463
|
try {
|
|
456
464
|
let format = "text";
|
|
457
|
-
if (
|
|
465
|
+
if (jsonMode)
|
|
458
466
|
options.format && console.warn(ansis.yellow(`\u26A0 Both --json and --format "${options.format}" were provided. Using --json format (--format is ignored).`)), format = "json";
|
|
459
467
|
else if (options.format) {
|
|
460
468
|
const validFormats = ["text", "json", "markdown"];
|