Skip to content

Commit 98b6d2b

Browse files
AI slop reduction
1 parent 9b4a563 commit 98b6d2b

18 files changed

+381
-533
lines changed

.cursor/rules/errors.mdc

Lines changed: 28 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -9,85 +9,49 @@ alwaysApply: false
99
graph TD
1010
subgraph Core_Logic
1111
FS[FileSystemService: e.g., FileReadError] --> TM[TaskManager: Throws App Errors, e.g., ProjectNotFound, TaskNotDone]
12-
TM -->|Untagged App Error| CLI_Handler["cli.ts Command Handler"]
13-
TM -->|Untagged App Error| ToolExec["toolExecutors.ts: execute"]
12+
TM -->|App Error with code APP-xxxx| CLI_Handler["cli.ts Command Handler"]
13+
TM -->|App Error with code APP-xxxx| ToolExec["toolExecutors.ts: execute"]
1414
end
1515

1616
subgraph CLI_Path
17-
CLI_Handler -->|Untagged App Error| CLI_Catch["cli.ts catch block"]
17+
CLI_Handler -->|App Error| CLI_Catch["cli.ts catch block"]
1818
CLI_Catch -->|Error Object| FormatCLI["client errors.ts formatCliError"]
19-
FormatCLI -->|Formatted String| ConsoleOut["console.error Output"]
19+
FormatCLI -->|"Error [APP-xxxx]: message"| ConsoleOut["console.error Output"]
2020
end
2121

2222
subgraph MCP_Server_Path
23-
subgraph Validation
24-
ToolExecVal["toolExecutors.ts Validation"] -->|Throws Tagged Protocol Error jsonRpcCode -32602| ExecToolErrHandler
23+
subgraph Validation_Layer
24+
ToolExecVal["toolExecutors.ts Validation"] -->|App Error, e.g., MissingParameter| ExecToolErrHandler
2525
end
2626

27-
subgraph Execution
28-
ToolExec -->|Untagged App Error| ExecToolErrHandler["tools.ts executeToolAndHandleErrors catch block"]
27+
subgraph App_Execution
28+
ToolExec -->|App Error with code APP-xxxx| ExecToolErrHandler["tools.ts executeToolAndHandleErrors catch block"]
29+
ExecToolErrHandler -->|Map AppError to Protocol Error or Tool Result| ErrorMapping
30+
ErrorMapping -->|"If validation error (APP-1xxx)"| McpError["Create McpError with appropriate ErrorCode"]
31+
ErrorMapping -->|"If business logic error (APP-2xxx+)"| FormatResult["Format as isError true result"]
32+
33+
McpError -->|Throw| SDKHandler["server index.ts SDK Handler"]
34+
FormatResult -->|"{ content: [{ text: Error [APP-xxxx]: message }], isError: true }"| SDKHandler
2935
end
3036

31-
ExecToolErrHandler -->|Error Object| CheckTag["Check if error has jsonRpcCode"]
32-
CheckTag -- Tagged Error --> ReThrow["Re-throw Tagged Error"]
33-
CheckTag -- Untagged App Error --> NormalizeErr["utils errors.ts normalizeError"]
34-
NormalizeErr -->|McpError Object code -32000| FormatResult["Format as isError true result"]
35-
FormatResult -->|content list with isError true| SDKHandler["server index.ts SDK Handler"]
37+
SDKHandler -- Protocol Error --> SDKFormatError["SDK Formats as JSON-RPC Error Response"]
38+
SDKHandler -- Tool Result --> SDKFormatResult["SDK Formats as JSON-RPC Success Response"]
3639

37-
ReThrow -->|Tagged Protocol Error| SDKHandler
38-
SDKHandler -- Tagged Error --> SDKFormatError["SDK Formats Top-Level Error"]
39-
SDKHandler -- isError true Result --> SDKFormatResult["SDK Formats Result Field"]
40-
41-
SDKFormatError -->|JSON-RPC Error Response| MCPClient["MCP Client"]
42-
SDKFormatResult -->|JSON-RPC Success Response with error details| MCPClient
40+
SDKFormatError -->|"{ error: { code: -326xx, message: ... } }"| MCPClient["MCP Client"]
41+
SDKFormatResult -->|"{ result: { content: [...], isError: true } }"| MCPClient
4342
end
4443
```
4544

46-
**Explanation of Error Flow and Transformations:**
47-
48-
Errors primarily originate from two places:
49-
50-
1. **Core Logic (`TaskManager`, `FileSystemService`):** These modules throw standard JavaScript `Error` objects, often subclassed (e.g., `ProjectNotFoundError`, `FileReadError`) but *without* any special MCP/JSON-RPC tagging (`jsonRpcCode`). These represent application-specific or file system problems.
51-
2. **Tool Executors (`toolExecutors.ts`) Validation:** Before calling `TaskManager`, the executors validate input arguments. If validation fails, they create a *new* `Error` object and explicitly *tag* it with `jsonRpcCode = -32602` (Invalid Params).
52-
53-
The handling differs significantly between the CLI and the MCP Server:
54-
55-
**1. CLI Error Path (`cli.ts`)**
56-
57-
1. **Origination:** An untagged application error (e.g., `ProjectNotFoundError`) is thrown by `TaskManager`.
58-
2. **Propagation:** The error propagates directly up the call stack to the `catch` block within the specific command's action handler in `cli.ts`.
59-
3. **Transformation (`formatCliError`):**
60-
* The `catch` block calls `formatCliError` from `src/client/errors.ts`.
61-
* `formatCliError` takes the raw `Error` object.
62-
* It checks the error's `name` (e.g., 'ReadOnlyFileSystemError', 'FileReadError') to provide specific user-friendly messages for known file system issues.
63-
* For other errors, it checks if the error has a `.code` property (like the internal `ErrorCode` enum values, e.g., 'ERR_2000') and prepends it to the error message.
64-
* **Shape Change:** `Error` object -> Formatted `string` suitable for console output.
65-
4. **Output:** The formatted string is printed to `console.error`.
66-
67-
**2. MCP Server Error Path (`server/index.ts` via `tools.ts`)**
45+
**Explanation of Updated Error Flow and Transformations:**
6846

69-
1. **Origination:**
70-
* **Validation Error:** A *tagged* protocol error (`jsonRpcCode = -32602`) is thrown by `toolExecutors.ts` validation.
71-
* **Execution Error:** An *untagged* application error (e.g., `TaskNotDone`) is thrown by `TaskManager`.
72-
2. **Catching (`executeToolAndHandleErrors`):** Both types of errors are caught by the `try...catch` block in `executeToolAndHandleErrors` within `src/server/tools.ts`.
73-
3. **Branching & Transformation:**
74-
* **If Tagged Protocol Error:** `executeToolAndHandleErrors` detects the `jsonRpcCode` property and *re-throws* the error unchanged.
75-
* **If Untagged App Error:**
76-
* `executeToolAndHandleErrors` calls `normalizeError` from `src/utils/errors.ts`.
77-
* `normalizeError` takes the raw `Error` object.
78-
* It converts the error into an `McpError` object, typically assigning the `code` to `-32000` (Server Error - a generic JSON-RPC code for implementation-defined errors). It preserves the original error message (stripping any internal `[ERR_CODE]` prefix) and potentially includes the stack trace in the `data` field for debugging.
79-
* **Shape Change:** Raw `Error` object -> Standardized `McpError` object (with JSON-RPC code).
80-
* `executeToolAndHandleErrors` then formats this `McpError` into the MCP `isError: true` structure: `{ content: [{ type: "text", text: "Tool execution failed: <normalized_message>" }], isError: true }`.
81-
* **Shape Change:** `McpError` object -> MCP Tool Result `object` with `isError: true`.
82-
4. **SDK Handling (`@modelcontextprotocol/sdk Server`):** The MCP SDK `Server` instance (used in `server/index.ts`) handles the outcome from `executeToolAndHandleErrors`:
83-
* **If Error was Re-thrown (Tagged Protocol Error):** The SDK catches the *thrown* error. It automatically formats this into a standard JSON-RPC top-level error response (e.g., `{"jsonrpc": "2.0", "error": {"code": -32602, "message": "...", "data": ...}, "id": ...}`).
84-
* **If `isError: true` Object was Returned (Normalized App Error):** The SDK receives the *returned* object. It treats this as a *successful* tool execution from a protocol perspective, but one where the tool itself reported an error. It formats a standard JSON-RPC success response, placing the `isError: true` object inside the `result` field (e.g., `{"jsonrpc": "2.0", "result": {"content": [...], "isError": true}, "id": ...}`).
85-
5. **Output:** The final JSON-RPC response (either an error response or a success response containing an `isError` result) is sent to the connected MCP Client.
47+
Errors are consistently through a unified `AppError` system:
8648

87-
**Key Functions Changing Error Shapes:**
49+
1. **Validation Errors** (`APP-1xxx` series)
50+
- Used for validation issues (e.g., MissingParameter, InvalidArgument)
51+
- Thrown by tool executors during parameter validation
52+
- Mapped to protocol-level McpErrors in `executeToolAndHandleErrors`
8853

89-
1. **`toolExecutors.ts` (Validation Logic):** Creates *new* `Error` objects and *tags* them with `jsonRpcCode`. (Raw Error -> Tagged Error)
90-
2. **`normalizeError` (`src/utils/errors.ts`):** Standardizes various error inputs into `McpError` objects, often using the generic `-32000` code for application errors. (Raw Error -> McpError)
91-
3. **`executeToolAndHandleErrors` (`src/server/tools.ts`):** Packages the `McpError` from `normalizeError` into the MCP-specific `{ content: [...], isError: true }` return format. (McpError -> MCP Result Object)
92-
4. **`formatCliError` (`src/client/errors.ts`):** Converts `Error` objects into user-friendly `string` messages for the CLI. (Error -> String)
93-
5. **`@modelcontextprotocol/sdk Server`:** Formats *thrown*, tagged errors into the top-level JSON-RPC `error` object and *returned* `isError: true` results into the JSON-RPC `result` object. (Tagged Error -> JSON-RPC Error / MCP Result Object -> JSON-RPC Result)
54+
2. **Business Logic Errors** (`APP-2xxx` and higher)
55+
- Used for all business logic and application-specific errors
56+
- Include specific error codes (e.g., APP-2000 for ProjectNotFoundError)
57+
- Returned as serialized CallToolResults with `isError: true`

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ This will show the available commands and options.
4040
The task manager supports multiple LLM providers for generating project plans. You can configure one or more of the following environment variables depending on which providers you want to use:
4141

4242
- `OPENAI_API_KEY`: Required for using OpenAI models (e.g., GPT-4)
43-
- `GEMINI_API_KEY`: Required for using Google's Gemini models
43+
- `GOOGLE_GENERATIVE_AI_API_KEY`: Required for using Google's Gemini models
4444
- `DEEPSEEK_API_KEY`: Required for using Deepseek models
4545

4646
To generate project plans using the CLI, set these environment variables in your shell:
4747

4848
```bash
4949
export OPENAI_API_KEY="your-api-key"
50-
export GEMINI_API_KEY="your-api-key"
50+
export GOOGLE_GENERATIVE_AI_API_KEY="your-api-key"
5151
export DEEPSEEK_API_KEY="your-api-key"
5252
```
5353

@@ -61,7 +61,7 @@ Or you can include them in your MCP client configuration to generate project pla
6161
"args": ["-y", "taskqueue-mcp"],
6262
"env": {
6363
"OPENAI_API_KEY": "your-api-key",
64-
"GEMINI_API_KEY": "your-api-key",
64+
"GOOGLE_GENERATIVE_AI_API_KEY": "your-api-key",
6565
"DEEPSEEK_API_KEY": "your-api-key"
6666
}
6767
}

src/client/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
TaskState,
55
Task,
66
Project
7-
} from "../types/index.js";
7+
} from "../types/data.js";
88
import { TaskManager } from "../server/TaskManager.js";
99
import { formatCliError } from "./errors.js";
1010
import { formatProjectsList, formatTaskProgressTable } from "./taskFormattingUtils.js";

src/client/errors.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
1-
import { ErrorCode } from "../types/index.js";
1+
import { AppError } from "../types/errors.js";
22

33
/**
44
* Formats an error message for CLI output
55
*/
6-
export function formatCliError(error: Error & { code?: ErrorCode | number }): string {
7-
// Handle our custom file system errors with user-friendly messages
8-
if (error.name === 'ReadOnlyFileSystemError') {
9-
return "Cannot save tasks: The file system is read-only. Please check your permissions.";
10-
}
11-
if (error.name === 'FileWriteError') {
12-
return "Failed to save tasks: There was an error writing to the file.";
13-
}
14-
if (error.name === 'FileReadError') {
15-
return "Failed to read file: The file could not be accessed or does not exist.";
6+
export function formatCliError(error: Error): string {
7+
// Handle our custom file system errors by prefixing the error code
8+
if (error instanceof AppError) {
9+
return `${error.code}: ${error.message}`;
1610
}
1711

18-
// For other errors, include the error code if available
19-
const codePrefix = error.code ? `[${error.code}] ` : '';
20-
return `${codePrefix}${error.message}`;
12+
// For unknown errors, just return the error message
13+
return error.message;
2114
}

src/client/taskFormattingUtils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Table from 'cli-table3'; // Import the library
22
import chalk from 'chalk'; // Import chalk for consistent styling
3-
import { ListProjectsSuccessData, Project } from "../types/index.js";
3+
import { ListProjectsSuccessData } from "../types/response.js";
4+
import { Project } from "../types/data.js";
45

56
/**
67
* Formats the project details and a progress table for its tasks using cli-table3.

src/server/FileSystemService.ts

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,8 @@
11
import { readFile, writeFile, mkdir } from 'node:fs/promises';
22
import { dirname, join, resolve } from "node:path";
33
import { homedir } from "node:os";
4-
import { TaskManagerFile, ErrorCode } from "../types/index.js";
5-
6-
// Custom error classes for FileSystemService
7-
export class ReadOnlyFileSystemError extends Error {
8-
constructor(originalError?: unknown) {
9-
super('Cannot save tasks: read-only file system');
10-
this.name = 'ReadOnlyFileSystemError';
11-
(this as any).code = ErrorCode.ReadOnlyFileSystem;
12-
(this as any).originalError = originalError;
13-
}
14-
}
15-
16-
export class FileWriteError extends Error {
17-
constructor(message: string, originalError?: unknown) {
18-
super(message);
19-
this.name = 'FileWriteError';
20-
(this as any).code = ErrorCode.FileWriteError;
21-
(this as any).originalError = originalError;
22-
}
23-
}
24-
25-
export class FileReadError extends Error {
26-
constructor(message: string, originalError?: unknown) {
27-
super(message);
28-
this.name = 'FileReadError';
29-
(this as any).code = ErrorCode.FileReadError;
30-
(this as any).originalError = originalError;
31-
}
32-
}
4+
import { AppError, AppErrorCode } from "../types/errors.js";
5+
import { TaskManagerFile } from "../types/data.js";
336

347
export interface InitializedTaskData {
358
data: TaskManagerFile;
@@ -189,9 +162,9 @@ export class FileSystemService {
189162
);
190163
} catch (error) {
191164
if (error instanceof Error && error.message.includes("EROFS")) {
192-
throw new ReadOnlyFileSystemError(error);
165+
throw new AppError("Cannot save tasks: read-only file system", AppErrorCode.ReadOnlyFileSystem, error);
193166
}
194-
throw new FileWriteError("Failed to save tasks file", error);
167+
throw new AppError("Failed to save tasks file", AppErrorCode.FileWriteError, error);
195168
}
196169
});
197170
}
@@ -208,9 +181,9 @@ export class FileSystemService {
208181
return await readFile(filePath, 'utf-8');
209182
} catch (error) {
210183
if (error instanceof Error && error.message.includes('ENOENT')) {
211-
throw new FileReadError(`Attachment file not found: ${filename}`, error);
184+
throw new AppError(`Attachment file not found: ${filename}`, AppErrorCode.FileReadError, error);
212185
}
213-
throw new FileReadError(`Failed to read attachment file: ${filename}`, error);
186+
throw new AppError(`Failed to read attachment file: ${filename}`, AppErrorCode.FileReadError, error);
214187
}
215188
}
216189
}

0 commit comments

Comments
 (0)