diff --git a/src/helpers/coverage.js b/src/helpers/coverage.js index 0030c020fae..63047dea54f 100644 --- a/src/helpers/coverage.js +++ b/src/helpers/coverage.js @@ -60,6 +60,33 @@ function collectPropertyValues(data, propName) { // matching is deferred to v2. function collectValuesByPath(data, path) { const values = [] + + // For $defs paths (e.g. "#$defs/hookCommand.shell"), path-based traversal + // cannot work because the path references the schema definition, not the + // test data structure. Fall back to a deep name-based search for the + // terminal property name so that test files exercising the property via + // $ref usage are still matched. + if (path.startsWith('#')) { + const propName = path.split('.').pop() + function deepCollect(current) { + if (!current || typeof current !== 'object') return + if (Array.isArray(current)) { + for (const item of current) deepCollect(item) + return + } + for (const [key, val] of Object.entries(current)) { + if (key === propName && val !== undefined && val !== null) { + values.push(val) + } + if (typeof val === 'object' && val !== null) { + deepCollect(val) + } + } + } + deepCollect(data) + return values + } + const segments = path.split('.') function traverse(current, remaining) { diff --git a/src/negative_test/claude-code-settings/invalid-enum-coverage.json b/src/negative_test/claude-code-settings/invalid-enum-coverage.json new file mode 100644 index 00000000000..b550d40329e --- /dev/null +++ b/src/negative_test/claude-code-settings/invalid-enum-coverage.json @@ -0,0 +1,21 @@ +{ + "defaultShell": "zsh", + "defaultView": "list", + "disableAutoMode": "enable", + "hooks": { + "PreToolUse": [ + { + "hooks": [ + { + "command": "echo 'hook'", + "shell": "zsh", + "type": "command" + } + ] + } + ] + }, + "permissions": { + "disableAutoMode": "enable" + } +} diff --git a/src/schemas/json/claude-code-settings.json b/src/schemas/json/claude-code-settings.json index 2765e8f8f14..e9ef6ad1767 100644 --- a/src/schemas/json/claude-code-settings.json +++ b/src/schemas/json/claude-code-settings.json @@ -56,6 +56,19 @@ "statusMessage": { "type": "string", "description": "Custom spinner message displayed while the hook runs" + }, + "shell": { + "type": "string", + "enum": ["bash", "powershell"], + "description": "Shell interpreter. 'bash' uses $SHELL (bash/zsh/sh); 'powershell' uses pwsh. Defaults to bash" + }, + "once": { + "type": "boolean", + "description": "If true, hook runs once and is removed after execution" + }, + "asyncRewake": { + "type": "boolean", + "description": "If true, hook runs in background and wakes the model on exit code 2. Implies async" } } }, @@ -87,6 +100,10 @@ "statusMessage": { "type": "string", "description": "Custom spinner message displayed while the hook runs" + }, + "once": { + "type": "boolean", + "description": "If true, hook runs once and is removed after execution" } } }, @@ -118,6 +135,10 @@ "statusMessage": { "type": "string", "description": "Custom spinner message displayed while the hook runs" + }, + "once": { + "type": "boolean", + "description": "If true, hook runs once and is removed after execution" } } }, @@ -160,6 +181,10 @@ "statusMessage": { "type": "string", "description": "Custom spinner message displayed while the hook runs" + }, + "once": { + "type": "boolean", + "description": "If true, hook runs once and is removed after execution" } } } @@ -194,12 +219,24 @@ "type": "string", "description": "JSON Schema reference for Claude Code settings" }, + "agent": { + "type": "string", + "description": "Name of an agent (built-in or custom) to use for the main thread. Applies the agent's system prompt, tool restrictions, and model" + }, "apiKeyHelper": { "type": "string", "description": "Path to a script that outputs authentication values", "examples": ["/bin/generate_temp_api_key.sh"], "minLength": 1 }, + "autoDreamEnabled": { + "type": "boolean", + "description": "Enable background memory consolidation (auto-dream). When set, overrides the server-side default" + }, + "autoMemoryDirectory": { + "type": "string", + "description": "Custom directory path for auto-memory storage. Supports ~/ prefix for home directory expansion. Ignored if set in projectSettings (checked-in .claude/settings.json) for security. When unset, defaults to ~/.claude/projects//memory/" + }, "autoMemoryEnabled": { "type": "boolean", "description": "Enable automatic memory saves that capture useful context to .claude/memory/. Also configurable via CLAUDE_CODE_DISABLE_AUTO_MEMORY environment variable (set to 1 to disable, 0 to enable). See https://code.claude.com/docs/en/memory#auto-memory", @@ -223,6 +260,10 @@ "examples": ["aws sso login --profile myprofile"], "minLength": 1 }, + "channelsEnabled": { + "type": "boolean", + "description": "Teams/Enterprise opt-in for channel notifications (MCP servers with the claude/channel capability pushing inbound messages). Default off. Set true to allow; users then select servers via --channels" + }, "claudeMdExcludes": { "type": "array", "items": { @@ -329,12 +370,12 @@ "type": "string", "enum": [ "acceptEdits", + "auto", "bypassPermissions", "default", "delegate", "dontAsk", - "plan", - "auto" + "plan" ], "description": "Default permission mode.\n\"default\": prompts on first use.\n\"acceptEdits\": auto-accepts file edits.\n\"plan\": read-only, no modifications.\nUNDOCUMENTED. \"delegate\": coordination-only for agent team leads (agent teams are experimental; enable via CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS).\n\"dontAsk\": auto-denies unless pre-approved via permissions.\n\"bypassPermissions\": skips all prompts (use only in isolated environments).\n\"auto\": auto-approves tool calls with background safety checks that verify actions align with your request.\nSee https://code.claude.com/docs/en/permissions" }, @@ -343,6 +384,11 @@ "enum": ["disable"], "description": "Disable the ability to bypass permission prompts" }, + "disableAutoMode": { + "type": "string", + "enum": ["disable"], + "description": "Disable auto mode" + }, "additionalDirectories": { "type": "array", "items": { @@ -370,6 +416,10 @@ "description": "Preferred language for Claude's responses", "examples": ["japanese", "spanish", "french"] }, + "minimumVersion": { + "type": "string", + "description": "Minimum version to stay on - prevents downgrades when switching to stable channel" + }, "model": { "type": "string", "description": "Override the default model used by Claude Code. For finer control, use environment variables: ANTHROPIC_MODEL (runtime override), ANTHROPIC_DEFAULT_OPUS_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL (per-class pinning), CLAUDE_CODE_SUBAGENT_MODEL (subagent model). See https://code.claude.com/docs/en/model-config" @@ -484,6 +534,16 @@ }, "description": "Enterprise allowlist of MCP servers that can be used. Applies to all scopes including enterprise servers from managed-mcp.json. If undefined, all servers are allowed. If empty array, no servers are allowed. Denylist takes precedence - if a server is on both lists, it is denied. See https://code.claude.com/docs/en/mcp#restriction-options" }, + "defaultShell": { + "type": "string", + "enum": ["bash", "powershell"], + "description": "Default shell for input-box ! commands. Defaults to 'bash' on all platforms (no Windows auto-flip)" + }, + "defaultView": { + "type": "string", + "enum": ["chat", "transcript"], + "description": "Default transcript view: chat (SendUserMessage checkpoints only) or transcript (full)" + }, "deniedMcpServers": { "type": "array", "items": { @@ -608,6 +668,13 @@ "$ref": "#/$defs/hookMatcher" } }, + "StopFailure": { + "type": "array", + "description": "Hooks that run when an agent fails to stop cleanly", + "items": { + "$ref": "#/$defs/hookMatcher" + } + }, "SubagentStart": { "type": "array", "description": "Hooks that run when a subagent is spawned", @@ -715,6 +782,11 @@ } } }, + "disableAutoMode": { + "type": "string", + "enum": ["disable"], + "description": "Disable auto mode" + }, "disableAllHooks": { "type": "boolean", "description": "Disable all hooks and statusLine execution. When true in managed settings, user and project-level disableAllHooks cannot override it. See https://code.claude.com/docs/en/hooks#disable-or-remove-hooks" @@ -1150,6 +1222,11 @@ "examples": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"], "minLength": 1 }, + "gcpAuthRefresh": { + "type": "string", + "description": "Command to refresh GCP authentication (e.g., gcloud auth application-default login)", + "minLength": 1 + }, "otelHeadersHelper": { "type": "string", "description": "Path to a script that outputs OpenTelemetry headers", @@ -1297,6 +1374,24 @@ } }, "additionalProperties": false + }, + "ripgrep": { + "type": "object", + "description": "Custom ripgrep configuration for bundled ripgrep support", + "properties": { + "command": { + "type": "string", + "description": "Path to custom ripgrep binary" + }, + "args": { + "type": "array", + "description": "Additional arguments to pass to ripgrep", + "items": { + "type": "string" + } + } + }, + "required": ["command"] } }, "additionalProperties": false @@ -1434,6 +1529,29 @@ } }, "description": "User configuration values for MCP servers keyed by server name" + }, + "options": { + "type": "object", + "description": "Non-sensitive option values from plugin manifest userConfig, keyed by option name. Sensitive values go to secure storage instead", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } } }, "additionalProperties": false @@ -1602,6 +1720,124 @@ } ] } + }, + "promptSuggestionEnabled": { + "type": "boolean", + "description": "When false, prompt suggestions are disabled. When absent or true, prompt suggestions are enabled" + }, + "remote": { + "type": "object", + "description": "Remote session configuration", + "properties": { + "defaultEnvironmentId": { + "type": "string", + "description": "Default environment ID to use for remote sessions" + } + } + }, + "showClearContextOnPlanAccept": { + "type": "boolean", + "description": "When true, the plan-approval dialog offers a \"clear context\" option. Defaults to false" + }, + "showThinkingSummaries": { + "type": "boolean", + "description": "Show thinking summaries in the transcript view (ctrl+o). Default: false" + }, + "skipAutoPermissionPrompt": { + "type": "boolean", + "description": "Whether the user has accepted the auto mode opt-in dialog" + }, + "skipDangerousModePermissionPrompt": { + "type": "boolean", + "description": "Whether the user has accepted the bypass permissions mode dialog" + }, + "autoMode": { + "type": "object", + "description": "Auto mode classifier prompt customization", + "properties": { + "allow": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Rules for the auto mode classifier allow section" + }, + "soft_deny": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Rules for the auto mode classifier deny section" + }, + "environment": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Entries for the auto mode classifier environment section" + } + } + }, + "sshConfigs": { + "type": "array", + "description": "SSH connection configurations for remote environments. Typically set in managed settings by enterprise administrators to pre-configure SSH connections for team members", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this SSH config. Used to match configs across settings sources" + }, + "name": { + "type": "string", + "description": "Display name for the SSH connection" + }, + "sshHost": { + "type": "string", + "description": "SSH host in format \"user@hostname\" or \"hostname\", or a host alias from ~/.ssh/config" + }, + "sshPort": { + "type": "integer", + "description": "SSH port (default: 22)" + }, + "sshIdentityFile": { + "type": "string", + "description": "Path to SSH identity file (private key)" + }, + "startDirectory": { + "type": "string", + "description": "Default working directory on the remote host. Supports tilde expansion (e.g. ~/projects)" + } + }, + "required": ["id", "name", "sshHost"] + } + }, + "strictPluginOnlyCustomization": { + "description": "When set in managed settings, blocks non-plugin customization sources for the listed surfaces. Array form locks specific surfaces (e.g. [\"skills\", \"hooks\"]); true locks all four; false is an explicit no-op", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": ["skills", "agents", "hooks", "mcp"] + } + } + ] + }, + "syntaxHighlightingDisabled": { + "type": "boolean", + "description": "Whether to disable syntax highlighting in diffs" + }, + "terminalTitleFromRename": { + "type": "boolean", + "description": "Whether /rename updates the terminal tab title (defaults to true). Set to false to keep auto-generated topic titles" + }, + "voiceEnabled": { + "type": "boolean", + "description": "Enable voice mode (hold-to-talk dictation)" } }, "title": "Claude Code Settings" diff --git a/src/test/claude-code-settings/enum-coverage.json b/src/test/claude-code-settings/enum-coverage.json new file mode 100644 index 00000000000..a0960297df9 --- /dev/null +++ b/src/test/claude-code-settings/enum-coverage.json @@ -0,0 +1,32 @@ +{ + "defaultShell": "powershell", + "defaultView": "chat", + "hooks": { + "PreToolUse": [ + { + "hooks": [ + { + "command": "echo 'hook'", + "shell": "bash", + "type": "command" + } + ] + } + ], + "Stop": [ + { + "hooks": [ + { + "command": "echo 'done'", + "shell": "powershell", + "type": "command" + } + ] + } + ] + }, + "permissions": { + "defaultMode": "auto", + "disableAutoMode": "disable" + } +} diff --git a/src/test/claude-code-settings/modern-complete-config.json b/src/test/claude-code-settings/modern-complete-config.json index 20968266ed2..be59864a617 100644 --- a/src/test/claude-code-settings/modern-complete-config.json +++ b/src/test/claude-code-settings/modern-complete-config.json @@ -1,5 +1,6 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", + "agent": "code-reviewer", "allowManagedHooksOnly": false, "allowManagedPermissionRulesOnly": false, "allowedHttpHookUrls": ["https://hooks.example.com/*", "http://localhost:*"], @@ -9,7 +10,14 @@ "commit": "Generated with AI\n\nCo-Authored-By: AI ", "pr": "" }, + "autoDreamEnabled": true, + "autoMemoryDirectory": "~/.claude/custom-memory/", "autoMemoryEnabled": false, + "autoMode": { + "allow": ["All read-only operations"], + "environment": ["WSL2 on Windows"], + "soft_deny": ["Deleting files"] + }, "autoUpdatesChannel": "latest", "availableModels": ["sonnet", "haiku"], "awsAuthRefresh": "aws sso login --profile myprofile", @@ -21,12 +29,16 @@ } ], "claudeMdExcludes": [ + "**/vendor/CLAUDE.md", "**/other-team/CLAUDE.md", "/home/user/monorepo/.claude/rules/**" ], "cleanupPeriodDays": 60, "companyAnnouncements": ["Welcome to the team!"], + "defaultShell": "bash", + "defaultView": "transcript", "disableAllHooks": false, + "disableAutoMode": "disable", "disabledMcpjsonServers": ["untrusted-server"], "effortLevel": "medium", "enableAllProjectMcpServers": true, @@ -56,6 +68,7 @@ }, "forceLoginMethod": "console", "forceLoginOrgUUID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "gcpAuthRefresh": "gcloud auth application-default login", "hooks": { "ConfigChange": [ { @@ -68,6 +81,18 @@ "matcher": "user_settings" } ], + "Elicitation": [ + { + "hooks": [ + { + "asyncRewake": true, + "command": "echo 'Elicitation' >> ~/.claude-code/events.log", + "shell": "bash", + "type": "command" + } + ] + } + ], "InstructionsLoaded": [ { "hooks": [ @@ -89,6 +114,17 @@ "matcher": "Bash" } ], + "PostCompact": [ + { + "hooks": [ + { + "command": "echo 'Context compacted' >> ~/.claude-code/compact.log", + "once": true, + "type": "command" + } + ] + } + ], "PostToolUseFailure": [ { "hooks": [ @@ -131,6 +167,16 @@ ] } ], + "StopFailure": [ + { + "hooks": [ + { + "command": "echo 'Stop failed' >> ~/.claude-code/errors.log", + "type": "command" + } + ] + } + ], "WorktreeCreate": [ { "hooks": [ @@ -156,6 +202,7 @@ "includeCoAuthoredBy": true, "includeGitInstructions": false, "language": "english", + "minimumVersion": "2.1.0", "model": "opus", "modelOverrides": { "claude-opus-4-6": "arn:aws:bedrock:us-east-2:123456789012:application-inference-profile/opus-prod" @@ -195,11 +242,19 @@ "formatter": { "autoFormat": "true" } + }, + "options": { + "lineWidth": 80, + "useTabs": false } } }, "pluginTrustMessage": "All plugins from our internal marketplace are pre-approved by the security team.", "prefersReducedMotion": true, + "promptSuggestionEnabled": true, + "remote": { + "defaultEnvironmentId": "env-abc123" + }, "respectGitignore": false, "sandbox": { "allowUnsandboxedCommands": false, @@ -209,6 +264,8 @@ "enabled": true, "excludedCommands": ["docker", "git"], "filesystem": { + "allowManagedReadPathsOnly": false, + "allowRead": ["~/.ssh/known_hosts"], "allowWrite": ["~/.kube", "//tmp/build"], "denyRead": ["~/.ssh/id_rsa"], "denyWrite": ["//etc", "//usr"] @@ -221,9 +278,17 @@ "allowedDomains": ["github.com", "*.npmjs.org", "registry.yarnpkg.com"], "httpProxyPort": 8080, "socksProxyPort": 8081 + }, + "ripgrep": { + "args": ["--hidden"], + "command": "/usr/bin/rg" } }, + "showClearContextOnPlanAccept": true, + "showThinkingSummaries": true, "showTurnDuration": false, + "skipAutoPermissionPrompt": true, + "skipDangerousModePermissionPrompt": true, "skipWebFetchPreflight": false, "skippedMarketplaces": ["untrusted-marketplace"], "skippedPlugins": ["risky-plugin@unknown-marketplace"], @@ -236,14 +301,28 @@ "mode": "replace", "verbs": ["Analyzing", "Building"] }, + "sshConfigs": [ + { + "id": "dev-server", + "name": "Dev Server", + "sshHost": "user@dev.example.com", + "sshPort": 22, + "startDirectory": "~/projects" + } + ], "statusLine": { "command": "~/.claude/statusline.sh", "padding": 1, "type": "command" }, + "strictPluginOnlyCustomization": ["skills", "hooks"], + "syntaxHighlightingDisabled": false, "teammateMode": "tmux", "terminalProgressBarEnabled": false, + "terminalTitleFromRename": true, + "voiceEnabled": false, "worktree": { - "sparsePaths": ["packages/my-app", "shared/utils"] + "sparsePaths": ["packages/my-app", "shared/utils"], + "symlinkDirectories": ["node_modules", ".cache"] } }