#
tokens: 41366/50000 49/49 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── LICENSE
├── package-lock.json
├── package.json
├── public
│   └── images
│       └── mcp-task-manager-logo.svg
├── README.md
├── src
│   ├── config
│   │   └── ConfigurationManager.ts
│   ├── createServer.ts
│   ├── db
│   │   ├── DatabaseManager.ts
│   │   └── schema.sql
│   ├── repositories
│   │   ├── ProjectRepository.ts
│   │   └── TaskRepository.ts
│   ├── server.ts
│   ├── services
│   │   ├── index.ts
│   │   ├── ProjectService.ts
│   │   └── TaskService.ts
│   ├── tools
│   │   ├── addTaskParams.ts
│   │   ├── addTaskTool.ts
│   │   ├── createProjectParams.ts
│   │   ├── createProjectTool.ts
│   │   ├── deleteProjectParams.ts
│   │   ├── deleteProjectTool.ts
│   │   ├── deleteTaskParams.ts
│   │   ├── deleteTaskTool.ts
│   │   ├── expandTaskParams.ts
│   │   ├── expandTaskTool.ts
│   │   ├── exportProjectParams.ts
│   │   ├── exportProjectTool.ts
│   │   ├── getNextTaskParams.ts
│   │   ├── getNextTaskTool.ts
│   │   ├── importProjectParams.ts
│   │   ├── importProjectTool.ts
│   │   ├── index.ts
│   │   ├── listTasksParams.ts
│   │   ├── listTasksTool.ts
│   │   ├── setTaskStatusParams.ts
│   │   ├── setTaskStatusTool.ts
│   │   ├── showTaskParams.ts
│   │   ├── showTaskTool.ts
│   │   ├── updateTaskParams.ts
│   │   └── updateTaskTool.ts
│   ├── types
│   │   ├── exampleServiceTypes.ts
│   │   ├── index.ts
│   │   └── taskTypes.ts
│   └── utils
│       ├── errors.ts
│       ├── index.ts
│       └── logger.ts
├── tasks.md
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "semi": true,
3 |   "trailingComma": "es5",
4 |   "singleQuote": false,
5 |   "printWidth": 80,
6 |   "tabWidth": 2,
7 |   "endOfLine": "lf"
8 | }
9 | 
```

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "root": true,
 3 |   "parser": "@typescript-eslint/parser",
 4 |   "plugins": [
 5 |     "@typescript-eslint",
 6 |     "prettier"
 7 |   ],
 8 |   "extends": [
 9 |     "eslint:recommended",
10 |     "plugin:@typescript-eslint/recommended",
11 |     "prettier" // Make sure this is last
12 |   ],
13 |   "rules": {
14 |     "prettier/prettier": "warn",
15 |     "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
16 |     "@typescript-eslint/no-explicit-any": "warn", // Use warn instead of error initially
17 |     "no-console": "off" // Allow console logging for server apps, or configure properly
18 |   },
19 |   "env": {
20 |     "node": true,
21 |     "es2022": true
22 |   },
23 |   "parserOptions": {
24 |     "ecmaVersion": "latest",
25 |     "sourceType": "module"
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Created by https://www.toptal.com/developers/gitignore/api/node
  2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
  3 | 
  4 | ### Node ###
  5 | # Logs
  6 | logs
  7 | *.log
  8 | npm-debug.log*
  9 | yarn-debug.log*
 10 | yarn-error.log*
 11 | lerna-debug.log*
 12 | .pnpm-debug.log*
 13 | 
 14 | # Diagnostic reports (https://nodejs.org/api/report.html)
 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 16 | 
 17 | # Runtime data
 18 | pids
 19 | *.pid
 20 | *.seed
 21 | *.pid.lock
 22 | 
 23 | # Directory for instrumented libs generated by jscoverage/JSCover
 24 | lib-cov
 25 | 
 26 | # Coverage directory used by tools like istanbul
 27 | coverage
 28 | *.lcov
 29 | 
 30 | # nyc test coverage
 31 | .nyc_output
 32 | 
 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 34 | .grunt
 35 | 
 36 | # Bower dependency directory (https://bower.io/)
 37 | bower_components
 38 | 
 39 | # node-waf configuration
 40 | .lock-wscript
 41 | 
 42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 43 | build/Release
 44 | 
 45 | # Dependency directories
 46 | node_modules/
 47 | jspm_packages/
 48 | 
 49 | # Snowpack dependency directory (https://snowpack.dev/)
 50 | web_modules/
 51 | 
 52 | # TypeScript cache
 53 | *.tsbuildinfo
 54 | 
 55 | # Optional npm cache directory
 56 | .npm
 57 | 
 58 | # Optional eslint cache
 59 | .eslintcache
 60 | 
 61 | # Optional stylelint cache
 62 | .stylelintcache
 63 | 
 64 | # Microbundle cache
 65 | .rpt2_cache/
 66 | .rts2_cache_cjs/
 67 | .rts2_cache_es/
 68 | .rts2_cache_umd/
 69 | 
 70 | # Optional REPL history
 71 | .node_repl_history
 72 | 
 73 | # Output of 'npm pack'
 74 | *.tgz
 75 | 
 76 | # Yarn Integrity file
 77 | .yarn-integrity
 78 | 
 79 | # dotenv environment variable files
 80 | .env
 81 | .env.development.local
 82 | .env.test.local
 83 | .env.production.local
 84 | .env.local
 85 | 
 86 | # parcel-bundler cache (https://parceljs.org/)
 87 | .cache
 88 | .parcel-cache
 89 | 
 90 | # Next.js build output
 91 | .next
 92 | out
 93 | 
 94 | # Nuxt.js build / generate output
 95 | .nuxt
 96 | dist
 97 | 
 98 | # Gatsby files
 99 | .cache/
100 | # Comment in the public line in if your project uses Gatsby and not Next.js
101 | # https://nextjs.org/blog/next-9-1#public-directory-support
102 | # public
103 | 
104 | # vuepress build output
105 | .vuepress/dist
106 | 
107 | # vuepress v2.x temp and cache directory
108 | .temp
109 | 
110 | # Docusaurus cache and generated files
111 | .docusaurus
112 | 
113 | # Serverless directories
114 | .serverless/
115 | 
116 | # FuseBox cache
117 | .fusebox/
118 | 
119 | # DynamoDB Local files
120 | .dynamodb/
121 | 
122 | # TernJS port file
123 | .tern-port
124 | 
125 | # Stores VSCode versions used for testing VSCode extensions
126 | .vscode-test
127 | 
128 | # yarn v2
129 | .yarn/cache
130 | .yarn/unplugged
131 | .yarn/build-state.yml
132 | .yarn/install-state.gz
133 | .pnp.*
134 | 
135 | ### Node Patch ###
136 | # Serverless Webpack directories
137 | .webpack/
138 | 
139 | # Optional stylelint cache
140 | 
141 | # SvelteKit build / generate output
142 | .svelte-kit
143 | 
144 | dist/
145 | .generalrules
146 | .clinerules
147 | .cursorrules
148 | 
149 | 
150 | 
151 | # End of https://www.toptal.com/developers/gitignore/api/node
152 | .taskmanagerrules
153 | docs/
154 | data/taskmanager.db
155 | data/taskmanager.*
156 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP Task Manager Server
  2 | 
  3 | <div align="center">
  4 |   <img src="public/images/mcp-task-manager-logo.svg" alt="MCP Task Manager Logo" width="200" height="200" />
  5 | </div>
  6 | 
  7 | A local Model Context Protocol (MCP) server providing backend tools for client-driven project and task management using a SQLite database.
  8 | 
  9 | ## Overview
 10 | 
 11 | This server acts as a persistent backend for local MCP clients (like AI agents or scripts) that need to manage structured task data within distinct projects. It handles data storage and provides a standardized set of tools for interaction, while the strategic workflow logic resides within the client.
 12 | 
 13 | **Key Features:**
 14 | 
 15 | * **Project-Based:** Tasks are organized within distinct projects.
 16 | * **SQLite Persistence:** Uses a local SQLite file (`./data/taskmanager.db` by default) for simple, self-contained data storage.
 17 | * **Client-Driven:** Provides tools for clients; does not dictate workflow.
 18 | * **MCP Compliant:** Adheres to the Model Context Protocol for tool definition and communication.
 19 | * **Task Management:** Supports creating projects, adding tasks, listing/showing tasks, updating status, expanding tasks into subtasks, and identifying the next actionable task.
 20 | * **Import/Export:** Allows exporting project data to JSON and importing from JSON to create new projects.
 21 | 
 22 | ## Implemented MCP Tools
 23 | 
 24 | The following tools are available for MCP clients:
 25 | 
 26 | * **`createProject`**:
 27 |   * **Description:** Creates a new, empty project.
 28 |   * **Params:** `projectName` (string, optional, max 255)
 29 |   * **Returns:** `{ project_id: string }`
 30 | * **`addTask`**:
 31 |   * **Description:** Adds a new task to a project.
 32 |   * **Params:** `project_id` (string, required, UUID), `description` (string, required, 1-1024), `dependencies` (string[], optional, max 50), `priority` (enum 'high'|'medium'|'low', optional, default 'medium'), `status` (enum 'todo'|'in-progress'|'review'|'done', optional, default 'todo')
 33 |   * **Returns:** Full `TaskData` object of the created task.
 34 | * **`listTasks`**:
 35 |   * **Description:** Lists tasks for a project, with optional filtering and subtask inclusion.
 36 |   * **Params:** `project_id` (string, required, UUID), `status` (enum 'todo'|'in-progress'|'review'|'done', optional), `include_subtasks` (boolean, optional, default false)
 37 |   * **Returns:** Array of `TaskData` or `StructuredTaskData` objects.
 38 | * **`showTask`**:
 39 |   * **Description:** Retrieves full details for a specific task, including dependencies and direct subtasks.
 40 |   * **Params:** `project_id` (string, required, UUID), `task_id` (string, required)
 41 |   * **Returns:** `FullTaskData` object.
 42 | * **`setTaskStatus`**:
 43 |   * **Description:** Updates the status of one or more tasks.
 44 |   * **Params:** `project_id` (string, required, UUID), `task_ids` (string[], required, 1-100), `status` (enum 'todo'|'in-progress'|'review'|'done', required)
 45 |   * **Returns:** `{ success: true, updated_count: number }`
 46 | * **`expandTask`**:
 47 |   * **Description:** Breaks a parent task into subtasks, optionally replacing existing ones.
 48 |   * **Params:** `project_id` (string, required, UUID), `task_id` (string, required), `subtask_descriptions` (string[], required, 1-20, each 1-512), `force` (boolean, optional, default false)
 49 |   * **Returns:** Updated parent `FullTaskData` object including new subtasks.
 50 | * **`getNextTask`**:
 51 |   * **Description:** Identifies the next actionable task based on status ('todo'), dependencies ('done'), priority, and creation date.
 52 |   * **Params:** `project_id` (string, required, UUID)
 53 |   * **Returns:** `FullTaskData` object of the next task, or `null` if none are ready.
 54 | * **`exportProject`**:
 55 |   * **Description:** Exports complete project data as a JSON string.
 56 |   * **Params:** `project_id` (string, required, UUID), `format` (enum 'json', optional, default 'json')
 57 |   * **Returns:** JSON string representing the project.
 58 | * **`importProject`**:
 59 |   * **Description:** Creates a *new* project from an exported JSON string.
 60 |   * **Params:** `project_data` (string, required, JSON), `new_project_name` (string, optional, max 255)
 61 |   * **Returns:** `{ project_id: string }` of the newly created project.
 62 | * **`updateTask`**:
 63 |   * **Description:** Updates specific details (description, priority, dependencies) of an existing task.
 64 |   * **Params:** `project_id` (string, required, UUID), `task_id` (string, required, UUID), `description` (string, optional, 1-1024), `priority` (enum 'high'|'medium'|'low', optional), `dependencies` (string[], optional, max 50, replaces existing)
 65 |   * **Returns:** Updated `FullTaskData` object.
 66 | * **`deleteTask`**:
 67 |   * **Description:** Deletes one or more tasks (and their subtasks/dependency links via cascade).
 68 |   * **Params:** `project_id` (string, required, UUID), `task_ids` (string[], required, 1-100)
 69 |   * **Returns:** `{ success: true, deleted_count: number }`
 70 | * **`deleteProject`**:
 71 |   * **Description:** Permanently deletes a project and ALL associated data. **Use with caution!**
 72 |   * **Params:** `project_id` (string, required, UUID)
 73 |   * **Returns:** `{ success: true }`
 74 | 
 75 | *(Note: Refer to the corresponding `src/tools/*Params.ts` files for detailed Zod schemas and parameter descriptions.)*
 76 | 
 77 | ## Getting Started
 78 | 
 79 | 1. **Prerequisites:** Node.js (LTS recommended), npm.
 80 | 2. **Install Dependencies:**
 81 | 
 82 |     ```bash
 83 |     npm install
 84 |     ```
 85 | 
 86 | 3. **Run in Development Mode:** (Uses `ts-node` and `nodemon` for auto-reloading)
 87 | 
 88 |     ```bash
 89 |     npm run dev
 90 |     ```
 91 | 
 92 |     The server will connect via stdio. Logs (JSON format) will be printed to stderr. The SQLite database will be created/updated in `./data/taskmanager.db`.
 93 | 4. **Build for Production:**
 94 | 
 95 |     ```bash
 96 |     npm run build
 97 |     ```
 98 | 
 99 | 5. **Run Production Build:**
100 | 
101 |     ```bash
102 |     npm start
103 |     ```
104 | 
105 | ## Configuration
106 | 
107 | * **Database Path:** The location of the SQLite database file can be overridden by setting the `DATABASE_PATH` environment variable. The default is `./data/taskmanager.db`.
108 | * **Log Level:** The logging level can be set using the `LOG_LEVEL` environment variable (e.g., `debug`, `info`, `warn`, `error`). The default is `info`.
109 | 
110 | ## Project Structure
111 | 
112 | * `/src`: Source code.
113 |   * `/config`: Configuration management.
114 |   * `/db`: Database manager and schema (`schema.sql`).
115 |   * `/repositories`: Data access layer (SQLite interaction).
116 |   * `/services`: Core business logic.
117 |   * `/tools`: MCP tool definitions (*Params.ts) and implementation (*Tool.ts).
118 |   * `/types`: Shared TypeScript interfaces (currently minimal, mostly in repos/services).
119 |   * `/utils`: Logging, custom errors, etc.
120 |   * `createServer.ts`: Server instance creation.
121 |   * `server.ts`: Main application entry point.
122 | * `/dist`: Compiled JavaScript output.
123 | * `/docs`: Project documentation (PRD, Feature Specs, RFC).
124 | * `/data`: Default location for the SQLite database file (created automatically).
125 | * `tasks.md`: Manual task tracking file for development.
126 | * Config files (`package.json`, `tsconfig.json`, `.eslintrc.json`, etc.)
127 | 
128 | ## Linting and Formatting
129 | 
130 | * **Lint:** `npm run lint`
131 | * **Format:** `npm run format`
132 | 
133 | (Code is automatically linted/formatted on commit via Husky/lint-staged).
134 | 
```

--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './logger.js';
2 | export * from './errors.js';
3 | // Add other util exports here
4 | 
```

--------------------------------------------------------------------------------
/src/services/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from './ProjectService.js';
2 | export * from './TaskService.js'; // Added TaskService export
3 | // Remove or comment out ExampleService if it's not being used
4 | // export * from './ExampleService.js';
5 | // Add other service exports here
6 | 
```

--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Export all types and interfaces from this barrel file
 2 | export * from './exampleServiceTypes.js';
 3 | export * from './taskTypes.js'; // Added export for task types
 4 | // export * from './yourServiceTypes.js'; // Add new type exports here
 5 | 
 6 | // Define common types used across services/tools if any
 7 | export interface CommonContext {
 8 |     sessionId?: string;
 9 |     userId?: string;
10 | }
11 | 
```

--------------------------------------------------------------------------------
/src/types/exampleServiceTypes.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Types specific to the ExampleService
 2 | 
 3 | /**
 4 |  * Configuration options for ExampleService.
 5 |  */
 6 | export interface ExampleServiceConfig {
 7 |     greeting: string;
 8 |     enableDetailedLogs: boolean;
 9 | }
10 | 
11 | /**
12 |  * Data structure handled by ExampleService.
13 |  */
14 | export interface ExampleServiceData {
15 |     name: string;
16 |     message: string;
17 |     processedTimestamp: string;
18 |     metrics?: ExampleServiceMetrics;
19 | }
20 | 
21 | /**
22 |  * Metrics collected during ExampleService processing.
23 |  */
24 | export interface ExampleServiceMetrics {
25 |     processingTimeMs: number;
26 | }
27 | 
```

--------------------------------------------------------------------------------
/src/tools/deleteProjectParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "deleteProject";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Permanently deletes a project and ALL associated tasks and dependencies.
 7 | Requires the project ID. This is a highly destructive operation and cannot be undone.
 8 | Returns a success confirmation upon completion.
 9 | `;
10 | 
11 | // Zod schema for the parameters, matching FR-013
12 | export const TOOL_PARAMS = z.object({
13 |     project_id: z.string()
14 |         .uuid("The project_id must be a valid UUID.")
15 |         .describe("The unique identifier (UUID) of the project to permanently delete. This project must exist."), // Required, UUID format
16 | 
17 | });
18 | 
19 | // Define the expected type for arguments based on the Zod schema
20 | export type DeleteProjectArgs = z.infer<typeof TOOL_PARAMS>;
21 | 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createServer } from "./createServer.js";
 2 | import { logger } from "./utils/index.js";
 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 4 | // import { WebSocketServerTransport } from "@modelcontextprotocol/sdk/server/ws.js"; // Example for WebSocket
 5 | 
 6 | const main = async () => {
 7 |     try {
 8 |         const server = createServer();
 9 |         logger.info("Starting MCP server");
10 | 
11 |         // Choose your transport
12 |         const transport = new StdioServerTransport();
13 |         // const transport = new WebSocketServerTransport({ port: 8080 }); // Example
14 | 
15 |         logger.info("Connecting transport", { transport: transport.constructor.name });
16 |         await server.connect(transport);
17 | 
18 |         logger.info("MCP Server connected and listening");
19 | 
20 |     } catch (error) {
21 |         logger.error("Failed to start server", error);
22 |         process.exit(1); // Exit if server fails to start
23 |     }
24 | };
25 | 
26 | main();
27 | 
```

--------------------------------------------------------------------------------
/src/createServer.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { ConfigurationManager } from "./config/ConfigurationManager.js";
 3 | import { registerTools } from "./tools/index.js";
 4 | import { logger } from "./utils/index.js";
 5 | 
 6 | /**
 7 |  * Creates and configures an MCP server instance.
 8 |  * This is the central function for server creation and tool registration.
 9 |  * @returns {McpServer} The configured MCP server instance
10 |  */
11 | export function createServer(): McpServer {
12 |     logger.info("Creating MCP server instance");
13 | 
14 |     // Initialize the server
15 |     const server = new McpServer({
16 |         name: "mcp-server",
17 |         version: "1.0.0",
18 |         description: "MCP Server based on recommended practices"
19 |     });
20 | 
21 |     // Get configuration
22 |     const configManager = ConfigurationManager.getInstance();
23 | 
24 |     // Register all tools
25 |     registerTools(server);
26 | 
27 |     logger.info("MCP server instance created successfully");
28 |     return server;
29 | }
30 | 
```

--------------------------------------------------------------------------------
/src/tools/getNextTaskParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "getNextTask";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Identifies and returns the next actionable task within a specified project.
 7 | A task is considered actionable if its status is 'todo' and all its dependencies (if any) have a status of 'done'.
 8 | If multiple tasks are ready, the one with the highest priority ('high' > 'medium' > 'low') is chosen.
 9 | If priorities are equal, the task created earliest is chosen.
10 | Returns the full details of the next task, or null if no task is currently ready.
11 | `;
12 | 
13 | // Zod schema for the parameters, matching FR-007 and getNextTaskTool.md spec
14 | export const TOOL_PARAMS = z.object({
15 |     project_id: z.string()
16 |         .uuid("The project_id must be a valid UUID.")
17 |         .describe("The unique identifier (UUID) of the project to find the next task for."), // Required, UUID format
18 | });
19 | 
20 | // Define the expected type for arguments based on the Zod schema
21 | export type GetNextTaskArgs = z.infer<typeof TOOL_PARAMS>;
22 | 
```

--------------------------------------------------------------------------------
/src/tools/showTaskParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "showTask";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Retrieves the full details of a single, specific task, including its dependencies and direct subtasks.
 7 | Requires the project ID and the task ID.
 8 | Returns a task object containing all details if found.
 9 | `;
10 | 
11 | // Zod schema for the parameters, matching FR-004 and showTaskTool.md spec
12 | export const TOOL_PARAMS = z.object({
13 |     project_id: z.string()
14 |         .uuid("The project_id must be a valid UUID.")
15 |         .describe("The unique identifier (UUID) of the project the task belongs to."), // Required, UUID format
16 | 
17 |     task_id: z.string()
18 |         // Add .uuid() if task IDs are also UUIDs, otherwise keep as string
19 |         .min(1, "Task ID cannot be empty.")
20 |         .describe("The unique identifier of the task to retrieve details for."), // Required, string (or UUID)
21 | });
22 | 
23 | // Define the expected type for arguments based on the Zod schema
24 | export type ShowTaskArgs = z.infer<typeof TOOL_PARAMS>;
25 | 
```

--------------------------------------------------------------------------------
/src/tools/exportProjectParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "exportProject";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Exports the complete data set for a specified project as a JSON string.
 7 | This includes project metadata, all tasks (hierarchically structured), and their dependencies.
 8 | Requires the project ID. The format is fixed to JSON for V1.
 9 | Returns the JSON string representing the project data.
10 | `;
11 | 
12 | // Zod schema for the parameters, matching FR-009 and exportProjectTool.md spec
13 | export const TOOL_PARAMS = z.object({
14 |     project_id: z.string()
15 |         .uuid("The project_id must be a valid UUID.")
16 |         .describe("The unique identifier (UUID) of the project to export."), // Required, UUID format
17 | 
18 |     format: z.literal('json') // Only allow 'json' for V1
19 |         .optional()
20 |         .default('json')
21 |         .describe("Optional format for the export. Currently only 'json' is supported (default)."), // Optional, enum (fixed), default
22 | });
23 | 
24 | // Define the expected type for arguments based on the Zod schema
25 | export type ExportProjectArgs = z.infer<typeof TOOL_PARAMS>;
26 | 
```

--------------------------------------------------------------------------------
/src/tools/createProjectParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "createProject";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Creates a new, empty project entry in the Task Management Server database.
 7 | This tool is used by clients (e.g., AI agents) to initiate a new workspace for tasks.
 8 | It returns the unique identifier (UUID) assigned to the newly created project.
 9 | An optional name can be provided; otherwise, a default name including a timestamp will be generated.
10 | `;
11 | 
12 | // Define the shape of the parameters for the server.tool method
13 | export const TOOL_PARAMS = {
14 |     projectName: z.string()
15 |         .max(255, "Project name cannot exceed 255 characters.") // Max length constraint
16 |         .optional() // Optional parameter
17 |         .describe("Optional human-readable name for the new project (max 255 chars). If omitted, a default name like 'New Project [timestamp]' will be used."), // Detailed description
18 | };
19 | 
20 | // Create a Zod schema object from the shape for validation and type inference
21 | const toolParamsSchema = z.object(TOOL_PARAMS);
22 | 
23 | // Define the expected type for arguments based on the Zod schema
24 | export type CreateProjectArgs = z.infer<typeof toolParamsSchema>;
25 | 
```

--------------------------------------------------------------------------------
/src/utils/errors.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Custom error types for the Task Management Server.
 3 |  * These can be caught in the service layer and mapped to specific
 4 |  * McpError codes in the tool layer.
 5 |  */
 6 | 
 7 | // Example: Base service error
 8 | export class ServiceError extends Error {
 9 |     constructor(message: string, public details?: any) {
10 |         super(message);
11 |         this.name = 'ServiceError';
12 |     }
13 | }
14 | 
15 | // Example: Validation specific error
16 | export class ValidationError extends ServiceError {
17 |     constructor(message: string, details?: any) {
18 |         super(message, details);
19 |         this.name = 'ValidationError';
20 |     }
21 | }
22 | 
23 | // Example: Not found specific error
24 | export class NotFoundError extends ServiceError {
25 |     constructor(message: string = "Resource not found", details?: any) {
26 |         super(message, details);
27 |         this.name = 'NotFoundError';
28 |     }
29 | }
30 | 
31 | // Example: Conflict specific error (e.g., trying to create something that exists)
32 | export class ConflictError extends ServiceError {
33 |     constructor(message: string = "Resource conflict", details?: any) {
34 |         super(message, details);
35 |         this.name = 'ConflictError';
36 |     }
37 | }
38 | 
39 | // Add other custom error types as needed
40 | 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { pino, Logger } from 'pino'; // Try named import for the function
 2 | 
 3 | /**
 4 |  * Pino logger instance configured for structured JSON logging to stderr.
 5 |  * MCP servers typically use stdout for protocol messages, so logs go to stderr.
 6 |  */
 7 | export const logger: Logger = pino(
 8 |     {
 9 |         level: process.env.LOG_LEVEL || 'info', // Default to 'info', configurable via env var
10 |         formatters: {
11 |             level: (label: string) => { // Add type for label
12 |                 // Standardize level labels if desired, e.g., uppercase
13 |                 return { level: label.toUpperCase() };
14 |             },
15 |             // bindings: (bindings) => {
16 |             //     // Add custom bindings if needed, e.g., hostname, pid
17 |             //     return { pid: bindings.pid, hostname: bindings.hostname };
18 |             // },
19 |         },
20 |         timestamp: pino.stdTimeFunctions.isoTime, // Use ISO 8601 timestamps
21 |     },
22 |     pino.destination(2) // Direct output to stderr (file descriptor 2)
23 | );
24 | 
25 | // Example usage (replace console.log/error calls throughout the app):
26 | // logger.info('Server starting...');
27 | // logger.debug({ userId: '123' }, 'User logged in');
28 | // logger.error(new Error('Something failed'), 'Failed to process request');
29 | 
```

--------------------------------------------------------------------------------
/src/tools/deleteTaskParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "deleteTask";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Deletes one or more tasks within a specified project.
 7 | Requires the project ID and an array of task IDs to delete.
 8 | Note: Deleting a task also deletes its subtasks and dependency links due to database cascade rules.
 9 | Returns the count of successfully deleted tasks.
10 | `;
11 | 
12 | // Zod schema for the parameters, matching FR-012
13 | export const TOOL_PARAMS = z.object({
14 |     project_id: z.string()
15 |         .uuid("The project_id must be a valid UUID.")
16 |         .describe("The unique identifier (UUID) of the project containing the tasks to delete. This project must exist."), // Required, UUID format
17 | 
18 |     task_ids: z.array(
19 |             z.string()
20 |                 .uuid("Each task ID must be a valid UUID.")
21 |                 .describe("A unique identifier (UUID) of a task to delete.")
22 |         )
23 |         .min(1, "At least one task ID must be provided.")
24 |         .max(100, "Cannot delete more than 100 tasks per call.")
25 |         .describe("An array of task IDs (UUIDs, 1-100) to be deleted from the specified project."), // Required, array of UUID strings, limits
26 | 
27 | });
28 | 
29 | // Define the expected type for arguments based on the Zod schema
30 | export type DeleteTaskArgs = z.infer<typeof TOOL_PARAMS>;
31 | 
```

--------------------------------------------------------------------------------
/src/tools/importProjectParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "importProject";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Creates a *new* project by importing data from a JSON string.
 7 | The JSON data must conform to the structure previously generated by the 'exportProject' tool.
 8 | Performs validation on the input data (parsing, basic structure, size limit).
 9 | Returns the unique project_id of the newly created project upon success.
10 | `;
11 | 
12 | // Zod schema for the parameters, matching FR-010 and importProjectTool.md spec
13 | export const TOOL_PARAMS = z.object({
14 |     project_data: z.string()
15 |         .min(1, "Project data cannot be empty.")
16 |         // Size validation happens in the service layer before parsing
17 |         .describe("Required. A JSON string containing the full project data, conforming to the export structure. Max size e.g., 10MB."), // Required, string
18 | 
19 |     new_project_name: z.string()
20 |         .max(255, "New project name cannot exceed 255 characters.")
21 |         .optional()
22 |         .describe("Optional name for the newly created project (max 255 chars). If omitted, a name based on the original project name and import timestamp will be used."), // Optional, string, max length
23 | });
24 | 
25 | // Define the expected type for arguments based on the Zod schema
26 | export type ImportProjectArgs = z.infer<typeof TOOL_PARAMS>;
27 | 
```

--------------------------------------------------------------------------------
/src/tools/listTasksParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "listTasks";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Retrieves a list of tasks for a specified project.
 7 | Allows optional filtering by task status ('todo', 'in-progress', 'review', 'done').
 8 | Provides an option to include nested subtasks directly within their parent task objects in the response.
 9 | Returns an array of task objects.
10 | `;
11 | 
12 | // Re-use enum from addTaskParams or define locally if preferred
13 | const TaskStatusEnum = z.enum(['todo', 'in-progress', 'review', 'done']);
14 | 
15 | // Zod schema for the parameters, matching FR-003 and listTasksTool.md spec
16 | export const TOOL_PARAMS = z.object({
17 |     project_id: z.string()
18 |         .uuid("The project_id must be a valid UUID.")
19 |         .describe("The unique identifier (UUID) of the project whose tasks are to be listed. This project must exist."), // Required, UUID format
20 | 
21 |     status: TaskStatusEnum
22 |         .optional()
23 |         .describe("Optional filter to return only tasks matching the specified status."), // Optional, enum
24 | 
25 |     include_subtasks: z.boolean()
26 |         .optional()
27 |         .default(false) // Default value
28 |         .describe("Optional flag (default false). If true, the response will include subtasks nested within their parent tasks."), // Optional, boolean, default
29 | });
30 | 
31 | // Define the expected type for arguments based on the Zod schema
32 | export type ListTasksArgs = z.infer<typeof TOOL_PARAMS>;
33 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-task-manager-server",
 3 |   "version": "0.1.0",
 4 |   "description": "My new MCP Server",
 5 |   "main": "dist/server.js",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "start": "node dist/server.js",
 9 |     "build": "tsc && copyfiles -f src/db/*.sql dist/db",
10 |     "dev": "nodemon --watch src --ext ts --exec \"node --loader ts-node/esm src/server.ts\"",
11 |     "lint": "eslint . --ext .ts",
12 |     "format": "prettier --write \"src/**/*.ts\"",
13 |     "test": "echo \"Error: no test specified\" && exit 1",
14 |     "prepare": "husky install || true"
15 |   },
16 |   "keywords": [
17 |     "mcp",
18 |     "model-context-protocol"
19 |   ],
20 |   "license": "ISC",
21 |   "dependencies": {
22 |     "@modelcontextprotocol/sdk": "^1.9.0",
23 |     "@types/better-sqlite3": "^7.6.13",
24 |     "@types/inquirer": "^9.0.7",
25 |     "@types/uuid": "^10.0.0",
26 |     "better-sqlite3": "^11.9.1",
27 |     "chalk": "^5.3.0",
28 |     "inquirer": "^12.5.0",
29 |     "pino": "^9.6.0",
30 |     "uuid": "^11.1.0",
31 |     "zod": "^3.23.8"
32 |   },
33 |   "devDependencies": {
34 |     "@types/node": "^20.14.2",
35 |     "@typescript-eslint/eslint-plugin": "^7.13.0",
36 |     "@typescript-eslint/parser": "^7.13.0",
37 |     "copyfiles": "^2.4.1",
38 |     "eslint": "^8.57.0",
39 |     "eslint-config-prettier": "^9.1.0",
40 |     "eslint-plugin-prettier": "^5.1.3",
41 |     "husky": "^9.0.11",
42 |     "lint-staged": "^15.2.5",
43 |     "nodemon": "^3.1.3",
44 |     "prettier": "^3.3.2",
45 |     "ts-node": "^10.9.2",
46 |     "typescript": "^5.4.5"
47 |   },
48 |   "lint-staged": {
49 |     "*.ts": [
50 |       "eslint --fix",
51 |       "prettier --write"
52 |     ]
53 |   }
54 | }
55 | 
```

--------------------------------------------------------------------------------
/src/tools/setTaskStatusParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "setTaskStatus";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Updates the status ('todo', 'in-progress', 'review', 'done') for one or more tasks within a specified project.
 7 | Requires the project ID, an array of task IDs (1-100), and the target status.
 8 | Verifies all tasks exist in the project before updating. Returns the count of updated tasks.
 9 | `;
10 | 
11 | // Re-use enum from other param files
12 | const TaskStatusEnum = z.enum(['todo', 'in-progress', 'review', 'done']);
13 | 
14 | // Zod schema for the parameters, matching FR-005 and setTaskStatusTool.md spec
15 | export const TOOL_PARAMS = z.object({
16 |     project_id: z.string()
17 |         .uuid("The project_id must be a valid UUID.")
18 |         .describe("The unique identifier (UUID) of the project containing the tasks."), // Required, UUID format
19 | 
20 |     task_ids: z.array(
21 |             z.string().min(1, "Task ID cannot be empty.")
22 |             // Add .uuid() if task IDs are UUIDs
23 |             .describe("A unique identifier of a task to update.")
24 |         )
25 |         .min(1, "At least one task ID must be provided.")
26 |         .max(100, "Cannot update more than 100 tasks per call.")
27 |         .describe("An array of task IDs (1-100) whose status should be updated."), // Required, array of strings, limits
28 | 
29 |     status: TaskStatusEnum
30 |         .describe("The target status to set for the specified tasks."), // Required, enum
31 | });
32 | 
33 | // Define the expected type for arguments based on the Zod schema
34 | export type SetTaskStatusArgs = z.infer<typeof TOOL_PARAMS>;
35 | 
```

--------------------------------------------------------------------------------
/tasks.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Task Manager Server - Development Tasks
 2 | 
 3 | This file tracks the implementation progress based on the defined milestones.
 4 | 
 5 | ## Milestone 1: Core Setup & `createProject` Tool
 6 | 
 7 | - [x] **Create `tasks.md`:** Initial file creation.
 8 | - [x] **Define DB Schema:** Create `src/db/schema.sql` with tables and indexes.
 9 | - [x] **Implement DB Manager:** Create `src/db/DatabaseManager.ts` for connection, init, WAL.
10 | - [x] **Update Config:** Ensure `src/config/ConfigurationManager.ts` handles DB path.
11 | - [x] **Implement Project Repo:** Create `src/repositories/ProjectRepository.ts` with `create` method.
12 | - [x] **Implement Project Service:** Create `src/services/ProjectService.ts` with `createProject` method.
13 | - [x] **Implement `createProject` Params:** Create `src/tools/createProjectParams.ts`.
14 | - [x] **Implement `createProject` Tool:** Create `src/tools/createProjectTool.ts`.
15 | - [x] **Implement Utilities:** Create/update `src/utils/logger.ts`, `src/utils/errors.ts`, `src/utils/index.ts`.
16 | - [x] **Update Server Setup:** Modify `src/server.ts`, `src/createServer.ts`, `src/tools/index.ts`, `src/services/index.ts`.
17 | - [ ] **Write Tests:** Unit test `ProjectService`, Integration test `createProject` tool. *(Skipped/Deferred)*
18 | 
19 | ## Milestone 2: Core Task Management Tools
20 | 
21 | - [x] Implement `addTask` tool (FR-002)
22 | - [x] Implement `listTasks` tool (FR-003)
23 | - [x] Implement `showTask` tool (FR-004)
24 | - [x] Implement `setTaskStatus` tool (FR-005)
25 | 
26 | ## Milestone 3: Advanced & I/O Tools
27 | 
28 | - [x] Implement `expandTask` tool (FR-006)
29 | - [x] Implement `getNextTask` tool (FR-007)
30 | - [x] Implement `exportProject` tool (FR-009)
31 | - [x] Implement `importProject` tool (FR-010)
32 | - [x] Implement structured logging (NFR-006).
33 | - [x] Finalize documentation (README, tool descriptions).
34 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
 4 |     "module": "NodeNext", /* Specify what module code is generated. */
 5 |     "moduleResolution": "NodeNext", /* Specify how TypeScript looks up a file from a given module specifier. */
 6 |     "outDir": "./dist", /* Specify an output folder for all emitted files. */
 7 |     "rootDir": "./src", /* Specify the root folder within your source files. */
 8 |     "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
 9 |     "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
10 |     "strict": true, /* Enable all strict type-checking options. */
11 |     "skipLibCheck": true, /* Skip type checking all .d.ts files. */
12 |     "resolveJsonModule": true, /* Enable importing .json files */
13 |     "sourceMap": true, /* Create source map files for emitted JavaScript files. */
14 |     "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
15 |     "declarationMap": true, /* Create sourcemaps for d.ts files. */
16 |     "allowJs": true, /* Allow JavaScript files to be a part of your program. */
17 |   },
18 |   "ts-node": { /* ts-node specific options */
19 |     "transpileOnly": true, /* Skip type checking for faster execution */
20 |     "files": true /* Include files in tsconfig.json */
21 |   },
22 |   "include": [
23 |     "src/**/*"
24 |   ], /* Specifies an array of filenames or patterns to include in the program */
25 |   "exclude": [
26 |     "node_modules",
27 |     "dist"
28 |   ] /* Specifies an array of filenames or patterns that should be skipped when resolving include */
29 | }
```

--------------------------------------------------------------------------------
/src/types/taskTypes.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Represents the possible status values for a task.
 3 |  * Using string literal union as per .clinerules (no enums).
 4 |  */
 5 | export type TaskStatus = 'todo' | 'in-progress' | 'review' | 'done';
 6 | 
 7 | /**
 8 |  * Represents the possible priority levels for a task.
 9 |  * Using string literal union as per .clinerules (no enums).
10 |  */
11 | export type TaskPriority = 'high' | 'medium' | 'low';
12 | 
13 | /**
14 |  * Interface representing a Task object as returned by the API.
15 |  */
16 | export interface Task {
17 |     task_id: string; // UUID format
18 |     project_id: string; // UUID format
19 |     parent_task_id: string | null; // UUID format or null
20 |     description: string;
21 |     status: TaskStatus;
22 |     priority: TaskPriority;
23 |     created_at: string; // ISO8601 format
24 |     updated_at: string; // ISO8601 format
25 |     dependencies?: string[]; // Array of task_ids this task depends on
26 |     subtasks?: Task[]; // Array of subtasks (populated if requested, e.g., listTasks with include_subtasks=true)
27 | }
28 | 
29 | /**
30 |  * Interface representing the payload for updating a task (FR-011).
31 |  * All fields are optional, but at least one must be provided for an update.
32 |  */
33 | export interface TaskUpdatePayload {
34 |     description?: string;
35 |     priority?: TaskPriority;
36 |     dependencies?: string[]; // Represents the complete new list of dependencies
37 | }
38 | 
39 | /**
40 |  * Interface representing the structure of a Task as stored in the database.
41 |  * May differ slightly from the API representation (e.g., no nested subtasks/dependencies).
42 |  */
43 | export interface TaskDbObject {
44 |     task_id: string;
45 |     project_id: string;
46 |     parent_task_id: string | null;
47 |     description: string;
48 |     status: TaskStatus;
49 |     priority: TaskPriority;
50 |     created_at: string;
51 |     updated_at: string;
52 | }
53 | 
54 | /**
55 |  * Interface representing a record in the task_dependencies table.
56 |  */
57 | export interface TaskDependencyDbObject {
58 |     task_id: string;
59 |     depends_on_task_id: string;
60 | }
61 | 
```

--------------------------------------------------------------------------------
/public/images/mcp-task-manager-logo.svg:
--------------------------------------------------------------------------------

```
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
 3 |   <!-- Background circle -->
 4 |   <circle cx="100" cy="100" r="90" fill="#2a2a2a" />
 5 |   
 6 |   <!-- Outer ring -->
 7 |   <circle cx="100" cy="100" r="85" fill="none" stroke="#4a86e8" stroke-width="4" />
 8 |   
 9 |   <!-- Task board grid -->
10 |   <rect x="50" y="60" width="100" height="80" rx="5" ry="5" fill="#333" stroke="#4a86e8" stroke-width="2" />
11 |   
12 |   <!-- Task columns -->
13 |   <rect x="55" y="65" width="28" height="70" rx="3" ry="3" fill="#444" />
14 |   <rect x="87" y="65" width="28" height="70" rx="3" ry="3" fill="#444" />
15 |   <rect x="119" y="65" width="28" height="70" rx="3" ry="3" fill="#444" />
16 |   
17 |   <!-- Task items -->
18 |   <rect x="57" y="70" width="24" height="8" rx="2" ry="2" fill="#6aa84f" /> <!-- Done -->
19 |   <rect x="57" y="82" width="24" height="8" rx="2" ry="2" fill="#6aa84f" /> <!-- Done -->
20 |   <rect x="89" y="70" width="24" height="8" rx="2" ry="2" fill="#f1c232" /> <!-- In Progress -->
21 |   <rect x="121" y="70" width="24" height="8" rx="2" ry="2" fill="#cc4125" /> <!-- Todo -->
22 |   <rect x="121" y="82" width="24" height="8" rx="2" ry="2" fill="#cc4125" /> <!-- Todo -->
23 |   <rect x="121" y="94" width="24" height="8" rx="2" ry="2" fill="#cc4125" /> <!-- Todo -->
24 |   
25 |   <!-- "MCP" text -->
26 |   <text x="100" y="155" font-family="Arial, sans-serif" font-weight="bold" font-size="24" fill="white" text-anchor="middle">MCP TASKS</text>
27 |   
28 |   <!-- Database icon to represent SQLite -->
29 |   <g transform="translate(160, 40) scale(0.6)">
30 |     <path d="M10,30 C10,42 30,42 30,30 L30,10 C30,0 10,0 10,10 L10,30 Z" fill="#e69138" />
31 |     <rect x="10" y="10" width="20" height="5" fill="#2a2a2a" />
32 |     <rect x="10" y="20" width="20" height="5" fill="#2a2a2a" />
33 |   </g>
34 |   
35 |   <!-- Connection lines representing dependencies -->
36 |   <path d="M81 74 L89 74" stroke="#4a86e8" stroke-width="1.5" />
37 |   <path d="M113 74 L121 74" stroke="#4a86e8" stroke-width="1.5" />
38 | </svg> 
```

--------------------------------------------------------------------------------
/src/tools/expandTaskParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "expandTask";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Breaks down a specified parent task into multiple subtasks based on provided descriptions.
 7 | Requires the project ID, the parent task ID, and an array of descriptions for the new subtasks.
 8 | Optionally allows forcing the replacement of existing subtasks using the 'force' flag.
 9 | Returns the updated parent task details, including the newly created subtasks.
10 | `;
11 | 
12 | // Zod schema for the parameters, matching FR-006 and expandTaskTool.md spec
13 | export const TOOL_PARAMS = z.object({
14 |     project_id: z.string()
15 |         .uuid("The project_id must be a valid UUID.")
16 |         .describe("The unique identifier (UUID) of the project containing the parent task."), // Required, UUID format
17 | 
18 |     task_id: z.string()
19 |         // Add .uuid() if task IDs are also UUIDs
20 |         .min(1, "Parent task ID cannot be empty.")
21 |         .describe("The unique identifier of the parent task to be expanded."), // Required, string (or UUID)
22 | 
23 |     subtask_descriptions: z.array(
24 |             z.string()
25 |                 .min(1, "Subtask description cannot be empty.")
26 |                 .max(512, "Subtask description cannot exceed 512 characters.")
27 |                 .describe("A textual description for one of the new subtasks (1-512 characters).")
28 |         )
29 |         .min(1, "At least one subtask description must be provided.")
30 |         .max(20, "Cannot create more than 20 subtasks per call.")
31 |         .describe("An array of descriptions (1-20) for the new subtasks to be created under the parent task."), // Required, array of strings, limits
32 | 
33 |     force: z.boolean()
34 |         .optional()
35 |         .default(false)
36 |         .describe("Optional flag (default false). If true, any existing subtasks of the parent task will be deleted before creating the new ones. If false and subtasks exist, the operation will fail."), // Optional, boolean, default
37 | });
38 | 
39 | // Define the expected type for arguments based on the Zod schema
40 | export type ExpandTaskArgs = z.infer<typeof TOOL_PARAMS>;
41 | 
```

--------------------------------------------------------------------------------
/src/tools/addTaskParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | 
 3 | export const TOOL_NAME = "addTask";
 4 | 
 5 | export const TOOL_DESCRIPTION = `
 6 | Adds a new task to a specified project within the Task Management Server.
 7 | Requires the project ID and a description for the task.
 8 | Optionally accepts a list of dependency task IDs, a priority level, and an initial status.
 9 | Returns the full details of the newly created task upon success.
10 | `;
11 | 
12 | // Allowed enum values for status and priority
13 | const TaskStatusEnum = z.enum(['todo', 'in-progress', 'review', 'done']);
14 | const TaskPriorityEnum = z.enum(['high', 'medium', 'low']);
15 | 
16 | // Zod schema for the parameters, matching FR-002 and addTaskTool.md spec
17 | export const TOOL_PARAMS = z.object({
18 |     project_id: z.string()
19 |         .uuid("The project_id must be a valid UUID.")
20 |         .describe("The unique identifier (UUID) of the project to add the task to. This project must already exist."), // Required, UUID format
21 | 
22 |     description: z.string()
23 |         .min(1, "Task description cannot be empty.")
24 |         .max(1024, "Task description cannot exceed 1024 characters.")
25 |         .describe("The textual description of the task to be performed (1-1024 characters)."), // Required, length limits
26 | 
27 |     dependencies: z.array(z.string().describe("A task ID that this new task depends on.")) // Allow any string for now, existence checked in service (or deferred)
28 |         .max(50, "A task cannot have more than 50 dependencies.")
29 |         .optional()
30 |         .describe("An optional list of task IDs (strings) that must be completed before this task can start (max 50)."), // Optional, array of strings, count limit
31 | 
32 |     priority: TaskPriorityEnum
33 |         .optional()
34 |         .default('medium') // Default value
35 |         .describe("Optional task priority. Defaults to 'medium' if not specified."), // Optional, enum, default
36 | 
37 |     status: TaskStatusEnum
38 |         .optional()
39 |         .default('todo') // Default value
40 |         .describe("Optional initial status of the task. Defaults to 'todo' if not specified."), // Optional, enum, default
41 | });
42 | 
43 | // Define the expected type for arguments based on the Zod schema
44 | export type AddTaskArgs = z.infer<typeof TOOL_PARAMS>;
45 | 
```

--------------------------------------------------------------------------------
/src/tools/exportProjectTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, ExportProjectArgs } from "./exportProjectParams.js";
 4 | import { ProjectService } from "../services/ProjectService.js"; // Assuming ProjectService is exported via services/index.js
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js";
 7 | 
 8 | /**
 9 |  * Registers the exportProject tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param projectService - An instance of the ProjectService.
13 |  */
14 | export const exportProjectTool = (server: McpServer, projectService: ProjectService): void => {
15 | 
16 |     const processRequest = async (args: ExportProjectArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
18 |         try {
19 |             // Zod schema ensures format is 'json' if provided, or defaults to 'json'
20 |             const jsonString = await projectService.exportProject(args.project_id);
21 | 
22 |             // Format the successful response
23 |             logger.info(`[${TOOL_NAME}] Successfully exported project ${args.project_id}`);
24 |             return {
25 |                 content: [{
26 |                     type: "text" as const,
27 |                     text: jsonString // Return the JSON string directly
28 |                 }]
29 |             };
30 |         } catch (error: unknown) {
31 |             // Handle potential errors
32 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
33 | 
34 |             if (error instanceof NotFoundError) {
35 |                 // Project not found
36 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
37 |             } else {
38 |                 // Generic internal error
39 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while exporting the project.';
40 |                 throw new McpError(ErrorCode.InternalError, message);
41 |             }
42 |         }
43 |     };
44 | 
45 |     // Register the tool with the server
46 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
47 | 
48 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
49 | };
50 | 
```

--------------------------------------------------------------------------------
/src/tools/showTaskTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, ShowTaskArgs } from "./showTaskParams.js";
 4 | import { TaskService } from "../services/TaskService.js";
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js";
 7 | 
 8 | /**
 9 |  * Registers the showTask tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const showTaskTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     const processRequest = async (args: ShowTaskArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
18 |         try {
19 |             // Call the service method to get the task details
20 |             const task = await taskService.getTaskById(args.project_id, args.task_id);
21 | 
22 |             // Format the successful response
23 |             logger.info(`[${TOOL_NAME}] Found task ${args.task_id} in project ${args.project_id}`);
24 |             return {
25 |                 content: [{
26 |                     type: "text" as const,
27 |                     text: JSON.stringify(task) // Return the full task object
28 |                 }]
29 |             };
30 |         } catch (error: unknown) {
31 |             // Handle potential errors
32 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
33 | 
34 |             if (error instanceof NotFoundError) {
35 |                 // Specific error if the project or task wasn't found
36 |                 // Map to InvalidParams as the provided ID(s) are invalid in this context
37 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
38 |             } else {
39 |                 // Generic internal error
40 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while retrieving the task.';
41 |                 throw new McpError(ErrorCode.InternalError, message);
42 |             }
43 |         }
44 |     };
45 | 
46 |     // Register the tool with the server
47 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
48 | 
49 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
50 | };
51 | 
```

--------------------------------------------------------------------------------
/src/db/schema.sql:
--------------------------------------------------------------------------------

```sql
 1 | -- Database schema for the MCP Task Manager Server
 2 | -- Based on RFC-2025-001
 3 | 
 4 | -- Enable foreign key support
 5 | PRAGMA foreign_keys = ON;
 6 | 
 7 | -- Use Write-Ahead Logging for better concurrency
 8 | PRAGMA journal_mode = WAL;
 9 | 
10 | -- Table: projects
11 | -- Stores project metadata
12 | CREATE TABLE IF NOT EXISTS projects (
13 |     project_id TEXT PRIMARY KEY NOT NULL, -- UUID format
14 |     name TEXT NOT NULL,
15 |     created_at TEXT NOT NULL -- ISO8601 format (e.g., YYYY-MM-DDTHH:MM:SS.SSSZ)
16 | );
17 | 
18 | -- Table: tasks
19 | -- Stores individual task details
20 | CREATE TABLE IF NOT EXISTS tasks (
21 |     task_id TEXT PRIMARY KEY NOT NULL, -- UUID format
22 |     project_id TEXT NOT NULL,
23 |     parent_task_id TEXT NULL, -- For subtasks
24 |     description TEXT NOT NULL,
25 |     status TEXT NOT NULL CHECK(status IN ('todo', 'in-progress', 'review', 'done')),
26 |     priority TEXT NOT NULL CHECK(priority IN ('high', 'medium', 'low')),
27 |     created_at TEXT NOT NULL, -- ISO8601 format
28 |     updated_at TEXT NOT NULL, -- ISO8601 format
29 |     FOREIGN KEY (project_id) REFERENCES projects(project_id) ON DELETE CASCADE,
30 |     FOREIGN KEY (parent_task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
31 | );
32 | 
33 | -- Table: task_dependencies
34 | -- Stores prerequisite relationships between tasks
35 | CREATE TABLE IF NOT EXISTS task_dependencies (
36 |     task_id TEXT NOT NULL, -- The task that depends on another
37 |     depends_on_task_id TEXT NOT NULL, -- The task that must be completed first
38 |     PRIMARY KEY (task_id, depends_on_task_id),
39 |     FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE,
40 |     FOREIGN KEY (depends_on_task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
41 | );
42 | 
43 | -- Indexes for performance optimization
44 | 
45 | -- Index on tasks table
46 | CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id);
47 | CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
48 | CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
49 | CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id ON tasks(parent_task_id);
50 | CREATE INDEX IF NOT EXISTS idx_tasks_created_at ON tasks(created_at);
51 | 
52 | -- Indexes on task_dependencies table
53 | CREATE INDEX IF NOT EXISTS idx_task_dependencies_task_id ON task_dependencies(task_id);
54 | CREATE INDEX IF NOT EXISTS idx_task_dependencies_depends_on_task_id ON task_dependencies(depends_on_task_id);
55 | 
```

--------------------------------------------------------------------------------
/src/tools/listTasksTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, ListTasksArgs } from "./listTasksParams.js";
 4 | import { TaskService } from "../services/TaskService.js";
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js";
 7 | 
 8 | /**
 9 |  * Registers the listTasks tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const listTasksTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     const processRequest = async (args: ListTasksArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
18 |         try {
19 |             // Call the service method to list tasks
20 |             const tasks = await taskService.listTasks({
21 |                 project_id: args.project_id,
22 |                 status: args.status,
23 |                 include_subtasks: args.include_subtasks,
24 |             });
25 | 
26 |             // Format the successful response
27 |             logger.info(`[${TOOL_NAME}] Found ${tasks.length} tasks for project ${args.project_id}`);
28 |             return {
29 |                 content: [{
30 |                     type: "text" as const,
31 |                     text: JSON.stringify(tasks) // Return the array of task objects
32 |                 }]
33 |             };
34 |         } catch (error: unknown) {
35 |             // Handle potential errors
36 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
37 | 
38 |             if (error instanceof NotFoundError) {
39 |                 // Specific error if the project wasn't found
40 |                 throw new McpError(ErrorCode.InvalidParams, error.message); // Map NotFound to InvalidParams for project_id
41 |             } else {
42 |                 // Generic internal error
43 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while listing tasks.';
44 |                 throw new McpError(ErrorCode.InternalError, message);
45 |             }
46 |         }
47 |     };
48 | 
49 |     // Register the tool with the server
50 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
51 | 
52 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
53 | };
54 | 
```

--------------------------------------------------------------------------------
/src/tools/getNextTaskTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, GetNextTaskArgs } from "./getNextTaskParams.js";
 4 | import { TaskService } from "../services/TaskService.js";
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js";
 7 | 
 8 | /**
 9 |  * Registers the getNextTask tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const getNextTaskTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     const processRequest = async (args: GetNextTaskArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
18 |         try {
19 |             // Call the service method to get the next task
20 |             const nextTask = await taskService.getNextTask(args.project_id);
21 | 
22 |             // Format the successful response
23 |             if (nextTask) {
24 |                 logger.info(`[${TOOL_NAME}] Next task found: ${nextTask.task_id} in project ${args.project_id}`);
25 |             } else {
26 |                 logger.info(`[${TOOL_NAME}] No ready task found for project ${args.project_id}`);
27 |             }
28 | 
29 |             return {
30 |                 content: [{
31 |                     type: "text" as const,
32 |                     // Return the full task object or null
33 |                     text: JSON.stringify(nextTask)
34 |                 }]
35 |             };
36 |         } catch (error: unknown) {
37 |             // Handle potential errors
38 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
39 | 
40 |             if (error instanceof NotFoundError) {
41 |                 // Project not found
42 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
43 |             } else {
44 |                 // Generic internal error
45 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while getting the next task.';
46 |                 throw new McpError(ErrorCode.InternalError, message);
47 |             }
48 |         }
49 |     };
50 | 
51 |     // Register the tool with the server
52 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
53 | 
54 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
55 | };
56 | 
```

--------------------------------------------------------------------------------
/src/tools/setTaskStatusTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, SetTaskStatusArgs } from "./setTaskStatusParams.js";
 4 | import { TaskService } from "../services/TaskService.js";
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js";
 7 | 
 8 | /**
 9 |  * Registers the setTaskStatus tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const setTaskStatusTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     const processRequest = async (args: SetTaskStatusArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
18 |         try {
19 |             // Call the service method to update the status
20 |             const updatedCount = await taskService.setTaskStatus(
21 |                 args.project_id,
22 |                 args.task_ids,
23 |                 args.status
24 |             );
25 | 
26 |             // Format the successful response
27 |             const responsePayload = { success: true, updated_count: updatedCount };
28 |             logger.info(`[${TOOL_NAME}] Updated status for ${updatedCount} tasks in project ${args.project_id}`);
29 |             return {
30 |                 content: [{
31 |                     type: "text" as const,
32 |                     text: JSON.stringify(responsePayload)
33 |                 }]
34 |             };
35 |         } catch (error: unknown) {
36 |             // Handle potential errors
37 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
38 | 
39 |             if (error instanceof NotFoundError) {
40 |                 // Specific error if the project or any task wasn't found
41 |                 // Map to InvalidParams as the provided ID(s) are invalid
42 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
43 |             } else {
44 |                 // Generic internal error
45 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while setting task status.';
46 |                 throw new McpError(ErrorCode.InternalError, message);
47 |             }
48 |         }
49 |     };
50 | 
51 |     // Register the tool with the server
52 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
53 | 
54 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
55 | };
56 | 
```

--------------------------------------------------------------------------------
/src/tools/importProjectTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, ImportProjectArgs } from "./importProjectParams.js";
 4 | import { ProjectService } from "../services/ProjectService.js";
 5 | import { logger } from '../utils/logger.js';
 6 | import { ValidationError } from "../utils/errors.js"; // Import specific errors
 7 | 
 8 | /**
 9 |  * Registers the importProject tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param projectService - An instance of the ProjectService.
13 |  */
14 | export const importProjectTool = (server: McpServer, projectService: ProjectService): void => {
15 | 
16 |     const processRequest = async (args: ImportProjectArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request (project name: ${args.new_project_name || 'Default'})`);
18 |         try {
19 |             // Call the service method to import the project
20 |             const result = await projectService.importProject(
21 |                 args.project_data,
22 |                 args.new_project_name
23 |             );
24 | 
25 |             // Format the successful response
26 |             const responsePayload = { project_id: result.project_id };
27 |             logger.info(`[${TOOL_NAME}] Successfully imported project. New ID: ${result.project_id}`);
28 |             return {
29 |                 content: [{
30 |                     type: "text" as const,
31 |                     text: JSON.stringify(responsePayload)
32 |                 }]
33 |             };
34 |         } catch (error: unknown) {
35 |             // Handle potential errors
36 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
37 | 
38 |             if (error instanceof ValidationError) {
39 |                 // JSON parsing, schema validation, size limit, or other data issues
40 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
41 |             } else {
42 |                 // Generic internal error (likely database related from the transaction)
43 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred during project import.';
44 |                 throw new McpError(ErrorCode.InternalError, message);
45 |             }
46 |         }
47 |     };
48 | 
49 |     // Register the tool with the server
50 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
51 | 
52 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
53 | };
54 | 
```

--------------------------------------------------------------------------------
/src/tools/deleteTaskTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, DeleteTaskArgs } from "./deleteTaskParams.js";
 4 | import { TaskService } from "../services/TaskService.js"; // Assuming TaskService is exported from index
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js"; // Import custom errors
 7 | 
 8 | /**
 9 |  * Registers the deleteTask tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const deleteTaskTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     const processRequest = async (args: DeleteTaskArgs): Promise<{ content: { type: 'text', text: string }[] }> => {
17 |         logger.info(`[${TOOL_NAME}] Received request to delete ${args.task_ids.length} tasks from project ${args.project_id}`);
18 |         try {
19 |             // Call the service method to delete the tasks
20 |             const deletedCount = await taskService.deleteTasks(args.project_id, args.task_ids);
21 | 
22 |             // Format the successful response
23 |             logger.info(`[${TOOL_NAME}] Successfully deleted ${deletedCount} tasks from project ${args.project_id}`);
24 |             return {
25 |                 content: [{
26 |                     type: "text" as const,
27 |                     text: JSON.stringify({ success: true, deleted_count: deletedCount })
28 |                 }]
29 |             };
30 |         } catch (error: unknown) {
31 |             // Handle potential errors according to systemPatterns.md mapping
32 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
33 | 
34 |             if (error instanceof NotFoundError) {
35 |                 // Project or one/more tasks not found - Map to InvalidParams as per convention
36 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
37 |             } else {
38 |                 // Generic internal error
39 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while deleting tasks.';
40 |                 throw new McpError(ErrorCode.InternalError, message);
41 |             }
42 |         }
43 |     };
44 | 
45 |     // Register the tool with the server
46 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest); // Using .shape as this schema doesn't use .refine()
47 | 
48 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
49 | };
50 | 
```

--------------------------------------------------------------------------------
/src/tools/deleteProjectTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, DeleteProjectArgs } from "./deleteProjectParams.js";
 4 | import { ProjectService } from "../services/ProjectService.js"; // Assuming ProjectService is exported from index
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError } from "../utils/errors.js"; // Import custom errors
 7 | 
 8 | /**
 9 |  * Registers the deleteProject tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param projectService - An instance of the ProjectService.
13 |  */
14 | export const deleteProjectTool = (server: McpServer, projectService: ProjectService): void => {
15 | 
16 |     const processRequest = async (args: DeleteProjectArgs): Promise<{ content: { type: 'text', text: string }[] }> => {
17 |         logger.warn(`[${TOOL_NAME}] Received request to DELETE project ${args.project_id}. This is a destructive operation.`); // Log deletion intent clearly
18 |         try {
19 |             // Call the service method to delete the project
20 |             const success = await projectService.deleteProject(args.project_id);
21 | 
22 |             // Format the successful response
23 |             logger.info(`[${TOOL_NAME}] Successfully deleted project ${args.project_id}`);
24 |             return {
25 |                 content: [{
26 |                     type: "text" as const,
27 |                     text: JSON.stringify({ success: success }) // Return true if deleted
28 |                 }]
29 |             };
30 |         } catch (error: unknown) {
31 |             // Handle potential errors according to systemPatterns.md mapping
32 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
33 | 
34 |             if (error instanceof NotFoundError) {
35 |                 // Project not found - Map to InvalidParams as per convention
36 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
37 |             } else {
38 |                 // Generic internal error
39 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while deleting the project.';
40 |                 throw new McpError(ErrorCode.InternalError, message);
41 |             }
42 |         }
43 |     };
44 | 
45 |     // Register the tool with the server
46 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest); // Using .shape as this schema doesn't use .refine()
47 | 
48 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
49 | };
50 | 
```

--------------------------------------------------------------------------------
/src/tools/createProjectTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, CreateProjectArgs } from "./createProjectParams.js";
 4 | import { ProjectService } from "../services/ProjectService.js"; // Assuming ProjectService is exported from services/index.js or directly
 5 | import { logger } from '../utils/logger.js'; // Assuming logger exists
 6 | // Import custom errors if needed for specific mapping
 7 | // import { ServiceError } from "../utils/errors.js";
 8 | 
 9 | /**
10 |  * Registers the createProject tool with the MCP server.
11 |  *
12 |  * @param server - The McpServer instance.
13 |  * @param projectService - An instance of the ProjectService.
14 |  */
15 | export const createProjectTool = (server: McpServer, projectService: ProjectService): void => {
16 | 
17 |     // Define the asynchronous function that handles the actual tool logic
18 |     const processRequest = async (args: CreateProjectArgs) => {
19 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
20 |         try {
21 |             // Call the service method to create the project
22 |             const newProject = await projectService.createProject(args.projectName);
23 | 
24 |             // Format the successful response according to MCP standards
25 |             const responsePayload = { project_id: newProject.project_id };
26 |             logger.info(`[${TOOL_NAME}] Project created successfully: ${newProject.project_id}`);
27 | 
28 |             return {
29 |                 content: [{
30 |                     type: "text" as const, // Required type assertion
31 |                     text: JSON.stringify(responsePayload)
32 |                 }]
33 |             };
34 |         } catch (error: unknown) {
35 |             // Handle potential errors from the service layer
36 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
37 | 
38 |             // Basic error mapping: Assume internal error unless a specific known error type is caught
39 |             // TODO: Add more specific error mapping if ProjectService throws custom errors
40 |             // (e.g., catch (error instanceof ValidationError) { throw new McpError(ErrorCode.InvalidParams, ...)})
41 |             const message = error instanceof Error ? error.message : 'An unknown error occurred during project creation.';
42 |             throw new McpError(ErrorCode.InternalError, message);
43 |         }
44 |     };
45 | 
46 |     // Register the tool with the server
47 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, processRequest);
48 | 
49 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
50 | };
51 | 
```

--------------------------------------------------------------------------------
/src/tools/expandTaskTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, ExpandTaskArgs } from "./expandTaskParams.js";
 4 | import { TaskService } from "../services/TaskService.js";
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError, ConflictError } from "../utils/errors.js"; // Import specific errors
 7 | 
 8 | /**
 9 |  * Registers the expandTask tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const expandTaskTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     const processRequest = async (args: ExpandTaskArgs) => {
17 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
18 |         try {
19 |             // Call the service method to expand the task
20 |             const updatedParentTask = await taskService.expandTask({
21 |                 project_id: args.project_id,
22 |                 task_id: args.task_id,
23 |                 subtask_descriptions: args.subtask_descriptions,
24 |                 force: args.force,
25 |             });
26 | 
27 |             // Format the successful response
28 |             logger.info(`[${TOOL_NAME}] Successfully expanded task ${args.task_id} in project ${args.project_id}`);
29 |             return {
30 |                 content: [{
31 |                     type: "text" as const,
32 |                     // Return the updated parent task details, including new subtasks
33 |                     text: JSON.stringify(updatedParentTask)
34 |                 }]
35 |             };
36 |         } catch (error: unknown) {
37 |             // Handle potential errors
38 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
39 | 
40 |             if (error instanceof NotFoundError) {
41 |                 // Project or parent task not found
42 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
43 |             } else if (error instanceof ConflictError) {
44 |                 // Subtasks exist and force=false - map to InvalidParams as the request is invalid without force=true
45 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
46 |             } else {
47 |                 // Generic internal error
48 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while expanding the task.';
49 |                 throw new McpError(ErrorCode.InternalError, message);
50 |             }
51 |         }
52 |     };
53 | 
54 |     // Register the tool with the server
55 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
56 | 
57 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
58 | };
59 | 
```

--------------------------------------------------------------------------------
/src/tools/updateTaskParams.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from 'zod';
 2 | import { TaskPriority, TaskStatus } from '../types/taskTypes.js'; // Import shared types
 3 | 
 4 | export const TOOL_NAME = "updateTask";
 5 | 
 6 | export const TOOL_DESCRIPTION = `
 7 | Updates specific details of an existing task within a project.
 8 | Requires the project ID and task ID. Allows updating description, priority, and/or dependencies.
 9 | At least one optional field (description, priority, dependencies) must be provided.
10 | Returns the full details of the updated task upon success.
11 | `;
12 | 
13 | // Define the possible priority values based on the shared type
14 | const priorities: [TaskPriority, ...TaskPriority[]] = ['high', 'medium', 'low'];
15 | 
16 | // Base Zod schema without refinement - needed for server.tool registration
17 | export const UPDATE_TASK_BASE_SCHEMA = z.object({
18 |     project_id: z.string()
19 |         .uuid("The project_id must be a valid UUID.")
20 |         .describe("The unique identifier (UUID) of the project containing the task to update. This project must exist."), // Required, UUID format
21 | 
22 |     task_id: z.string()
23 |         .uuid("The task_id must be a valid UUID.") // Assuming task IDs are UUIDs for consistency
24 |         .describe("The unique identifier (UUID) of the task to update. This task must exist within the specified project."), // Required, UUID format
25 | 
26 |     description: z.string()
27 |         .min(1, "Description cannot be empty if provided.")
28 |         .max(1024, "Description cannot exceed 1024 characters.")
29 |         .optional()
30 |         .describe("Optional. The new textual description for the task (1-1024 characters)."), // Optional, string, limits
31 | 
32 |     priority: z.enum(priorities)
33 |         .optional()
34 |         .describe("Optional. The new priority level for the task ('high', 'medium', or 'low')."), // Optional, enum
35 | 
36 |     dependencies: z.array(
37 |             z.string()
38 |                 .uuid("Each dependency task ID must be a valid UUID.")
39 |                 .describe("A task ID (UUID) that this task should depend on.")
40 |         )
41 |         .max(50, "A task cannot have more than 50 dependencies.")
42 |         .optional()
43 |         .describe("Optional. The complete list of task IDs (UUIDs) that this task depends on. Replaces the existing list entirely. Max 50 dependencies."), // Optional, array of UUID strings, limit
44 | });
45 | 
46 | // Refined schema for validation and type inference
47 | export const TOOL_PARAMS = UPDATE_TASK_BASE_SCHEMA.refine(
48 |     data => data.description !== undefined || data.priority !== undefined || data.dependencies !== undefined, {
49 |         message: "At least one field to update (description, priority, or dependencies) must be provided.",
50 |         // path: [], // No specific path, applies to the object
51 |     }
52 | );
53 | 
54 | // Define the expected type for arguments based on the *refined* Zod schema
55 | export type UpdateTaskArgs = z.infer<typeof TOOL_PARAMS>;
56 | 
```

--------------------------------------------------------------------------------
/src/repositories/ProjectRepository.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Database as Db } from 'better-sqlite3';
 2 | import { logger } from '../utils/logger.js'; // Assuming logger exists
 3 | 
 4 | export interface ProjectData {
 5 |     project_id: string;
 6 |     name: string;
 7 |     created_at: string; // ISO8601 format
 8 | }
 9 | 
10 | export class ProjectRepository {
11 |     private db: Db;
12 | 
13 |     // Pass the database connection instance
14 |     constructor(db: Db) {
15 |         this.db = db;
16 |     }
17 | 
18 |     /**
19 |      * Creates a new project record in the database.
20 |      * @param project - The project data to insert.
21 |      * @throws {Error} If the database operation fails.
22 |      */
23 |     public create(project: ProjectData): void {
24 |         const sql = `
25 |             INSERT INTO projects (project_id, name, created_at)
26 |             VALUES (@project_id, @name, @created_at)
27 |         `;
28 |         try {
29 |             const stmt = this.db.prepare(sql);
30 |             const info = stmt.run(project);
31 |             logger.info(`[ProjectRepository] Created project ${project.project_id}, changes: ${info.changes}`);
32 |         } catch (error) {
33 |             logger.error(`[ProjectRepository] Failed to create project ${project.project_id}:`, error);
34 |             // Re-throw the error to be handled by the service layer
35 |             throw error;
36 |         }
37 |     }
38 | 
39 |     /**
40 |      * Finds a project by its ID.
41 |      * @param projectId - The ID of the project to find.
42 |      * @returns The project data if found, otherwise undefined.
43 |      */
44 |     public findById(projectId: string): ProjectData | undefined {
45 |         const sql = `SELECT project_id, name, created_at FROM projects WHERE project_id = ?`;
46 |         try {
47 |             const stmt = this.db.prepare(sql);
48 |             const project = stmt.get(projectId) as ProjectData | undefined;
49 |             return project;
50 |         } catch (error) {
51 |             logger.error(`[ProjectRepository] Failed to find project ${projectId}:`, error);
52 |             throw error; // Re-throw
53 |         }
54 |     }
55 | 
56 |     /**
57 |      * Deletes a project by its ID.
58 |      * Relies on ON DELETE CASCADE in the schema to remove associated tasks/dependencies.
59 |      * @param projectId - The ID of the project to delete.
60 |      * @returns The number of projects deleted (0 or 1).
61 |      * @throws {Error} If the database operation fails.
62 |      */
63 |     public deleteProject(projectId: string): number {
64 |         const sql = `DELETE FROM projects WHERE project_id = ?`;
65 |         try {
66 |             const stmt = this.db.prepare(sql);
67 |             const info = stmt.run(projectId);
68 |             logger.info(`[ProjectRepository] Attempted to delete project ${projectId}. Rows affected: ${info.changes}`);
69 |             // Cascade delete handles tasks/dependencies in the background via schema definition.
70 |             return info.changes;
71 |         } catch (error) {
72 |             logger.error(`[ProjectRepository] Failed to delete project ${projectId}:`, error);
73 |             throw error; // Re-throw
74 |         }
75 |     }
76 | 
77 |     // Add other methods as needed (e.g., update, list)
78 | }
79 | 
```

--------------------------------------------------------------------------------
/src/tools/addTaskTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; // Correct path for McpServer
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; // Correct path for Error types
 3 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, AddTaskArgs } from "./addTaskParams.js";
 4 | import { TaskService } from "../services/TaskService.js"; // Assuming TaskService is exported via services/index.js
 5 | import { logger } from '../utils/logger.js';
 6 | import { NotFoundError, ValidationError } from "../utils/errors.js"; // Import custom errors
 7 | 
 8 | /**
 9 |  * Registers the addTask tool with the MCP server.
10 |  *
11 |  * @param server - The McpServer instance.
12 |  * @param taskService - An instance of the TaskService.
13 |  */
14 | export const addTaskTool = (server: McpServer, taskService: TaskService): void => {
15 | 
16 |     // Define the asynchronous function that handles the actual tool logic
17 |     const processRequest = async (args: AddTaskArgs) => {
18 |         logger.info(`[${TOOL_NAME}] Received request with args:`, args);
19 |         try {
20 |             // Call the service method to add the task
21 |             // The Zod schema handles basic type/format/length validation
22 |             const newTask = await taskService.addTask({
23 |                 project_id: args.project_id,
24 |                 description: args.description,
25 |                 dependencies: args.dependencies, // Pass optional fields
26 |                 priority: args.priority,
27 |                 status: args.status,
28 |             });
29 | 
30 |             // Format the successful response according to MCP standards
31 |             // Return the full details of the created task as per spec FR-FS-011
32 |             logger.info(`[${TOOL_NAME}] Task added successfully: ${newTask.task_id}`);
33 |             return {
34 |                 content: [{
35 |                     type: "text" as const,
36 |                     text: JSON.stringify(newTask) // Return the full task object
37 |                 }]
38 |             };
39 |         } catch (error: unknown) {
40 |             // Handle potential errors from the service layer
41 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
42 | 
43 |             if (error instanceof NotFoundError) {
44 |                 // Specific error if the project wasn't found - map to InvalidParams as project_id is invalid
45 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
46 |             } else if (error instanceof ValidationError) {
47 |                 // Specific error for validation issues within the service (e.g., dependency check if implemented)
48 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
49 |             } else {
50 |                 // Generic internal error for database issues or unexpected problems
51 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while adding the task.';
52 |                 throw new McpError(ErrorCode.InternalError, message);
53 |             }
54 |         }
55 |     };
56 | 
57 |     // Register the tool with the server, passing the shape of the Zod object
58 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS.shape, processRequest);
59 | 
60 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
61 | };
62 | 
```

--------------------------------------------------------------------------------
/src/tools/updateTaskTool.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
 3 | // Import the base schema shape for registration and the refined schema for validation/types
 4 | import { TOOL_NAME, TOOL_DESCRIPTION, TOOL_PARAMS, UPDATE_TASK_BASE_SCHEMA, UpdateTaskArgs } from "./updateTaskParams.js";
 5 | import { TaskService, FullTaskData } from "../services/TaskService.js"; // Assuming TaskService is exported from index
 6 | import { logger } from '../utils/logger.js';
 7 | import { NotFoundError, ValidationError } from "../utils/errors.js"; // Import custom errors
 8 | 
 9 | /**
10 |  * Registers the updateTask tool with the MCP server.
11 |  *
12 |  * @param server - The McpServer instance.
13 |  * @param taskService - An instance of the TaskService.
14 |  */
15 | export const updateTaskTool = (server: McpServer, taskService: TaskService): void => {
16 | 
17 |     const processRequest = async (args: UpdateTaskArgs): Promise<{ content: { type: 'text', text: string }[] }> => {
18 |         logger.info(`[${TOOL_NAME}] Received request with args:`, { ...args, dependencies: args.dependencies ? `[${args.dependencies.length} items]` : undefined }); // Avoid logging potentially large arrays
19 |         try {
20 |             // Call the service method to update the task
21 |             // The service method now returns FullTaskData
22 |             const updatedTask: FullTaskData = await taskService.updateTask({
23 |                 project_id: args.project_id,
24 |                 task_id: args.task_id,
25 |                 description: args.description,
26 |                 priority: args.priority,
27 |                 dependencies: args.dependencies,
28 |             });
29 | 
30 |             // Format the successful response
31 |             logger.info(`[${TOOL_NAME}] Successfully updated task ${args.task_id} in project ${args.project_id}`);
32 |             return {
33 |                 content: [{
34 |                     type: "text" as const,
35 |                     text: JSON.stringify(updatedTask) // Return the full updated task details
36 |                 }]
37 |             };
38 |         } catch (error: unknown) {
39 |             // Handle potential errors according to systemPatterns.md mapping
40 |             logger.error(`[${TOOL_NAME}] Error processing request:`, error);
41 | 
42 |             if (error instanceof ValidationError) {
43 |                 // Validation error from service (e.g., no fields provided, invalid deps)
44 |                  throw new McpError(ErrorCode.InvalidParams, error.message);
45 |             } else if (error instanceof NotFoundError) {
46 |                 // Project or task not found - Map to InvalidParams as per SDK limitations/convention
47 |                 throw new McpError(ErrorCode.InvalidParams, error.message);
48 |             } else {
49 |                 // Generic internal error
50 |                 const message = error instanceof Error ? error.message : 'An unknown error occurred while updating the task.';
51 |                 throw new McpError(ErrorCode.InternalError, message);
52 |             }
53 |         }
54 |     };
55 | 
56 |     // Register the tool with the server using the base schema's shape
57 |     server.tool(TOOL_NAME, TOOL_DESCRIPTION, UPDATE_TASK_BASE_SCHEMA.shape, processRequest);
58 | 
59 |     logger.info(`[${TOOL_NAME}] Tool registered successfully.`);
60 | };
61 | 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { ConfigurationManager } from "../config/ConfigurationManager.js";
 3 | import { logger } from "../utils/index.js"; // Now using barrel file
 4 | import { DatabaseManager } from "../db/DatabaseManager.js";
 5 | import { ProjectRepository } from "../repositories/ProjectRepository.js";
 6 | import { TaskRepository } from "../repositories/TaskRepository.js"; // Added TaskRepository import
 7 | import { ProjectService, TaskService } from "../services/index.js"; // Using barrel file, added TaskService
 8 | 
 9 | // Import tool registration functions
10 | // import { exampleTool } from "./exampleTool.js"; // Commenting out example
11 | import { createProjectTool } from "./createProjectTool.js";
12 | import { addTaskTool } from "./addTaskTool.js";
13 | import { listTasksTool } from "./listTasksTool.js";
14 | import { showTaskTool } from "./showTaskTool.js";
15 | import { setTaskStatusTool } from "./setTaskStatusTool.js";
16 | import { expandTaskTool } from "./expandTaskTool.js";
17 | import { getNextTaskTool } from "./getNextTaskTool.js";
18 | import { exportProjectTool } from "./exportProjectTool.js";
19 | import { importProjectTool } from "./importProjectTool.js";
20 | import { updateTaskTool } from "./updateTaskTool.js"; // Import the new tool
21 | import { deleteTaskTool } from "./deleteTaskTool.js"; // Import deleteTask tool
22 | import { deleteProjectTool } from "./deleteProjectTool.js"; // Import deleteProject tool
23 | // import { yourTool } from "./yourTool.js"; // Add other new tool imports here
24 | 
25 | /**
26 |  * Register all defined tools with the MCP server instance.
27 |  * This function centralizes tool registration logic.
28 |  * It also instantiates necessary services and repositories.
29 |  */
30 | export function registerTools(server: McpServer): void {
31 |     logger.info("Registering tools...");
32 |     const configManager = ConfigurationManager.getInstance();
33 | 
34 |     // --- Instantiate Dependencies ---
35 |     // Note: Consider dependency injection frameworks for larger applications
36 |     try {
37 |         const dbManager = DatabaseManager.getInstance();
38 |         const db = dbManager.getDb(); // Get the initialized DB connection
39 | 
40 |         // Instantiate Repositories
41 |         const projectRepository = new ProjectRepository(db);
42 |         const taskRepository = new TaskRepository(db); // Instantiate TaskRepository
43 | 
44 |         // Instantiate Services
45 |         const projectService = new ProjectService(db, projectRepository, taskRepository); // Pass db and both repos
46 |         const taskService = new TaskService(db, taskRepository, projectRepository); // Instantiate TaskService, passing db and repos
47 | 
48 |         // --- Register Tools ---
49 |         // Register each tool, passing necessary services
50 | 
51 |         // exampleTool(server, configManager.getExampleServiceConfig()); // Example commented out
52 | 
53 |         createProjectTool(server, projectService);
54 |         addTaskTool(server, taskService);
55 |         listTasksTool(server, taskService);
56 |         showTaskTool(server, taskService);
57 |         setTaskStatusTool(server, taskService);
58 |         expandTaskTool(server, taskService);
59 |         getNextTaskTool(server, taskService);
60 |         exportProjectTool(server, projectService);
61 |         importProjectTool(server, projectService); // Register importProjectTool (uses ProjectService)
62 |         updateTaskTool(server, taskService); // Register the new updateTask tool
63 |         deleteTaskTool(server, taskService); // Register deleteTask tool
64 |         deleteProjectTool(server, projectService); // Register deleteProject tool (uses ProjectService)
65 |         // ... etc.
66 | 
67 |         logger.info("All tools registered successfully.");
68 | 
69 |     } catch (error) {
70 |         logger.error("Failed to instantiate dependencies or register tools:", error);
71 |         // Depending on the desired behavior, you might want to exit the process
72 |         // process.exit(1);
73 |         throw new Error("Failed to initialize server components during tool registration.");
74 |     }
75 | }
76 | 
```

--------------------------------------------------------------------------------
/src/db/DatabaseManager.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import Database, { Database as Db } from 'better-sqlite3';
 2 | import fs from 'node:fs';
 3 | import path from 'node:path';
 4 | import { fileURLToPath } from 'node:url'; // Added for ES Module dirname
 5 | import { ConfigurationManager } from '../config/ConfigurationManager.js';
 6 | import { logger } from '../utils/logger.js'; // Assuming logger exists
 7 | 
 8 | export class DatabaseManager {
 9 |     private static instance: DatabaseManager;
10 |     private db!: Db; // Added definite assignment assertion
11 |     private dbPath: string;
12 | 
13 |     private constructor() {
14 |         const configManager = ConfigurationManager.getInstance();
15 |         // TODO: Get path from configManager once implemented
16 |         // For now, use a default relative path
17 |         this.dbPath = configManager.getDatabasePath(); // Assuming this method exists
18 |         logger.info(`[DatabaseManager] Using database path: ${this.dbPath}`);
19 | 
20 |         this.initializeDatabase();
21 |     }
22 | 
23 |     public static getInstance(): DatabaseManager {
24 |         if (!DatabaseManager.instance) {
25 |             DatabaseManager.instance = new DatabaseManager();
26 |         }
27 |         return DatabaseManager.instance;
28 |     }
29 | 
30 |     private initializeDatabase(): void {
31 |         try {
32 |             const dbDir = path.dirname(this.dbPath);
33 |             if (!fs.existsSync(dbDir)) {
34 |                 logger.info(`[DatabaseManager] Creating database directory: ${dbDir}`);
35 |                 fs.mkdirSync(dbDir, { recursive: true });
36 |             }
37 | 
38 |             const dbExists = fs.existsSync(this.dbPath);
39 |             logger.info(`[DatabaseManager] Database file ${this.dbPath} exists: ${dbExists}`);
40 | 
41 |             // Pass a wrapper function for verbose logging to match expected signature
42 |             this.db = new Database(this.dbPath, {
43 |                 verbose: (message?: any, ...additionalArgs: any[]) => logger.debug({ sql: message, params: additionalArgs }, 'SQLite Query')
44 |             });
45 | 
46 |             // Always enable foreign keys and WAL mode upon connection
47 |             this.db.pragma('foreign_keys = ON');
48 |             // Assert type for pragma result
49 |             const journalMode = this.db.pragma('journal_mode = WAL') as [{ journal_mode: string }];
50 |             logger.info(`[DatabaseManager] Journal mode set to: ${journalMode[0]?.journal_mode ?? 'unknown'}`);
51 | 
52 | 
53 |             // Check if initialization is needed (simple check: does 'projects' table exist?)
54 |             const tableCheck = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='projects';").get();
55 | 
56 |             if (!tableCheck) {
57 |                 logger.info('[DatabaseManager] Projects table not found. Initializing schema...');
58 |                 // Revert to looking for schema.sql relative to the compiled JS file's directory (__dirname)
59 |                 const __filename = fileURLToPath(import.meta.url);
60 |                 const __dirname = path.dirname(__filename); // This will be dist/db when running compiled code
61 |                 const schemaPath = path.join(__dirname, 'schema.sql');
62 | 
63 |                 logger.info(`[DatabaseManager] Looking for schema file at: ${schemaPath}`);
64 |                 if (!fs.existsSync(schemaPath)) {
65 |                     logger.error(`[DatabaseManager] Schema file not found at ${schemaPath}. Ensure build process copied it correctly.`);
66 |                     throw new Error(`Schema file not found at ${schemaPath}. Build process might be incomplete.`);
67 |                 }
68 |                 const schemaSql = fs.readFileSync(schemaPath, 'utf8');
69 |                 this.db.exec(schemaSql);
70 |                 logger.info('[DatabaseManager] Database schema initialized successfully.');
71 |             } else {
72 |                 logger.info('[DatabaseManager] Database schema already initialized.');
73 |             }
74 |         } catch (error) {
75 |             logger.error('[DatabaseManager] Failed to initialize database:', error);
76 |             // Propagate the error to prevent the server from starting with a broken DB connection
77 |             throw error;
78 |         }
79 |     }
80 | 
81 |     public getDb(): Db {
82 |         if (!this.db) {
83 |             // This should ideally not happen if constructor succeeded
84 |             logger.error('[DatabaseManager] Database connection not available.');
85 |             throw new Error('Database connection not available.');
86 |         }
87 |         return this.db;
88 |     }
89 | 
90 |     // Optional: Add a close method for graceful shutdown
91 |     public closeDb(): void {
92 |         if (this.db) {
93 |             this.db.close();
94 |             logger.info('[DatabaseManager] Database connection closed.');
95 |         }
96 |     }
97 | }
98 | 
```

--------------------------------------------------------------------------------
/src/config/ConfigurationManager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // Import config types for services as they are added
  2 | import { ExampleServiceConfig } from '../types/index.js';
  3 | 
  4 | // Define the structure for all configurations managed
  5 | interface ManagedConfigs {
  6 |     exampleService: Required<ExampleServiceConfig>;
  7 | // Add other service config types here:
  8 |     // yourService: Required<YourServiceConfig>;
  9 |     databasePath: string; // Added for database file location
 10 | }
 11 | 
 12 | /**
 13 |  * Centralized configuration management for all services.
 14 |  * Implements singleton pattern to ensure consistent configuration.
 15 |  */
 16 | export class ConfigurationManager {
 17 |     private static instance: ConfigurationManager | null = null;
 18 |     private static instanceLock = false;
 19 | 
 20 |     private config: ManagedConfigs;
 21 | 
 22 |     private constructor() {
 23 |         // Initialize with default configurations
 24 |         this.config = {
 25 |             exampleService: {
 26 |                 // Define defaults for ExampleService
 27 |                 greeting: "Hello",
 28 |                 enableDetailedLogs: false,
 29 |             },
 30 |             // Initialize other service configs with defaults:
 31 |             // yourService: {
 32 |             //   someSetting: 'default value',
 33 |             //   retryCount: 3,
 34 |             // },
 35 |             // Default database path
 36 |             databasePath: './data/taskmanager.db',
 37 |         };
 38 | 
 39 |         // Optional: Load overrides from environment variables or config files here
 40 |         this.loadEnvironmentOverrides();
 41 |     }
 42 | 
 43 |     /**
 44 |      * Get the singleton instance of ConfigurationManager.
 45 |      * Basic lock to prevent race conditions during initial creation.
 46 |      */
 47 |     public static getInstance(): ConfigurationManager {
 48 |         if (!ConfigurationManager.instance) {
 49 |             if (!ConfigurationManager.instanceLock) {
 50 |                 ConfigurationManager.instanceLock = true; // Lock
 51 |                 try {
 52 |                     ConfigurationManager.instance = new ConfigurationManager();
 53 |                 } finally {
 54 |                     ConfigurationManager.instanceLock = false; // Unlock
 55 |                 }
 56 |             } else {
 57 |                 // Basic busy wait if locked (consider a more robust async lock if high contention is expected)
 58 |                 while (ConfigurationManager.instanceLock) { }
 59 |                 // Re-check instance after wait
 60 |                 if (!ConfigurationManager.instance) {
 61 |                     // This path is less likely but handles edge cases if lock logic needs refinement
 62 |                     return ConfigurationManager.getInstance();
 63 |                 }
 64 |             }
 65 |         }
 66 |         return ConfigurationManager.instance;
 67 |     }
 68 | 
 69 |     // --- Getters for specific configurations ---
 70 | 
 71 |     public getExampleServiceConfig(): Required<ExampleServiceConfig> {
 72 |         // Return a copy to prevent accidental modification of the internal state
 73 |         return { ...this.config.exampleService };
 74 |     }
 75 | 
 76 |     // Add getters for other service configs:
 77 |     // public getYourServiceConfig(): Required<YourServiceConfig> {
 78 |     //   return { ...this.config.yourService };
 79 |     // }
 80 | 
 81 |     public getDatabasePath(): string {
 82 |         // Return a copy to prevent accidental modification (though less critical for a string)
 83 |         return this.config.databasePath;
 84 |     }
 85 | 
 86 |     // --- Updaters for specific configurations (if runtime updates are needed) ---
 87 | 
 88 |     public updateExampleServiceConfig(update: Partial<ExampleServiceConfig>): void {
 89 |         this.config.exampleService = {
 90 |             ...this.config.exampleService,
 91 |             ...update,
 92 |         };
 93 |         // Optional: Notify relevant services about the config change
 94 |     }
 95 | 
 96 |     // Add updaters for other service configs:
 97 |     // public updateYourServiceConfig(update: Partial<YourServiceConfig>): void {
 98 |     //   this.config.yourService = {
 99 |     //     ...this.config.yourService,
100 |     //     ...update,
101 |     //   };
102 |     // }
103 | 
104 |     /**
105 |      * Example method to load configuration overrides from environment variables.
106 |      * Call this in the constructor.
107 |      */
108 |     private loadEnvironmentOverrides(): void {
109 |         // Example for ExampleService
110 |         if (process.env.EXAMPLE_GREETING) {
111 |             this.config.exampleService.greeting = process.env.EXAMPLE_GREETING;
112 |         }
113 |         if (process.env.EXAMPLE_ENABLE_LOGS) {
114 |             this.config.exampleService.enableDetailedLogs = process.env.EXAMPLE_ENABLE_LOGS.toLowerCase() === 'true';
115 |         }
116 | 
117 |         // Override for Database Path
118 |         if (process.env.DATABASE_PATH) {
119 |             this.config.databasePath = process.env.DATABASE_PATH;
120 |         }
121 | 
122 |         // Add logic for other services based on their environment variables
123 |         // if (process.env.YOUR_SERVICE_RETRY_COUNT) {
124 |         //   const retryCount = parseInt(process.env.YOUR_SERVICE_RETRY_COUNT, 10);
125 |         //   if (!isNaN(retryCount)) {
126 |         //     this.config.yourService.retryCount = retryCount;
127 |         //   }
128 |         // }
129 |     }
130 | }
131 | 
```

--------------------------------------------------------------------------------
/src/services/ProjectService.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { v4 as uuidv4 } from 'uuid';
  2 | import { Database as Db } from 'better-sqlite3'; // Import Db type
  3 | import { ProjectRepository, ProjectData } from '../repositories/ProjectRepository.js';
  4 | import { TaskRepository, TaskData, DependencyData } from '../repositories/TaskRepository.js';
  5 | import { logger } from '../utils/logger.js';
  6 | import { NotFoundError, ValidationError, ConflictError } from '../utils/errors.js'; // Import errors
  7 | 
  8 | // Define structure for the export/import JSON
  9 | interface ExportTask extends TaskData {
 10 |     dependencies: string[]; // List of task IDs this task depends on
 11 |     subtasks: ExportTask[]; // Nested subtasks
 12 | }
 13 | 
 14 | interface ExportData {
 15 |     project_metadata: ProjectData;
 16 |     tasks: ExportTask[]; // Root tasks
 17 | }
 18 | 
 19 | 
 20 | export class ProjectService {
 21 |     private projectRepository: ProjectRepository;
 22 |     private taskRepository: TaskRepository;
 23 |     private db: Db; // Add db instance
 24 | 
 25 |     constructor(
 26 |         db: Db, // Inject Db instance
 27 |         projectRepository: ProjectRepository,
 28 |         taskRepository: TaskRepository
 29 |     ) {
 30 |         this.db = db; // Store db instance
 31 |         this.projectRepository = projectRepository;
 32 |         this.taskRepository = taskRepository;
 33 |     }
 34 | 
 35 |     /**
 36 |      * Creates a new project.
 37 |      */
 38 |     public async createProject(projectName?: string): Promise<ProjectData> {
 39 |         const projectId = uuidv4();
 40 |         const now = new Date().toISOString();
 41 |         const finalProjectName = projectName?.trim() || `New Project ${now}`;
 42 |         const newProject: ProjectData = {
 43 |             project_id: projectId,
 44 |             name: finalProjectName,
 45 |             created_at: now,
 46 |         };
 47 |         logger.info(`[ProjectService] Attempting to create project: ${projectId} with name "${finalProjectName}"`);
 48 |         try {
 49 |             this.projectRepository.create(newProject);
 50 |             logger.info(`[ProjectService] Successfully created project: ${projectId}`);
 51 |             return newProject;
 52 |         } catch (error) {
 53 |             logger.error(`[ProjectService] Error creating project ${projectId}:`, error);
 54 |             throw error;
 55 |         }
 56 |     }
 57 | 
 58 |     /**
 59 |      * Retrieves a project by its ID.
 60 |      */
 61 |     public async getProjectById(projectId: string): Promise<ProjectData | undefined> {
 62 |         logger.info(`[ProjectService] Attempting to find project: ${projectId}`);
 63 |         try {
 64 |             const project = this.projectRepository.findById(projectId);
 65 |             if (project) {
 66 |                 logger.info(`[ProjectService] Found project: ${projectId}`);
 67 |             } else {
 68 |                 logger.warn(`[ProjectService] Project not found: ${projectId}`);
 69 |             }
 70 |             return project;
 71 |         } catch (error) {
 72 |             logger.error(`[ProjectService] Error finding project ${projectId}:`, error);
 73 |             throw error;
 74 |         }
 75 |     }
 76 | 
 77 |     /**
 78 |      * Exports all data for a given project as a JSON string.
 79 |      */
 80 |     public async exportProject(projectId: string): Promise<string> {
 81 |         logger.info(`[ProjectService] Attempting to export project: ${projectId}`);
 82 |         const projectMetadata = this.projectRepository.findById(projectId);
 83 |         if (!projectMetadata) {
 84 |             logger.warn(`[ProjectService] Project not found for export: ${projectId}`);
 85 |             throw new NotFoundError(`Project with ID ${projectId} not found.`);
 86 |         }
 87 | 
 88 |         try {
 89 |             const allTasks = this.taskRepository.findAllTasksForProject(projectId);
 90 |             const allDependencies = this.taskRepository.findAllDependenciesForProject(projectId);
 91 | 
 92 |             const taskMap: Map<string, ExportTask> = new Map();
 93 |             const rootTasks: ExportTask[] = [];
 94 |             const dependencyMap: Map<string, string[]> = new Map();
 95 | 
 96 |             for (const dep of allDependencies) {
 97 |                 if (!dependencyMap.has(dep.task_id)) {
 98 |                     dependencyMap.set(dep.task_id, []);
 99 |                 }
100 |                 dependencyMap.get(dep.task_id)!.push(dep.depends_on_task_id);
101 |             }
102 | 
103 |             for (const task of allTasks) {
104 |                 taskMap.set(task.task_id, {
105 |                     ...task,
106 |                     dependencies: dependencyMap.get(task.task_id) || [],
107 |                     subtasks: [],
108 |                 });
109 |             }
110 | 
111 |             for (const task of allTasks) {
112 |                 const exportTask = taskMap.get(task.task_id)!;
113 |                 if (task.parent_task_id && taskMap.has(task.parent_task_id)) {
114 |                     const parent = taskMap.get(task.parent_task_id)!;
115 |                     if (!parent.subtasks) parent.subtasks = [];
116 |                     parent.subtasks.push(exportTask);
117 |                 } else if (!task.parent_task_id) {
118 |                     rootTasks.push(exportTask);
119 |                 }
120 |             }
121 | 
122 |             const exportData: ExportData = {
123 |                 project_metadata: projectMetadata,
124 |                 tasks: rootTasks,
125 |             };
126 | 
127 |             const jsonString = JSON.stringify(exportData, null, 2);
128 |             logger.info(`[ProjectService] Successfully prepared export data for project ${projectId}`);
129 |             return jsonString;
130 | 
131 |         } catch (error) {
132 |             logger.error(`[ProjectService] Error exporting project ${projectId}:`, error);
133 |             throw error;
134 |         }
135 |     }
136 | 
137 |     /**
138 |      * Imports project data from a JSON string, creating a new project.
139 |      */
140 |     public async importProject(projectDataString: string, newProjectName?: string): Promise<{ project_id: string }> {
141 |         logger.info(`[ProjectService] Attempting to import project...`);
142 |         let importData: ExportData;
143 |         try {
144 |             if (projectDataString.length > 10 * 1024 * 1024) { // Example 10MB limit
145 |                 throw new ValidationError('Input data exceeds size limit (e.g., 10MB).');
146 |             }
147 |             importData = JSON.parse(projectDataString);
148 |             // TODO: Implement rigorous schema validation (Zod?)
149 |             if (!importData || !importData.project_metadata || !Array.isArray(importData.tasks)) {
150 |                 throw new ValidationError('Invalid import data structure: Missing required fields.');
151 |             }
152 |             logger.debug(`[ProjectService] Successfully parsed import data.`);
153 |         } catch (error) {
154 |             logger.error('[ProjectService] Failed to parse or validate import JSON:', error);
155 |             if (error instanceof SyntaxError) {
156 |                 throw new ValidationError(`Invalid JSON format: ${error.message}`);
157 |             }
158 |             throw new ValidationError(`Invalid import data: ${error instanceof Error ? error.message : 'Unknown validation error'}`);
159 |         }
160 | 
161 |         const importTransaction = this.db.transaction(() => {
162 |             const newProjectId = uuidv4();
163 |             const now = new Date().toISOString();
164 |             const finalProjectName = newProjectName?.trim() || `${importData.project_metadata.name} (Imported ${now})`;
165 |             const newProject: ProjectData = {
166 |                 project_id: newProjectId,
167 |                 name: finalProjectName.substring(0, 255),
168 |                 created_at: now,
169 |             };
170 |             this.projectRepository.create(newProject);
171 |             logger.info(`[ProjectService] Created new project ${newProjectId} for import.`);
172 | 
173 |             const idMap = new Map<string, string>();
174 |             const processTask = (task: ExportTask, parentDbId: string | null) => {
175 |                 const newTaskId = uuidv4();
176 |                 idMap.set(task.task_id, newTaskId);
177 |                 const newTaskData: TaskData = {
178 |                     task_id: newTaskId,
179 |                     project_id: newProjectId,
180 |                     parent_task_id: parentDbId,
181 |                     description: task.description,
182 |                     status: task.status,
183 |                     priority: task.priority,
184 |                     created_at: task.created_at,
185 |                     updated_at: task.updated_at,
186 |                 };
187 |                 this.taskRepository.create(newTaskData, []); // Create task first
188 |                 if (task.subtasks && task.subtasks.length > 0) {
189 |                     task.subtasks.forEach(subtask => processTask(subtask, newTaskId));
190 |                 }
191 |             };
192 |             importData.tasks.forEach(rootTask => processTask(rootTask, null));
193 |             logger.info(`[ProjectService] Processed ${idMap.size} tasks for import.`);
194 | 
195 |             const insertDependencyStmt = this.db.prepare(`
196 |                 INSERT INTO task_dependencies (task_id, depends_on_task_id)
197 |                 VALUES (?, ?) ON CONFLICT DO NOTHING
198 |             `);
199 |             let depCount = 0;
200 |             const processDeps = (task: ExportTask) => {
201 |                 const newTaskId = idMap.get(task.task_id);
202 |                 if (newTaskId && task.dependencies && task.dependencies.length > 0) {
203 |                     for (const oldDepId of task.dependencies) {
204 |                         const newDepId = idMap.get(oldDepId);
205 |                         if (newDepId) {
206 |                             insertDependencyStmt.run(newTaskId, newDepId);
207 |                             depCount++;
208 |                         } else {
209 |                             logger.warn(`[ProjectService] Dependency task ID ${oldDepId} not found in import map for task ${task.task_id}. Skipping dependency.`);
210 |                         }
211 |                     }
212 |                 }
213 |                 if (task.subtasks && task.subtasks.length > 0) {
214 |                     task.subtasks.forEach(processDeps);
215 |                 }
216 |             };
217 |             importData.tasks.forEach(processDeps);
218 |             logger.info(`[ProjectService] Processed ${depCount} dependencies for import.`);
219 | 
220 |             return { project_id: newProjectId };
221 |         });
222 | 
223 |         try {
224 |             const result = importTransaction();
225 |             logger.info(`[ProjectService] Successfully imported project. New project ID: ${result.project_id}`);
226 |             return result;
227 |         } catch (error) {
228 |             logger.error(`[ProjectService] Error during import transaction:`, error);
229 |             if (error instanceof NotFoundError || error instanceof ValidationError || error instanceof ConflictError) {
230 |                 throw error;
231 |             }
232 |             throw new Error(`Failed to import project: ${error instanceof Error ? error.message : 'Unknown database error'}`);
233 |         }
234 |     }
235 | 
236 |     /**
237 |      * Deletes a project and all its associated data (tasks, dependencies).
238 |      * @param projectId - The ID of the project to delete.
239 |      * @returns A boolean indicating success (true if deleted, false if not found initially).
240 |      * @throws {NotFoundError} If the project is not found.
241 |      * @throws {Error} If the database operation fails.
242 |      */
243 |     public async deleteProject(projectId: string): Promise<boolean> {
244 |         logger.info(`[ProjectService] Attempting to delete project: ${projectId}`);
245 | 
246 |         // 1. Validate Project Existence *before* attempting delete
247 |         const projectExists = this.projectRepository.findById(projectId);
248 |         if (!projectExists) {
249 |             logger.warn(`[ProjectService] Project not found for deletion: ${projectId}`);
250 |             throw new NotFoundError(`Project with ID ${projectId} not found.`);
251 |         }
252 | 
253 |         // 2. Call Repository delete method
254 |         try {
255 |             // The repository method handles the actual DELETE operation on the projects table.
256 |             // Cascade delete defined in the schema handles tasks and dependencies.
257 |             const deletedCount = this.projectRepository.deleteProject(projectId);
258 | 
259 |             if (deletedCount !== 1) {
260 |                 // This shouldn't happen if findById succeeded, but log a warning if it does.
261 |                 logger.warn(`[ProjectService] Expected to delete 1 project, but repository reported ${deletedCount} deletions for project ${projectId}.`);
262 |                 // Still return true as the project is gone, but log indicates potential issue.
263 |             }
264 | 
265 |             logger.info(`[ProjectService] Successfully deleted project ${projectId} and associated data.`);
266 |             return true; // Indicate success
267 | 
268 |         } catch (error) {
269 |             logger.error(`[ProjectService] Error deleting project ${projectId}:`, error);
270 |             throw error; // Re-throw database or other errors
271 |         }
272 |     }
273 | }
274 | 
```

--------------------------------------------------------------------------------
/src/repositories/TaskRepository.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Database as Db, Statement } from 'better-sqlite3';
  2 | import { logger } from '../utils/logger.js';
  3 | 
  4 | // Define the structure for task data in the database
  5 | // Aligning with schema.sql and feature specs
  6 | export interface TaskData {
  7 |     task_id: string; // UUID
  8 |     project_id: string; // UUID
  9 |     parent_task_id?: string | null; // UUID or null
 10 |     description: string;
 11 |     status: 'todo' | 'in-progress' | 'review' | 'done';
 12 |     priority: 'high' | 'medium' | 'low';
 13 |     created_at: string; // ISO8601
 14 |     updated_at: string; // ISO8601
 15 | }
 16 | 
 17 | // Define the structure for dependency data
 18 | export interface DependencyData {
 19 |     task_id: string;
 20 |     depends_on_task_id: string;
 21 | }
 22 | 
 23 | export class TaskRepository {
 24 |     private db: Db;
 25 |     private insertTaskStmt: Statement | null = null;
 26 |     private insertDependencyStmt: Statement | null = null;
 27 | 
 28 |     constructor(db: Db) {
 29 |         this.db = db;
 30 |         // Prepare statements for efficiency
 31 |         this.prepareStatements();
 32 |     }
 33 | 
 34 |     private prepareStatements(): void {
 35 |         try {
 36 |             this.insertTaskStmt = this.db.prepare(`
 37 |                 INSERT INTO tasks (
 38 |                     task_id, project_id, parent_task_id, description,
 39 |                     status, priority, created_at, updated_at
 40 |                 ) VALUES (
 41 |                     @task_id, @project_id, @parent_task_id, @description,
 42 |                     @status, @priority, @created_at, @updated_at
 43 |                 )
 44 |             `);
 45 | 
 46 |             this.insertDependencyStmt = this.db.prepare(`
 47 |                 INSERT INTO task_dependencies (task_id, depends_on_task_id)
 48 |                 VALUES (@task_id, @depends_on_task_id)
 49 |                 ON CONFLICT(task_id, depends_on_task_id) DO NOTHING -- Ignore if dependency already exists
 50 |             `);
 51 |         } catch (error) {
 52 |             logger.error('[TaskRepository] Failed to prepare statements:', error);
 53 |             // Handle error appropriately, maybe re-throw or set a flag
 54 |             throw error;
 55 |         }
 56 |     }
 57 | 
 58 |     /**
 59 |      * Creates a new task and optionally its dependencies in the database.
 60 |      * Uses a transaction to ensure atomicity.
 61 |      * @param task - The core task data to insert.
 62 |      * @param dependencies - An array of dependency task IDs for this task.
 63 |      * @throws {Error} If the database operation fails.
 64 |      */
 65 |     public create(task: TaskData, dependencies: string[] = []): void {
 66 |         if (!this.insertTaskStmt || !this.insertDependencyStmt) {
 67 |             logger.error('[TaskRepository] Statements not prepared. Cannot create task.');
 68 |             throw new Error('TaskRepository statements not initialized.');
 69 |         }
 70 | 
 71 |         // Use a transaction for atomicity
 72 |         const transaction = this.db.transaction((taskData: TaskData, deps: string[]) => {
 73 |             // Insert the main task
 74 |             const taskInfo = this.insertTaskStmt!.run(taskData);
 75 |             if (taskInfo.changes !== 1) {
 76 |                 throw new Error(`Failed to insert task ${taskData.task_id}. Changes: ${taskInfo.changes}`);
 77 |             }
 78 | 
 79 |             // Insert dependencies
 80 |             for (const depId of deps) {
 81 |                 const depData: DependencyData = {
 82 |                     task_id: taskData.task_id,
 83 |                     depends_on_task_id: depId,
 84 |                 };
 85 |                 const depInfo = this.insertDependencyStmt!.run(depData);
 86 |                 // We don't strictly need to check changes here due to ON CONFLICT DO NOTHING
 87 |             }
 88 |             return taskInfo.changes; // Indicate success
 89 |         });
 90 | 
 91 |         try {
 92 |             transaction(task, dependencies);
 93 |             logger.info(`[TaskRepository] Created task ${task.task_id} with ${dependencies.length} dependencies.`);
 94 |         } catch (error) {
 95 |             logger.error(`[TaskRepository] Failed to create task ${task.task_id} transaction:`, error);
 96 |             throw error; // Re-throw to be handled by the service layer
 97 |         }
 98 |     }
 99 | 
100 |     /**
101 |      * Finds tasks by project ID, optionally filtering by status.
102 |      * Does not handle subtask nesting directly in this query for V1 simplicity.
103 |      * @param projectId - The ID of the project.
104 |      * @param statusFilter - Optional status to filter by.
105 |      * @returns An array of matching task data.
106 |      */
107 |     public findByProjectId(projectId: string, statusFilter?: TaskData['status']): TaskData[] {
108 |         let sql = `
109 |             SELECT task_id, project_id, parent_task_id, description, status, priority, created_at, updated_at
110 |             FROM tasks
111 |             WHERE project_id = ?
112 |         `;
113 |         const params: (string | null)[] = [projectId];
114 | 
115 |         if (statusFilter) {
116 |             sql += ` AND status = ?`;
117 |             params.push(statusFilter);
118 |         }
119 | 
120 |         // For simplicity in V1, we only fetch top-level tasks or all tasks depending on include_subtasks strategy in service
121 |         // If we only wanted top-level: sql += ` AND parent_task_id IS NULL`;
122 |         // If fetching all and structuring in service, this query is fine.
123 | 
124 |         sql += ` ORDER BY created_at ASC`; // Default sort order
125 | 
126 |         try {
127 |             const stmt = this.db.prepare(sql);
128 |             const tasks = stmt.all(...params) as TaskData[];
129 |             logger.debug(`[TaskRepository] Found ${tasks.length} tasks for project ${projectId} with status filter '${statusFilter || 'none'}'`);
130 |             return tasks;
131 |         } catch (error) {
132 |             logger.error(`[TaskRepository] Failed to find tasks for project ${projectId}:`, error);
133 |             throw error; // Re-throw
134 |         }
135 |     }
136 | 
137 |     /**
138 |      * Finds a single task by its ID and project ID.
139 |      * @param projectId - The project ID.
140 |      * @param taskId - The task ID.
141 |      * @returns The task data if found, otherwise undefined.
142 |      */
143 |     public findById(projectId: string, taskId: string): TaskData | undefined {
144 |         const sql = `
145 |             SELECT task_id, project_id, parent_task_id, description, status, priority, created_at, updated_at
146 |             FROM tasks
147 |             WHERE project_id = ? AND task_id = ?
148 |         `;
149 |         try {
150 |             const stmt = this.db.prepare(sql);
151 |             const task = stmt.get(projectId, taskId) as TaskData | undefined;
152 |             logger.debug(`[TaskRepository] Found task ${taskId} in project ${projectId}: ${!!task}`);
153 |             return task;
154 |         } catch (error) {
155 |             logger.error(`[TaskRepository] Failed to find task ${taskId} in project ${projectId}:`, error);
156 |             throw error;
157 |         }
158 |     }
159 | 
160 |     /**
161 |      * Finds the direct subtasks for a given parent task ID.
162 |      * @param parentTaskId - The ID of the parent task.
163 |      * @returns An array of direct subtask data.
164 |      */
165 |     public findSubtasks(parentTaskId: string): TaskData[] {
166 |         const sql = `
167 |             SELECT task_id, project_id, parent_task_id, description, status, priority, created_at, updated_at
168 |             FROM tasks
169 |             WHERE parent_task_id = ?
170 |             ORDER BY created_at ASC
171 |         `;
172 |         try {
173 |             const stmt = this.db.prepare(sql);
174 |             const subtasks = stmt.all(parentTaskId) as TaskData[];
175 |             logger.debug(`[TaskRepository] Found ${subtasks.length} subtasks for parent ${parentTaskId}`);
176 |             return subtasks;
177 |         } catch (error) {
178 |             logger.error(`[TaskRepository] Failed to find subtasks for parent ${parentTaskId}:`, error);
179 |             throw error;
180 |         }
181 |     }
182 | 
183 |     /**
184 |      * Finds the IDs of tasks that the given task depends on.
185 |      * @param taskId - The ID of the task whose dependencies are needed.
186 |      * @returns An array of task IDs that this task depends on.
187 |      */
188 |     public findDependencies(taskId: string): string[] {
189 |         const sql = `SELECT depends_on_task_id FROM task_dependencies WHERE task_id = ?`;
190 |         try {
191 |             const stmt = this.db.prepare(sql);
192 |             // Ensure result is always an array of strings
193 |             const results = stmt.all(taskId) as { depends_on_task_id: string }[];
194 |             const dependencyIds = results.map(row => row.depends_on_task_id);
195 |             logger.debug(`[TaskRepository] Found ${dependencyIds.length} dependencies for task ${taskId}`);
196 |             return dependencyIds;
197 |         } catch (error) {
198 |             logger.error(`[TaskRepository] Failed to find dependencies for task ${taskId}:`, error);
199 |             throw error;
200 |         }
201 |     }
202 | 
203 | 
204 |     /**
205 |      * Updates the status and updated_at timestamp for a list of tasks within a project.
206 |      * Assumes task existence has already been verified.
207 |      * @param projectId - The project ID.
208 |      * @param taskIds - An array of task IDs to update.
209 |      * @param status - The new status to set.
210 |      * @param timestamp - The ISO8601 timestamp for updated_at.
211 |      * @returns The number of rows affected by the update.
212 |      * @throws {Error} If the database operation fails.
213 |      */
214 |     public updateStatus(projectId: string, taskIds: string[], status: TaskData['status'], timestamp: string): number {
215 |         if (taskIds.length === 0) {
216 |             return 0;
217 |         }
218 | 
219 |         // Create placeholders for the IN clause
220 |         const placeholders = taskIds.map(() => '?').join(',');
221 |         const sql = `
222 |             UPDATE tasks
223 |             SET status = ?, updated_at = ?
224 |             WHERE project_id = ? AND task_id IN (${placeholders})
225 |         `;
226 |         const params = [status, timestamp, projectId, ...taskIds];
227 | 
228 |         try {
229 |             const stmt = this.db.prepare(sql);
230 |             const info = stmt.run(...params);
231 |             logger.info(`[TaskRepository] Updated status for ${info.changes} tasks in project ${projectId} to ${status}.`);
232 |             return info.changes;
233 |         } catch (error) {
234 |             logger.error(`[TaskRepository] Failed to update status for tasks in project ${projectId}:`, error);
235 |             throw error;
236 |         }
237 |     }
238 | 
239 |     /**
240 |      * Checks if all provided task IDs exist within the specified project.
241 |      * @param projectId - The project ID.
242 |      * @param taskIds - An array of task IDs to check.
243 |      * @returns An object indicating if all exist and a list of missing IDs if not.
244 |      * @throws {Error} If the database operation fails.
245 |      */
246 |     public checkTasksExist(projectId: string, taskIds: string[]): { allExist: boolean; missingIds: string[] } {
247 |         if (taskIds.length === 0) {
248 |             return { allExist: true, missingIds: [] };
249 |         }
250 | 
251 |         const placeholders = taskIds.map(() => '?').join(',');
252 |         const sql = `
253 |             SELECT task_id FROM tasks
254 |             WHERE project_id = ? AND task_id IN (${placeholders})
255 |         `;
256 |         const params = [projectId, ...taskIds];
257 | 
258 |         try {
259 |             const stmt = this.db.prepare(sql);
260 |             const foundTasks = stmt.all(...params) as { task_id: string }[];
261 |             const foundIds = new Set(foundTasks.map(t => t.task_id));
262 | 
263 |             const missingIds = taskIds.filter(id => !foundIds.has(id));
264 |             const allExist = missingIds.length === 0;
265 | 
266 |             if (!allExist) {
267 |                 logger.warn(`[TaskRepository] Missing tasks in project ${projectId}:`, missingIds);
268 |             }
269 |             return { allExist, missingIds };
270 | 
271 |         } catch (error) {
272 |             logger.error(`[TaskRepository] Failed to check task existence in project ${projectId}:`, error);
273 |             throw error;
274 |         }
275 |     }
276 | 
277 |     /**
278 |      * Deletes all direct subtasks of a given parent task.
279 |      * @param parentTaskId - The ID of the parent task whose subtasks should be deleted.
280 |      * @returns The number of subtasks deleted.
281 |      * @throws {Error} If the database operation fails.
282 |      */
283 |     public deleteSubtasks(parentTaskId: string): number {
284 |         const sql = `DELETE FROM tasks WHERE parent_task_id = ?`;
285 |         try {
286 |             const stmt = this.db.prepare(sql);
287 |             const info = stmt.run(parentTaskId);
288 |             logger.info(`[TaskRepository] Deleted ${info.changes} subtasks for parent ${parentTaskId}.`);
289 |             return info.changes;
290 |         } catch (error) {
291 |             logger.error(`[TaskRepository] Failed to delete subtasks for parent ${parentTaskId}:`, error);
292 |             throw error;
293 |         }
294 |     }
295 | 
296 |     /**
297 |      * Finds tasks that are ready to be worked on (status 'todo' and all dependencies 'done').
298 |      * Orders them by priority ('high', 'medium', 'low') then creation date.
299 |      * @param projectId - The project ID.
300 |      * @returns An array of ready task data, ordered by priority and creation date.
301 |      */
302 |     public findReadyTasks(projectId: string): TaskData[] {
303 |         // This query finds tasks in the project with status 'todo'
304 |         // AND for which no dependency exists OR all existing dependencies have status 'done'.
305 |         const sql = `
306 |             SELECT t.task_id, t.project_id, t.parent_task_id, t.description, t.status, t.priority, t.created_at, t.updated_at
307 |             FROM tasks t
308 |             WHERE t.project_id = ? AND t.status = 'todo'
309 |             AND NOT EXISTS (
310 |                 SELECT 1
311 |                 FROM task_dependencies td
312 |                 JOIN tasks dep_task ON td.depends_on_task_id = dep_task.task_id
313 |                 WHERE td.task_id = t.task_id AND dep_task.status != 'done'
314 |             )
315 |             ORDER BY
316 |                 CASE t.priority
317 |                     WHEN 'high' THEN 1
318 |                     WHEN 'medium' THEN 2
319 |                     WHEN 'low' THEN 3
320 |                     ELSE 4 -- Should not happen based on CHECK constraint
321 |                 END ASC,
322 |                 t.created_at ASC
323 |         `;
324 |         try {
325 |             const stmt = this.db.prepare(sql);
326 |             const tasks = stmt.all(projectId) as TaskData[];
327 |             logger.debug(`[TaskRepository] Found ${tasks.length} ready tasks for project ${projectId}`);
328 |             return tasks;
329 |         } catch (error) {
330 |             logger.error(`[TaskRepository] Failed to find ready tasks for project ${projectId}:`, error);
331 |             throw error;
332 |         }
333 |     }
334 | 
335 |     /**
336 |      * Finds ALL tasks for a given project ID, ordered by creation date.
337 |      * @param projectId - The project ID.
338 |      * @returns An array of all task data for the project.
339 |      */
340 |     public findAllTasksForProject(projectId: string): TaskData[] {
341 |         const sql = `
342 |             SELECT task_id, project_id, parent_task_id, description, status, priority, created_at, updated_at
343 |             FROM tasks
344 |             WHERE project_id = ?
345 |             ORDER BY created_at ASC
346 |         `;
347 |         try {
348 |             const stmt = this.db.prepare(sql);
349 |             const tasks = stmt.all(projectId) as TaskData[];
350 |             logger.debug(`[TaskRepository] Found all ${tasks.length} tasks for project ${projectId}`);
351 |             return tasks;
352 |         } catch (error) {
353 |             logger.error(`[TaskRepository] Failed to find all tasks for project ${projectId}:`, error);
354 |             throw error;
355 |         }
356 |     }
357 | 
358 |     /**
359 |      * Finds ALL dependencies for tasks within a given project ID.
360 |      * @param projectId - The project ID.
361 |      * @returns An array of all dependency relationships for the project.
362 |      */
363 |     public findAllDependenciesForProject(projectId: string): DependencyData[] {
364 |         // Select dependencies where the *dependent* task belongs to the project
365 |         const sql = `
366 |             SELECT td.task_id, td.depends_on_task_id
367 |             FROM task_dependencies td
368 |             JOIN tasks t ON td.task_id = t.task_id
369 |             WHERE t.project_id = ?
370 |         `;
371 |         try {
372 |             const stmt = this.db.prepare(sql);
373 |             const dependencies = stmt.all(projectId) as DependencyData[];
374 |             logger.debug(`[TaskRepository] Found ${dependencies.length} dependencies for project ${projectId}`);
375 |             return dependencies;
376 |         } catch (error) {
377 |             logger.error(`[TaskRepository] Failed to find all dependencies for project ${projectId}:`, error);
378 |             throw error;
379 |         }
380 |     }
381 | 
382 | 
383 |     // --- Add other methods later ---
384 |     /**
385 |      * Updates a task's description, priority, and/or dependencies.
386 |      * Handles dependency replacement atomically within a transaction.
387 |      * @param projectId - The project ID.
388 |      * @param taskId - The task ID to update.
389 |      * @param updatePayload - Object containing optional fields to update.
390 |      * @param timestamp - The ISO8601 timestamp for updated_at.
391 |      * @returns The updated task data.
392 |      * @throws {Error} If the task doesn't exist or the database operation fails.
393 |      */
394 |     public updateTask(
395 |         projectId: string,
396 |         taskId: string,
397 |         updatePayload: { description?: string; priority?: TaskData['priority']; dependencies?: string[] },
398 |         timestamp: string
399 |     ): TaskData {
400 | 
401 |         const transaction = this.db.transaction(() => {
402 |             const setClauses: string[] = [];
403 |             const params: (string | null)[] = [];
404 | 
405 |             if (updatePayload.description !== undefined) {
406 |                 setClauses.push('description = ?');
407 |                 params.push(updatePayload.description);
408 |             }
409 |             if (updatePayload.priority !== undefined) {
410 |                 setClauses.push('priority = ?');
411 |                 params.push(updatePayload.priority);
412 |             }
413 | 
414 |             // Always update the timestamp
415 |             setClauses.push('updated_at = ?');
416 |             params.push(timestamp);
417 | 
418 |             // If nothing else to update, we still update the timestamp
419 |             if (setClauses.length === 1 && updatePayload.dependencies === undefined) {
420 |                  logger.warn(`[TaskRepository] updateTask called for ${taskId} with no fields to update other than timestamp.`);
421 |                  // Or potentially throw an error if this shouldn't happen based on service validation
422 |             }
423 | 
424 |             // Update the main task table if there are fields to update
425 |             let changes = 0;
426 |             if (setClauses.length > 0) {
427 |                 const updateSql = `
428 |                     UPDATE tasks
429 |                     SET ${setClauses.join(', ')}
430 |                     WHERE project_id = ? AND task_id = ?
431 |                 `;
432 |                 params.push(projectId, taskId);
433 | 
434 |                 const updateStmt = this.db.prepare(updateSql);
435 |                 const info = updateStmt.run(...params);
436 |                 changes = info.changes;
437 | 
438 |                 if (changes !== 1) {
439 |                     // Check if the task actually exists before throwing generic error
440 |                     const exists = this.findById(projectId, taskId);
441 |                     if (!exists) {
442 |                          throw new Error(`Task ${taskId} not found in project ${projectId}.`); // Will be caught and mapped later
443 |                     } else {
444 |                         throw new Error(`Failed to update task ${taskId}. Expected 1 change, got ${changes}.`);
445 |                     }
446 |                 }
447 |                 logger.debug(`[TaskRepository] Updated task ${taskId} fields.`);
448 |             }
449 | 
450 | 
451 |             // Handle dependencies if provided (replaces existing)
452 |             if (updatePayload.dependencies !== undefined) {
453 |                 if (!this.insertDependencyStmt) {
454 |                     throw new Error('TaskRepository insertDependencyStmt not initialized.');
455 |                 }
456 |                 // 1. Delete existing dependencies for this task
457 |                 const deleteDepsStmt = this.db.prepare(`DELETE FROM task_dependencies WHERE task_id = ?`);
458 |                 const deleteInfo = deleteDepsStmt.run(taskId);
459 |                 logger.debug(`[TaskRepository] Deleted ${deleteInfo.changes} existing dependencies for task ${taskId}.`);
460 | 
461 |                 // 2. Insert new dependencies
462 |                 const newDeps = updatePayload.dependencies;
463 |                 for (const depId of newDeps) {
464 |                     const depData: DependencyData = {
465 |                         task_id: taskId,
466 |                         depends_on_task_id: depId,
467 |                     };
468 |                     // ON CONFLICT DO NOTHING handles duplicates or self-references if schema allows
469 |                     this.insertDependencyStmt.run(depData);
470 |                 }
471 |                 logger.debug(`[TaskRepository] Inserted ${newDeps.length} new dependencies for task ${taskId}.`);
472 |             }
473 | 
474 |             // Fetch and return the updated task data
475 |             const updatedTask = this.findById(projectId, taskId);
476 |             if (!updatedTask) {
477 |                 // Should not happen if update succeeded, but safety check
478 |                 throw new Error(`Failed to retrieve updated task ${taskId} after update.`);
479 |             }
480 |             return updatedTask;
481 |         });
482 | 
483 |         try {
484 |             const result = transaction();
485 |             logger.info(`[TaskRepository] Successfully updated task ${taskId}.`);
486 |             return result;
487 |         } catch (error) {
488 |             logger.error(`[TaskRepository] Failed transaction for updating task ${taskId}:`, error);
489 |             throw error; // Re-throw to be handled by the service layer
490 |         }
491 |     }
492 | 
493 | 
494 |     /**
495 |      * Deletes multiple tasks by their IDs within a specific project.
496 |      * Relies on ON DELETE CASCADE for subtasks and dependencies.
497 |      * @param projectId - The project ID.
498 |      * @param taskIds - An array of task IDs to delete.
499 |      * @returns The number of tasks deleted.
500 |      * @throws {Error} If the database operation fails.
501 |      */
502 |     public deleteTasks(projectId: string, taskIds: string[]): number {
503 |         if (taskIds.length === 0) {
504 |             return 0;
505 |         }
506 | 
507 |         // Create placeholders for the IN clause
508 |         const placeholders = taskIds.map(() => '?').join(',');
509 |         const sql = `
510 |             DELETE FROM tasks
511 |             WHERE project_id = ? AND task_id IN (${placeholders})
512 |         `;
513 |         const params = [projectId, ...taskIds];
514 | 
515 |         try {
516 |             const stmt = this.db.prepare(sql);
517 |             const info = stmt.run(...params);
518 |             logger.info(`[TaskRepository] Deleted ${info.changes} tasks from project ${projectId}.`);
519 |             // Note: Cascade deletes for subtasks/dependencies happen automatically via schema.
520 |             return info.changes;
521 |         } catch (error) {
522 |             logger.error(`[TaskRepository] Failed to delete tasks from project ${projectId}:`, error);
523 |             throw error;
524 |         }
525 |     }
526 | 
527 | 
528 |     // --- Add other methods later ---
529 |     // deleteById(taskId: string): void;
530 | }
531 | 
```

--------------------------------------------------------------------------------
/src/services/TaskService.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { v4 as uuidv4 } from 'uuid';
  2 | import { TaskRepository, TaskData } from '../repositories/TaskRepository.js';
  3 | import { ProjectRepository } from '../repositories/ProjectRepository.js'; // Needed to check project existence
  4 | import { logger } from '../utils/logger.js';
  5 | import { NotFoundError, ValidationError } from '../utils/errors.js'; // Using custom errors
  6 | 
  7 | // Define the input structure for adding a task, based on feature spec
  8 | export interface AddTaskInput {
  9 |     project_id: string;
 10 |     description: string;
 11 |     dependencies?: string[];
 12 |     priority?: 'high' | 'medium' | 'low';
 13 |     status?: 'todo' | 'in-progress' | 'review' | 'done';
 14 | }
 15 | 
 16 | // Options for listing tasks
 17 | export interface ListTasksOptions {
 18 |     project_id: string;
 19 |     status?: TaskData['status'];
 20 |     include_subtasks?: boolean;
 21 | }
 22 | 
 23 | // Type for task data potentially including nested subtasks
 24 | export interface StructuredTaskData extends TaskData {
 25 |     subtasks?: StructuredTaskData[];
 26 | }
 27 | 
 28 | // Type for full task details including dependencies and subtasks
 29 | export interface FullTaskData extends TaskData {
 30 |     dependencies: string[];
 31 |     subtasks: TaskData[]; // For V1 showTask, just return direct subtasks without their own nesting/deps
 32 | }
 33 | 
 34 | // Input for expanding a task
 35 | export interface ExpandTaskInput {
 36 |     project_id: string;
 37 |     task_id: string; // Parent task ID
 38 |     subtask_descriptions: string[];
 39 |     force?: boolean;
 40 | }
 41 | 
 42 | 
 43 | import { Database as Db } from 'better-sqlite3'; // Import Db type
 44 | import { ConflictError } from '../utils/errors.js'; // Import ConflictError
 45 | 
 46 | export class TaskService {
 47 |     private taskRepository: TaskRepository;
 48 |     private projectRepository: ProjectRepository;
 49 |     private db: Db; // Add db instance
 50 | 
 51 |     constructor(
 52 |         db: Db, // Inject Db instance
 53 |         taskRepository: TaskRepository,
 54 |         projectRepository: ProjectRepository
 55 |     ) {
 56 |         this.db = db; // Store db instance
 57 |         this.taskRepository = taskRepository;
 58 |         this.projectRepository = projectRepository;
 59 |     }
 60 | 
 61 |     /**
 62 |      * Adds a new task to a specified project.
 63 |      */
 64 |     public async addTask(input: AddTaskInput): Promise<TaskData> {
 65 |         logger.info(`[TaskService] Attempting to add task to project: ${input.project_id}`);
 66 |         const projectExists = this.projectRepository.findById(input.project_id);
 67 |         if (!projectExists) {
 68 |             logger.warn(`[TaskService] Project not found: ${input.project_id}`);
 69 |             throw new NotFoundError(`Project with ID ${input.project_id} not found.`);
 70 |         }
 71 | 
 72 |         const taskId = uuidv4();
 73 |         const now = new Date().toISOString();
 74 |         const newTaskData: TaskData = {
 75 |             task_id: taskId,
 76 |             project_id: input.project_id,
 77 |             parent_task_id: null,
 78 |             description: input.description,
 79 |             status: input.status ?? 'todo',
 80 |             priority: input.priority ?? 'medium',
 81 |             created_at: now,
 82 |             updated_at: now,
 83 |         };
 84 | 
 85 |         // TODO: Validate Dependency Existence
 86 | 
 87 |         try {
 88 |             this.taskRepository.create(newTaskData, input.dependencies);
 89 |             logger.info(`[TaskService] Successfully added task ${taskId} to project ${input.project_id}`);
 90 |             return newTaskData;
 91 |         } catch (error) {
 92 |             logger.error(`[TaskService] Error adding task to project ${input.project_id}:`, error);
 93 |             throw error;
 94 |         }
 95 |     }
 96 | 
 97 |     /**
 98 |      * Lists tasks for a project.
 99 |      */
100 |     public async listTasks(options: ListTasksOptions): Promise<TaskData[] | StructuredTaskData[]> {
101 |         logger.info(`[TaskService] Attempting to list tasks for project: ${options.project_id}`, options);
102 |         const projectExists = this.projectRepository.findById(options.project_id);
103 |         if (!projectExists) {
104 |             logger.warn(`[TaskService] Project not found: ${options.project_id}`);
105 |             throw new NotFoundError(`Project with ID ${options.project_id} not found.`);
106 |         }
107 | 
108 |         try {
109 |             const allTasks = this.taskRepository.findByProjectId(options.project_id, options.status);
110 | 
111 |             if (!options.include_subtasks) {
112 |                 const topLevelTasks = allTasks.filter(task => !task.parent_task_id);
113 |                 logger.info(`[TaskService] Found ${topLevelTasks.length} top-level tasks for project ${options.project_id}`);
114 |                 return topLevelTasks;
115 |             } else {
116 |                 const taskMap: Map<string, StructuredTaskData> = new Map();
117 |                 const rootTasks: StructuredTaskData[] = [];
118 |                 for (const task of allTasks) {
119 |                     taskMap.set(task.task_id, { ...task, subtasks: [] });
120 |                 }
121 |                 for (const task of allTasks) {
122 |                     if (task.parent_task_id && taskMap.has(task.parent_task_id)) {
123 |                         const parent = taskMap.get(task.parent_task_id)!;
124 |                         parent.subtasks!.push(taskMap.get(task.task_id)!);
125 |                     } else if (!task.parent_task_id) {
126 |                         rootTasks.push(taskMap.get(task.task_id)!);
127 |                     }
128 |                 }
129 |                 logger.info(`[TaskService] Found ${rootTasks.length} structured root tasks for project ${options.project_id}`);
130 |                 return rootTasks;
131 |             }
132 |         } catch (error) {
133 |             logger.error(`[TaskService] Error listing tasks for project ${options.project_id}:`, error);
134 |             throw error;
135 |         }
136 |     }
137 | 
138 |     /**
139 |      * Retrieves the full details of a single task.
140 |      */
141 |     public async getTaskById(projectId: string, taskId: string): Promise<FullTaskData> {
142 |         logger.info(`[TaskService] Attempting to get task ${taskId} for project ${projectId}`);
143 |         const task = this.taskRepository.findById(projectId, taskId);
144 |         if (!task) {
145 |             logger.warn(`[TaskService] Task ${taskId} not found in project ${projectId}`);
146 |             throw new NotFoundError(`Task with ID ${taskId} not found in project ${projectId}.`);
147 |         }
148 | 
149 |         try {
150 |             const dependencies = this.taskRepository.findDependencies(taskId);
151 |             const subtasks = this.taskRepository.findSubtasks(taskId);
152 |             const fullTaskData: FullTaskData = {
153 |                 ...task,
154 |                 dependencies: dependencies,
155 |                 subtasks: subtasks,
156 |             };
157 |             logger.info(`[TaskService] Successfully retrieved task ${taskId}`);
158 |             return fullTaskData;
159 |         } catch (error) {
160 |             logger.error(`[TaskService] Error retrieving details for task ${taskId}:`, error);
161 |             throw error;
162 |         }
163 |     }
164 | 
165 |     /**
166 |      * Sets the status for one or more tasks within a project.
167 |      */
168 |     public async setTaskStatus(projectId: string, taskIds: string[], status: TaskData['status']): Promise<number> {
169 |         logger.info(`[TaskService] Attempting to set status to '${status}' for ${taskIds.length} tasks in project ${projectId}`);
170 |         const projectExists = this.projectRepository.findById(projectId);
171 |         if (!projectExists) {
172 |             logger.warn(`[TaskService] Project not found: ${projectId}`);
173 |             throw new NotFoundError(`Project with ID ${projectId} not found.`);
174 |         }
175 | 
176 |         const existenceCheck = this.taskRepository.checkTasksExist(projectId, taskIds);
177 |         if (!existenceCheck.allExist) {
178 |             logger.warn(`[TaskService] One or more tasks not found in project ${projectId}:`, existenceCheck.missingIds);
179 |             throw new NotFoundError(`One or more tasks not found in project ${projectId}: ${existenceCheck.missingIds.join(', ')}`);
180 |         }
181 | 
182 |         try {
183 |             const now = new Date().toISOString();
184 |             const updatedCount = this.taskRepository.updateStatus(projectId, taskIds, status, now);
185 |             if (updatedCount !== taskIds.length) {
186 |                 logger.warn(`[TaskService] Expected to update ${taskIds.length} tasks, but ${updatedCount} were affected.`);
187 |             }
188 |             logger.info(`[TaskService] Successfully updated status for ${updatedCount} tasks in project ${projectId}`);
189 |             return updatedCount;
190 |         } catch (error) {
191 |             logger.error(`[TaskService] Error setting status for tasks in project ${projectId}:`, error);
192 |             throw error;
193 |         }
194 | }
195 | 
196 | 
197 |     /**
198 |      * Expands a parent task by adding new subtasks.
199 |      * Optionally deletes existing subtasks first if 'force' is true.
200 |      * Uses a transaction to ensure atomicity.
201 |      * @param input - Details including parent task ID, project ID, subtask descriptions, and force flag.
202 |      * @returns The updated parent task details (including new subtasks).
203 |      * @throws {NotFoundError} If the project or parent task is not found.
204 |      * @throws {ConflictError} If subtasks exist and force is false.
205 |      * @throws {Error} If the database operation fails.
206 |      */
207 |     public async expandTask(input: ExpandTaskInput): Promise<FullTaskData> {
208 |         const { project_id, task_id: parentTaskId, subtask_descriptions, force = false } = input;
209 |         logger.info(`[TaskService] Attempting to expand task ${parentTaskId} in project ${project_id} with ${subtask_descriptions.length} subtasks (force=${force})`);
210 | 
211 |         // Use a transaction for the entire operation
212 |         const expandTransaction = this.db.transaction(() => {
213 |             // 1. Validate Parent Task Existence (within the transaction)
214 |             const parentTask = this.taskRepository.findById(project_id, parentTaskId);
215 |             if (!parentTask) {
216 |                 logger.warn(`[TaskService] Parent task ${parentTaskId} not found in project ${project_id}`);
217 |                 throw new NotFoundError(`Parent task with ID ${parentTaskId} not found in project ${project_id}.`);
218 |             }
219 | 
220 |             // 2. Check for existing subtasks
221 |             const existingSubtasks = this.taskRepository.findSubtasks(parentTaskId);
222 | 
223 |             // 3. Handle existing subtasks based on 'force' flag
224 |             if (existingSubtasks.length > 0) {
225 |                 if (!force) {
226 |                     logger.warn(`[TaskService] Conflict: Task ${parentTaskId} already has subtasks and force=false.`);
227 |                     throw new ConflictError(`Task ${parentTaskId} already has subtasks. Use force=true to replace them.`);
228 |                 } else {
229 |                     logger.info(`[TaskService] Force=true: Deleting ${existingSubtasks.length} existing subtasks for parent ${parentTaskId}.`);
230 |                     this.taskRepository.deleteSubtasks(parentTaskId);
231 |                     // Note: Dependencies of deleted subtasks are implicitly handled by ON DELETE CASCADE in schema
232 |                 }
233 |             }
234 | 
235 |             // 4. Create new subtasks
236 |             const now = new Date().toISOString();
237 |             const createdSubtasks: TaskData[] = [];
238 |             for (const description of subtask_descriptions) {
239 |                 const subtaskId = uuidv4();
240 |                 const newSubtaskData: TaskData = {
241 |                     task_id: subtaskId,
242 |                     project_id: project_id,
243 |                     parent_task_id: parentTaskId,
244 |                     description: description, // Assuming length validation done by Zod
245 |                     status: 'todo', // Default status
246 |                     priority: 'medium', // Default priority
247 |                     created_at: now,
248 |                     updated_at: now,
249 |                 };
250 |                 // Use the repository's create method (which handles its own transaction part for task+deps, but is fine here)
251 |                 // We pass an empty array for dependencies as expandTask doesn't set them for new subtasks
252 |                 this.taskRepository.create(newSubtaskData, []);
253 |                 createdSubtasks.push(newSubtaskData);
254 |             }
255 | 
256 |             // 5. Fetch updated parent task details (including new subtasks and existing dependencies)
257 |             // We re-fetch to get the consistent state after the transaction commits.
258 |             // Note: This requires the transaction function to return the necessary data.
259 |             // Alternatively, construct the FullTaskData manually here. Let's construct manually.
260 |             const dependencies = this.taskRepository.findDependencies(parentTaskId); // Fetch parent's dependencies
261 |             const finalParentData: FullTaskData = {
262 |                 ...parentTask, // Use data fetched at the start of transaction
263 |                 updated_at: now, // Update timestamp conceptually (though not saved unless status changes)
264 |                 dependencies: dependencies,
265 |                 subtasks: createdSubtasks, // Return the newly created subtasks
266 |             };
267 |             return finalParentData;
268 |         });
269 | 
270 |         try {
271 |             // Execute the transaction
272 |             const result = expandTransaction();
273 |             logger.info(`[TaskService] Successfully expanded task ${parentTaskId} with ${subtask_descriptions.length} new subtasks.`);
274 |             return result;
275 |         } catch (error) {
276 |             logger.error(`[TaskService] Error expanding task ${parentTaskId}:`, error);
277 |             // Re-throw specific errors or generic internal error
278 |             if (error instanceof NotFoundError || error instanceof ConflictError) {
279 |                 throw error;
280 |             }
281 |             throw new Error(`Failed to expand task: ${error instanceof Error ? error.message : 'Unknown error'}`);
282 |         }
283 |     }
284 | 
285 | 
286 |     /**
287 |      * Finds the next available task based on readiness (status 'todo', dependencies 'done')
288 |      * and prioritization (priority, creation date).
289 |      * @param projectId - The project ID.
290 |      * @returns The full details of the next task, or null if no task is ready.
291 |      * @throws {NotFoundError} If the project is not found.
292 |      * @throws {Error} If the database operation fails.
293 |      */
294 |     public async getNextTask(projectId: string): Promise<FullTaskData | null> {
295 |         logger.info(`[TaskService] Attempting to get next task for project ${projectId}`);
296 | 
297 |         // 1. Validate Project Existence
298 |         const projectExists = this.projectRepository.findById(projectId);
299 |         if (!projectExists) {
300 |             logger.warn(`[TaskService] Project not found: ${projectId}`);
301 |             throw new NotFoundError(`Project with ID ${projectId} not found.`);
302 |         }
303 | 
304 |         // 2. Find ready tasks using the repository method
305 |         try {
306 |             const readyTasks = this.taskRepository.findReadyTasks(projectId);
307 | 
308 |             if (readyTasks.length === 0) {
309 |                 logger.info(`[TaskService] No ready tasks found for project ${projectId}`);
310 |                 return null; // No task is ready
311 |             }
312 | 
313 |             // 3. The first task in the list is the highest priority one due to repo ordering
314 |             const nextTask = readyTasks[0];
315 |             logger.info(`[TaskService] Next task identified: ${nextTask.task_id}`);
316 | 
317 |             // 4. Fetch full details (dependencies, subtasks) for the selected task
318 |             // We could potentially optimize this if findReadyTasks returned more details,
319 |             // but for separation of concerns, we call getTaskById logic (or similar).
320 |             // Re-using getTaskById logic:
321 |             return await this.getTaskById(projectId, nextTask.task_id);
322 | 
323 |         } catch (error) {
324 |             logger.error(`[TaskService] Error getting next task for project ${projectId}:`, error);
325 |             throw error; // Re-throw repository or other errors
326 |         }
327 |     }
328 | 
329 |     /**
330 |      * Updates specific fields of an existing task.
331 |      * @param input - Contains project ID, task ID, and optional fields to update.
332 |      * @returns The full details of the updated task.
333 |      * @throws {ValidationError} If no update fields are provided or if dependencies are invalid.
334 |      * @throws {NotFoundError} If the project, task, or any specified dependency task is not found.
335 |      * @throws {Error} If the database operation fails.
336 |      */
337 |     public async updateTask(input: {
338 |         project_id: string;
339 |         task_id: string;
340 |         description?: string;
341 |         priority?: TaskData['priority'];
342 |         dependencies?: string[];
343 |     }): Promise<FullTaskData> {
344 |         const { project_id, task_id } = input;
345 |         logger.info(`[TaskService] Attempting to update task ${task_id} in project ${project_id}`);
346 | 
347 |         // 1. Validate that at least one field is being updated
348 |         if (input.description === undefined && input.priority === undefined && input.dependencies === undefined) {
349 |             throw new ValidationError("At least one field (description, priority, or dependencies) must be provided for update.");
350 |         }
351 | 
352 |         // 2. Validate Project Existence (using repo method)
353 |         const projectExists = this.projectRepository.findById(project_id);
354 |         if (!projectExists) {
355 |             logger.warn(`[TaskService] Project not found: ${project_id}`);
356 |             throw new NotFoundError(`Project with ID ${project_id} not found.`);
357 |         }
358 | 
359 |         // 3. Validate Task Existence (using repo method - findById also implicitly checks project scope)
360 |         // We need the task data anyway if dependencies are involved, so fetch it now.
361 |         const existingTask = this.taskRepository.findById(project_id, task_id);
362 |         if (!existingTask) {
363 |             logger.warn(`[TaskService] Task ${task_id} not found in project ${project_id}`);
364 |             throw new NotFoundError(`Task with ID ${task_id} not found in project ${project_id}.`);
365 |         }
366 | 
367 |         // 4. Validate Dependency Existence if provided
368 |         if (input.dependencies !== undefined) {
369 |             if (input.dependencies.length > 0) {
370 |                 const depCheck = this.taskRepository.checkTasksExist(project_id, input.dependencies);
371 |                 if (!depCheck.allExist) {
372 |                     logger.warn(`[TaskService] Invalid dependencies provided for task ${task_id}:`, depCheck.missingIds);
373 |                     throw new ValidationError(`One or more dependency tasks not found in project ${project_id}: ${depCheck.missingIds.join(', ')}`);
374 |                 }
375 |                 // Also check for self-dependency
376 |                 if (input.dependencies.includes(task_id)) {
377 |                      throw new ValidationError(`Task ${task_id} cannot depend on itself.`);
378 |                 }
379 |             }
380 |              // If input.dependencies is an empty array, it means "remove all dependencies"
381 |         }
382 | 
383 |         // 5. Prepare payload for repository
384 |         const updatePayload: { description?: string; priority?: TaskData['priority']; dependencies?: string[] } = {};
385 |         if (input.description !== undefined) updatePayload.description = input.description;
386 |         if (input.priority !== undefined) updatePayload.priority = input.priority;
387 |         if (input.dependencies !== undefined) updatePayload.dependencies = input.dependencies;
388 | 
389 |         // 6. Call Repository update method
390 |         try {
391 |             const now = new Date().toISOString();
392 |             // The repo method handles the transaction for task update + dependency replacement
393 |             const updatedTaskData = this.taskRepository.updateTask(project_id, task_id, updatePayload, now);
394 | 
395 |             // 7. Fetch full details (including potentially updated dependencies and existing subtasks)
396 |             // Re-use logic similar to getTaskById
397 |             const finalDependencies = this.taskRepository.findDependencies(task_id);
398 |             const finalSubtasks = this.taskRepository.findSubtasks(task_id);
399 | 
400 |             const fullUpdatedTask: FullTaskData = {
401 |                 ...updatedTaskData, // Use the data returned by the update method
402 |                 dependencies: finalDependencies,
403 |                 subtasks: finalSubtasks,
404 |             };
405 | 
406 |             logger.info(`[TaskService] Successfully updated task ${task_id} in project ${project_id}`);
407 |             return fullUpdatedTask;
408 | 
409 |         } catch (error) {
410 |             logger.error(`[TaskService] Error updating task ${task_id} in project ${project_id}:`, error);
411 |             // Re-throw specific errors if needed, otherwise let the generic error propagate
412 |              if (error instanceof Error && error.message.includes('not found')) {
413 |                  // Map repo's generic error for not found back to specific NotFoundError
414 |                  throw new NotFoundError(error.message);
415 |              }
416 |             throw error; // Re-throw other errors (like DB constraint errors or unexpected ones)
417 |         }
418 |     }
419 | 
420 | 
421 |     /**
422 |      * Deletes one or more tasks within a project.
423 |      * @param projectId - The project ID.
424 |      * @param taskIds - An array of task IDs to delete.
425 |      * @returns The number of tasks successfully deleted.
426 |      * @throws {NotFoundError} If the project or any of the specified tasks are not found.
427 |      * @throws {Error} If the database operation fails.
428 |      */
429 |     public async deleteTasks(projectId: string, taskIds: string[]): Promise<number> {
430 |         logger.info(`[TaskService] Attempting to delete ${taskIds.length} tasks from project ${projectId}`);
431 | 
432 |         // 1. Validate Project Existence
433 |         const projectExists = this.projectRepository.findById(projectId);
434 |         if (!projectExists) {
435 |             logger.warn(`[TaskService] Project not found: ${projectId}`);
436 |             throw new NotFoundError(`Project with ID ${projectId} not found.`);
437 |         }
438 | 
439 |         // 2. Validate Task Existence *before* attempting delete
440 |         // This ensures we report an accurate count and catch non-existent IDs early.
441 |         const existenceCheck = this.taskRepository.checkTasksExist(projectId, taskIds);
442 |         if (!existenceCheck.allExist) {
443 |             logger.warn(`[TaskService] Cannot delete: One or more tasks not found in project ${projectId}:`, existenceCheck.missingIds);
444 |             // Throw NotFoundError here, as InvalidParams might be confusing if some IDs were valid
445 |             throw new NotFoundError(`One or more tasks to delete not found in project ${projectId}: ${existenceCheck.missingIds.join(', ')}`);
446 |         }
447 | 
448 |         // 3. Call Repository delete method
449 |         try {
450 |             // The repository method handles the actual DELETE operation
451 |             const deletedCount = this.taskRepository.deleteTasks(projectId, taskIds);
452 | 
453 |             // Double-check count (optional, but good sanity check)
454 |             if (deletedCount !== taskIds.length) {
455 |                 logger.warn(`[TaskService] Expected to delete ${taskIds.length} tasks, but repository reported ${deletedCount} deletions.`);
456 |                 // This might indicate a race condition or unexpected DB behavior, though unlikely with cascade.
457 |                 // For V1, we'll trust the repo count but log the warning.
458 |             }
459 | 
460 |             logger.info(`[TaskService] Successfully deleted ${deletedCount} tasks from project ${projectId}`);
461 |             return deletedCount;
462 | 
463 |         } catch (error) {
464 |             logger.error(`[TaskService] Error deleting tasks from project ${projectId}:`, error);
465 |             throw error; // Re-throw database or other errors
466 |         }
467 |     }
468 | 
469 | 
470 |     // --- Add other task service methods later ---
471 | }
472 | 
```