diff --git a/.changeset/stupid-comics-pull.md b/.changeset/stupid-comics-pull.md new file mode 100644 index 00000000..3f71bff1 --- /dev/null +++ b/.changeset/stupid-comics-pull.md @@ -0,0 +1,24 @@ +--- +"task-master-ai": minor +--- + +Claude Code provider now respects your global, project, and local Claude Code configuration files. + +When using the Claude Code AI provider, Task Master now automatically loads your Claude Code settings from: + +- **Global config** (`~/.claude/` directory) - Your personal preferences across all projects +- **Project config** (`.claude/` directory) - Project-specific settings like CLAUDE.md instructions +- **Local config** - Workspace-specific overrides + +This means your CLAUDE.md files, custom instructions, and Claude Code settings will now be properly applied when Task Master uses Claude Code as an AI provider. Previously, these settings were being ignored. + +**What's improved:** + +- ✅ CLAUDE.md files are now automatically loaded and applied (global and local) +- ✅ Your custom Claude Code settings are respected +- ✅ Project-specific instructions work as expected +- ✅ No manual configuration needed - works out of the box + +**Issues:** +- Resolves #1391 +- Resolves #1315 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1343084b..e52cf8b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@streamparser/json": "^0.0.22", "@supabase/supabase-js": "^2.57.4", "ai": "^5.0.51", - "ai-sdk-provider-claude-code": "^1.1.4", + "ai-sdk-provider-claude-code": "^2.1.0", "ai-sdk-provider-codex-cli": "^0.3.0", "ai-sdk-provider-gemini-cli": "^1.1.1", "ajv": "^8.17.1", @@ -68,7 +68,7 @@ "steno": "^4.0.2", "undici": "^7.16.0", "uuid": "^11.1.0", - "zod": "^4.1.11" + "zod": "^4.1.12" }, "bin": { "task-master": "dist/task-master.js", @@ -4110,6 +4110,28 @@ "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.0.4", "cpu": [ @@ -4124,6 +4146,155 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@inquirer/ansi": { "version": "1.0.0", "license": "MIT", @@ -10289,12 +10460,14 @@ } }, "node_modules/ai-sdk-provider-claude-code": { - "version": "1.1.4", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ai-sdk-provider-claude-code/-/ai-sdk-provider-claude-code-2.1.0.tgz", + "integrity": "sha512-g+Nb+TKQs7RMP8PEn7NBjEHwVyIqHthXKSUqADO37oGPwOeMATuWCcuYAFmbeSdpIirV6iKRXRhdFipv2yTzqQ==", "license": "MIT", "dependencies": { "@ai-sdk/provider": "2.0.0", - "@ai-sdk/provider-utils": "3.0.3", - "@anthropic-ai/claude-code": "1.0.94", + "@ai-sdk/provider-utils": "3.0.9", + "@anthropic-ai/claude-agent-sdk": "^0.1.20", "jsonc-parser": "^3.3.1" }, "engines": { @@ -10305,13 +10478,14 @@ } }, "node_modules/ai-sdk-provider-claude-code/node_modules/@ai-sdk/provider-utils": { - "version": "3.0.3", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.9.tgz", + "integrity": "sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ==", "license": "Apache-2.0", "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", - "eventsource-parser": "^3.0.3", - "zod-to-json-schema": "^3.24.1" + "eventsource-parser": "^3.0.5" }, "engines": { "node": ">=18" @@ -10320,19 +10494,11 @@ "zod": "^3.25.76 || ^4" } }, - "node_modules/ai-sdk-provider-claude-code/node_modules/@ai-sdk/provider-utils/node_modules/zod-to-json-schema": { - "version": "3.24.6", - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - }, - "node_modules/ai-sdk-provider-claude-code/node_modules/@anthropic-ai/claude-code": { - "version": "1.0.94", + "node_modules/ai-sdk-provider-claude-code/node_modules/@anthropic-ai/claude-agent-sdk": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.1.22.tgz", + "integrity": "sha512-lDwbcwh5goRVUHRRqvX9v6Vj38VME1L+QIaLWzdqbK3Lwkkz9PVLNQFRDEyUOSNd9Fm4cAlfQZ0RCVe8tuo9TA==", "license": "SEE LICENSE IN README.md", - "bin": { - "claude": "cli.js" - }, "engines": { "node": ">=18.0.0" }, @@ -10343,6 +10509,9 @@ "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" + }, + "peerDependencies": { + "zod": "^3.24.1" } }, "node_modules/ai-sdk-provider-codex-cli": { @@ -27843,7 +28012,9 @@ "license": "MIT" }, "node_modules/zod": { - "version": "4.1.11", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index a00edb6e..04fc2a85 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "@streamparser/json": "^0.0.22", "@supabase/supabase-js": "^2.57.4", "ai": "^5.0.51", - "ai-sdk-provider-claude-code": "^1.1.4", + "ai-sdk-provider-claude-code": "^2.1.0", "ai-sdk-provider-codex-cli": "^0.3.0", "ai-sdk-provider-gemini-cli": "^1.1.1", "ajv": "^8.17.1", @@ -106,7 +106,7 @@ "steno": "^4.0.2", "undici": "^7.16.0", "uuid": "^11.1.0", - "zod": "^4.1.11" + "zod": "^4.1.12" }, "optionalDependencies": { "@anthropic-ai/claude-code": "^1.0.88", diff --git a/src/ai-providers/claude-code.js b/src/ai-providers/claude-code.js index 19146cc6..00a5513a 100644 --- a/src/ai-providers/claude-code.js +++ b/src/ai-providers/claude-code.js @@ -2,12 +2,13 @@ * src/ai-providers/claude-code.js * * Claude Code provider implementation using the ai-sdk-provider-claude-code package. - * This provider uses the local Claude Code CLI with OAuth token authentication. + * This provider uses the local Claude Agent SDK (via Claude Code CLI) with OAuth token authentication. * * Authentication: * - Uses CLAUDE_CODE_OAUTH_TOKEN managed by Claude Code CLI * - Token is set up via: claude setup-token * - No manual API key configuration required + * */ import { createClaudeCode } from 'ai-sdk-provider-claude-code'; @@ -104,9 +105,42 @@ export class ClaudeCodeProvider extends BaseAIProvider { const settings = getClaudeCodeSettingsForCommand(params.commandName) || {}; - return createClaudeCode({ - defaultSettings: settings - }); + // Environment variable isolation to prevent API key conflicts + // The ai-sdk-provider-claude-code SDK automatically picks up ANTHROPIC_API_KEY, + // which can cause conflicts if that key is intended for the Anthropic provider. + const originalAnthropicKey = process.env.ANTHROPIC_API_KEY; + const claudeCodeKey = process.env.CLAUDE_CODE_API_KEY; + + try { + // If CLAUDE_CODE_API_KEY is set, use it exclusively + if (claudeCodeKey) { + process.env.ANTHROPIC_API_KEY = claudeCodeKey; + } else if (originalAnthropicKey) { + // If only ANTHROPIC_API_KEY exists, temporarily unset it to force OAuth mode + delete process.env.ANTHROPIC_API_KEY; + } + + return createClaudeCode({ + defaultSettings: { + // Restore previous default behavior from pre-2.0 versions + // These must be inside defaultSettings to be applied by the provider + systemPrompt: { + type: 'preset', + preset: 'claude_code' + }, + // Enable loading of CLAUDE.md and settings.json files + settingSources: ['user', 'project', 'local'], + ...settings + } + }); + } finally { + // Restore original environment state + if (originalAnthropicKey) { + process.env.ANTHROPIC_API_KEY = originalAnthropicKey; + } else { + delete process.env.ANTHROPIC_API_KEY; + } + } } catch (error) { // Provide more helpful error message const msg = String(error?.message || ''); diff --git a/tests/unit/ai-providers/claude-code.test.js b/tests/unit/ai-providers/claude-code.test.js index d914c532..fc0ec6e3 100644 --- a/tests/unit/ai-providers/claude-code.test.js +++ b/tests/unit/ai-providers/claude-code.test.js @@ -99,6 +99,111 @@ describe('ClaudeCodeProvider', () => { expect(client.chat).toBeDefined(); expect(client.chat).toBe(client.languageModel); }); + + it('should pass systemPrompt configuration to createClaudeCode', async () => { + const { createClaudeCode } = await import('ai-sdk-provider-claude-code'); + + provider.getClient({}); + + expect(createClaudeCode).toHaveBeenCalledWith( + expect.objectContaining({ + defaultSettings: expect.objectContaining({ + systemPrompt: { + type: 'preset', + preset: 'claude_code' + } + }) + }) + ); + }); + + it('should pass settingSources configuration to createClaudeCode', async () => { + const { createClaudeCode } = await import('ai-sdk-provider-claude-code'); + + provider.getClient({}); + + expect(createClaudeCode).toHaveBeenCalledWith( + expect.objectContaining({ + defaultSettings: expect.objectContaining({ + settingSources: ['user', 'project', 'local'] + }) + }) + ); + }); + + it('should pass defaultSettings from config to createClaudeCode', async () => { + const { createClaudeCode } = await import('ai-sdk-provider-claude-code'); + const { getClaudeCodeSettingsForCommand } = await import( + '../../../scripts/modules/config-manager.js' + ); + + const mockSettings = { maxTokens: 4096, temperature: 0.7 }; + getClaudeCodeSettingsForCommand.mockReturnValueOnce(mockSettings); + + provider.getClient({ commandName: 'test-command' }); + + expect(createClaudeCode).toHaveBeenCalledWith( + expect.objectContaining({ + defaultSettings: expect.objectContaining({ + ...mockSettings, + systemPrompt: { + type: 'preset', + preset: 'claude_code' + }, + settingSources: ['user', 'project', 'local'] + }) + }) + ); + }); + + it('should pass complete configuration object to createClaudeCode', async () => { + const { createClaudeCode } = await import('ai-sdk-provider-claude-code'); + const { getClaudeCodeSettingsForCommand } = await import( + '../../../scripts/modules/config-manager.js' + ); + + const mockSettings = { maxTokens: 2048 }; + getClaudeCodeSettingsForCommand.mockReturnValueOnce(mockSettings); + + provider.getClient({ commandName: 'analyze' }); + + // Verify the complete configuration structure matches v2.0 migration requirements + expect(createClaudeCode).toHaveBeenCalledWith({ + defaultSettings: { + ...mockSettings, + // Restores pre-2.0 behavior: explicit system prompt preset + systemPrompt: { + type: 'preset', + preset: 'claude_code' + }, + // Restores pre-2.0 behavior: enables loading of CLAUDE.md and settings.json + settingSources: ['user', 'project', 'local'] + } + }); + }); + + it('should pass empty defaultSettings when config returns null', async () => { + const { createClaudeCode } = await import('ai-sdk-provider-claude-code'); + const { getClaudeCodeSettingsForCommand } = await import( + '../../../scripts/modules/config-manager.js' + ); + + getClaudeCodeSettingsForCommand.mockReturnValueOnce(null); + + provider.getClient({}); + + expect(createClaudeCode).toHaveBeenCalledWith( + expect.objectContaining({ + defaultSettings: expect.objectContaining({ + systemPrompt: { + type: 'preset', + preset: 'claude_code' + }, + settingSources: ['user', 'project', 'local'] + }) + }) + ); + }); }); describe('model support', () => {