chore: apply requested changes
This commit is contained in:
@@ -35,7 +35,7 @@
|
|||||||
"@types/inquirer": "^9.0.3",
|
"@types/inquirer": "^9.0.3",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
"tsx": "^4.20.4",
|
"tsx": "^4.20.4",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.9.2",
|
||||||
"vitest": "^2.1.8"
|
"vitest": "^2.1.8"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -240,7 +240,7 @@
|
|||||||
"check-types": "tsc --noEmit"
|
"check-types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"task-master-ai": "0.27.2"
|
"task-master-ai": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
@@ -276,7 +276,7 @@
|
|||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "4.1.11",
|
"tailwindcss": "4.1.11",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"glob@<8": "^10.4.5",
|
"glob@<8": "^10.4.5",
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ The Claude Code provider uses token authentication managed by the Claude Code CL
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
1. **Install Claude Code CLI** (if not already installed):
|
1. **Install Claude Code CLI** (if not already installed):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Installation method depends on your system
|
# Installation method depends on your system
|
||||||
# Follow Claude Code documentation for installation
|
# Follow Claude Code documentation for installation
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Set up OAuth token** using Claude Code CLI:
|
2. **Set up OAuth token** using Claude Code CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
claude setup-token
|
claude setup-token
|
||||||
```
|
```
|
||||||
@@ -75,6 +77,7 @@ export CLAUDE_CODE_OAUTH_TOKEN="your_oauth_token_here"
|
|||||||
```
|
```
|
||||||
|
|
||||||
This is only needed in specific scenarios like:
|
This is only needed in specific scenarios like:
|
||||||
|
|
||||||
- CI/CD pipelines
|
- CI/CD pipelines
|
||||||
- Docker containers
|
- Docker containers
|
||||||
- When you want to use a different token than the CLI default
|
- When you want to use a different token than the CLI default
|
||||||
@@ -110,6 +113,7 @@ task-master models --setup
|
|||||||
**Problem**: Task Master cannot connect to Claude Code CLI.
|
**Problem**: Task Master cannot connect to Claude Code CLI.
|
||||||
|
|
||||||
**Solutions**:
|
**Solutions**:
|
||||||
|
|
||||||
- Ensure Claude Code CLI is installed and in your PATH
|
- Ensure Claude Code CLI is installed and in your PATH
|
||||||
- Run `claude setup-token` to configure authentication
|
- Run `claude setup-token` to configure authentication
|
||||||
- Verify Claude Code CLI works: `claude --help`
|
- Verify Claude Code CLI works: `claude --help`
|
||||||
@@ -119,6 +123,7 @@ task-master models --setup
|
|||||||
**Problem**: Token authentication is failing.
|
**Problem**: Token authentication is failing.
|
||||||
|
|
||||||
**Solutions**:
|
**Solutions**:
|
||||||
|
|
||||||
- Re-run `claude setup-token` to refresh your OAuth token
|
- Re-run `claude setup-token` to refresh your OAuth token
|
||||||
- Check if your token has expired
|
- Check if your token has expired
|
||||||
- Verify Claude Code CLI can authenticate: try a simple `claude` command
|
- Verify Claude Code CLI can authenticate: try a simple `claude` command
|
||||||
@@ -128,6 +133,7 @@ task-master models --setup
|
|||||||
**Problem**: Specified Claude Code model is not supported.
|
**Problem**: Specified Claude Code model is not supported.
|
||||||
|
|
||||||
**Solutions**:
|
**Solutions**:
|
||||||
|
|
||||||
- Use supported models: `sonnet` or `opus`
|
- Use supported models: `sonnet` or `opus`
|
||||||
- Check model availability: `task-master models --list`
|
- Check model availability: `task-master models --list`
|
||||||
- Verify your Claude Code CLI has access to the requested model
|
- Verify your Claude Code CLI has access to the requested model
|
||||||
@@ -135,18 +141,21 @@ task-master models --setup
|
|||||||
### Debug Steps
|
### Debug Steps
|
||||||
|
|
||||||
1. **Test Claude Code CLI directly**:
|
1. **Test Claude Code CLI directly**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
claude --help
|
claude --help
|
||||||
# Should show help without errors
|
# Should show help without errors
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Test authentication**:
|
2. **Test authentication**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
claude setup-token --verify
|
claude setup-token --verify
|
||||||
# Should confirm token is valid
|
# Should confirm token is valid
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Test Task Master integration**:
|
3. **Test Task Master integration**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
task-master models --test claude-code:sonnet
|
task-master models --test claude-code:sonnet
|
||||||
# Should successfully connect and test the model
|
# Should successfully connect and test the model
|
||||||
@@ -164,6 +173,7 @@ When running in Docker, you'll need to:
|
|||||||
|
|
||||||
1. Install Claude Code CLI in your container
|
1. Install Claude Code CLI in your container
|
||||||
2. Set up authentication via environment variable:
|
2. Set up authentication via environment variable:
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
ENV CLAUDE_CODE_OAUTH_TOKEN="your_token_here"
|
ENV CLAUDE_CODE_OAUTH_TOKEN="your_token_here"
|
||||||
```
|
```
|
||||||
|
|||||||
5328
package-lock.json
generated
5328
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -127,8 +127,8 @@
|
|||||||
"@changesets/changelog-github": "^0.5.1",
|
"@changesets/changelog-github": "^0.5.1",
|
||||||
"@changesets/cli": "^2.28.1",
|
"@changesets/cli": "^2.28.1",
|
||||||
"@manypkg/cli": "^0.25.1",
|
"@manypkg/cli": "^0.25.1",
|
||||||
"@tm/cli": "*",
|
|
||||||
"@tm/ai-sdk-provider-grok-cli": "*",
|
"@tm/ai-sdk-provider-grok-cli": "*",
|
||||||
|
"@tm/cli": "*",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/marked-terminal": "^6.1.1",
|
"@types/marked-terminal": "^6.1.1",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
"ts-jest": "^29.4.2",
|
"ts-jest": "^29.4.2",
|
||||||
"tsdown": "^0.15.2",
|
"tsdown": "^0.15.2",
|
||||||
"tsx": "^4.20.4",
|
"tsx": "^4.20.4",
|
||||||
"turbo": "^2.5.6",
|
"turbo": "2.5.6",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,12 @@
|
|||||||
"name": "@tm/ai-sdk-provider-grok-cli",
|
"name": "@tm/ai-sdk-provider-grok-cli",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "AI SDK provider for Grok CLI integration",
|
"description": "AI SDK provider for Grok CLI integration",
|
||||||
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
|
"type": "module",
|
||||||
"license": "MIT",
|
"types": "./src/index.ts",
|
||||||
"sideEffects": false,
|
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"module": "./dist/index.mjs",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts"
|
".": "./src/index.ts"
|
||||||
},
|
},
|
||||||
"files": ["dist/**/*", "README.md"],
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
@@ -28,14 +24,11 @@
|
|||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"@ai-sdk/provider": "^2.0.0",
|
|
||||||
"@ai-sdk/provider-utils": "^3.0.10",
|
|
||||||
"jsonc-parser": "^3.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
|
"keywords": ["ai", "grok", "x.ai", "cli", "language-model", "provider"],
|
||||||
|
"files": ["dist/**/*", "README.md"],
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
|
|||||||
*/
|
*/
|
||||||
private async executeGrokCli(
|
private async executeGrokCli(
|
||||||
args: string[],
|
args: string[],
|
||||||
options: { timeout?: number } = {}
|
options: { timeout?: number; apiKey?: string } = {}
|
||||||
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
||||||
// Default timeout based on model type
|
// Default timeout based on model type
|
||||||
let defaultTimeout = 120000; // 2 minutes default
|
let defaultTimeout = 120000; // 2 minutes default
|
||||||
@@ -123,7 +123,11 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const child = spawn('grok', args, {
|
const child = spawn('grok', args, {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
cwd: this.settings.workingDirectory || process.cwd()
|
cwd: this.settings.workingDirectory || process.cwd(),
|
||||||
|
env:
|
||||||
|
options.apiKey === undefined
|
||||||
|
? process.env
|
||||||
|
: { ...process.env, GROK_CLI_API_KEY: options.apiKey }
|
||||||
});
|
});
|
||||||
|
|
||||||
let stdout = '';
|
let stdout = '';
|
||||||
@@ -295,7 +299,7 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.executeGrokCli(args);
|
const result = await this.executeGrokCli(args, { apiKey });
|
||||||
|
|
||||||
if (result.exitCode !== 0) {
|
if (result.exitCode !== 0) {
|
||||||
// Handle authentication errors
|
// Handle authentication errors
|
||||||
@@ -323,11 +327,14 @@ export class GrokCliLanguageModel implements LanguageModelV2 {
|
|||||||
let text = response.text || '';
|
let text = response.text || '';
|
||||||
|
|
||||||
// Extract JSON if in object-json mode
|
// Extract JSON if in object-json mode
|
||||||
if (
|
const isObjectJson = (
|
||||||
'mode' in options &&
|
o: unknown
|
||||||
(options as any).mode?.type === 'object-json' &&
|
): o is { mode: { type: 'object-json' } } =>
|
||||||
text
|
!!o &&
|
||||||
) {
|
typeof o === 'object' &&
|
||||||
|
'mode' in o &&
|
||||||
|
(o as any).mode?.type === 'object-json';
|
||||||
|
if (isObjectJson(options) && text) {
|
||||||
text = extractJson(text);
|
text = extractJson(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,49 +9,49 @@ describe('extractJson', () => {
|
|||||||
it('should extract JSON from markdown code blocks', () => {
|
it('should extract JSON from markdown code blocks', () => {
|
||||||
const text = '```json\n{"name": "test", "value": 42}\n```';
|
const text = '```json\n{"name": "test", "value": 42}\n```';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract JSON from generic code blocks', () => {
|
it('should extract JSON from generic code blocks', () => {
|
||||||
const text = '```\n{"name": "test", "value": 42}\n```';
|
const text = '```\n{"name": "test", "value": 42}\n```';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove JavaScript variable declarations', () => {
|
it('should remove JavaScript variable declarations', () => {
|
||||||
const text = 'const result = {"name": "test", "value": 42};';
|
const text = 'const result = {"name": "test", "value": 42};';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle let variable declarations', () => {
|
it('should handle let variable declarations', () => {
|
||||||
const text = 'let data = {"name": "test", "value": 42};';
|
const text = 'let data = {"name": "test", "value": 42};';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle var variable declarations', () => {
|
it('should handle var variable declarations', () => {
|
||||||
const text = 'var config = {"name": "test", "value": 42};';
|
const text = 'var config = {"name": "test", "value": 42};';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract JSON arrays', () => {
|
it('should extract JSON arrays', () => {
|
||||||
const text = '[{"name": "test1"}, {"name": "test2"}]';
|
const text = '[{"name": "test1"}, {"name": "test2"}]';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('[{"name": "test1"}, {"name": "test2"}]');
|
expect(JSON.parse(result)).toEqual([{ name: 'test1' }, { name: 'test2' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should convert JavaScript object literals to JSON', () => {
|
it('should convert JavaScript object literals to JSON', () => {
|
||||||
const text = "{name: 'test', value: 42}";
|
const text = "{name: 'test', value: 42}";
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return valid JSON as-is', () => {
|
it('should return valid JSON (canonical formatting)', () => {
|
||||||
const text = '{"name": "test", "value": 42}';
|
const text = '{"name": "test", "value": 42}';
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": 42}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 42 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return original text when JSON parsing fails completely', () => {
|
it('should return original text when JSON parsing fails completely', () => {
|
||||||
@@ -76,6 +76,6 @@ describe('extractJson', () => {
|
|||||||
it('should handle mixed quotes in object literals', () => {
|
it('should handle mixed quotes in object literals', () => {
|
||||||
const text = `{name: "test", value: 'mixed quotes'}`;
|
const text = `{name: "test", value: 'mixed quotes'}`;
|
||||||
const result = extractJson(text);
|
const result = extractJson(text);
|
||||||
expect(result).toBe('{"name": "test", "value": "mixed quotes"}');
|
expect(JSON.parse(result)).toEqual({ name: 'test', value: 'mixed quotes' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -121,6 +121,19 @@ describe('createPromptFromMessages', () => {
|
|||||||
|
|
||||||
expect(result).toBe('custom: Custom message');
|
expect(result).toBe('custom: Custom message');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should trim whitespace from message content', () => {
|
||||||
|
const messages = [
|
||||||
|
{ role: 'user', content: ' Hello with spaces ' },
|
||||||
|
{ role: 'assistant', content: '\n\nResponse with newlines\n\n' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = createPromptFromMessages(messages);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'User: Hello with spaces\n\nAssistant: Response with newlines'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('escapeShellArg', () => {
|
describe('escapeShellArg', () => {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export function convertFromGrokCliResponse(responseText: string): {
|
|||||||
|
|
||||||
// Find the last assistant message
|
// Find the last assistant message
|
||||||
const assistantMessage = messages
|
const assistantMessage = messages
|
||||||
.filter((msg: any) => msg.role === 'assistant')
|
.filter((msg) => msg.role === 'assistant')
|
||||||
.pop();
|
.pop();
|
||||||
|
|
||||||
if (assistantMessage && assistantMessage.content) {
|
if (assistantMessage && assistantMessage.content) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "ESNext",
|
||||||
"lib": ["ES2022"],
|
"lib": ["ES2022"],
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv-mono": "^1.5.1",
|
"dotenv-mono": "^1.5.1",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.9.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tsup": "^8.5.0"
|
"tsup": "^8.5.0"
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ function validateClaudeCodeSettings(settings) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Define CommandSpecificSchema using the base schema
|
// Define CommandSpecificSchema using the base schema
|
||||||
const CommandSpecificSchema = z.partialRecord(
|
const CommandSpecificSchema = z.record(
|
||||||
z.enum(AI_COMMAND_NAMES),
|
z.enum(AI_COMMAND_NAMES),
|
||||||
BaseSettingsSchema
|
BaseSettingsSchema
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -392,12 +392,29 @@ Ensure the JSON is valid and properly formatted.`;
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
let nextId = maxPreservedId + 1;
|
let nextId = maxPreservedId + 1;
|
||||||
const normalizedGeneratedSubtasks = processedGeneratedSubtasks.map(
|
const idMapping = new Map();
|
||||||
(st) => ({
|
const normalizedGeneratedSubtasks = processedGeneratedSubtasks
|
||||||
|
.map((st) => {
|
||||||
|
const originalId = st.id;
|
||||||
|
const newId = nextId++;
|
||||||
|
idMapping.set(originalId, newId);
|
||||||
|
return {
|
||||||
...st,
|
...st,
|
||||||
id: nextId++
|
id: newId
|
||||||
|
};
|
||||||
})
|
})
|
||||||
);
|
.map((st) => ({
|
||||||
|
...st,
|
||||||
|
dependencies: (st.dependencies || []).map((dep) => {
|
||||||
|
if (typeof dep !== 'string' || !dep.startsWith(`${task.id}.`)) {
|
||||||
|
return dep;
|
||||||
|
}
|
||||||
|
const [, siblingIdPart] = dep.split('.');
|
||||||
|
const originalSiblingId = Number.parseInt(siblingIdPart, 10);
|
||||||
|
const remappedSiblingId = idMapping.get(originalSiblingId);
|
||||||
|
return remappedSiblingId ? `${task.id}.${remappedSiblingId}` : dep;
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
// Update task with preserved subtasks + newly generated ones
|
// Update task with preserved subtasks + newly generated ones
|
||||||
task.subtasks = [...preservedSubtasks, ...normalizedGeneratedSubtasks];
|
task.subtasks = [...preservedSubtasks, ...normalizedGeneratedSubtasks];
|
||||||
@@ -406,7 +423,7 @@ Ensure the JSON is valid and properly formatted.`;
|
|||||||
updatedTask: task,
|
updatedTask: task,
|
||||||
regenerated: true,
|
regenerated: true,
|
||||||
preserved: preservedSubtasks.length,
|
preserved: preservedSubtasks.length,
|
||||||
generated: processedGeneratedSubtasks.length
|
generated: normalizedGeneratedSubtasks.length
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(
|
log(
|
||||||
|
|||||||
@@ -33,12 +33,13 @@ import { ContextGatherer } from '../utils/contextGatherer.js';
|
|||||||
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
|
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
|
||||||
|
|
||||||
// Zod schema for post-parsing validation of the updated task object
|
// Zod schema for post-parsing validation of the updated task object
|
||||||
const updatedTaskSchema = z.object({
|
const updatedTaskSchema = z
|
||||||
id: z.int(),
|
.object({
|
||||||
|
id: z.number().int(),
|
||||||
title: z.string(), // Title should be preserved, but check it exists
|
title: z.string(), // Title should be preserved, but check it exists
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
dependencies: z.array(z.union([z.int(), z.string()])),
|
dependencies: z.array(z.union([z.number().int(), z.string()])),
|
||||||
priority: z.string().nullable().prefault('medium'),
|
priority: z.string().nullable().prefault('medium'),
|
||||||
details: z.string().nullable().prefault(''),
|
details: z.string().nullable().prefault(''),
|
||||||
testStrategy: z.string().nullable().prefault(''),
|
testStrategy: z.string().nullable().prefault(''),
|
||||||
@@ -46,20 +47,22 @@ const updatedTaskSchema = z.object({
|
|||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z
|
id: z
|
||||||
|
.number()
|
||||||
.int()
|
.int()
|
||||||
.positive()
|
.positive()
|
||||||
.describe('Sequential subtask ID starting from 1'),
|
.describe('Sequential subtask ID starting from 1'),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
dependencies: z.array(z.int()).nullable().prefault([]),
|
dependencies: z.array(z.number().int()).nullable().prefault([]),
|
||||||
details: z.string().nullable().prefault(''),
|
details: z.string().nullable().prefault(''),
|
||||||
testStrategy: z.string().nullable().prefault('')
|
testStrategy: z.string().nullable().prefault('')
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.nullable()
|
.nullable()
|
||||||
.prefault([])
|
.prefault([])
|
||||||
}); // Allows parsing even if AI adds extra fields, but validation focuses on schema
|
})
|
||||||
|
.strip(); // Enforce the canonical task shape and drop unknown fields
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a single updated task object from AI's text response.
|
* Parses a single updated task object from AI's text response.
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
|
|||||||
import { flattenTasksWithSubtasks, findProjectRoot } from '../utils.js';
|
import { flattenTasksWithSubtasks, findProjectRoot } from '../utils.js';
|
||||||
|
|
||||||
// Zod schema for validating the structure of tasks AFTER parsing
|
// Zod schema for validating the structure of tasks AFTER parsing
|
||||||
const updatedTaskSchema = z.object({
|
const updatedTaskSchema = z
|
||||||
|
.object({
|
||||||
id: z.int(),
|
id: z.int(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
description: z.string(),
|
description: z.string(),
|
||||||
@@ -39,7 +40,8 @@ const updatedTaskSchema = z.object({
|
|||||||
details: z.string().nullable(),
|
details: z.string().nullable(),
|
||||||
testStrategy: z.string().nullable(),
|
testStrategy: z.string().nullable(),
|
||||||
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
|
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
|
||||||
}); // Allow potential extra fields during parsing if needed, then validate structure
|
})
|
||||||
|
.strip(); // Enforce the canonical task shape and drop unknown fields
|
||||||
|
|
||||||
// Preprocessing schema that adds defaults before validation
|
// Preprocessing schema that adds defaults before validation
|
||||||
const preprocessTaskSchema = z.preprocess((task) => {
|
const preprocessTaskSchema = z.preprocess((task) => {
|
||||||
|
|||||||
@@ -24,25 +24,18 @@ const { ClaudeCodeProvider } = await import(
|
|||||||
);
|
);
|
||||||
|
|
||||||
describe('Claude Code Error Handling', () => {
|
describe('Claude Code Error Handling', () => {
|
||||||
it('should handle missing Claude Code CLI gracefully', () => {
|
beforeEach(() => {
|
||||||
const provider = new ClaudeCodeProvider();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
expect(() => provider.getClient()).toThrow(/Claude Code CLI not available/);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle CLI errors during client creation', () => {
|
it('should throw a CLI-not-available error (with or without commandName)', () => {
|
||||||
const provider = new ClaudeCodeProvider();
|
|
||||||
|
|
||||||
expect(() => provider.getClient({ commandName: 'test' })).toThrow(
|
|
||||||
/Claude Code CLI not available/
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should provide a helpful CLI-not-available error', () => {
|
|
||||||
const provider = new ClaudeCodeProvider();
|
const provider = new ClaudeCodeProvider();
|
||||||
expect(() => provider.getClient()).toThrow(
|
expect(() => provider.getClient()).toThrow(
|
||||||
/Claude Code CLI not available/i
|
/Claude Code CLI not available/i
|
||||||
);
|
);
|
||||||
|
expect(() => provider.getClient({ commandName: 'test' })).toThrow(
|
||||||
|
/Claude Code CLI not available/i
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should still support basic provider functionality', () => {
|
it('should still support basic provider functionality', () => {
|
||||||
@@ -52,6 +45,7 @@ describe('Claude Code Error Handling', () => {
|
|||||||
expect(provider.name).toBe('Claude Code');
|
expect(provider.name).toBe('Claude Code');
|
||||||
expect(provider.getSupportedModels()).toEqual(['sonnet', 'opus']);
|
expect(provider.getSupportedModels()).toEqual(['sonnet', 'opus']);
|
||||||
expect(provider.isModelSupported('sonnet')).toBe(true);
|
expect(provider.isModelSupported('sonnet')).toBe(true);
|
||||||
|
expect(provider.isModelSupported('haiku')).toBe(false);
|
||||||
expect(provider.isRequiredApiKey()).toBe(false);
|
expect(provider.isRequiredApiKey()).toBe(false);
|
||||||
expect(() => provider.validateAuth()).not.toThrow();
|
expect(() => provider.validateAuth()).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user