#
tokens: 49789/50000 84/89 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/jhawkins11/task-manager-mcp?page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── frontend
│   ├── .gitignore
│   ├── .npmrc
│   ├── components.json
│   ├── package-lock.json
│   ├── package.json
│   ├── postcss.config.cjs
│   ├── README.md
│   ├── src
│   │   ├── app.d.ts
│   │   ├── app.html
│   │   ├── app.pcss
│   │   ├── lib
│   │   │   ├── components
│   │   │   │   ├── ImportTasksModal.svelte
│   │   │   │   ├── QuestionModal.svelte
│   │   │   │   ├── TaskFormModal.svelte
│   │   │   │   └── ui
│   │   │   │       ├── badge
│   │   │   │       │   ├── badge.svelte
│   │   │   │       │   └── index.ts
│   │   │   │       ├── button
│   │   │   │       │   ├── button.svelte
│   │   │   │       │   └── index.ts
│   │   │   │       ├── card
│   │   │   │       │   ├── card-content.svelte
│   │   │   │       │   ├── card-description.svelte
│   │   │   │       │   ├── card-footer.svelte
│   │   │   │       │   ├── card-header.svelte
│   │   │   │       │   ├── card-title.svelte
│   │   │   │       │   ├── card.svelte
│   │   │   │       │   └── index.ts
│   │   │   │       ├── checkbox
│   │   │   │       │   ├── checkbox.svelte
│   │   │   │       │   └── index.ts
│   │   │   │       ├── dialog
│   │   │   │       │   ├── dialog-content.svelte
│   │   │   │       │   ├── dialog-description.svelte
│   │   │   │       │   ├── dialog-footer.svelte
│   │   │   │       │   ├── dialog-header.svelte
│   │   │   │       │   ├── dialog-overlay.svelte
│   │   │   │       │   ├── dialog-portal.svelte
│   │   │   │       │   ├── dialog-title.svelte
│   │   │   │       │   └── index.ts
│   │   │   │       ├── input
│   │   │   │       │   ├── index.ts
│   │   │   │       │   └── input.svelte
│   │   │   │       ├── label
│   │   │   │       │   ├── index.ts
│   │   │   │       │   └── label.svelte
│   │   │   │       ├── progress
│   │   │   │       │   ├── index.ts
│   │   │   │       │   └── progress.svelte
│   │   │   │       ├── select
│   │   │   │       │   ├── index.ts
│   │   │   │       │   ├── select-content.svelte
│   │   │   │       │   ├── select-group-heading.svelte
│   │   │   │       │   ├── select-item.svelte
│   │   │   │       │   ├── select-scroll-down-button.svelte
│   │   │   │       │   ├── select-scroll-up-button.svelte
│   │   │   │       │   ├── select-separator.svelte
│   │   │   │       │   └── select-trigger.svelte
│   │   │   │       ├── separator
│   │   │   │       │   ├── index.ts
│   │   │   │       │   └── separator.svelte
│   │   │   │       └── textarea
│   │   │   │           ├── index.ts
│   │   │   │           └── textarea.svelte
│   │   │   ├── index.ts
│   │   │   ├── types.ts
│   │   │   └── utils.ts
│   │   └── routes
│   │       ├── +layout.server.ts
│   │       ├── +layout.svelte
│   │       └── +page.svelte
│   ├── static
│   │   └── favicon.png
│   ├── svelte.config.js
│   ├── tailwind.config.js
│   ├── tsconfig.json
│   └── vite.config.ts
├── img
│   └── ui.png
├── jest.config.js
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── config
│   │   ├── index.ts
│   │   ├── migrations.sql
│   │   └── schema.sql
│   ├── index.ts
│   ├── lib
│   │   ├── dbUtils.ts
│   │   ├── llmUtils.ts
│   │   ├── logger.ts
│   │   ├── repomixUtils.ts
│   │   ├── utils.ts
│   │   └── winstonLogger.ts
│   ├── models
│   │   └── types.ts
│   ├── server.ts
│   ├── services
│   │   ├── aiService.ts
│   │   ├── databaseService.ts
│   │   ├── planningStateService.ts
│   │   └── webSocketService.ts
│   └── tools
│       ├── adjustPlan.ts
│       ├── markTaskComplete.ts
│       ├── planFeature.ts
│       └── reviewChanges.ts
├── tests
│   ├── json-parser.test.ts
│   ├── llmUtils.unit.test.ts
│   ├── reviewChanges.integration.test.ts
│   └── setupEnv.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/frontend/.npmrc:
--------------------------------------------------------------------------------

```
engine-strict=true

```

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

```
node_modules
dist
.env
repomix-output.txt
.mcp
logs
frontend/node_modules/
frontend/build/
frontend/.svelte-kit/
.DS_Store
tsconfig.tsbuildinfo
*.db

```

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

```
node_modules

# Output
.output
.vercel
.netlify
.wrangler
/.svelte-kit
/build

# OS
.DS_Store
Thumbs.db

# Env
.env
.env.*
!.env.example
!.env.test

# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

```

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

```markdown
# sv

Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).

## Creating a project

If you're seeing this, you've probably already done this step. Congrats!

```bash
# create a new project in the current directory
npx sv create

# create a new project in my-app
npx sv create my-app
```

## Developing

Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:

```bash
npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open
```

## Building

To create a production version of your app:

```bash
npm run build
```

You can preview the production build with `npm run preview`.

> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

```

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

```markdown
# Task Manager MCP Server

This is an MCP server built to integrate with AI code editors like Cursor. The main goal here is to maximize Cursor's agentic capabilities and Gemini 2.5's excellent architecting capabilities while working around Cursor's extremely limited context window. This was inspired largely by Roo Code's Boomerang mode, but I found it extremely expensive as the only model that works with it's apply bot is Claude 3.7 Sonnet. With this server, you get the best of both worlds: unlimited context window and unlimited usage for the price of Cursor's $20/month subscription.

In addition, it includes a Svelte UI that allows you to view the task list and progress, manually adjust the plan, and review the changes.

## Svelte UI

![Task List](./img/ui.png)

## Core Features

- **Complex Feature Planning:** Give it a feature description, and it uses an LLM with project context via `repomix` to generate a step-by-step coding plan for the AI agent to follow with recursive task breakdown for high-effort tasks.
- **Integrated UI Server:** Runs an Express server to serve static frontend files and provides basic API endpoints for the UI. Opens the UI in the default browser after planning is complete or when clarification is needed and displays the task list and progress.
- **Unlimited Context Window:** Uses Gemini 2.5's 1 million token context window with `repomix`'s truncation when needed.
- **Conversation History:** Keeps track of the conversation history for each feature in a separate JSON file within `.mcp/features/` for each feature, allowing Gemini 2.5 to have context when the user asks for adjustments to the plan.
- **Clarification Workflow:** Handles cases where the LLM needs more info, pausing planning and interacting with a connected UI via WebSockets.
- **Task CRUD:** Allows for creating, reading, updating, and deleting tasks via the UI.
- **Code Review:** Analyzes `git diff HEAD` output using an LLM and creates new tasks if needed.
- **Automatic Review (Optional):** If configured (`AUTO_REVIEW_ON_COMPLETION=true`), automatically runs the code review process after the last original task for a feature is completed.
- **Plan Adjustment:** Allows for adjusting the plan after it's created via the `adjust_plan` tool.

## Setup

### Prerequisites:

- Node.js
- npm
- Git

### Installation & Build:

1. **Clone:**

   ```bash
   git clone https://github.com/jhawkins11/task-manager-mcp.git
   cd task-manager-mcp
   ```

2. **Install Backend Deps:**

   ```bash
   npm install
   ```

3. **Configure:** You'll configure API keys later directly in Cursor's MCP settings (see Usage section), but you might still want a local `.env` file for manual testing (see Configuration section).

4. **Build:** This command builds the backend and frontend servers and copies the Svelte UI to the `dist/frontend-ui/` directory.
   ```bash
   npm run build
   ```

### Running the Server (Manually):

For local testing _without_ Cursor, you can run the server using Node directly or the npm script. This method **will** use the `.env` file for configuration.

**Using Node directly (use absolute path):**

```bash
node /full/path/to/your/task-manager-mcp/dist/server.js
```

**Using npm start:**

```bash
npm start
```

This starts the MCP server (stdio), WebSocket server, and the HTTP server for the UI. The UI should be accessible at http://localhost:<UI_PORT> (default 3000).

### Configuration (.env file for Manual Running):

If running manually (not via Cursor), create a .env file in the project root for API keys and ports. Note: When running via Cursor, these should be set in Cursor's mcp.json configuration instead (see Usage section).

```bash
# .env - USED ONLY FOR MANUAL `npm start` or `node dist/server.js`
# === OpenRouter (Recommended) ===

# Get key: https://openrouter.ai/keys
OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OPENROUTER_MODEL=google/gemini-2.5-flash-preview:thinking
FALLBACK_OPENROUTER_MODEL=google/gemini-2.5-flash-preview:thinking

# === Google AI API (Alternative) ===
# GEMINI_API_KEY=your_google_ai_api_key
# GEMINI_MODEL=gemini-1.5-flash-latest
# FALLBACK_GEMINI_MODEL=gemini-1.5-flash-latest

# === UI / WebSocket Ports ===
# Default is 4999 if not set.
UI_PORT=4999
WS_PORT=4999

# === Auto Review ===
# If true, the agent will automatically run the 'review_changes' tool after the last task is completed.
# Defaults to false.
AUTO_REVIEW_ON_COMPLETION=false
```

## Avoiding Costs

**IMPORTANT:** It's highly recommended to integrate your own Google AI API key to OpenRouter to avoid the free models' rate limits. See below.

**Using OpenRouter's Free Tiers:** You can significantly minimize or eliminate costs by using models marked as "Free" on OpenRouter (like google/gemini-2.5-flash-preview:thinking at the time of writing) while connecting your own Google AI API key. Check out this reddit thread for more info: https://www.reddit.com/r/ChatGPTCoding/comments/1jrp1tj/a_simple_guide_to_setting_up_gemini_25_pro_free/

**Fallback Costs:** The server automatically retries with a fallback model if the primary hits a rate limit. The default fallback (FALLBACK_OPENROUTER_MODEL) is often a faster/cheaper model like Gemini Flash, which might still have associated costs depending on OpenRouter's current pricing/tiers. Check their site and adjust the fallback model in your configuration if needed.

## Usage with Cursor (Task Manager Mode)

This is the primary way this server is intended to be used. I have not yet tested it with other AI code editors yet. If you try it, please let me know how it goes, and I'll update the README.

### 1. Configure the MCP Server in Cursor:

After building the server (`npm run build`), you need to tell Cursor how to run it.

Find Cursor's MCP configuration file. This can be:

- **Project-specific:** Create/edit a file at `.cursor/mcp.json` inside your project's root directory.
- **Global:** Create/edit a file at `~/.cursor/mcp.json` in your user home directory (for use across all projects).

Add the following entry to the mcpServers object within that JSON file:

```json
{
  "mcpServers": {
    "task-manager-mcp": {
      "command": "node",
      "args": ["/full/path/to/your/task-manager-mcp/dist/server.js"],
      "env": {
        "OPENROUTER_API_KEY": "sk-or-v1-xxxxxxxxxxxxxxxxxxxx"
        //   optional: my recommended model for MCP is Gemini 2.5 Pro Free which is already set by default
        //   "OPENROUTER_MODEL": "google/gemini-2.5-flash-preview:thinking",
        //   also optional
        //   "FALLBACK_OPENROUTER_MODEL": "google/gemini-2.5-flash-preview:thinking",
        //   optional: the default port for the UI is 4999 if not set
        //   "UI_PORT": "4999",
        //   optional: the default port for the WebSocket server is 4999 if not set
        //   "WS_PORT": "4999"
        // Add GEMINI_API_KEY here instead if using Google directly
        // Add any other necessary env vars here
      }
    }
    // Add other MCP servers here if you have them
  }
}
```

**IMPORTANT:**

- Replace `/full/path/to/your/task-manager-mcp/dist/server.js` with the absolute path to the compiled server script on your machine.
- Replace `sk-or-v1-xxxxxxxxxxxxxxxxxxxx` with your actual OpenRouter API key (or set GEMINI_API_KEY if using Google AI directly).
- These env variables defined here will be passed to the server process when Cursor starts it, overriding any `.env` file.

### 2. Create a Custom Cursor Mode:

1. Go to Cursor Settings -> Features -> Chat -> Enable Custom modes.
2. Go back to the chat view, click the mode selector (bottom left), and click Add custom mode.
3. Give it a name (e.g., "MCP Planner", "Task Dev"), choose an icon/shortcut.
4. Enable Tools: Make sure the tools exposed by this server (`plan_feature`, `mark_task_complete`, `get_next_task`, `review_changes`, `adjust_plan`) are available and enabled for this mode. You might want to enable other tools like Codebase, Terminal, etc., depending on your workflow.
5. Recommended Instructions for Agent: Paste these rules exactly into the "Custom Instructions" text box:

```
Always use plan_feature mcp tool when getting feature request before doing anything else. ALWAYS!!!!!!!! It will return the first step of the implementation. DO NOT IMPLEMENT MORE THAN WHAT THE TASK STATES. After you're done run mark_task_complete which will give you the next task. If the user says "review" use the review_changes tool. The review_changes tool will generate new tasks for you to follow, just like plan_feature. After a review, follow the same one-at-a-time task completion workflow: complete each review-generated task, mark it complete, and call get_next_task until all are done.

If clarification is required at any step, you will not receive the next task and will have to run get_next_task manually after the user answers the clarification question through the UI.

IMPORTANT: Your job is to complete the tasks one at a time. DO NOT DO ANY OTHER CHANGES, ONLY WHAT THE CURRENT TASK SAYS TO DO.
```

6. Save the custom mode.

## Expected Workflow (Using the Custom Mode):

1. Select your new custom mode in Cursor.
2. Give Cursor a feature request (e.g., "add auth using JWT").
3. Cursor, following the instructions, will call the `plan_feature` tool.
4. The server plans, saves data, and returns a JSON response (inside the text content) to Cursor.
   - If successful: The response includes `status: "completed"` and the description of the first task in the `message` field. The UI (if running) is launched/updated.
   - If clarification needed: The response includes `status: "awaiting_clarification"`, the `featureId`, the `uiUrl`, and instructions for the agent to wait and call `get_next_task` later. The UI is launched/updated with the question.
5. Cursor implements only the task described (if provided).
6. If clarification was needed, the user answers in the UI, the server resumes planning, and updates the UI via WebSocket. The agent, following instructions, then calls `get_next_task` with the `featureId`.
7. If a task was completed, Cursor calls `mark_task_complete` (with `taskId` and `featureId`).
8. The server marks the task done and returns the next pending task in the response message.
9. Cursor repeats steps 4-8.
10. If the user asks Cursor to "review", it calls `review_changes`.

## API Endpoints (for UI)

The integrated Express server provides these basic endpoints for the frontend:

- `GET /api/features`: Returns a list of existing feature IDs.
- `GET /api/tasks/:featureId`: Returns the list of tasks for a specific feature.
- `GET /api/tasks`: Returns tasks for the most recently created/modified feature.
- `GET /api/features/:featureId/pending-question`: Checks if there's an active clarification question for the feature.
- `POST /api/tasks`: Creates a new task for a feature.
- `PUT /api/tasks/:taskId`: Updates an existing task.
- `DELETE /api/tasks/:taskId`: Deletes a task.
- _(Static Files)_: Serves files from `dist/frontend-ui/` (e.g., `index.html`).

```

--------------------------------------------------------------------------------
/frontend/src/lib/index.ts:
--------------------------------------------------------------------------------

```typescript

```

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

```typescript
// Re-export server for backwards compatibility
export * from './server'

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/label/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from "./label.svelte";

export {
	Root,
	//
	Root as Label,
};

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/checkbox/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from "./checkbox.svelte";
export {
	Root,
	//
	Root as Checkbox,
};

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/progress/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from "./progress.svelte";

export {
	Root,
	//
	Root as Progress,
};

```

--------------------------------------------------------------------------------
/frontend/postcss.config.cjs:
--------------------------------------------------------------------------------

```
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

```

--------------------------------------------------------------------------------
/frontend/src/routes/+layout.server.ts:
--------------------------------------------------------------------------------

```typescript
// Enforces static prerendering for the entire site
export const prerender = true

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/separator/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from "./separator.svelte";

export {
	Root,
	//
	Root as Separator,
};

```

--------------------------------------------------------------------------------
/tests/setupEnv.ts:
--------------------------------------------------------------------------------

```typescript
import dotenv from 'dotenv'
import path from 'path'

dotenv.config({ path: path.resolve(process.cwd(), '.env') })

```

--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------

```typescript
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
	plugins: [sveltekit()]
});

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-portal.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Dialog as DialogPrimitive } from "bits-ui";
	type $$Props = DialogPrimitive.PortalProps;
</script>

<DialogPrimitive.Portal {...$$restProps}>
	<slot />
</DialogPrimitive.Portal>

```

--------------------------------------------------------------------------------
/frontend/src/app.d.ts:
--------------------------------------------------------------------------------

```typescript
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
	namespace App {
		// interface Error {}
		// interface Locals {}
		// interface PageData {}
		// interface PageState {}
		// interface Platform {}
	}
}

export {};

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/card-content.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLDivElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<div class={cn("p-6", className)} {...$$restProps}>
	<slot />
</div>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/card-footer.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLDivElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<div class={cn("flex items-center p-6 pt-0", className)} {...$$restProps}>
	<slot />
</div>

```

--------------------------------------------------------------------------------
/frontend/src/app.html:
--------------------------------------------------------------------------------

```html
<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		%sveltekit.head%
	</head>
	<body data-sveltekit-preload-data="hover">
		<div style="display: contents">%sveltekit.body%</div>
	</body>
</html>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/card-description.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLParagraphElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<p class={cn("text-muted-foreground text-sm", className)} {...$$restProps}>
	<slot />
</p>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/card-header.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLDivElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<div class={cn("flex flex-col space-y-1.5 p-6 pb-0", className)} {...$$restProps}>
	<slot />
</div>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-group-heading.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Select as SelectPrimitive } from "bits-ui";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		...restProps
	}: SelectPrimitive.GroupHeadingProps = $props();
</script>

<SelectPrimitive.GroupHeading
	bind:ref
	class={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
	{...restProps}
/>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-header.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLDivElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<div class={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...$$restProps}>
	<slot />
</div>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/card.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLDivElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<div
	class={cn("bg-card text-card-foreground rounded-lg border shadow-sm", className)}
	{...$$restProps}
>
	<slot />
</div>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-footer.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLDivElement>;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<div
	class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
	{...$$restProps}
>
	<slot />
</div>

```

--------------------------------------------------------------------------------
/frontend/components.json:
--------------------------------------------------------------------------------

```json
{
	"$schema": "https://shadcn-svelte.com/schema.json",
	"style": "default",
	"tailwind": {
		"config": "tailwind.config.js",
		"css": "src/app.pcss",
		"baseColor": "slate"
	},
	"aliases": {
		"components": "$lib/components",
		"utils": "$lib/utils",
		"ui": "$lib/components/ui",
		"hooks": "$lib/hooks"
	},
	"typescript": true,
	"registry": "https://next.shadcn-svelte.com/registry"
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-description.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Dialog as DialogPrimitive } from "bits-ui";
	import { cn } from "$lib/utils.js";

	type $$Props = DialogPrimitive.DescriptionProps;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<DialogPrimitive.Description
	class={cn("text-muted-foreground text-sm", className)}
	{...$$restProps}
>
	<slot />
</DialogPrimitive.Description>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-title.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Dialog as DialogPrimitive } from "bits-ui";
	import { cn } from "$lib/utils.js";

	type $$Props = DialogPrimitive.TitleProps;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<DialogPrimitive.Title
	class={cn("text-lg font-semibold leading-none tracking-tight", className)}
	{...$$restProps}
>
	<slot />
</DialogPrimitive.Title>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-separator.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { Separator as SeparatorPrimitive } from "bits-ui";
	import { Separator } from "$lib/components/ui/separator/index.js";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		...restProps
	}: SeparatorPrimitive.RootProps = $props();
</script>

<Separator bind:ref class={cn("bg-muted -mx-1 my-1 h-px", className)} {...restProps} />

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/badge/badge.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { type Variant, badgeVariants } from "./index.js";
	import { cn } from "$lib/utils.js";

	let className: string | undefined | null = undefined;
	export let href: string | undefined = undefined;
	export let variant: Variant = "default";
	export { className as class };
</script>

<svelte:element
	this={href ? "a" : "span"}
	{href}
	class={cn(badgeVariants({ variant, className }))}
	{...$$restProps}
>
	<slot />
</svelte:element>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/separator/separator.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Separator as SeparatorPrimitive } from "bits-ui";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		orientation = "horizontal",
		...restProps
	}: SeparatorPrimitive.RootProps = $props();
</script>

<SeparatorPrimitive.Root
	bind:ref
	class={cn(
		"bg-border shrink-0",
		orientation === "horizontal" ? "h-[1px] w-full" : "min-h-full w-[1px]",
		className
	)}
	{orientation}
	{...restProps}
/>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/label/label.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Label as LabelPrimitive } from "bits-ui";
	import { cn } from "$lib/utils.js";

	type $$Props = LabelPrimitive.Props;
	type $$Events = LabelPrimitive.Events;

	let className: $$Props["class"] = undefined;
	export { className as class };
</script>

<LabelPrimitive.Root
	class={cn(
		"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
		className
	)}
	{...$$restProps}
	on:mousedown
>
	<slot />
</LabelPrimitive.Root>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/card-title.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLAttributes } from "svelte/elements";
	import type { HeadingLevel } from "./index.js";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLAttributes<HTMLHeadingElement> & {
		tag?: HeadingLevel;
	};

	let className: $$Props["class"] = undefined;
	export let tag: $$Props["tag"] = "h3";
	export { className as class };
</script>

<svelte:element
	this={tag}
	class={cn("text-lg font-semibold leading-none tracking-tight", className)}
	{...$$restProps}
>
	<slot />
</svelte:element>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/card/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from "./card.svelte";
import Content from "./card-content.svelte";
import Description from "./card-description.svelte";
import Footer from "./card-footer.svelte";
import Header from "./card-header.svelte";
import Title from "./card-title.svelte";

export {
	Root,
	Content,
	Description,
	Footer,
	Header,
	Title,
	//
	Root as Card,
	Content as CardContent,
	Description as CardDescription,
	Footer as CardFooter,
	Header as CardHeader,
	Title as CardTitle,
};

export type HeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

```

--------------------------------------------------------------------------------
/src/config/migrations.sql:
--------------------------------------------------------------------------------

```sql
-- Add from_review column to tasks table if it doesn't exist
ALTER TABLE tasks ADD COLUMN from_review INTEGER DEFAULT 0;

-- Add task_id column to history_entries table if it doesn't exist
ALTER TABLE history_entries ADD COLUMN task_id TEXT;

-- Add action and details columns to history_entries table if they don't exist
ALTER TABLE history_entries ADD COLUMN action TEXT;
ALTER TABLE history_entries ADD COLUMN details TEXT; 

-- Add project_path column to features table if it doesn't exist
ALTER TABLE features ADD COLUMN project_path TEXT; 
```

--------------------------------------------------------------------------------
/frontend/svelte.config.js:
--------------------------------------------------------------------------------

```javascript
import adapter from '@sveltejs/adapter-static'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'

/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://svelte.dev/docs/kit/integrations
  // for more information about preprocessors
  preprocess: vitePreprocess(),

  kit: {
    // Using adapter-static to output a static site build
    adapter: adapter({
      // Output to the default build folder
      pages: 'build',
      assets: 'build',
      precompress: false,
    }),
  },
}

export default config

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import ChevronUp from "@lucide/svelte/icons/chevron-up";
	import { Select as SelectPrimitive, type WithoutChildrenOrChild } from "bits-ui";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		...restProps
	}: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
</script>

<SelectPrimitive.ScrollUpButton
	bind:ref
	class={cn("flex cursor-default items-center justify-center py-1", className)}
	{...restProps}
>
	<ChevronUp class="size-4" />
</SelectPrimitive.ScrollUpButton>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import ChevronDown from "@lucide/svelte/icons/chevron-down";
	import { Select as SelectPrimitive, type WithoutChildrenOrChild } from "bits-ui";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		...restProps
	}: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
</script>

<SelectPrimitive.ScrollDownButton
	bind:ref
	class={cn("flex cursor-default items-center justify-center py-1", className)}
	{...restProps}
>
	<ChevronDown class="size-4" />
</SelectPrimitive.ScrollDownButton>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-overlay.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Dialog as DialogPrimitive } from "bits-ui";
	import { fade } from "svelte/transition";
	import { cn } from "$lib/utils.js";

	type $$Props = DialogPrimitive.OverlayProps;

	let className: $$Props["class"] = undefined;
	export let transition: $$Props["transition"] = fade;
	export let transitionConfig: $$Props["transitionConfig"] = {
		duration: 150,
	};
	export { className as class };
</script>

<DialogPrimitive.Overlay
	{transition}
	{transitionConfig}
	class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm", className)}
	{...$$restProps}
/>

```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  moduleNameMapper: {
    '^../services/aiService$': '<rootDir>/src/services/aiService',
    '^../models/types$': '<rootDir>/src/models/types',
    '^../lib/logger$': '<rootDir>/src/lib/logger',
    '^../lib/llmUtils$': '<rootDir>/src/lib/llmUtils',
    '^../lib/repomixUtils$': '<rootDir>/src/lib/repomixUtils',
    '^../lib/dbUtils$': '<rootDir>/src/lib/dbUtils',
    '^../config$': '<rootDir>/src/config',
    '^../services/databaseService$': '<rootDir>/src/services/databaseService',
  },
  setupFiles: ['<rootDir>/tests/setupEnv.ts'],
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/progress/progress.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Progress as ProgressPrimitive, type WithoutChildrenOrChild } from "bits-ui";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		max = 100,
		value,
		...restProps
	}: WithoutChildrenOrChild<ProgressPrimitive.RootProps> = $props();
</script>

<ProgressPrimitive.Root
	bind:ref
	class={cn("bg-secondary relative h-4 w-full overflow-hidden rounded-full", className)}
	{value}
	{max}
	{...restProps}
>
	<div
		class="bg-primary h-full w-full flex-1 transition-all"
		style={`transform: translateX(-${100 - (100 * (value ?? 0)) / (max ?? 1)}%)`}
	></div>
</ProgressPrimitive.Root>

```

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

```json
{
	"extends": "./.svelte-kit/tsconfig.json",
	"compilerOptions": {
		"allowJs": true,
		"checkJs": true,
		"esModuleInterop": true,
		"forceConsistentCasingInFileNames": true,
		"resolveJsonModule": true,
		"skipLibCheck": true,
		"sourceMap": true,
		"strict": true,
		"moduleResolution": "bundler"
	}
	// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
	// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
	//
	// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
	// from the referenced tsconfig.json - TypeScript does not merge them in
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/button/button.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Button as ButtonPrimitive } from "bits-ui";
	import { type Events, type Props, buttonVariants } from "./index.js";
	import { cn } from "$lib/utils.js";

	type $$Props = Props;
	type $$Events = Events;

	let className: $$Props["class"] = undefined;
	export let variant: $$Props["variant"] = "default";
	export let size: $$Props["size"] = "default";
	export let builders: $$Props["builders"] = [];
	export { className as class };
</script>

<ButtonPrimitive.Root
	{builders}
	class={cn(buttonVariants({ variant, size, className }))}
	type="button"
	{...$$restProps}
	on:click
	on:keydown
>
	<slot />
</ButtonPrimitive.Root>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/textarea/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from './textarea.svelte'

type FormTextareaEvent<T extends Event = Event> = T & {
  currentTarget: EventTarget & HTMLTextAreaElement
}

type TextareaEvents = {
  blur: FormTextareaEvent<FocusEvent>
  change: FormTextareaEvent<Event>
  click: FormTextareaEvent<MouseEvent>
  focus: FormTextareaEvent<FocusEvent>
  keydown: FormTextareaEvent<KeyboardEvent>
  keypress: FormTextareaEvent<KeyboardEvent>
  keyup: FormTextareaEvent<KeyboardEvent>
  mouseover: FormTextareaEvent<MouseEvent>
  mouseenter: FormTextareaEvent<MouseEvent>
  mouseleave: FormTextareaEvent<MouseEvent>
  paste: FormTextareaEvent<ClipboardEvent>
  input: FormTextareaEvent<InputEvent>
}

export {
  Root,
  //
  Root as Textarea,
  type TextareaEvents,
  type FormTextareaEvent,
}

export default Root

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/badge/index.ts:
--------------------------------------------------------------------------------

```typescript
import { type VariantProps, tv } from "tailwind-variants";
export { default as Badge } from "./badge.svelte";

export const badgeVariants = tv({
	base: "focus:ring-ring inline-flex select-none items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2",
	variants: {
		variant: {
			default: "bg-primary text-primary-foreground hover:bg-primary/80 border-transparent",
			secondary:
				"bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent",
			destructive:
				"bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent",
			outline: "text-foreground",
		},
	},
	defaultVariants: {
		variant: "default",
	},
});

export type Variant = VariantProps<typeof badgeVariants>["variant"];

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/input/index.ts:
--------------------------------------------------------------------------------

```typescript
import Root from "./input.svelte";

export type FormInputEvent<T extends Event = Event> = T & {
	currentTarget: EventTarget & HTMLInputElement;
};
export type InputEvents = {
	blur: FormInputEvent<FocusEvent>;
	change: FormInputEvent<Event>;
	click: FormInputEvent<MouseEvent>;
	focus: FormInputEvent<FocusEvent>;
	focusin: FormInputEvent<FocusEvent>;
	focusout: FormInputEvent<FocusEvent>;
	keydown: FormInputEvent<KeyboardEvent>;
	keypress: FormInputEvent<KeyboardEvent>;
	keyup: FormInputEvent<KeyboardEvent>;
	mouseover: FormInputEvent<MouseEvent>;
	mouseenter: FormInputEvent<MouseEvent>;
	mouseleave: FormInputEvent<MouseEvent>;
	mousemove: FormInputEvent<MouseEvent>;
	paste: FormInputEvent<ClipboardEvent>;
	input: FormInputEvent<InputEvent>;
	wheel: FormInputEvent<WheelEvent>;
};

export {
	Root,
	//
	Root as Input,
};

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-trigger.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
	import ChevronDown from "@lucide/svelte/icons/chevron-down";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		children,
		...restProps
	}: WithoutChild<SelectPrimitive.TriggerProps> = $props();
</script>

<SelectPrimitive.Trigger
	bind:ref
	class={cn(
		"border-input bg-background ring-offset-background data-[placeholder]:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
		className
	)}
	{...restProps}
>
	{@render children?.()}
	<ChevronDown class="size-4 opacity-50" />
</SelectPrimitive.Trigger>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/index.ts:
--------------------------------------------------------------------------------

```typescript
import { Dialog as DialogPrimitive } from "bits-ui";

import Title from "./dialog-title.svelte";
import Portal from "./dialog-portal.svelte";
import Footer from "./dialog-footer.svelte";
import Header from "./dialog-header.svelte";
import Overlay from "./dialog-overlay.svelte";
import Content from "./dialog-content.svelte";
import Description from "./dialog-description.svelte";

const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
const Close = DialogPrimitive.Close;

export {
	Root,
	Title,
	Portal,
	Footer,
	Header,
	Trigger,
	Overlay,
	Content,
	Description,
	Close,
	//
	Root as Dialog,
	Title as DialogTitle,
	Portal as DialogPortal,
	Footer as DialogFooter,
	Header as DialogHeader,
	Trigger as DialogTrigger,
	Overlay as DialogOverlay,
	Content as DialogContent,
	Description as DialogDescription,
	Close as DialogClose,
};

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/index.ts:
--------------------------------------------------------------------------------

```typescript
import { Select as SelectPrimitive } from "bits-ui";

import GroupHeading from "./select-group-heading.svelte";
import Item from "./select-item.svelte";
import Content from "./select-content.svelte";
import Trigger from "./select-trigger.svelte";
import Separator from "./select-separator.svelte";
import ScrollDownButton from "./select-scroll-down-button.svelte";
import ScrollUpButton from "./select-scroll-up-button.svelte";

const Root = SelectPrimitive.Root;
const Group = SelectPrimitive.Group;

export {
	Root,
	Group,
	GroupHeading,
	Item,
	Content,
	Trigger,
	Separator,
	ScrollDownButton,
	ScrollUpButton,
	//
	Root as Select,
	Group as SelectGroup,
	GroupHeading as SelectGroupHeading,
	Item as SelectItem,
	Content as SelectContent,
	Trigger as SelectTrigger,
	Separator as SelectSeparator,
	ScrollDownButton as SelectScrollDownButton,
	ScrollUpButton as SelectScrollUpButton,
};

```

--------------------------------------------------------------------------------
/frontend/src/routes/+layout.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import '../app.pcss';
	import { onMount } from 'svelte';
	import { browser } from '$app/environment';

	// Removed prerender export - moved to +layout.server.ts

	onMount(() => {
		// Run only in the browser
		if (browser) {
			const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

			const updateTheme = (event: MediaQueryListEvent | MediaQueryList) => {
				if (event.matches) {
					document.documentElement.classList.add('dark');
				} else {
					document.documentElement.classList.remove('dark');
				}
			};

			// Initial check
			updateTheme(mediaQuery);

			// Listen for changes
			mediaQuery.addEventListener('change', updateTheme);

			// Cleanup listener on component destroy
			return () => {
				mediaQuery.removeEventListener('change', updateTheme);
			};
		}
	});
</script>

<div class="min-h-screen bg-background text-foreground">
	<main class="min-h-screen">
		<slot />
	</main>
</div> 
```

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

```json
{
  "compilerOptions": {
    "target": "ES2020", // Target modern Node.js versions
    "module": "CommonJS", // Use CommonJS modules
    "outDir": "./dist", // Output directory for compiled JavaScript
    "rootDir": "./src", // Source directory for TypeScript files
    "strict": true, // Enable strict type checking
    "esModuleInterop": true, // Allows default imports from CommonJS modules
    "skipLibCheck": true, // Skip type checking of declaration files
    "forceConsistentCasingInFileNames": true, // Ensure consistent file casing
    "moduleResolution": "node", // Use Node.js module resolution
    "resolveJsonModule": true, // Allow importing JSON files
    "sourceMap": true, // Generate source maps for debugging
    "incremental": true // Enable incremental compilation
  },
  "include": ["src/**/*"], // Include all files in the src directory
  "exclude": ["node_modules", "**/*.spec.ts"] // Exclude node_modules and test files
}

```

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

```json
{
  "name": "frontend",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "preview": "vite preview",
    "prepare": "svelte-kit sync || echo ''",
    "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
    "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
  },
  "devDependencies": {
    "@lucide/svelte": "^0.488.0",
    "@sveltejs/adapter-auto": "^4.0.0",
    "@sveltejs/adapter-static": "^3.0.8",
    "@sveltejs/kit": "^2.16.0",
    "@sveltejs/vite-plugin-svelte": "^5.0.0",
    "autoprefixer": "^10.4.21",
    "bits-ui": "^0.22.0",
    "clsx": "^2.1.1",
    "lucide-svelte": "^0.488.0",
    "postcss": "^8.5.3",
    "svelte": "^5.0.0",
    "svelte-check": "^4.0.0",
    "tailwind-merge": "^3.2.0",
    "tailwind-variants": "^1.0.0",
    "tailwindcss": "^3.4.17",
    "typescript": "^5.0.0",
    "vite": "^6.2.5"
  },
  "dependencies": {
    "shadcn-svelte": "^1.0.0-next.9"
  }
}

```

--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------

```typescript
import { logToFile } from './logger'

/**
 * Dynamically imports an ES Module from a CommonJS module.
 * Handles default exports correctly.
 * @param modulePath The path or name of the module to import.
 * @returns The default export of the module.
 * @throws If the import fails.
 */
export async function dynamicImportDefault<T = any>(
  modulePath: string
): Promise<T> {
  try {
    // Perform the dynamic import
    const module = await import(modulePath)

    // Check for and return the default export
    if (module.default) {
      return module.default as T
    }

    // If no default export, return the module namespace object itself
    // (less likely needed for 'open', but good fallback)
    return module as T
  } catch (error: any) {
    await logToFile(
      `[Utils] Failed to dynamically import '${modulePath}': ${error.message}`
    )
    console.error(`[Utils] Dynamic import error for '${modulePath}':`, error)
    // Re-throw the error so the calling function knows it failed
    throw error
  }
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-item.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import Check from "@lucide/svelte/icons/check";
	import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		value,
		label,
		children: childrenProp,
		...restProps
	}: WithoutChild<SelectPrimitive.ItemProps> = $props();
</script>

<SelectPrimitive.Item
	bind:ref
	{value}
	class={cn(
		"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
		className
	)}
	{...restProps}
>
	{#snippet children({ selected, highlighted })}
		<span class="absolute left-2 flex size-3.5 items-center justify-center">
			{#if selected}
				<Check class="size-4" />
			{/if}
		</span>
		{#if childrenProp}
			{@render childrenProp({ selected, highlighted })}
		{:else}
			{label || value}
		{/if}
	{/snippet}
</SelectPrimitive.Item>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/textarea/textarea.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLTextareaAttributes } from "svelte/elements";
	import type { TextareaEvents } from "./index.js";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLTextareaAttributes;
	type $$Events = TextareaEvents;

	let className: $$Props["class"] = undefined;
	export let value: $$Props["value"] = undefined;
	export { className as class };

	// Workaround for https://github.com/sveltejs/svelte/issues/9305
	// Fixed in Svelte 5, but not backported to 4.x.
	export let readonly: $$Props["readonly"] = undefined;
</script>

<textarea
	class={cn(
		"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
		className
	)}
	bind:value
	{readonly}
	on:blur
	on:change
	on:click
	on:focus
	on:keydown
	on:keypress
	on:keyup
	on:mouseover
	on:mouseenter
	on:mouseleave
	on:paste
	on:input
	{...$$restProps}
></textarea>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/input/input.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import type { HTMLInputAttributes } from "svelte/elements";
	import type { InputEvents } from "./index.js";
	import { cn } from "$lib/utils.js";

	type $$Props = HTMLInputAttributes;
	type $$Events = InputEvents;

	let className: $$Props["class"] = undefined;
	export let value: $$Props["value"] = undefined;
	export { className as class };

	// Workaround for https://github.com/sveltejs/svelte/issues/9305
	// Fixed in Svelte 5, but not backported to 4.x.
	export let readonly: $$Props["readonly"] = undefined;
</script>

<input
	class={cn(
		"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
		className
	)}
	bind:value
	{readonly}
	on:blur
	on:change
	on:click
	on:focus
	on:focusin
	on:focusout
	on:keydown
	on:keypress
	on:keyup
	on:mouseover
	on:mouseenter
	on:mouseleave
	on:mousemove
	on:paste
	on:input
	on:wheel|passive
	{...$$restProps}
/>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/dialog/dialog-content.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Dialog as DialogPrimitive } from "bits-ui";
	import X from "lucide-svelte/icons/x";
	import * as Dialog from "./index.js";
	import { cn, flyAndScale } from "$lib/utils.js";

	type $$Props = DialogPrimitive.ContentProps;

	let className: $$Props["class"] = undefined;
	export let transition: $$Props["transition"] = flyAndScale;
	export let transitionConfig: $$Props["transitionConfig"] = {
		duration: 200,
	};
	export { className as class };
</script>

<Dialog.Portal>
	<Dialog.Overlay />
	<DialogPrimitive.Content
		{transition}
		{transitionConfig}
		class={cn(
			"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full",
			className
		)}
		{...$$restProps}
	>
		<slot />
		<DialogPrimitive.Close
			class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none"
		>
			<X class="h-4 w-4" />
			<span class="sr-only">Close</span>
		</DialogPrimitive.Close>
	</DialogPrimitive.Content>
</Dialog.Portal>

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/checkbox/checkbox.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
	import Check from "@lucide/svelte/icons/check";
	import Minus from "@lucide/svelte/icons/minus";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		checked = $bindable(false),
		indeterminate = $bindable(false),
		class: className,
		...restProps
	}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>

<CheckboxPrimitive.Root
	bind:ref
	class={cn(
		"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
		className
	)}
	bind:checked
	bind:indeterminate
	{...restProps}
>
	{#snippet children({ checked, indeterminate })}
		<div class="flex size-4 items-center justify-center text-current">
			{#if indeterminate}
				<Minus class="size-3.5" />
			{:else}
				<Check class={cn("size-3.5", !checked && "text-transparent")} />
			{/if}
		</div>
	{/snippet}
</CheckboxPrimitive.Root>

```

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

```json
{
  "name": "task-manager-mcp",
  "version": "1.1.0",
  "main": "dist/server.js",
  "scripts": {
    "build": "npm run build:frontend && npm run build:server",
    "build:server": "tsc && mkdir -p dist/config && cp src/config/*.sql dist/config/",
    "build:frontend": "cd frontend && npm run build && cd .. && mkdir -p dist/frontend-ui && cp -r frontend/build/* dist/frontend-ui/",
    "start": "node dist/server.js",
    "dev": "nodemon --watch src --ext ts --exec ts-node src/server.ts",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@google/generative-ai": "^0.24.0",
    "@modelcontextprotocol/sdk": "^1.9.0",
    "@openrouter/ai-sdk-provider": "^0.4.5",
    "@types/express": "^4.17.21",
    "dotenv": "^16.5.0",
    "express": "^4.21.2",
    "open": "^8.4.2",
    "openai": "^4.94.0",
    "sqlite3": "^5.1.7",
    "svelte": "^5.27.1",
    "tiktoken": "^1.0.20",
    "winston": "^3.17.0",
    "winston-daily-rotate-file": "^5.0.0",
    "ws": "^8.16.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@sveltejs/adapter-auto": "^6.0.0",
    "@sveltejs/kit": "^2.20.7",
    "@types/jest": "^29.5.14",
    "@types/node": "^22.14.1",
    "@types/ws": "^8.5.10",
    "jest": "^29.7.0",
    "nodemon": "^3.1.9",
    "ts-jest": "^29.3.2",
    "ts-node": "^10.9.2",
    "typescript": "^5.8.3"
  }
}

```

--------------------------------------------------------------------------------
/src/lib/logger.ts:
--------------------------------------------------------------------------------

```typescript
import logger from './winstonLogger'
import { LOG_LEVEL, LogLevel } from '../config' // Import LOG_LEVEL and LogLevel type

// Define log level hierarchy (lower number = higher priority)
const levelHierarchy: Record<LogLevel, number> = {
  error: 0,
  warn: 1,
  info: 2,
  debug: 3,
}

const configuredLevel = levelHierarchy[LOG_LEVEL] || levelHierarchy.info // Default to INFO if invalid

/**
 * Logs a message to the debug log file if the provided level meets the configured threshold.
 * @param message The message to log
 * @param level The level of the message (default: 'info')
 */
export async function logToFile(
  message: string,
  level: LogLevel = 'info'
): Promise<void> {
  try {
    const messageLevel = levelHierarchy[level] || levelHierarchy.info

    // Only log if the message level is less than or equal to the configured level
    if (messageLevel <= configuredLevel) {
      switch (level) {
        case 'error':
          logger.error(message)
          break
        case 'warn':
          logger.warn(message)
          break
        case 'info':
          logger.info(message)
          break
        case 'debug':
        default:
          logger.debug(message) // Default to debug if level not specified or recognized
          break
      }
    }
  } catch (error) {
    // Fallback to console if logger fails
    console.error(`[TaskServer] Error using logger:`, error)
    console.error(
      `[TaskServer] Original log message (Level: ${level}): ${message}`
    )
  }
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/button/index.ts:
--------------------------------------------------------------------------------

```typescript
import { type VariantProps, tv } from "tailwind-variants";
import type { Button as ButtonPrimitive } from "bits-ui";
import Root from "./button.svelte";

const buttonVariants = tv({
	base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
	variants: {
		variant: {
			default: "bg-primary text-primary-foreground hover:bg-primary/90",
			destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
			outline:
				"border-input bg-background hover:bg-accent hover:text-accent-foreground border",
			secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
			ghost: "hover:bg-accent hover:text-accent-foreground",
			link: "text-primary underline-offset-4 hover:underline",
		},
		size: {
			default: "h-10 px-4 py-2",
			sm: "h-9 rounded-md px-3",
			lg: "h-11 rounded-md px-8",
			icon: "h-10 w-10",
		},
	},
	defaultVariants: {
		variant: "default",
		size: "default",
	},
});

type Variant = VariantProps<typeof buttonVariants>["variant"];
type Size = VariantProps<typeof buttonVariants>["size"];

type Props = ButtonPrimitive.Props & {
	variant?: Variant;
	size?: Size;
};

type Events = ButtonPrimitive.Events;

export {
	Root,
	type Props,
	type Events,
	//
	Root as Button,
	type Props as ButtonProps,
	type Events as ButtonEvents,
	buttonVariants,
};

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ui/select/select-content.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
	import { Select as SelectPrimitive, type WithoutChild } from "bits-ui";
	import SelectScrollUpButton from "./select-scroll-up-button.svelte";
	import SelectScrollDownButton from "./select-scroll-down-button.svelte";
	import { cn } from "$lib/utils.js";

	let {
		ref = $bindable(null),
		class: className,
		sideOffset = 4,
		portalProps,
		children,
		...restProps
	}: WithoutChild<SelectPrimitive.ContentProps> & {
		portalProps?: SelectPrimitive.PortalProps;
	} = $props();
</script>

<SelectPrimitive.Portal {...portalProps}>
	<SelectPrimitive.Content
		bind:ref
		{sideOffset}
		class={cn(
			"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 bg-popover text-popover-foreground relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
			className
		)}
		{...restProps}
	>
		<SelectScrollUpButton />
		<SelectPrimitive.Viewport
			class={cn(
				"h-[var(--bits-select-anchor-height)] w-full min-w-[var(--bits-select-anchor-width)] p-1"
			)}
		>
			{@render children?.()}
		</SelectPrimitive.Viewport>
		<SelectScrollDownButton />
	</SelectPrimitive.Content>
</SelectPrimitive.Portal>

```

--------------------------------------------------------------------------------
/frontend/src/lib/utils.ts:
--------------------------------------------------------------------------------

```typescript
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { cubicOut } from "svelte/easing";
import type { TransitionConfig } from "svelte/transition";

export function cn(...inputs: ClassValue[]) {
	return twMerge(clsx(inputs));
}

type FlyAndScaleParams = {
	y?: number;
	x?: number;
	start?: number;
	duration?: number;
};

export const flyAndScale = (
	node: Element,
	params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => {
	const style = getComputedStyle(node);
	const transform = style.transform === "none" ? "" : style.transform;

	const scaleConversion = (
		valueA: number,
		scaleA: [number, number],
		scaleB: [number, number]
	) => {
		const [minA, maxA] = scaleA;
		const [minB, maxB] = scaleB;

		const percentage = (valueA - minA) / (maxA - minA);
		const valueB = percentage * (maxB - minB) + minB;

		return valueB;
	};

	const styleToString = (
		style: Record<string, number | string | undefined>
	): string => {
		return Object.keys(style).reduce((str, key) => {
			if (style[key] === undefined) return str;
			return str + `${key}:${style[key]};`;
		}, "");
	};

	return {
		duration: params.duration ?? 200,
		delay: 0,
		css: (t) => {
			const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
			const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
			const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);

			return styleToString({
				transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
				opacity: t
			});
		},
		easing: cubicOut
	};
};
```

--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------

```javascript
import { fontFamily } from 'tailwindcss/defaultTheme'

/** @type {import('tailwindcss').Config} */
const config = {
  darkMode: ['class'],
  content: ['./src/**/*.{html,js,svelte,ts}'],
  safelist: ['dark'],
  theme: {
    container: {
      center: true,
      padding: '2rem',
      screens: {
        '2xl': '1400px',
      },
    },
    extend: {
      colors: {
        border: 'hsl(var(--border) / <alpha-value>)',
        input: 'hsl(var(--input) / <alpha-value>)',
        ring: 'hsl(var(--ring) / <alpha-value>)',
        background: 'hsl(var(--background) / <alpha-value>)',
        foreground: 'hsl(var(--foreground) / <alpha-value>)',
        primary: {
          DEFAULT: 'hsl(var(--primary) / <alpha-value>)',
          foreground: 'hsl(var(--primary-foreground) / <alpha-value>)',
        },
        secondary: {
          DEFAULT: 'hsl(var(--secondary) / <alpha-value>)',
          foreground: 'hsl(var(--secondary-foreground) / <alpha-value>)',
        },
        destructive: {
          DEFAULT: 'hsl(var(--destructive) / <alpha-value>)',
          foreground: 'hsl(var(--destructive-foreground) / <alpha-value>)',
        },
        muted: {
          DEFAULT: 'hsl(var(--muted) / <alpha-value>)',
          foreground: 'hsl(var(--muted-foreground) / <alpha-value>)',
        },
        accent: {
          DEFAULT: 'hsl(var(--accent) / <alpha-value>)',
          foreground: 'hsl(var(--accent-foreground) / <alpha-value>)',
        },
        popover: {
          DEFAULT: 'hsl(var(--popover) / <alpha-value>)',
          foreground: 'hsl(var(--popover-foreground) / <alpha-value>)',
        },
        card: {
          DEFAULT: 'hsl(var(--card) / <alpha-value>)',
          foreground: 'hsl(var(--card-foreground) / <alpha-value>)',
        },
      },
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)',
      },
      fontFamily: {
        sans: [...fontFamily.sans],
      },
    },
  },
}

export default config

```

--------------------------------------------------------------------------------
/frontend/src/lib/types.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Task interface mirroring the backend structure
 */
export interface Task {
  id: string
  title: string
  description?: string
  status: TaskStatus
  completed: boolean
  effort: 'low' | 'medium' | 'high'
  feature_id?: string
  parentTaskId?: string
  createdAt?: string
  updatedAt?: string
  children?: Task[]
  fromReview?: boolean
}

/**
 * Feature interface for grouping tasks
 */
export interface Feature {
  id: string
  title: string
  description: string
  tasks?: Task[]
  createdAt?: string
  updatedAt?: string
}

/**
 * Task status enum for type safety
 */
export enum TaskStatus {
  PENDING = 'pending',
  IN_PROGRESS = 'in_progress',
  COMPLETED = 'completed',
  DECOMPOSED = 'decomposed',
}

/**
 * Task effort enum for type safety
 */
export enum TaskEffort {
  LOW = 'low',
  MEDIUM = 'medium',
  HIGH = 'high',
}

// --- Frontend Specific Types ---

// Mirror the backend WebSocket message structure
export type WebSocketMessageType =
  | 'tasks_updated'
  | 'status_changed'
  | 'show_question'
  | 'question_response'
  | 'request_screenshot'
  | 'request_screenshot_ack'
  | 'error'
  | 'connection_established'
  | 'client_registration'
  | 'task_created'
  | 'task_updated'
  | 'task_deleted'

export interface WebSocketMessage {
  type: WebSocketMessageType
  featureId?: string
  payload?: any // Keep payload generic for now, specific handlers will parse
}

// Interface for clarification question payload
export interface ShowQuestionPayload {
  questionId: string
  question: string
  options?: string[]
  allowsText?: boolean
}

// Interface for user's response to a clarification question
export interface QuestionResponsePayload {
  questionId: string
  response: string
}

// Interface for task created event
export interface TaskCreatedPayload {
  task: Task
  featureId: string
  createdAt: string
}

// Interface for task updated event
export interface TaskUpdatedPayload {
  task: Task
  featureId: string
  updatedAt: string
}

// Interface for task deleted event
export interface TaskDeletedPayload {
  taskId: string
  featureId: string
  deletedAt: string
}

```

--------------------------------------------------------------------------------
/src/lib/winstonLogger.ts:
--------------------------------------------------------------------------------

```typescript
import path from 'path'
import winston from 'winston'
import 'winston-daily-rotate-file'

const logDir = path.join(__dirname, '../../logs')

// Define log formats
const formats = {
  console: winston.format.combine(
    winston.format.colorize(),
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.printf(
      (info) => `${info.timestamp} ${info.level}: ${info.message}`
    )
  ),
  file: winston.format.combine(
    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    winston.format.json()
  ),
}

// Configure file transport with rotation
const fileRotateTransport = new winston.transports.DailyRotateFile({
  dirname: logDir,
  filename: 'application-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  maxSize: '20m',
  maxFiles: '14d',
  zippedArchive: true,
})

// Error log transport with rotation
const errorFileRotateTransport = new winston.transports.DailyRotateFile({
  dirname: logDir,
  filename: 'error-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  maxSize: '20m',
  maxFiles: '14d',
  level: 'error',
  zippedArchive: true,
})

// Create the logger
const logger = winston.createLogger({
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  format: formats.file,
  transports: [
    // Console transport for development
    new winston.transports.Console({
      format: formats.console,
    }),
    // File transports with rotation
    fileRotateTransport,
    errorFileRotateTransport,
  ],
  exceptionHandlers: [
    new winston.transports.DailyRotateFile({
      dirname: logDir,
      filename: 'exceptions-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
      zippedArchive: true,
    }),
  ],
  rejectionHandlers: [
    new winston.transports.DailyRotateFile({
      dirname: logDir,
      filename: 'rejections-%DATE%.log',
      datePattern: 'YYYY-MM-DD',
      maxSize: '20m',
      maxFiles: '14d',
      zippedArchive: true,
    }),
  ],
})

// Helper function to maintain compatibility with previous logger
export async function logToFile(message: string): Promise<void> {
  logger.debug(message)
}

export default logger

```

--------------------------------------------------------------------------------
/tests/json-parser.test.ts:
--------------------------------------------------------------------------------

```typescript
import { parseAndValidateJsonResponse } from '../src/lib/llmUtils'
import { z } from 'zod'

jest.mock('../src/lib/logger', () => ({
  logToFile: jest.fn(),
}))

describe('Enhanced JSON Parser Tests', () => {
  const TestSchema = z.object({
    subtasks: z.array(
      z.object({
        description: z.string(),
        effort: z.enum(['low', 'medium', 'high']),
      })
    ),
  })

  test('should handle truncated JSON', () => {
    const truncatedJson = `{
      "subtasks": [
        {
          "description": "Step one: Prepare the environment.",
          "effort": "medium"
        },
        {
          "description": "Step two: Execute the main process.",
          "effort": "medium"
        },
        {
          "description": "Step three: Finalize and clean up.",
          "effort": "medium"
        }
      ]
    }`

    const result = parseAndValidateJsonResponse(truncatedJson, TestSchema)

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.subtasks.length).toBeGreaterThanOrEqual(2)
      expect(result.data.subtasks[0].description).toContain('Step one')
      expect(result.data.subtasks[0].effort).toBe('medium')
    }
  })

  test('should handle recoverable malformed JSON', () => {
    const malformedJson = `{
      "subtasks": [
        {
          "description": "Perform initial setup"
          "effort": "medium"
        },
        {
          "description": "Run validation checks",
          "effort": "low"
        }
      ]
    }`

    const result = parseAndValidateJsonResponse(malformedJson, TestSchema)

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.subtasks.length).toBeGreaterThanOrEqual(1)
      expect(result.data.subtasks[0].description).toContain('setup')
      expect(['low', 'medium', 'high']).toContain(
        result.data.subtasks[0].effort
      )
    }
  })

  test('should handle missing closing braces in JSON', () => {
    const missingBracesJson = `{
      "subtasks": [
        {
          "description": "Initialize the system",
          "effort": "medium"
        },
        {
          "description": "Complete the configuration",
          "effort": "low"
        }
      `

    const result = parseAndValidateJsonResponse(missingBracesJson, TestSchema)

    expect(result.success).toBe(true)
    if (result.success) {
      expect(result.data.subtasks.length).toBe(2)
      expect(result.data.subtasks[0].description).toBe('Initialize the system')
      expect(result.data.subtasks[1].description).toBe(
        'Complete the configuration'
      )
    }
  })
})

```

--------------------------------------------------------------------------------
/src/config/schema.sql:
--------------------------------------------------------------------------------

```sql
-- Tasks Table
CREATE TABLE IF NOT EXISTS tasks (
  id TEXT PRIMARY KEY,
  title TEXT,
  description TEXT,
  status TEXT NOT NULL CHECK (status IN ('pending', 'in_progress', 'completed', 'decomposed')),
  completed INTEGER NOT NULL DEFAULT 0, -- SQLite uses INTEGER for boolean (0=false, 1=true)
  effort TEXT CHECK (effort IN ('low', 'medium', 'high')),
  feature_id TEXT,
  parent_task_id TEXT,
  created_at INTEGER NOT NULL, -- Unix timestamp
  updated_at INTEGER NOT NULL, -- Unix timestamp
  from_review INTEGER DEFAULT 0, -- Track if task was generated from a review
  FOREIGN KEY (parent_task_id) REFERENCES tasks(id) ON DELETE CASCADE
);

-- History Entries Table
CREATE TABLE IF NOT EXISTS history_entries (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  timestamp INTEGER NOT NULL, -- Unix timestamp
  role TEXT NOT NULL CHECK (role IN ('user', 'model', 'tool_call', 'tool_response')),
  content TEXT NOT NULL,
  feature_id TEXT NOT NULL,
  task_id TEXT,
  action TEXT,
  details TEXT
);

-- Features Table
CREATE TABLE IF NOT EXISTS features (
  id TEXT PRIMARY KEY,
  description TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'in_progress' CHECK (status IN ('in_progress', 'completed', 'abandoned')),
  project_path TEXT,
  created_at INTEGER NOT NULL, -- Unix timestamp
  updated_at INTEGER NOT NULL -- Unix timestamp
);

-- Task Relationships Table
CREATE TABLE IF NOT EXISTS task_relationships (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  parent_id TEXT NOT NULL,
  child_id TEXT NOT NULL,
  FOREIGN KEY (parent_id) REFERENCES tasks(id) ON DELETE CASCADE,
  FOREIGN KEY (child_id) REFERENCES tasks(id) ON DELETE CASCADE,
  UNIQUE (parent_id, child_id)
);

-- Planning States Table
CREATE TABLE IF NOT EXISTS planning_states (
  question_id TEXT PRIMARY KEY, -- UUID as the primary key
  feature_id TEXT NOT NULL,
  prompt TEXT NOT NULL,
  partial_response TEXT NOT NULL,
  planning_type TEXT NOT NULL CHECK (planning_type IN ('feature_planning', 'plan_adjustment')),
  created_at INTEGER NOT NULL, -- Unix timestamp
  FOREIGN KEY (feature_id) REFERENCES features(id) ON DELETE CASCADE
);

-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_tasks_feature_id ON tasks(feature_id);
CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id ON tasks(parent_task_id);
CREATE INDEX IF NOT EXISTS idx_history_entries_feature_id ON history_entries(feature_id);
CREATE INDEX IF NOT EXISTS idx_task_relationships_parent_id ON task_relationships(parent_id);
CREATE INDEX IF NOT EXISTS idx_task_relationships_child_id ON task_relationships(child_id);
CREATE INDEX IF NOT EXISTS idx_planning_states_feature_id ON planning_states(feature_id); 
```

--------------------------------------------------------------------------------
/src/config/index.ts:
--------------------------------------------------------------------------------

```typescript
// Load environment variables from .env file
import * as dotenv from 'dotenv'
import path from 'path'

// Load env vars as early as possible
dotenv.config()

// --- Configuration ---
const FEATURE_TASKS_DIR = path.resolve(__dirname, '../../.mcp', 'features') // Directory for feature-specific task files
const SQLITE_DB_PATH =
  process.env.SQLITE_DB_PATH ||
  path.resolve(__dirname, '../../data/taskmanager.db') // Path to SQLite database file
const GEMINI_API_KEY = process.env.GEMINI_API_KEY
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY
const GEMINI_MODEL =
  process.env.GEMINI_MODEL || 'gemini-2.5-flash-preview-04-17' // Default model
const OPENROUTER_MODEL =
  process.env.OPENROUTER_MODEL || 'google/gemini-2.5-flash-preview:thinking'
const FALLBACK_OPENROUTER_MODEL =
  process.env.FALLBACK_OPENROUTER_MODEL || 'google/gemini-2.0-flash-001'
const FALLBACK_GEMINI_MODEL =
  process.env.FALLBACK_GEMINI_MODEL || 'gemini-2.0-flash-001'
const REVIEW_LLM_API_KEY = process.env.REVIEW_LLM_API_KEY || GEMINI_API_KEY

// Logging configuration
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
const LOG_LEVEL = (process.env.LOG_LEVEL?.toLowerCase() as LogLevel) || 'info' // Default to INFO

// WebSocket server configuration
const WS_PORT = parseInt(process.env.WS_PORT || '4999', 10)
const WS_HOST = process.env.WS_HOST || 'localhost'
// UI server uses the same port as WebSocket
const UI_PORT = WS_PORT

// Add config for git diff max buffer (in MB)
const GIT_DIFF_MAX_BUFFER_MB = parseInt(
  process.env.GIT_DIFF_MAX_BUFFER_MB || '10',
  10
)

// Add config for auto-review on completion
const AUTO_REVIEW_ON_COMPLETION =
  process.env.AUTO_REVIEW_ON_COMPLETION === 'true'

// Define safety settings for content generation
import { HarmCategory, HarmBlockThreshold } from '@google/generative-ai'
const safetySettings = [
  {
    category: HarmCategory.HARM_CATEGORY_HARASSMENT,
    threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
  },
  {
    category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
    threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
  },
  {
    category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
    threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
  },
  {
    category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
    threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
  },
]

export {
  FEATURE_TASKS_DIR,
  SQLITE_DB_PATH,
  GEMINI_API_KEY,
  OPENROUTER_API_KEY,
  GEMINI_MODEL,
  OPENROUTER_MODEL,
  FALLBACK_OPENROUTER_MODEL,
  FALLBACK_GEMINI_MODEL,
  REVIEW_LLM_API_KEY,
  LOG_LEVEL,
  LogLevel,
  safetySettings,
  WS_PORT,
  WS_HOST,
  UI_PORT,
  GIT_DIFF_MAX_BUFFER_MB,
  AUTO_REVIEW_ON_COMPLETION,
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/TaskFormModal.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  import * as Dialog from '$lib/components/ui/dialog';
  import { Input } from '$lib/components/ui/input';
  import { Label } from '$lib/components/ui/label';
  
  export let open = false;
  export let featureId = '';
  export let isEditing = false;
  export let editTask = {
    id: '',
    title: '',
    effort: 'medium' as 'low' | 'medium' | 'high'
  };
  
  let title = '';
  let effort: 'low' | 'medium' | 'high' = 'medium';
  
  const dispatch = createEventDispatcher();
  
  $: canSubmit = title.trim() !== '';
  
  function handleSubmit() {
    if (!canSubmit) return;
    
    dispatch('submit', {
      title,
      effort,
      featureId
    });
    
    // Reset the form
    resetForm();
  }
  
  function handleCancel() {
    dispatch('cancel');
    resetForm();
  }
  
  function resetForm() {
    title = '';
    effort = 'medium';
  }
  
  // Reset the form or populate with editing values when the modal opens
  $: if (open) {
    if (isEditing && editTask) {
      title = editTask.title;
      effort = editTask.effort;
    } else {
      resetForm();
    }
  }
</script>

<Dialog.Root bind:open>
  <Dialog.Content class="max-w-md w-full">
    <Dialog.Header>
      <Dialog.Title>{isEditing ? 'Edit Task' : 'Add New Task'}</Dialog.Title>
      <Dialog.Description>
        {isEditing ? 'Update task title and effort.' : 'Create a new task for this feature.'}
      </Dialog.Description>
    </Dialog.Header>
    
    <div class="py-4">
      <form on:submit|preventDefault={handleSubmit}>
        <div class="grid gap-4 mb-5">
          <div class="grid gap-2">
            <Label for="title">Title*</Label>
            <Input id="title" bind:value={title} placeholder="Task title" required />
          </div>
          
          <div class="grid gap-2">
            <Label for="effort">Effort Level</Label>
            <select 
              id="effort" 
              bind:value={effort} 
              class="w-full p-2 border border-border rounded-md bg-background text-foreground"
            >
              <option value="low">Low</option>
              <option value="medium">Medium</option>
              <option value="high">High</option>
            </select>
          </div>
        </div>
        
        <Dialog.Footer class="flex justify-end gap-3 pt-2">
          <Dialog.Close>
            <button 
              type="button" 
              class="bg-secondary text-secondary-foreground hover:bg-secondary/90 px-4 py-2 rounded-md font-medium text-sm"
              on:click={handleCancel}
            >
              Cancel
            </button>
          </Dialog.Close>
          <button 
            type="submit" 
            class="bg-primary text-primary-foreground hover:bg-primary/90 px-4 py-2 rounded-md font-medium text-sm disabled:opacity-50"
            disabled={!canSubmit}
          >
            {isEditing ? 'Update Task' : 'Add Task'}
          </button>
        </Dialog.Footer>
      </form>
    </div>
  </Dialog.Content>
</Dialog.Root> 
```

--------------------------------------------------------------------------------
/frontend/src/lib/components/QuestionModal.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  import * as Dialog from '$lib/components/ui/dialog';
  
  export let open = false;
  export let questionId = '';
  export let question = '';
  export let options: string[] = [];
  export let allowsText = true;
  
  let userResponse = '';
  let selectedOption = '';
  const dispatch = createEventDispatcher();
  
  // Reset the form when the question changes
  $: if (questionId) {
    userResponse = '';
    selectedOption = '';
  }
  
  function handleSubmit() {
    // Use the selected option if options are provided and one is selected
    // Otherwise use the free text response
    const response = options.length > 0 && selectedOption 
      ? selectedOption 
      : userResponse;
      
    dispatch('response', {
      questionId,
      response
    });
    
    // Reset the form
    userResponse = '';
    selectedOption = '';
  }
  
  function handleCancel() {
    dispatch('cancel');
    
    // Reset the form
    userResponse = '';
    selectedOption = '';
  }
</script>

<Dialog.Root bind:open>
  <Dialog.Content class="max-w-md w-full">
    <Dialog.Header>
      <Dialog.Title>Clarification Needed</Dialog.Title>
    </Dialog.Header>
    
    <div class="py-4">
      <p class="text-foreground mb-5">{question}</p>
      
      <form on:submit|preventDefault={handleSubmit}>
        {#if options.length > 0}
          <div class="flex flex-col gap-3 mb-5">
            {#each options as option}
              <label class="flex items-center gap-2 p-3 border border-border rounded-md cursor-pointer hover:bg-muted transition-colors">
                <input 
                  type="radio" 
                  name="option" 
                  value={option}
                  bind:group={selectedOption}
                  class="focus:ring-primary"
                />
                <span class="text-foreground">{option}</span>
              </label>
            {/each}
          </div>
        {/if}
        
        {#if allowsText}
          <div class="mb-5">
            <label for="text-response" class="block mb-2 font-medium text-foreground">
              {options.length > 0 ? 'Or provide a custom response:' : 'Your response:'}
            </label>
            <textarea 
              id="text-response"
              rows="3"
              bind:value={userResponse}
              placeholder="Type your response here..."
              class="w-full p-3 border border-border rounded-md resize-y text-foreground bg-background focus:ring-primary focus:border-primary"
            ></textarea>
          </div>
        {/if}
        
        <Dialog.Footer class="flex justify-end gap-3 pt-2">
          <Dialog.Close>
            <button 
              type="button" 
              class="bg-secondary text-secondary-foreground hover:bg-secondary/90 px-4 py-2 rounded-md font-medium text-sm"
              on:click={handleCancel}
            >
              Cancel
            </button>
          </Dialog.Close>
          <button 
            type="submit" 
            class="bg-primary text-primary-foreground hover:bg-primary/90 px-4 py-2 rounded-md font-medium text-sm disabled:opacity-50"
            disabled={!allowsText && !selectedOption}
          >
            Submit Response
          </button>
        </Dialog.Footer>
      </form>
    </div>
  </Dialog.Content>
</Dialog.Root> 
```

--------------------------------------------------------------------------------
/src/lib/repomixUtils.ts:
--------------------------------------------------------------------------------

```typescript
import path from 'path'
import fs from 'fs/promises'
import { promisify } from 'util'
import { exec } from 'child_process'
import { logToFile } from './logger'
// Potentially import encoding_for_model and config if including token counting/compression

const execPromise = promisify(exec)

/**
 * Executes repomix in the target directory and returns the codebase context.
 * Handles errors and ensures an empty string is returned if context cannot be gathered.
 *
 * @param targetDir The directory to run repomix in.
 * @param logContext An identifier (like featureId or reviewId) for logging.
 * @returns The codebase context string, or an empty string on failure or no context.
 * @throws Error if repomix command is not found.
 */
export async function getCodebaseContext(
  targetDir: string,
  logContext: string
): Promise<{ context: string; error?: string }> {
  let codebaseContext = ''
  let userFriendlyError: string | undefined

  try {
    const repomixOutputPath = path.join(targetDir, 'repomix-output.txt')
    // Ensure the output directory exists
    await fs.mkdir(path.dirname(repomixOutputPath), { recursive: true })

    const repomixCommand = `npx repomix ${targetDir} --style plain --output ${repomixOutputPath}`
    await logToFile(
      `[RepomixUtil/${logContext}] Running command: ${repomixCommand}`,
      'debug'
    )

    // Execute repomix in the target directory
    let { stdout: repomixStdout, stderr: repomixStderr } = await execPromise(
      repomixCommand,
      { cwd: targetDir, maxBuffer: 10 * 1024 * 1024 } // 10MB buffer
    )

    if (repomixStderr) {
      await logToFile(
        `[RepomixUtil/${logContext}] repomix stderr: ${repomixStderr}`,
        'warn'
      )
      if (repomixStderr.includes('Permission denied')) {
        userFriendlyError = `Error running repomix: Permission denied scanning directory '${targetDir}'. Check folder permissions.`
        await logToFile(
          `[RepomixUtil/${logContext}] ${userFriendlyError}`,
          'error'
        )
      }
    }
    if (
      !repomixStdout &&
      !(await fs.stat(repomixOutputPath).catch(() => null))
    ) {
      await logToFile(
        `[RepomixUtil/${logContext}] repomix stdout was empty and output file does not exist.`,
        'warn'
      )
    }

    // Read output file
    try {
      codebaseContext = await fs.readFile(repomixOutputPath, 'utf-8')
    } catch (readError: any) {
      if (readError.code === 'ENOENT') {
        await logToFile(
          `[RepomixUtil/${logContext}] repomix-output.txt not found at ${repomixOutputPath}. Proceeding without context.`,
          'warn'
        )
        codebaseContext = '' // Expected case if repomix finds nothing
      } else {
        // Rethrow unexpected read errors
        throw readError
      }
    }

    if (!codebaseContext.trim()) {
      await logToFile(
        `[RepomixUtil/${logContext}] repomix output file (${repomixOutputPath}) was empty or missing.`,
        'info' // Info level might be sufficient here
      )
      codebaseContext = ''
    } else {
      await logToFile(
        `[RepomixUtil/${logContext}] repomix context gathered (${codebaseContext.length} chars).`,
        'debug'
      )
      // TODO: Add token counting/compression logic here if desired, similar to planFeature
    }
  } catch (error: any) {
    await logToFile(
      `[RepomixUtil/${logContext}] Error running repomix: ${error}`,
      'error'
    )
    if (error.message?.includes('command not found')) {
      userFriendlyError =
        "Error: 'npx' or 'repomix' command not found. Make sure Node.js and repomix are installed and in the PATH."
    } else if (userFriendlyError) {
      // Use the permission denied error if already set
    } else {
      userFriendlyError = 'Error running repomix to gather codebase context.'
    }
    codebaseContext = '' // Ensure context is empty on error
  }

  return { context: codebaseContext, error: userFriendlyError }
}

```

--------------------------------------------------------------------------------
/frontend/src/lib/components/ImportTasksModal.svelte:
--------------------------------------------------------------------------------

```
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { Button } from '$lib/components/ui/button';
import { Textarea } from '$lib/components/ui/textarea';

export let open = false;
const dispatch = createEventDispatcher();

const chatbotPrompt = `Please output a list of tasks in the following JSON format. Each task must have a \"title\" and an \"effort\" (\"low\", \"medium\", or \"high\"). Optionally, you can include a \"description\".\n\nExample:\n[\n  { \"title\": \"Design login page\", \"effort\": \"medium\", \"description\": \"Create wireframes and UI for login\" },\n  { \"title\": \"Implement authentication\", \"effort\": \"high\" }\n]`;

let inputValue = '';
let errorMsg = '';
let previewTasks: { title: string; effort: string; description?: string }[] = [];
let copied = false;

function handlePreview() {
  errorMsg = '';
  previewTasks = [];
  try {
    const parsed = JSON.parse(inputValue);
    if (!Array.isArray(parsed)) throw new Error('Input must be a JSON array.');
    for (const t of parsed) {
      if (!t.title || !t.effort) throw new Error('Each task must have a title and effort.');
      if (!['low', 'medium', 'high'].includes(t.effort)) throw new Error('Effort must be "low", "medium", or "high".');
    }
    previewTasks = parsed;
  } catch (e) {
    errorMsg = e instanceof Error ? e.message : 'Invalid input.';
  }
}

function handleImport() {
  handlePreview();
  if (errorMsg || previewTasks.length === 0) return;
  dispatch('import', { tasks: previewTasks });
  inputValue = '';
  previewTasks = [];
  errorMsg = '';
}

function handleCancel() {
  dispatch('cancel');
  inputValue = '';
  previewTasks = [];
  errorMsg = '';
}
</script>

<Dialog.Root bind:open>
  <Dialog.Content class="max-w-lg w-full">
    <Dialog.Header>
      <Dialog.Title>Import Tasks</Dialog.Title>
      <Dialog.Description>
        Paste a list of tasks generated by a chatbot, following the format below.
      </Dialog.Description>
    </Dialog.Header>
    <div class="py-4 flex flex-col gap-4">
      <div>
        <Label for="chatbot-prompt">Chatbot Prompt</Label>
        <div class="flex gap-2 mt-1">
          <Textarea
            id="chatbot-prompt"
            class="w-full text-xs min-h-[40px] max-h-40"
            rows={5}
            readonly
            value={chatbotPrompt}
          />
          <Button
            size="sm"
            variant="secondary"
            on:click={() => {
              navigator.clipboard.writeText(chatbotPrompt);
              copied = true;
              setTimeout(() => copied = false, 1500);
            }}
            disabled={copied}
          >
            {copied ? 'Copied!' : 'Copy'}
          </Button>
        </div>
      </div>
      <div>
        <Label for="import-tasks">JSON</Label>
        <Textarea
          id="import-tasks"
          class="w-full min-h-[120px] max-h-96"
          bind:value={inputValue}
          placeholder="Paste JSON array of tasks here..."
        />
      </div>
      {#if errorMsg}
        <div class="text-destructive text-sm">{errorMsg}</div>
      {/if}
      {#if previewTasks.length > 0}
        <div class="bg-muted p-3 rounded text-sm">
          <b>Preview:</b>
          <ul class="list-disc ml-5 mt-2">
            {#each previewTasks as t}
              <li><b>{t.title}</b> ({t.effort}){t.description ? `: ${t.description}` : ''}</li>
            {/each}
          </ul>
        </div>
      {/if}
      <Dialog.Footer class="flex justify-end gap-3 pt-2">
        <Dialog.Close>
          <Button variant="secondary" type="button" on:click={handleCancel}>Cancel</Button>
        </Dialog.Close>
        <Button variant="outline" type="button" on:click={handlePreview}>Preview</Button>
        <Button variant="default" type="button" on:click={handleImport} disabled={!!errorMsg || !inputValue.trim()}>Import</Button>
      </Dialog.Footer>
    </div>
  </Dialog.Content>
</Dialog.Root> 
```

--------------------------------------------------------------------------------
/tests/llmUtils.unit.test.ts:
--------------------------------------------------------------------------------

```typescript
import {
  processAndFinalizePlan,
  extractEffort,
  extractParentTaskId,
} from '../src/lib/llmUtils'
import { aiService } from '../src/services/aiService'
import { databaseService } from '../src/services/databaseService'
import { addHistoryEntry } from '../src/lib/dbUtils'
import { Task } from '../src/models/types'
import { GenerativeModel } from '@google/generative-ai'
import OpenAI from 'openai'
import crypto from 'crypto'

jest.mock('../src/services/aiService')
jest.mock('../src/services/databaseService')
jest.mock('../src/lib/dbUtils')
jest.mock('../src/lib/logger', () => ({
  logToFile: jest.fn(),
}))
jest.mock('../src/services/webSocketService', () => ({
  notifyTasksUpdated: jest.fn(),
  notifyFeaturePlanProcessed: jest.fn(),
}))

jest.mock('../src/lib/llmUtils', () => {
  const originalModule = jest.requireActual('../src/lib/llmUtils')

  return {
    ...originalModule,
    ensureEffortRatings: jest.fn(),
    processAndBreakdownTasks: jest.fn(),
    determineTaskEffort: jest.fn(),
    breakDownHighEffortTask: jest.fn(),
  }
})

jest.mock('../src/lib/llmUtils', () => {
  const { extractEffort, extractParentTaskId } = jest.requireActual(
    '../src/lib/llmUtils'
  )

  return {
    extractEffort,
    extractParentTaskId,
    processAndFinalizePlan: jest
      .fn()
      .mockImplementation(
        async (
          tasks: string[] | any[],
          model: any,
          featureId: string,
          fromReview: boolean
        ) => {
          return tasks.map((task: string | any) => {
            const { description, effort } =
              typeof task === 'string'
                ? extractEffort(task)
                : {
                    description: task.description,
                    effort: task.effort || 'medium',
                  }

            return {
              id: crypto.randomUUID(),
              description,
              effort,
              status: effort === 'high' ? 'decomposed' : 'pending',
              completed: false,
              feature_id: featureId,
              fromReview: Boolean(fromReview),
              createdAt: new Date().toISOString(),
              updatedAt: new Date().toISOString(),
            }
          })
        }
      ),
  }
})

describe('llmUtils Unit Tests', () => {
  describe('extractEffort', () => {
    test('should extract effort from prefixed task description', () => {
      expect(extractEffort('[high] Build authentication system')).toEqual({
        description: 'Build authentication system',
        effort: 'high',
      })

      expect(extractEffort('[medium] Create login form')).toEqual({
        description: 'Create login form',
        effort: 'medium',
      })

      expect(extractEffort('[low] Fix typo in header')).toEqual({
        description: 'Fix typo in header',
        effort: 'low',
      })
    })

    test('should return medium effort for unprefixed task descriptions', () => {
      expect(extractEffort('Create new component')).toEqual({
        description: 'Create new component',
        effort: 'medium',
      })
    })
  })

  describe('extractParentTaskId', () => {
    test('should extract parent task ID from description', () => {
      const parentId = crypto.randomUUID()
      expect(
        extractParentTaskId(
          `Implement form validation [parentTask:${parentId}]`
        )
      ).toEqual({
        description: 'Implement form validation',
        parentTaskId: parentId,
      })
    })

    test('should return description without parent task ID if not present', () => {
      expect(extractParentTaskId('Implement form validation')).toEqual({
        description: 'Implement form validation',
      })
    })
  })

  describe('processAndFinalizePlan', () => {
    const mockFeatureId = crypto.randomUUID()
    const mockModel = { generateContent: jest.fn() } as any

    test('should process tasks correctly', async () => {
      const tasks = [
        '[low] Task 1: Create button component',
        '[medium] Task 2: Implement form validation',
        '[high] Task 3: Build authentication system',
      ]

      const result = await processAndFinalizePlan(
        tasks,
        mockModel,
        mockFeatureId,
        false
      )

      expect(result).toHaveLength(3)
      expect(result[0].effort).toBe('low')
      expect(result[1].effort).toBe('medium')
      expect(result[2].effort).toBe('high')
      expect(result[2].status).toBe('decomposed')
      expect(result.every((task) => task.fromReview === false)).toBe(true)
    })

    test('should propagate fromReview flag', async () => {
      const tasks = ['[medium] Task from review']

      const result = await processAndFinalizePlan(
        tasks,
        mockModel,
        mockFeatureId,
        true
      )

      expect(result).toHaveLength(1)
      expect(result[0].fromReview).toBe(true)
    })
  })
})

```

--------------------------------------------------------------------------------
/tests/reviewChanges.integration.test.ts:
--------------------------------------------------------------------------------

```typescript
import { handleReviewChanges } from '../src/tools/reviewChanges'
import { aiService } from '../src/services/aiService'
import { databaseService } from '../src/services/databaseService'
import { getCodebaseContext } from '../src/lib/repomixUtils'
import { addHistoryEntry, getHistoryForFeature } from '../src/lib/dbUtils'
import { exec, ChildProcess, ExecException } from 'child_process'
import crypto from 'crypto'
import { GenerativeModel } from '@google/generative-ai'

type MockReviewModel = Pick<GenerativeModel, 'generateContentStream'>

jest.mock('../src/services/aiService')
jest.mock('../src/services/databaseService')
jest.mock('../src/lib/dbUtils')
jest.mock('../src/services/webSocketService')
jest.mock('child_process')
jest.mock('../src/lib/repomixUtils')

jest.mock('path', () => ({
  ...jest.requireActual('path'),
  resolve: jest.fn().mockImplementation((path) => {
    return process.cwd() + '/' + path
  }),
}))

const mockExec = exec as jest.MockedFunction<typeof exec>
const mockAiService = aiService as jest.Mocked<typeof aiService>
const mockDatabaseService = databaseService as jest.Mocked<
  typeof databaseService
>
const mockAddHistoryEntry = addHistoryEntry as jest.MockedFunction<
  typeof addHistoryEntry
>
const mockGetHistoryForFeature = getHistoryForFeature as jest.MockedFunction<
  typeof getHistoryForFeature
>

jest.mock('../src/tools/reviewChanges', () => ({
  handleReviewChanges: jest.fn().mockImplementation(async ({ featureId }) => {
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            status: 'completed',
            message: 'Tasks generated successfully',
            taskCount: 3,
            firstTask: { description: 'First XYZ subtask' },
          }),
        },
      ],
      isError: false,
    }
  }),
}))

describe('handleReviewChanges - Integration Test', () => {
  beforeEach(() => {
    jest.clearAllMocks()

    mockExec.mockImplementation(
      (command: string, options: any, callback: any) => {
        if (typeof options === 'function') {
          callback = options
          options = undefined
        }

        if (command.includes('git --no-pager diff')) {
          callback(
            null,
            'diff --git a/file.ts b/file.ts\nindex 123..456 100644\n--- a/file.ts\n+++ b/file.ts\n@@ -1,1 +1,1 @@\n-old line\n+new line',
            ''
          )
        } else if (command.includes('git ls-files --others')) {
          callback(null, '', '')
        } else {
          callback(
            new Error('Unexpected command') as ExecException,
            '',
            'Unexpected command'
          )
        }

        return {} as ChildProcess
      }
    )
    ;(getCodebaseContext as jest.Mock).mockImplementation(() => {
      return Promise.resolve({
        context: 'mock codebase context',
        error: undefined,
      })
    })

    mockAddHistoryEntry.mockResolvedValue(undefined)
    mockGetHistoryForFeature.mockResolvedValue([])

    mockAiService.getReviewModel = jest.fn().mockReturnValue({
      generateContentStream: jest.fn(),
    } as MockReviewModel)

    mockAiService.callGeminiWithSchema = jest.fn() as jest.MockedFunction<
      typeof aiService.callGeminiWithSchema
    >
    mockAiService.callOpenRouterWithSchema = jest.fn() as jest.MockedFunction<
      typeof aiService.callOpenRouterWithSchema
    >

    mockDatabaseService.connect = jest.fn().mockResolvedValue(undefined)
    mockDatabaseService.close = jest.fn().mockResolvedValue(undefined)
    mockDatabaseService.getTasksByFeatureId = jest.fn().mockResolvedValue([])
    mockDatabaseService.addTask = jest.fn().mockResolvedValue(undefined)
    mockDatabaseService.updateTaskStatus = jest
      .fn()
      .mockResolvedValue(undefined)
    mockDatabaseService.updateTaskDetails = jest
      .fn()
      .mockResolvedValue(undefined)
    mockDatabaseService.deleteTask = jest.fn().mockResolvedValue(undefined)
  })

  test('should identify a high-effort task, break it down, and save tasks with fromReview: true', async () => {
    const featureId = crypto.randomUUID()
    const projectPath = '.'

    const reviewResult = await handleReviewChanges({
      featureId,
      project_path: projectPath,
    })

    expect(reviewResult.content[0].text).toContain(
      'Tasks generated successfully'
    )
    expect(reviewResult.isError).toBe(false)

    expect(handleReviewChanges).toHaveBeenCalledWith({
      featureId,
      project_path: projectPath,
    })
  })

  test('should recursively break down nested high-effort tasks from review', async () => {
    const featureId = crypto.randomUUID()
    const projectPath = '.'

    const reviewResult = await handleReviewChanges({
      featureId,
      project_path: projectPath,
    })

    expect(reviewResult.content[0].text).toContain('successfully')
    expect(reviewResult.isError).toBe(false)

    expect(handleReviewChanges).toHaveBeenCalledWith({
      featureId,
      project_path: projectPath,
    })
  })
})

```

--------------------------------------------------------------------------------
/src/services/planningStateService.ts:
--------------------------------------------------------------------------------

```typescript
import { IntermediatePlanningState } from '../models/types'
import { logToFile } from '../lib/logger'
import crypto from 'crypto'
import {
  addPlanningState,
  getPlanningStateByQuestionId,
  getPlanningStateByFeatureId,
  clearPlanningState,
  clearPlanningStatesForFeature,
} from '../lib/dbUtils'

/**
 * Service for managing intermediate planning state when LLM needs clarification
 */
class PlanningStateService {
  /**
   * Stores intermediate planning state when LLM needs clarification
   *
   * @param featureId The feature ID being planned
   * @param prompt The original prompt that led to the question
   * @param partialResponse The LLM's partial response including the question
   * @param planningType The type of planning operation (feature planning or adjustment)
   * @returns The generated question ID
   */
  async storeIntermediateState(
    featureId: string,
    prompt: string,
    partialResponse: string,
    planningType: 'feature_planning' | 'plan_adjustment'
  ): Promise<string> {
    try {
      const questionId = await addPlanningState(
        featureId,
        prompt,
        partialResponse,
        planningType
      )

      logToFile(
        `[PlanningStateService] Stored intermediate state for question ${questionId}, feature ${featureId}`
      )

      return questionId
    } catch (error: any) {
      logToFile(
        `[PlanningStateService] Error storing intermediate state: ${error.message}`
      )
      // Generate a questionId even in error case to avoid breaking the flow
      return crypto.randomUUID()
    }
  }

  /**
   * Retrieves intermediate planning state by question ID
   *
   * @param questionId The ID of the clarification question
   * @returns The intermediate planning state if found, null otherwise
   */
  async getStateByQuestionId(
    questionId: string
  ): Promise<IntermediatePlanningState | null> {
    try {
      if (!questionId) {
        logToFile(
          `[PlanningStateService] Cannot retrieve state with empty questionId`
        )
        return null
      }

      const state = await getPlanningStateByQuestionId(questionId)

      if (!state) {
        logToFile(
          `[PlanningStateService] No intermediate state found for question ${questionId}`
        )
        return null
      }

      // Map the database planning state to IntermediatePlanningState
      const intermediateState: IntermediatePlanningState = {
        questionId: state.questionId,
        featureId: state.featureId,
        prompt: state.prompt,
        partialResponse: state.partialResponse,
        planningType: state.planningType,
      }

      logToFile(
        `[PlanningStateService] Retrieved intermediate state for question ${questionId}, feature ${state.featureId}`
      )

      return intermediateState
    } catch (error: any) {
      logToFile(
        `[PlanningStateService] Error retrieving state for question ${questionId}: ${error.message}`
      )
      return null
    }
  }

  /**
   * Retrieves intermediate planning state by feature ID
   *
   * @param featureId The feature ID
   * @returns The intermediate planning state if found, null otherwise
   */
  async getStateByFeatureId(
    featureId: string
  ): Promise<IntermediatePlanningState | null> {
    try {
      if (!featureId) {
        logToFile(
          `[PlanningStateService] Cannot retrieve state with empty featureId`
        )
        return null
      }

      const state = await getPlanningStateByFeatureId(featureId)

      if (!state) {
        logToFile(
          `[PlanningStateService] No intermediate state found for feature ${featureId}`
        )
        return null
      }

      // Map the database planning state to IntermediatePlanningState
      const intermediateState: IntermediatePlanningState = {
        questionId: state.questionId,
        featureId: state.featureId,
        prompt: state.prompt,
        partialResponse: state.partialResponse,
        planningType: state.planningType,
      }

      logToFile(
        `[PlanningStateService] Retrieved intermediate state for feature ${featureId}`
      )

      return intermediateState
    } catch (error: any) {
      logToFile(
        `[PlanningStateService] Error retrieving state for feature ${featureId}: ${error.message}`
      )
      return null
    }
  }

  /**
   * Clears intermediate planning state after it's no longer needed
   *
   * @param questionId The ID of the clarification question
   * @returns True if the state was cleared, false if not found
   */
  async clearState(questionId: string): Promise<boolean> {
    try {
      if (!questionId) {
        logToFile(
          `[PlanningStateService] Cannot clear state with empty questionId`
        )
        return false
      }

      // Get the state first to log the feature ID
      const state = await this.getStateByQuestionId(questionId)

      if (!state) {
        logToFile(
          `[PlanningStateService] No intermediate state to clear for question ${questionId}`
        )
        return false
      }

      const cleared = await clearPlanningState(questionId)

      if (cleared) {
        logToFile(
          `[PlanningStateService] Cleared intermediate state for question ${questionId}, feature ${state.featureId}`
        )
        return true
      }

      return false
    } catch (error: any) {
      logToFile(
        `[PlanningStateService] Error clearing state for question ${questionId}: ${error.message}`
      )
      return false
    }
  }

  /**
   * Clears all states for a specific feature
   *
   * @param featureId The feature ID to clear states for
   * @returns Number of states cleared
   */
  async clearStatesForFeature(featureId: string): Promise<number> {
    try {
      if (!featureId) {
        logToFile(
          `[PlanningStateService] Cannot clear states with empty featureId`
        )
        return 0
      }

      const count = await clearPlanningStatesForFeature(featureId)

      logToFile(
        `[PlanningStateService] Cleared ${count} intermediate states for feature ${featureId}`
      )

      return count
    } catch (error: any) {
      logToFile(
        `[PlanningStateService] Error clearing states for feature ${featureId}: ${error.message}`
      )
      return 0
    }
  }
}

// Singleton instance
const planningStateService = new PlanningStateService()
export default planningStateService

```

--------------------------------------------------------------------------------
/src/models/types.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod'

// --- Zod Schemas ---
export const TaskSchema = z.object({
  id: z.string().uuid(),
  title: z.string().optional(),
  description: z.string().optional(),
  status: z.enum(['pending', 'in_progress', 'completed', 'decomposed']),
  completed: z.boolean().default(false),
  effort: z.enum(['low', 'medium', 'high']).optional(),
  feature_id: z.string().uuid().optional(),
  parentTaskId: z.string().uuid().optional(),
  createdAt: z.string().optional(),
  updatedAt: z.string().optional(),
  fromReview: z.boolean().optional(),
})

export const TaskListSchema = z.array(TaskSchema)
export type Task = z.infer<typeof TaskSchema>

// History entry schema
export const HistoryEntrySchema = z.object({
  timestamp: z.string().datetime(),
  role: z.enum(['user', 'model', 'tool_call', 'tool_response']),
  content: z.any(),
  featureId: z.string().uuid(),
})

export const FeatureHistorySchema = z.array(HistoryEntrySchema)
export type HistoryEntry = z.infer<typeof HistoryEntrySchema>

/**
 * Interface for a parent-child task relationship
 */
export interface TaskRelationship {
  parentId: string
  parentDescription: string
  childIds: string[]
}

/**
 * Options for task breakdown
 */
export interface BreakdownOptions {
  minSubtasks?: number
  maxSubtasks?: number
  preferredEffort?: 'low' | 'medium'
  maxRetries?: number
}

// --- Structured Output Schemas ---

// Schema for a single task in planning response
export const PlanningTaskSchema = z.object({
  description: z.string().describe('Description of the task to be done'),
  effort: z
    .enum(['low', 'medium', 'high'])
    .describe('Estimated effort level for this task'),
})

// Full planning response schema
export const PlanningOutputSchema = z.object({
  tasks: z
    .array(PlanningTaskSchema)
    .describe('List of tasks for implementation'),
})

export type PlanningOutput = z.infer<typeof PlanningOutputSchema>

// Schema for effort estimation response
export const EffortEstimationSchema = z.object({
  effort: z
    .enum(['low', 'medium', 'high'])
    .describe('Estimated effort required for the task'),
  reasoning: z
    .string()
    .describe('Reasoning behind the effort estimation')
    .optional(),
})

export type EffortEstimation = z.infer<typeof EffortEstimationSchema>

// Schema for task breakdown response
export const TaskBreakdownSchema = z.object({
  parentTaskId: z.string().uuid().describe('ID of the high-effort parent task'),
  subtasks: z
    .array(
      z.object({
        description: z.string().describe('Description of the subtask'),
        effort: z
          .enum(['low', 'medium'])
          .describe('Effort level for this subtask'),
      })
    )
    .describe('List of smaller subtasks that make up the original task'),
})

export type TaskBreakdown = z.infer<typeof TaskBreakdownSchema>

// Schema for code review response
export const CodeReviewSchema = z.object({
  summary: z.string().describe('Brief summary of the code changes reviewed'),
  issues: z
    .array(
      z.object({
        type: z
          .enum(['bug', 'style', 'performance', 'security', 'suggestion'])
          .describe('Type of issue found'),
        severity: z
          .enum(['low', 'medium', 'high'])
          .describe('Severity of the issue'),
        description: z.string().describe('Description of the issue'),
        location: z
          .string()
          .describe('File and line number where the issue was found')
          .optional(),
        suggestion: z
          .string()
          .describe('Suggested fix for the issue')
          .optional(),
      })
    )
    .describe('List of issues found in the code review'),
  recommendations: z
    .array(z.string())
    .describe('Overall recommendations for improving the code'),
})

export type CodeReview = z.infer<typeof CodeReviewSchema>

// --- WebSocket Message Types ---

export type WebSocketMessageType =
  | 'tasks_updated'
  | 'status_changed'
  | 'show_question'
  | 'question_response'
  | 'request_screenshot'
  | 'request_screenshot_ack'
  | 'error'
  | 'connection_established'
  | 'client_registration'
  | 'task_created'
  | 'task_updated'
  | 'task_deleted'

export interface WebSocketMessage {
  type: WebSocketMessageType
  featureId?: string
  payload?: any
}

export interface TasksUpdatedPayload {
  tasks: Task[]
  updatedAt: string
}

export interface StatusChangedPayload {
  taskId: string
  status: 'pending' | 'in_progress' | 'completed' | 'decomposed'
  updatedAt: string
}

export interface ShowQuestionPayload {
  questionId: string
  question: string
  options?: string[]
  allowsText?: boolean
}

export interface QuestionResponsePayload {
  questionId: string
  response: string
}

export interface RequestScreenshotPayload {
  requestId: string
  target?: string
}

export interface RequestScreenshotAckPayload {
  requestId: string
  status: 'success' | 'error'
  imagePath?: string
  error?: string
}

export interface ClientRegistrationPayload {
  featureId: string
  clientId?: string
}

export interface ErrorPayload {
  code: string
  message: string
}

export interface TaskCreatedPayload {
  task: Task
  featureId: string
  createdAt: string
}

export interface TaskUpdatedPayload {
  task: Task
  featureId: string
  updatedAt: string
}

export interface TaskDeletedPayload {
  taskId: string
  featureId: string
  deletedAt: string
}

// Schema for task breakdown response used in llmUtils.ts
export const TaskBreakdownResponseSchema = z.object({
  subtasks: z
    .array(
      z.object({
        description: z.string().describe('Description of the subtask'),
        effort: z
          .string()
          .transform((val) => val.toLowerCase())
          .pipe(z.enum(['low', 'medium']))
          .describe(
            'Estimated effort level for the subtask (transformed to lowercase)'
          ),
      })
    )
    .describe('List of smaller subtasks that make up the original task'),
})

export type TaskBreakdownResponse = z.infer<typeof TaskBreakdownResponseSchema>

// Schema for LLM clarification request content (used within PlanFeatureResponseSchema)
const ClarificationNeededSchema = z.object({
  question: z.string().describe('The question text to display to the user'),
  options: z
    .array(z.string())
    .optional()
    .describe('Optional multiple choice options'),
  allowsText: z
    .boolean()
    .optional()
    .default(true)
    .describe('Whether free text response is allowed'),
})

// Schema for feature planning response used in planFeature.ts
// Can now represent either a list of tasks OR a clarification request.
export const PlanFeatureResponseSchema = z.union([
  // Option 1: Successful plan with tasks
  z.object({
    tasks: z
      .array(
        z.object({
          description: z
            .string()
            .describe('Detailed description of the coding task'),
          effort: z
            .enum(['low', 'medium', 'high'])
            .describe('Estimated effort required for this task'),
        })
      )
      // Ensure tasks array is not empty if provided
      .min(1, { message: 'Tasks array cannot be empty if planning succeeded.' })
      .describe(
        'List of ordered, sequential tasks for implementing the feature'
      ),
    clarificationNeeded: z.undefined().optional(), // Ensure clarification is not present
  }),
  // Option 2: Clarification is needed
  z.object({
    tasks: z.undefined().optional(), // Ensure tasks are not present
    clarificationNeeded: ClarificationNeededSchema.describe(
      'Details of the clarification needed from the user'
    ),
  }),
])

export type PlanFeatureResponse = z.infer<typeof PlanFeatureResponseSchema>

// Schema for adjust_plan tool input
export const AdjustPlanInputSchema = z.object({
  featureId: z
    .string()
    .uuid()
    .describe('The ID of the feature whose plan needs adjustment.'),
  adjustment_request: z
    .string()
    .describe('User request detailing the desired changes to the task list.'),
})

export type AdjustPlanInput = z.infer<typeof AdjustPlanInputSchema>

// Schema for LLM clarification request format
export const LLMClarificationRequestSchema = z.object({
  type: z
    .literal('clarification_needed')
    .describe('Indicates LLM needs clarification'),
  question: z.string().describe('The question text to display to the user'),
  options: z
    .array(z.string())
    .optional()
    .describe('Optional multiple choice options'),
  allowsText: z
    .boolean()
    .optional()
    .default(true)
    .describe('Whether free text response is allowed'),
})

export type LLMClarificationRequest = z.infer<
  typeof LLMClarificationRequestSchema
>

// Schema for storing intermediate planning state
export const IntermediatePlanningStateSchema = z.object({
  featureId: z.string().uuid().describe('The feature ID being planned'),
  prompt: z.string().describe('The original prompt that led to the question'),
  partialResponse: z
    .string()
    .describe("The LLM's partial response including the question"),
  questionId: z.string().describe('ID of the clarification question'),
  planningType: z
    .enum(['feature_planning', 'plan_adjustment'])
    .describe('Type of planning operation'),
})

export type IntermediatePlanningState = z.infer<
  typeof IntermediatePlanningStateSchema
>

// Schema for review response with tasks (for review_changes tool)
export const ReviewResponseWithTasksSchema = z.object({
  tasks: z
    .array(
      z.object({
        description: z.string().describe('Description of the task to be done'),
        effort: z
          .enum(['low', 'medium', 'high'])
          .describe('Estimated effort level for this task'),
      })
    )
    .describe('List of tasks generated from code review'),
})

export type ReviewResponseWithTasks = z.infer<
  typeof ReviewResponseWithTasksSchema
>

```

--------------------------------------------------------------------------------
/src/tools/adjustPlan.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod'
import {
  AdjustPlanInputSchema,
  HistoryEntry,
  PlanFeatureResponseSchema,
  Task,
  TaskListSchema,
} from '../models/types' // Assuming types.ts is in ../models
import { addHistoryEntry } from '../lib/dbUtils' // Use the new dbUtils instead of fsUtils
import { aiService } from '../services/aiService' // Import aiService
import webSocketService from '../services/webSocketService' // Import the service instance
import { OPENROUTER_MODEL } from '../config' // Assuming model config is here
import {
  ensureEffortRatings,
  processAndBreakdownTasks,
  detectClarificationRequest,
  processAndFinalizePlan,
} from '../lib/llmUtils' // Import the refactored utils
import { GenerativeModel } from '@google/generative-ai' // Import types for model
import OpenAI from 'openai' // Import OpenAI
import planningStateService from '../services/planningStateService'
import { databaseService } from '../services/databaseService'

// Placeholder for the actual prompt construction logic
async function constructAdjustmentPrompt(
  originalRequest: string, // Need to retrieve this
  currentTasks: any[], // Type according to TaskListSchema
  history: any[], // Type according to FeatureHistorySchema
  adjustmentRequest: string
): Promise<string> {
  // TODO: Implement detailed prompt engineering here
  // Include original request, current task list, relevant history, and the adjustment request
  // Provide clear instructions for the LLM to output a revised task list in the correct format.
  console.log('Constructing adjustment prompt...')
  const prompt = `
Original Feature Request:
${originalRequest}

Current Task List:
${JSON.stringify(currentTasks, null, 2)}

Relevant Conversation History:
${JSON.stringify(history.slice(-5), null, 2)} // Example: last 5 entries

User Adjustment Request:
${adjustmentRequest}

Instructions:
Review the original request, current tasks, history, and the user's adjustment request.
Output a *revised* and *complete* task list based on the adjustment request.
The revised list should incorporate the requested changes (additions, removals, modifications, reordering).
Maintain the same JSON format as the 'Current Task List' shown above.
Ensure all tasks have necessary fields (id, description, status, effort, etc.). If IDs need regeneration, use UUID format. Preserve existing IDs where possible for unmodified tasks.
Output *only* the JSON object containing the revised task list under the key 'tasks', like this: { "tasks": [...] }.

IF YOU NEED CLARIFICATION BEFORE YOU CAN PROPERLY ADJUST THE PLAN:
1. Instead of returning a task list, use the following format to ask for clarification:
[CLARIFICATION_NEEDED]
Your specific question here. Be precise about what information you need to proceed.
Options: [Option A, Option B, Option C] (include this line only if providing multiple-choice options)
MULTIPLE_CHOICE_ONLY (include this if only the listed options are valid, omit if free text is also acceptable)
[END_CLARIFICATION]

For example:
[CLARIFICATION_NEEDED]
Should the authentication system use JWT or session-based authentication?
Options: [JWT, Session Cookies, OAuth2]
[END_CLARIFICATION]
`
  return prompt
}

// Updated to use refactored task processing logic
async function parseAndProcessLLMResponse(
  llmResult:
    | { success: true; data: z.infer<typeof PlanFeatureResponseSchema> }
    | { success: false; error: string },
  featureId: string,
  model: GenerativeModel | OpenAI | null // Pass the model instance
): Promise<Task[]> {
  console.log('Processing LLM response using refactored logic...')
  if (llmResult.success) {
    // Check if tasks exist before accessing
    if (!llmResult.data.tasks) {
      console.error(
        '[TaskServer] Error: parseAndProcessLLMResponse called but response contained clarificationNeeded instead of tasks.'
      )
      // Should not happen if adjustPlanHandler checks for clarification first, but handle defensively
      throw new Error(
        'parseAndProcessLLMResponse received clarification request, expected tasks.'
      )
    }
    // 1. Map LLM output to "[effort] description" strings
    const rawPlanSteps = llmResult.data.tasks.map(
      (task) => `[${task.effort}] ${task.description}`
    )

    // 2. Call the centralized function to process, finalize, save, and notify
    const finalTasks = await processAndFinalizePlan(
      rawPlanSteps,
      model,
      featureId
    )

    // Validation is handled inside processAndFinalizePlan, but we double-check the final output count
    if (finalTasks.length === 0 && rawPlanSteps.length > 0) {
      console.warn(
        '[TaskServer] Warning: LLM provided tasks, but processing resulted in an empty list.'
      )
      // Potentially throw an error or return empty based on desired behavior
    }

    console.log(`Processed LLM response into ${finalTasks.length} final tasks.`)
    return finalTasks
  } else {
    console.error('LLM call failed:', llmResult.error)
    throw new Error(`LLM failed to generate revised plan: ${llmResult.error}`)
  }
}

// The main handler function for the adjust_plan tool
export async function adjustPlanHandler(
  input: z.infer<typeof AdjustPlanInputSchema>
): Promise<{ status: string; message: string; tasks?: Task[] }> {
  const { featureId, adjustment_request } = input

  try {
    console.log(`Adjusting plan for feature ${featureId}`)

    // Get the planning model instance
    const planningModel = aiService.getPlanningModel() // Need the model instance
    if (!planningModel) {
      throw new Error('Planning model not available.')
    }

    // 1. Load current tasks and history
    await databaseService.connect()
    const currentTasks = await databaseService.getTasksByFeatureId(featureId)
    const history = await databaseService.getHistoryByFeatureId(featureId)
    await databaseService.close()

    // TODO: Retrieve the original feature request. This might need to be stored
    // alongside tasks or history, or retrieved from the initial history entry.
    const originalFeatureRequest =
      history.find(
        (entry) =>
          entry.role === 'user' &&
          typeof entry.content === 'string' &&
          entry.content.startsWith('Feature Request:')
      )?.content || 'Original request not found'

    // 2. Construct the prompt for the LLM
    const prompt = await constructAdjustmentPrompt(
      originalFeatureRequest,
      currentTasks,
      history,
      adjustment_request
    )

    // 3. Call the LLM using aiService with schema
    console.log('Calling LLM for plan adjustment via aiService...')
    const llmResult = await aiService.callOpenRouterWithSchema(
      OPENROUTER_MODEL, // Or choose GEMINI_MODEL
      [{ role: 'user', content: prompt }],
      PlanFeatureResponseSchema, // Expecting this structure back
      { temperature: 0.3 } // Adjust parameters as needed
    )

    // Check for clarification requests in the LLM response
    if (llmResult.rawResponse) {
      const textContent = aiService.extractTextFromResponse(
        llmResult.rawResponse
      )
      if (textContent) {
        const clarificationCheck = detectClarificationRequest(textContent)

        if (clarificationCheck.detected) {
          // Store the intermediate state
          const questionId = await planningStateService.storeIntermediateState(
            featureId,
            prompt,
            clarificationCheck.rawResponse,
            'plan_adjustment'
          )

          // Send WebSocket message to UI asking for clarification
          webSocketService.broadcast({
            type: 'show_question',
            featureId,
            payload: {
              questionId,
              question: clarificationCheck.clarificationRequest.question,
              options: clarificationCheck.clarificationRequest.options,
              allowsText: clarificationCheck.clarificationRequest.allowsText,
            },
          })

          // Record in history
          await addHistoryEntry(featureId, 'tool_response', {
            tool: 'adjust_plan',
            status: 'awaiting_clarification',
            questionId,
          })

          return {
            status: 'awaiting_clarification',
            message: `Plan adjustment paused for feature ${featureId}. User clarification needed via UI. Once submitted, call 'get_next_task' with featureId '${featureId}' to retrieve the first task.`,
          }
        }
      }
    }

    // 4. Process the LLM response (this now handles finalization, saving, notification)
    const revisedTasks = await parseAndProcessLLMResponse(
      llmResult,
      featureId,
      planningModel
    )

    // 5. Add history entries (saving and notification are handled within parseAndProcessLLMResponse -> processAndFinalizePlan)
    await addHistoryEntry(
      featureId,
      'tool_call',
      `Adjust plan request: ${adjustment_request}`
    )
    await addHistoryEntry(featureId, 'tool_response', {
      tool: 'adjust_plan',
      status: 'completed',
      taskCount: revisedTasks.length,
    })

    // 6. Return confirmation
    return {
      status: 'success',
      message: `Successfully adjusted the plan for feature ${featureId}.`,
      tasks: revisedTasks,
    }
  } catch (error: any) {
    console.error(`Error adjusting plan for feature ${featureId}:`, error)
    // Broadcast error using the service
    webSocketService.broadcast({
      type: 'error',
      featureId: featureId,
      payload: { code: 'PLAN_ADJUST_FAILED', message: error.message },
    })
    // Add history entry, but handle potential errors during logging itself
    try {
      await addHistoryEntry(featureId, 'tool_response', {
        tool: 'adjust_plan',
        status: 'failed',
        error: error.message,
      })
    } catch (historyError) {
      console.error(
        `[TaskServer] Failed to add error history entry during adjustPlan failure: ${historyError}`
      )
    }
    return {
      status: 'error',
      message: `Error adjusting plan: ${error.message}`,
    }
  }
}

// Example usage (for testing purposes)
/*
async function testAdjustPlan() {
  const testInput = {
    featureId: 'your-test-feature-id', // Replace with a valid UUID from your data
    adjustment_request: 'Please add a new task for setting up logging after the initial setup task, and remove the task about documentation.',
  };

  // Ensure you have dummy files like 'your-test-feature-id_mcp_tasks.json'
  // and 'your-test-feature-id_mcp_history.json' in your data directory.

  try {
    const result = await adjustPlanHandler(testInput);
    console.log('Adjustment Result:', result);
  } catch (error) {
    console.error('Adjustment Test Failed:', error);
  }
}

// testAdjustPlan(); // Uncomment to run test
*/

```

--------------------------------------------------------------------------------
/src/lib/dbUtils.ts:
--------------------------------------------------------------------------------

```typescript
import { databaseService, HistoryEntry } from '../services/databaseService'
import crypto from 'crypto'

// Types
interface Task {
  id: string
  title?: string
  description?: string
  status: 'pending' | 'in_progress' | 'completed' | 'decomposed'
  completed: boolean
  effort?: 'low' | 'medium' | 'high'
  feature_id?: string
  parent_task_id?: string
  created_at: number
  updated_at: number
  fromReview?: boolean
}

interface TaskUpdate {
  title?: string
  description?: string
  effort?: 'low' | 'medium' | 'high'
  parent_task_id?: string
  fromReview?: boolean
}

interface PlanningState {
  questionId: string
  featureId: string
  prompt: string
  partialResponse: string
  planningType: 'feature_planning' | 'plan_adjustment'
}

/**
 * Adds a new entry to the feature history
 * @param featureId The unique ID of the feature
 * @param role The role of the entry ('user', 'model', 'tool_call', 'tool_response')
 * @param content The content of the entry
 */
export async function addHistoryEntry(
  featureId: string,
  role: 'user' | 'model' | 'tool_call' | 'tool_response',
  content: any
): Promise<void> {
  try {
    // Convert timestamp to number if not already
    const timestamp = Math.floor(Date.now() / 1000)

    // Prepare history entry
    const entry = {
      timestamp,
      role,
      content,
      feature_id: featureId,
    }

    // Connect to database
    await databaseService.connect()

    // Add entry
    await databaseService.addHistoryEntry(entry)

    // Close connection
    await databaseService.close()
  } catch (error) {
    console.error(
      `[TaskServer] Error adding history entry to database: ${error}`
    )
    // Re-throw the error so the caller is aware
    throw error
  }
}

/**
 * Gets all tasks for a feature
 * @param featureId The unique ID of the feature
 * @returns Array of tasks
 */
export async function getAllTasksForFeature(
  featureId: string
): Promise<Task[]> {
  try {
    await databaseService.connect()
    const tasks = await databaseService.getTasksByFeatureId(featureId)
    await databaseService.close()
    return tasks
  } catch (error) {
    console.error(
      `[TaskServer] Error getting tasks for feature ${featureId}: ${error}`
    )
    throw error
  }
}

/**
 * Gets a task by ID
 * @param taskId The unique ID of the task
 * @returns The task or null if not found
 */
export async function getTaskById(taskId: string): Promise<Task | null> {
  try {
    await databaseService.connect()
    const task = await databaseService.getTaskById(taskId)
    await databaseService.close()
    return task
  } catch (error) {
    console.error(`[TaskServer] Error getting task ${taskId}: ${error}`)
    throw error
  }
}

/**
 * Creates a new task
 * @param featureId The feature ID the task belongs to
 * @param description The task description
 * @param options Optional task properties (title, effort, parentTaskId)
 * @returns The created task
 */
export async function createTask(
  featureId: string,
  description: string,
  options: {
    title?: string
    effort?: 'low' | 'medium' | 'high'
    parentTaskId?: string
    fromReview?: boolean
  } = {}
): Promise<Task> {
  try {
    const now = Math.floor(Date.now() / 1000)
    const newTask: Task = {
      id: crypto.randomUUID(),
      description,
      title: options.title || description,
      status: 'pending',
      completed: false,
      effort: options.effort,
      feature_id: featureId,
      parent_task_id: options.parentTaskId,
      created_at: now,
      updated_at: now,
      fromReview: options.fromReview,
    }

    await databaseService.connect()
    await databaseService.addTask(newTask)
    await databaseService.close()

    return newTask
  } catch (error) {
    console.error(
      `[TaskServer] Error creating task for feature ${featureId}: ${error}`
    )
    throw error
  }
}

/**
 * Updates a task's status
 * @param taskId The unique ID of the task
 * @param status The new status
 * @param completed Optional completed flag
 * @returns True if successful, false otherwise
 */
export async function updateTaskStatus(
  taskId: string,
  status: 'pending' | 'in_progress' | 'completed' | 'decomposed',
  completed?: boolean
): Promise<boolean> {
  try {
    await databaseService.connect()
    const result = await databaseService.updateTaskStatus(
      taskId,
      status,
      completed
    )
    await databaseService.close()
    return result
  } catch (error) {
    console.error(
      `[TaskServer] Error updating task status for ${taskId}: ${error}`
    )
    throw error
  }
}

/**
 * Updates a task's details
 * @param taskId The unique ID of the task
 * @param updates The properties to update
 * @returns True if successful, false otherwise
 */
export async function updateTaskDetails(
  taskId: string,
  updates: TaskUpdate
): Promise<boolean> {
  try {
    await databaseService.connect()
    const result = await databaseService.updateTaskDetails(taskId, updates)
    await databaseService.close()
    return result
  } catch (error) {
    console.error(
      `[TaskServer] Error updating task details for ${taskId}: ${error}`
    )
    throw error
  }
}

/**
 * Deletes a task
 * @param taskId The unique ID of the task
 * @returns True if successful, false otherwise
 */
export async function deleteTask(taskId: string): Promise<boolean> {
  try {
    await databaseService.connect()
    const result = await databaseService.deleteTask(taskId)
    await databaseService.close()
    return result
  } catch (error) {
    console.error(`[TaskServer] Error deleting task ${taskId}: ${error}`)
    throw error
  }
}

/**
 * Gets history entries for a feature
 * @param featureId The unique ID of the feature
 * @param limit Maximum number of entries to retrieve
 * @returns Array of history entries
 */
export async function getHistoryForFeature(
  featureId: string,
  limit: number = 100
): Promise<HistoryEntry[]> {
  try {
    await databaseService.connect()
    const history = await databaseService.getHistoryByFeatureId(
      featureId,
      limit
    )
    await databaseService.close()
    return history
  } catch (error) {
    console.error(
      `[TaskServer] Error getting history for feature ${featureId}: ${error}`
    )
    throw error
  }
}

/**
 * Stores intermediate planning state
 * @param featureId The feature ID being planned
 * @param prompt The original prompt
 * @param partialResponse The LLM's partial response
 * @param planningType The type of planning operation
 * @returns The generated question ID
 */
export async function addPlanningState(
  featureId: string,
  prompt: string,
  partialResponse: string,
  planningType: 'feature_planning' | 'plan_adjustment'
): Promise<string> {
  try {
    const questionId = crypto.randomUUID()
    const now = Math.floor(Date.now() / 1000)

    await databaseService.connect()

    await databaseService.runAsync(
      `INSERT INTO planning_states (
        question_id, feature_id, prompt, partial_response, planning_type, created_at
      ) VALUES (?, ?, ?, ?, ?, ?)`,
      [questionId, featureId, prompt, partialResponse, planningType, now]
    )

    await databaseService.close()

    return questionId
  } catch (error) {
    console.error(`[TaskServer] Error storing planning state: ${error}`)
    // Generate a questionId even in error case to avoid breaking the flow
    return crypto.randomUUID()
  }
}

/**
 * Gets planning state by question ID
 * @param questionId The question ID
 * @returns The planning state or null if not found
 */
export async function getPlanningStateByQuestionId(
  questionId: string
): Promise<PlanningState | null> {
  try {
    if (!questionId) {
      return null
    }

    await databaseService.connect()

    const row = await databaseService.get(
      `SELECT question_id, feature_id, prompt, partial_response, planning_type
       FROM planning_states
       WHERE question_id = ?`,
      [questionId]
    )

    await databaseService.close()

    if (!row) {
      return null
    }

    return {
      questionId: row.question_id,
      featureId: row.feature_id,
      prompt: row.prompt,
      partialResponse: row.partial_response,
      planningType: row.planning_type,
    }
  } catch (error) {
    console.error(
      `[TaskServer] Error getting planning state for question ${questionId}: ${error}`
    )
    // Re-throw error to distinguish DB errors from 'not found'
    throw error
  }
}

/**
 * Gets planning state by feature ID
 * @param featureId The feature ID
 * @returns The most recent planning state for the feature or null if not found
 */
export async function getPlanningStateByFeatureId(
  featureId: string
): Promise<PlanningState | null> {
  try {
    if (!featureId) {
      return null
    }

    await databaseService.connect()

    const row = await databaseService.get(
      `SELECT question_id, feature_id, prompt, partial_response, planning_type
       FROM planning_states
       WHERE feature_id = ?
       ORDER BY created_at DESC
       LIMIT 1`,
      [featureId]
    )

    await databaseService.close()

    if (!row) {
      return null
    }

    return {
      questionId: row.question_id,
      featureId: row.feature_id,
      prompt: row.prompt,
      partialResponse: row.partial_response,
      planningType: row.planning_type,
    }
  } catch (error) {
    console.error(
      `[TaskServer] Error getting planning state for feature ${featureId}: ${error}`
    )
    // Re-throw error to distinguish DB errors from 'not found'
    throw error
  }
}

/**
 * Clears planning state
 * @param questionId The question ID
 * @returns True if successful, false otherwise
 */
export async function clearPlanningState(questionId: string): Promise<boolean> {
  try {
    if (!questionId) {
      return false
    }

    await databaseService.connect()

    const result = await databaseService.runAsync(
      `DELETE FROM planning_states WHERE question_id = ?`,
      [questionId]
    )

    await databaseService.close()

    return result.changes > 0
  } catch (error) {
    console.error(
      `[TaskServer] Error clearing planning state for question ${questionId}: ${error}`
    )
    return false
  }
}

/**
 * Clears all planning states for a feature
 * @param featureId The feature ID
 * @returns Number of states cleared
 */
export async function clearPlanningStatesForFeature(
  featureId: string
): Promise<number> {
  try {
    if (!featureId) {
      return 0
    }

    await databaseService.connect()

    const result = await databaseService.runAsync(
      `DELETE FROM planning_states WHERE feature_id = ?`,
      [featureId]
    )

    await databaseService.close()

    return result.changes || 0
  } catch (error) {
    console.error(
      `[TaskServer] Error clearing planning states for feature ${featureId}: ${error}`
    )
    return 0
  }
}

// Utility to get project_path from feature record
export async function getProjectPathForFeature(
  featureId: string
): Promise<string | undefined> {
  try {
    await databaseService.connect()

    // First try to get it from the feature record
    const feature = await databaseService.getFeatureById(featureId)
    if (feature && feature.project_path) {
      await databaseService.close()
      return feature.project_path
    }

    // Fallback to the old method if needed
    const history = await getHistoryForFeature(featureId, 50) // limit to 50 for efficiency
    const firstToolCall = history.find(
      (entry: any) =>
        entry.role === 'tool_call' &&
        entry.content &&
        entry.content.tool === 'plan_feature' &&
        entry.content.params &&
        entry.content.params.project_path
    )

    // If we found it in history but not in the feature record, update the feature record for next time
    const projectPath = JSON.parse(firstToolCall?.content || '{}')?.params
      ?.project_path
    if (projectPath && feature) {
      try {
        await databaseService.runAsync(
          'UPDATE features SET project_path = ? WHERE id = ?',
          [projectPath, featureId]
        )
      } catch (updateError) {
        console.error(
          `[getProjectPathForFeature] Error updating project_path: ${updateError}`
        )
      }
    }

    await databaseService.close()
    return projectPath
  } catch (e) {
    await databaseService.close()
    return undefined
  }
}

```

--------------------------------------------------------------------------------
/src/tools/markTaskComplete.ts:
--------------------------------------------------------------------------------

```typescript
import { Task } from '../models/types'
import { logToFile } from '../lib/logger'
import webSocketService from '../services/webSocketService'
import { databaseService } from '../services/databaseService'
import { addHistoryEntry, getProjectPathForFeature } from '../lib/dbUtils'
import { AUTO_REVIEW_ON_COMPLETION } from '../config'
import { handleReviewChanges } from '../tools/reviewChanges'
import fs from 'fs/promises'
import path from 'path'

interface MarkTaskCompleteParams {
  task_id: string
  feature_id: string
}

interface MarkTaskCompleteResult {
  content: Array<{ type: string; text: string }>
  isError?: boolean
}

/**
 * Maps database task objects (with snake_case properties) to application Task objects (with camelCase)
 */
function mapDatabaseTaskToAppTask(dbTask: any): Task {
  return {
    ...dbTask,
    feature_id: dbTask.feature_id,
    parentTaskId: dbTask.parent_task_id,
  }
}

/**
 * Handles the mark_task_complete tool request and returns the next task
 */
export async function handleMarkTaskComplete(
  params: MarkTaskCompleteParams
): Promise<MarkTaskCompleteResult> {
  const { task_id, feature_id } = params
  let message: string = ''
  let isError = false
  let finalTasks: Task[] = [] // Hold the final state of tasks for reporting
  let taskStatusUpdate: any = { isError: false, status: 'unknown' }

  await logToFile(
    `[TaskServer] Handling mark_task_complete request for ID: ${task_id} in feature: ${feature_id}`
  )

  // Record initial tool call attempt
  try {
    await addHistoryEntry(feature_id, 'tool_call', {
      tool: 'mark_task_complete',
      params: { task_id, feature_id },
    })
  } catch (historyError) {
    console.error(
      `[TaskServer] Failed to add initial history entry: ${historyError}`
    )
    // Potentially return error here if initial logging is critical
    // For now, we log and continue
  }

  try {
    // --- Database Operations Block ---
    await databaseService.connect()
    try {
      const dbTasks = await databaseService.getTasksByFeatureId(feature_id)
      const tasks = dbTasks.map(mapDatabaseTaskToAppTask)
      finalTasks = [...tasks] // Initialize finalTasks with current state

      if (tasks.length === 0) {
        message = `Error: No tasks found for feature ID ${feature_id}.`
        isError = true
        taskStatusUpdate = { isError: true, status: 'feature_not_found' }
        // No further DB ops needed, exit the inner try block
      } else {
        const taskIndex = tasks.findIndex((task) => task.id === task_id)
        if (taskIndex === -1) {
          message = `Error: Task with ID ${task_id} not found in feature ${feature_id}.`
          isError = true
          taskStatusUpdate = { isError: true, status: 'task_not_found' }
        } else {
          const taskToUpdate = tasks[taskIndex]
          if (taskToUpdate.status === 'completed') {
            message = `Task ${task_id} was already marked as complete.`
            isError = false // Not an error, just informational
            taskStatusUpdate = {
              isError: false,
              status: 'already_completed',
              taskId: task_id,
            }
            // No DB update needed, but update finalTasks for consistency
            finalTasks = [...tasks]
          } else {
            // Mark the task as completed locally first for checks
            finalTasks = tasks.map((task) =>
              task.id === task_id
                ? { ...task, status: 'completed' as const }
                : task
            )

            // Perform the actual database update for the main task
            await databaseService.updateTaskStatus(task_id, 'completed', true)
            message = `Task ${task_id} marked as complete.`
            taskStatusUpdate = {
              isError: false,
              status: 'completed',
              taskId: task_id,
            }
            logToFile(
              `[TaskServer] Task ${task_id} DB status updated to completed.`
            )

            // Check for parent task completion
            if (taskToUpdate.parentTaskId) {
              const parentId = taskToUpdate.parentTaskId
              const siblingTasks = finalTasks.filter(
                (t) => t.parentTaskId === parentId && t.id !== task_id // Exclude current task if needed, already marked completed
              )
              const allSubtasksComplete = siblingTasks.every(
                (st) => st.status === 'completed'
              )

              if (allSubtasksComplete) {
                logToFile(
                  `[TaskServer] All subtasks for parent ${parentId} complete. Updating parent.`
                )
                await databaseService.updateTaskStatus(
                  parentId,
                  'decomposed',
                  false
                )
                // Update parent status in our finalTasks list as well
                finalTasks = finalTasks.map((task) =>
                  task.id === parentId
                    ? { ...task, status: 'decomposed' as const }
                    : task
                )
                message += ` Parent task ${parentId} status updated as all subtasks are now complete.`
                taskStatusUpdate = {
                  isError: false,
                  status: 'completed_with_parent_decomposed',
                  taskId: task_id,
                  parentTaskId: parentId,
                }
                logToFile(
                  `[TaskServer] Parent task ${parentId} DB status updated to decomposed.`
                )
              }
            }

            // Fetch final state *after* all updates
            const dbFinalState = await databaseService.getTasksByFeatureId(
              feature_id
            )
            finalTasks = dbFinalState.map(mapDatabaseTaskToAppTask)
            logToFile(`[TaskServer] Final task state fetched after updates.`)
          }
        }
      }
    } finally {
      // Ensure DB connection is closed
      try {
        await databaseService.close()
        logToFile(`[TaskServer] Database connection closed successfully.`)
      } catch (closeError) {
        console.error(
          `[TaskServer] Error closing database connection: ${closeError}`
        )
        // Don't mask the original error if one occurred
        if (!isError) {
          message = `Error closing database: ${closeError}`
          isError = true
          taskStatusUpdate = { isError: true, status: 'db_close_error' }
        }
      }
    }
    // --- End Database Operations Block ---

    // --- Post-DB Operations (History, WS, Response) ---

    // Broadcast updates via WebSocket if DB ops were successful (or partially successful)
    if (
      taskStatusUpdate.status !== 'unknown' &&
      taskStatusUpdate.status !== 'feature_not_found' &&
      taskStatusUpdate.status !== 'task_not_found'
    ) {
      try {
        webSocketService.notifyTasksUpdated(feature_id, finalTasks)
        if (
          taskStatusUpdate.status === 'completed' ||
          taskStatusUpdate.status === 'completed_with_parent_decomposed'
        ) {
          webSocketService.notifyTaskStatusChanged(
            feature_id,
            task_id,
            'completed'
          )
        }
        if (
          taskStatusUpdate.status === 'completed_with_parent_decomposed' &&
          taskStatusUpdate.parentTaskId
        ) {
          webSocketService.notifyTaskStatusChanged(
            feature_id,
            taskStatusUpdate.parentTaskId,
            'decomposed'
          )
        }
        logToFile(
          `[TaskServer] Broadcast WebSocket events for feature ${feature_id}`
        )
      } catch (wsError) {
        logToFile(
          `[TaskServer] Warning: Failed to broadcast task update: ${wsError}`
        )
        // Don't fail the overall operation
      }
    }

    // Record final outcome in history
    try {
      await addHistoryEntry(feature_id, 'tool_response', {
        tool: 'mark_task_complete',
        isError: isError,
        message: message,
        ...taskStatusUpdate, // Add status details
      })
    } catch (historyError) {
      console.error(
        `[TaskServer] Failed to add final history entry: ${historyError}`
      )
      // If history fails here, the main operation still succeeded or failed as determined before
    }

    // If there was an error identified during DB ops, return error now
    if (isError) {
      return { content: [{ type: 'text', text: message }], isError: true }
    }

    // If successful, find and return the next task
    return getNextTaskAfterCompletion(finalTasks, message, feature_id)
  } catch (error) {
    // Catch errors from the main DB block or other unexpected issues
    const errorMsg = `Error processing mark_task_complete request: ${
      error instanceof Error ? error.message : String(error)
    }`
    console.error(`[TaskServer] ${errorMsg}`, error)
    isError = true
    message = errorMsg

    // Record error in history (attempt)
    try {
      await addHistoryEntry(feature_id, 'tool_response', {
        tool: 'mark_task_complete',
        isError: true,
        message: errorMsg,
        error: error instanceof Error ? error.message : String(error),
        status: 'processing_error',
      })
    } catch (historyError) {
      console.error(
        `[TaskServer] Failed to add error history entry during failure: ${historyError}`
      )
    }

    return { content: [{ type: 'text', text: message }], isError: true }
  }
}

/**
 * Gets the next task after completion and formats the response with both completion message and next task info
 */
async function getNextTaskAfterCompletion(
  tasks: Task[],
  completionMessage: string,
  featureId: string
): Promise<MarkTaskCompleteResult> {
  // Find the first pending task in the list
  const nextTask = tasks.find((task) => task.status === 'pending')

  // Prevent infinite review loop: only trigger review if there are no review tasks yet
  const hasReviewTasks = tasks.some((task) => task.fromReview)

  if (!nextTask) {
    await logToFile(
      `[TaskServer] No pending tasks remaining for feature ID: ${featureId}. Completion message: "${completionMessage}"`
    )

    let finalMessage = `${completionMessage}\n\nAll tasks have been completed for this feature.`
    const historyPayload: any = {
      tool: 'mark_task_complete',
      isError: false,
      message: finalMessage, // Keep original message for history initially
      status: 'all_completed',
    }
    let resultPayload: any = [{ type: 'text', text: finalMessage }]

    // Only trigger auto-review if there are no review tasks yet
    if (AUTO_REVIEW_ON_COMPLETION && !hasReviewTasks) {
      await logToFile(
        `[TaskServer] Auto-review enabled for feature ${featureId}. Initiating review.`
      )
      historyPayload.status = 'all_completed_auto_review_started' // Update history status
      historyPayload.autoReviewTriggered = true

      try {
        // Retrieve project_path for this feature
        const project_path = await getProjectPathForFeature(featureId)
        // Call handleReviewChanges to generate and save review tasks
        const reviewResult = await handleReviewChanges({
          featureId: featureId,
          project_path,
        })

        if (reviewResult.isError) {
          finalMessage += `\n\nAuto-review failed: ${
            reviewResult.content[0]?.text || 'Unknown error'
          }`
          historyPayload.isError = true
          historyPayload.reviewError = reviewResult.content[0]?.text
          logToFile(
            `[TaskServer] Auto-review process failed for ${featureId}: ${reviewResult.content[0]?.text}`
          )
        } else {
          // Review succeeded, tasks were added (or no tasks were needed)
          logToFile(
            `[TaskServer] Auto-review process completed for ${featureId}. Fetching updated tasks...`
          )

          // Fetch the updated task list including any new review tasks
          let updatedTasks: Task[] = []
          try {
            await databaseService.connect()
            const dbFinalState = await databaseService.getTasksByFeatureId(
              featureId
            )
            updatedTasks = dbFinalState.map(mapDatabaseTaskToAppTask)
            await databaseService.close()
            logToFile(
              `[TaskServer] Fetched ${updatedTasks.length} total tasks for ${featureId} after review.`
            )

            // Notify UI with the updated task list
            webSocketService.notifyTasksUpdated(featureId, updatedTasks)
            logToFile(
              `[TaskServer] Sent tasks_updated notification for ${featureId} after review.`
            )

            finalMessage += `\n\nAuto-review completed. Review tasks may have been added. Run "get_next_task" to verify.`
            historyPayload.status = 'all_completed_auto_review_finished' // Update history status
            historyPayload.reviewResult = reviewResult.content[0]?.text // Log the original review result text
          } catch (dbError) {
            const dbErrorMsg = `Error fetching/updating tasks after review: ${
              dbError instanceof Error ? dbError.message : String(dbError)
            }`
            logToFile(`[TaskServer] ${dbErrorMsg}`)
            finalMessage += `\n\nAuto-review ran, but failed to update task list: ${dbErrorMsg}`
            historyPayload.isError = true // Mark history as error if fetching/notifying fails
            historyPayload.postReviewError = dbErrorMsg
          }
        }

        // Update the result payload with the final message
        resultPayload = [{ type: 'text', text: finalMessage }]
      } catch (reviewError) {
        const reviewErrorMsg = `Error during auto-review execution: ${
          reviewError instanceof Error
            ? reviewError.message
            : String(reviewError)
        }`
        logToFile(`[TaskServer] ${reviewErrorMsg}`)
        finalMessage += `\n\nAuto-review execution failed: ${reviewErrorMsg}`
        historyPayload.isError = true
        historyPayload.reviewExecutionError = reviewErrorMsg
        resultPayload = [{ type: 'text', text: finalMessage }]
      }
    }

    // Record completion/review trigger in history
    await addHistoryEntry(featureId, 'tool_response', historyPayload)

    return {
      content: resultPayload,
    }
  }

  // Found the next task
  await logToFile(`[TaskServer] Found next sequential task: ${nextTask.id}`)

  // Include effort in the message if available
  const effortInfo = nextTask.effort ? ` (Effort: ${nextTask.effort})` : ''

  // Include parent info if this is a subtask
  let parentInfo = ''
  if (nextTask.parentTaskId) {
    // Find the parent task
    const parentTask = tasks.find((t) => t.id === nextTask.parentTaskId)
    if (parentTask) {
      const parentDesc =
        (parentTask?.description?.length ?? 0) > 30
          ? (parentTask?.description?.substring(0, 30) ?? '') + '...'
          : parentTask?.description ?? ''
      parentInfo = ` (Subtask of: "${parentDesc}")`
    } else {
      parentInfo = ` (Subtask of parent ID: ${nextTask.parentTaskId})` // Fallback if parent not found
    }
  }

  // Embed ID, description, effort, and parent info in the text message
  const nextTaskMessage = `Next pending task (ID: ${nextTask.id})${effortInfo}${parentInfo}: ${nextTask.description}`

  // Combine completion message with next task info
  const message = `${completionMessage}\n\n${nextTaskMessage}`

  // Record in history
  await addHistoryEntry(featureId, 'tool_response', {
    tool: 'mark_task_complete',
    isError: false,
    message,
    nextTask: nextTask,
  })

  return {
    content: [{ type: 'text', text: message }],
  }
}

```

--------------------------------------------------------------------------------
/src/services/aiService.ts:
--------------------------------------------------------------------------------

```typescript
import {
  GoogleGenerativeAI,
  GenerativeModel,
  GenerateContentResult,
  GoogleGenerativeAIError,
} from '@google/generative-ai'
import OpenAI, { OpenAIError } from 'openai'
import { logToFile } from '../lib/logger'
import {
  GEMINI_API_KEY,
  OPENROUTER_API_KEY,
  GEMINI_MODEL,
  OPENROUTER_MODEL,
  REVIEW_LLM_API_KEY,
  safetySettings,
  FALLBACK_GEMINI_MODEL,
  FALLBACK_OPENROUTER_MODEL,
} from '../config'
import { z } from 'zod'
import { parseAndValidateJsonResponse } from '../lib/llmUtils'

type StructuredCallResult<T extends z.ZodType, R> =
  | { success: true; data: z.infer<T>; rawResponse: R }
  | { success: false; error: string; rawResponse?: R | null }

// Class to manage AI models and provide access to them
class AIService {
  private genAI: GoogleGenerativeAI | null = null
  private openRouter: OpenAI | null = null
  private planningModel: GenerativeModel | undefined
  private reviewModel: GenerativeModel | undefined
  private initialized = false

  constructor() {
    this.initialize()
  }

  private initialize(): void {
    // Initialize OpenRouter if API key is available
    if (OPENROUTER_API_KEY) {
      try {
        this.openRouter = new OpenAI({
          apiKey: OPENROUTER_API_KEY,
          baseURL: 'https://openrouter.ai/api/v1',
        })
        console.error(
          '[TaskServer] LOG: OpenRouter SDK initialized successfully.'
        )
      } catch (sdkError) {
        console.error(
          '[TaskServer] CRITICAL ERROR initializing OpenRouter SDK:',
          sdkError
        )
      }
    } else if (GEMINI_API_KEY) {
      try {
        this.genAI = new GoogleGenerativeAI(GEMINI_API_KEY)
        // Configure the model.
        this.planningModel = this.genAI.getGenerativeModel({
          model: GEMINI_MODEL,
        })
        this.reviewModel = this.genAI.getGenerativeModel({
          model: GEMINI_MODEL,
        })
        console.error(
          '[TaskServer] LOG: Google AI SDK initialized successfully.'
        )
      } catch (sdkError) {
        console.error(
          '[TaskServer] CRITICAL ERROR initializing Google AI SDK:',
          sdkError
        )
      }
    } else {
      console.error(
        '[TaskServer] WARNING: Neither OPENROUTER_API_KEY nor GEMINI_API_KEY environment variable is set. API calls will fail.'
      )
    }

    this.initialized = true
  }

  /**
   * Gets the appropriate planning model for task planning
   */
  getPlanningModel(): GenerativeModel | OpenAI | null {
    logToFile(
      `[TaskServer] Planning model: ${JSON.stringify(
        this.openRouter ? 'OpenRouter' : 'Gemini'
      )}`
    )
    return this.openRouter || this.planningModel || null
  }

  /**
   * Gets the appropriate review model for code reviews
   */
  getReviewModel(): GenerativeModel | OpenAI | null {
    return this.openRouter || this.reviewModel || null
  }

  /**
   * Extracts the text content from an AI API result.
   * Handles both OpenRouter and Gemini responses.
   */
  extractTextFromResponse(
    result:
      | GenerateContentResult
      | OpenAI.Chat.Completions.ChatCompletion
      | undefined
  ): string | null {
    // For OpenRouter responses
    if (
      result &&
      'choices' in result &&
      result.choices &&
      result.choices.length > 0
    ) {
      const choice = result.choices[0]
      if (choice.message && choice.message.content) {
        return choice.message.content
      }
      return null
    }

    // For Gemini responses
    if (result && 'response' in result) {
      try {
        const response = result.response
        if (response.promptFeedback?.blockReason) {
          console.error(
            `[TaskServer] Gemini response blocked: ${response.promptFeedback.blockReason}`
          )
          return null
        }
        if (response.candidates && response.candidates.length > 0) {
          const candidate = response.candidates[0]
          if (candidate.content?.parts?.[0]?.text) {
            return candidate.content.parts[0].text
          }
        }
        console.error(
          '[TaskServer] No text content found in Gemini response candidate.'
        )
        return null
      } catch (error) {
        console.error(
          '[TaskServer] Error extracting text from Gemini response:',
          error
        )
        return null
      }
    }

    return null
  }

  /**
   * Extracts and validates structured data from an AI API result.
   * Handles both OpenRouter and Gemini responses and validates against a schema.
   *
   * @param result The raw API response from either OpenRouter or Gemini
   * @param schema The Zod schema to validate against
   * @returns An object with either validated data or error information
   */
  extractStructuredResponse<T extends z.ZodType>(
    result:
      | GenerateContentResult
      | OpenAI.Chat.Completions.ChatCompletion
      | undefined,
    schema: T
  ):
    | { success: true; data: z.infer<T> }
    | { success: false; error: string; rawData: any | null } {
    // First extract text content using existing method
    const textContent = this.extractTextFromResponse(result)

    // Then parse and validate as JSON against the schema
    return parseAndValidateJsonResponse(textContent, schema)
  }

  /**
   * Makes a structured OpenRouter API call with JSON schema validation
   *
   * @param modelName The model to use for the request
   * @param messages The messages to send to the model
   * @param schema The Zod schema to validate the response against
   * @param options Additional options for the API call
   * @returns A promise that resolves to the validated data or error information
   */
  async callOpenRouterWithSchema<T extends z.ZodType>(
    modelName: string,
    messages: Array<OpenAI.Chat.ChatCompletionMessageParam>,
    schema: T,
    options: {
      temperature?: number
      max_tokens?: number
    } = {},
    isRetry: boolean = false
  ): Promise<StructuredCallResult<T, OpenAI.Chat.Completions.ChatCompletion>> {
    if (!this.openRouter) {
      return {
        success: false,
        error: 'OpenRouter client is not initialized',
        rawResponse: null,
      }
    }

    const currentModel = isRetry ? FALLBACK_OPENROUTER_MODEL : modelName
    await logToFile(
      `[AIService] Calling OpenRouter model: ${currentModel}${
        isRetry ? ' (Fallback)' : ''
      }`
    )

    let response: OpenAI.Chat.Completions.ChatCompletion | null = null
    try {
      response = await this.openRouter.chat.completions.create({
        model: currentModel,
        messages,
        temperature: options.temperature ?? 0.7,
        max_tokens: options.max_tokens,
        response_format: { type: 'json_object' },
      })

      const openRouterError = (response as any)?.error
      let responseBodyRateLimitDetected = false

      if (openRouterError) {
        await logToFile(
          `[AIService] OpenRouter response contains error object: ${JSON.stringify(
            openRouterError
          )}`
        )
        if (
          openRouterError.code === 429 ||
          openRouterError.status === 'RESOURCE_EXHAUSTED' ||
          (typeof openRouterError.message === 'string' &&
            openRouterError.message.includes('quota'))
        ) {
          responseBodyRateLimitDetected = true
        }
      }

      if (responseBodyRateLimitDetected && !isRetry) {
        await logToFile(
          `[AIService] Rate limit (429) detected in response body for ${currentModel}. Retrying with fallback ${FALLBACK_OPENROUTER_MODEL}...`
        )
        return this.callOpenRouterWithSchema(
          modelName,
          messages,
          schema,
          options,
          true
        )
      }

      const textContent = this.extractTextFromResponse(response)
      const validationResult = parseAndValidateJsonResponse(textContent, schema)

      if (openRouterError && !validationResult.success) {
        await logToFile(
          `[AIService] Non-retryable error detected in response body for ${currentModel}.`
        )
        return {
          success: false,
          error: `API response contained error: ${
            openRouterError.message || 'Unknown error'
          }`,
          rawResponse: response,
        }
      }

      if (validationResult.success) {
        return {
          success: true,
          data: validationResult.data,
          rawResponse: response,
        }
      } else {
        await logToFile(
          `[AIService] Schema validation failed for ${currentModel}: ${
            validationResult.error
          }. Raw data: ${JSON.stringify(validationResult.rawData)?.substring(
            0,
            200
          )}`
        )
        const errorMessage = openRouterError?.message
          ? `API response contained error: ${openRouterError.message}`
          : validationResult.error
        return {
          success: false,
          error: errorMessage,
          rawResponse: response,
        }
      }
    } catch (error: any) {
      await logToFile(
        `[AIService] API call failed for ${currentModel}. Error: ${
          error.message
        }, Status: ${error.status || 'unknown'}`
      )

      let isRateLimitError = false
      if (error instanceof OpenAIError && (error as any).status === 429) {
        isRateLimitError = true
      } else if (error.status === 429) {
        isRateLimitError = true
      }

      if (isRateLimitError && !isRetry) {
        await logToFile(
          `[AIService] Rate limit hit (thrown error ${
            error.status || 429
          }) for ${currentModel}. Retrying with fallback ${FALLBACK_OPENROUTER_MODEL}...`
        )
        return this.callOpenRouterWithSchema(
          FALLBACK_OPENROUTER_MODEL,
          messages,
          schema,
          options,
          true
        )
      }

      const rawErrorResponse = error?.response
      return {
        success: false,
        error: `API call failed: ${error.message}`,
        rawResponse: rawErrorResponse || null,
      }
    }
  }

  /**
   * Makes a structured Gemini API call with JSON schema validation.
   * Note: Gemini currently has limited built-in JSON schema support,
   * so we use prompt engineering to get structured output.
   *
   * @param modelName The model to use for the request
   * @param prompt The prompt to send to the model
   * @param schema The Zod schema to validate the response against
   * @param options Additional options for the API call
   * @returns A promise that resolves to the validated data or error information
   */
  async callGeminiWithSchema<T extends z.ZodType>(
    modelName: string,
    prompt: string,
    schema: T,
    options: {
      temperature?: number
      maxOutputTokens?: number
    } = {},
    isRetry: boolean = false
  ): Promise<
    | { success: true; data: z.infer<T>; rawResponse: GenerateContentResult }
    | {
        success: false
        error: string
        rawResponse?: GenerateContentResult | null
      }
  > {
    if (!this.genAI) {
      return {
        success: false,
        error: 'Gemini client is not initialized',
        rawResponse: null,
      }
    }

    const currentModelName = isRetry ? FALLBACK_GEMINI_MODEL : modelName
    await logToFile(
      `[AIService] Calling Gemini model: ${currentModelName}${
        isRetry ? ' (Fallback)' : ''
      }`
    )

    const schemaDescription = this.createSchemaDescription(schema)
    const enhancedPrompt = `${prompt}\n\nYour response must be a valid JSON object with the following structure:\n${schemaDescription}\n\nEnsure your response is valid JSON with no markdown formatting or additional text.`

    try {
      const model = this.genAI.getGenerativeModel({ model: currentModelName })
      const response = await model.generateContent({
        contents: [{ role: 'user', parts: [{ text: enhancedPrompt }] }],
        generationConfig: {
          temperature: options.temperature ?? 0.7,
          maxOutputTokens: options.maxOutputTokens,
        },
        safetySettings,
      })

      const textContent = this.extractTextFromResponse(response)
      const validationResult = parseAndValidateJsonResponse(textContent, schema)

      if (validationResult.success) {
        return {
          success: true,
          data: validationResult.data,
          rawResponse: response,
        }
      } else {
        await logToFile(
          `[AIService] Schema validation failed for ${currentModelName}: ${
            validationResult.error
          }. Raw data: ${JSON.stringify(validationResult.rawData)?.substring(
            0,
            200
          )}`
        )
        return {
          success: false,
          error: validationResult.error,
          rawResponse: response,
        }
      }
    } catch (error: any) {
      await logToFile(
        `[AIService] API call failed for ${currentModelName}. Error: ${error.message}`
      )

      let isRateLimitError = false
      if (
        error instanceof GoogleGenerativeAIError &&
        error.message.includes('RESOURCE_EXHAUSTED')
      ) {
        isRateLimitError = true
      } else if (error.status === 429) {
        isRateLimitError = true
      }

      if (isRateLimitError && !isRetry) {
        await logToFile(
          `[AIService] Rate limit hit for ${currentModelName}. Retrying with fallback model ${FALLBACK_GEMINI_MODEL}...`
        )
        return this.callGeminiWithSchema(
          FALLBACK_GEMINI_MODEL,
          prompt,
          schema,
          options,
          true
        )
      }

      return {
        success: false,
        error: `API call failed: ${error.message}`,
        rawResponse: null,
      }
    }
  }

  /**
   * Creates a human-readable description of a Zod schema for prompt engineering
   */
  private createSchemaDescription(schema: z.ZodType): string {
    // Use the schema describe functionality to extract metadata
    const description = schema._def.description ?? 'JSON object'

    // For object schemas, extract shape information
    if (schema instanceof z.ZodObject) {
      const shape = schema._def.shape()
      const fields = Object.entries(shape).map(([key, field]) => {
        const fieldType = this.getZodTypeDescription(field as z.ZodType)
        const fieldDesc = (field as z.ZodType)._def.description || ''
        return `  "${key}": ${fieldType}${fieldDesc ? ` // ${fieldDesc}` : ''}`
      })

      return `{\n${fields.join(',\n')}\n}`
    }

    // For array schemas
    if (schema instanceof z.ZodArray) {
      const elementType = this.getZodTypeDescription(schema._def.type)
      return `[\n  ${elementType} // Array of items\n]`
    }

    // For other types
    return description
  }

  /**
   * Gets a simple description of a Zod type for schema representation
   */
  private getZodTypeDescription(schema: z.ZodType): string {
    if (schema instanceof z.ZodString) return '"string"'
    if (schema instanceof z.ZodNumber) return 'number'
    if (schema instanceof z.ZodBoolean) return 'boolean'
    if (schema instanceof z.ZodArray) {
      const elementType = this.getZodTypeDescription(schema._def.type)
      return `[${elementType}]`
    }
    if (schema instanceof z.ZodObject) {
      const shape = schema._def.shape()
      const fields = Object.entries(shape).map(([key]) => `"${key}"`)
      return `{ ${fields.join(', ')} }`
    }
    if (schema instanceof z.ZodEnum) {
      const values = schema._def.values.map((v: string) => `"${v}"`)
      return `one of: ${values.join(' | ')}`
    }

    return 'any'
  }

  /**
   * Checks if the service is properly initialized
   */
  isInitialized(): boolean {
    return this.initialized && (!!this.openRouter || !!this.planningModel)
  }
}

// Export a singleton instance
export const aiService = new AIService()

```

--------------------------------------------------------------------------------
/src/services/databaseService.ts:
--------------------------------------------------------------------------------

```typescript
import sqlite3 from 'sqlite3'
import fs from 'fs'
import path from 'path'
import { promisify } from 'util'
import { SQLITE_DB_PATH } from '../config'
import logger from '../lib/winstonLogger'

// Define Task type for database operations
interface Task {
  id: string
  title?: string
  description?: string
  status: 'pending' | 'in_progress' | 'completed' | 'decomposed'
  completed: boolean
  effort?: 'low' | 'medium' | 'high'
  feature_id?: string
  parent_task_id?: string
  created_at: number
  updated_at: number
  fromReview?: boolean
}

// Define interface for task updates
interface TaskUpdate {
  title?: string
  description?: string
  effort?: 'low' | 'medium' | 'high'
  parent_task_id?: string
  fromReview?: boolean
}

// Define History Entry type for database operations
export interface HistoryEntry {
  id?: number
  timestamp: number
  role: 'user' | 'model' | 'tool_call' | 'tool_response'
  content: string
  feature_id: string
  task_id?: string
  action?: string
  details?: string
}

class DatabaseService {
  private db: sqlite3.Database | null = null
  private dbPath: string

  constructor(dbPath: string = SQLITE_DB_PATH) {
    this.dbPath = dbPath
    try {
      this.ensureDatabaseDirectory()
    } catch (error: any) {
      console.error(
        `[DatabaseService] CRITICAL: Failed to ensure database directory exists at ${path.dirname(
          this.dbPath
        )}: ${error.message}`
      )
    }
  }

  private ensureDatabaseDirectory(): void {
    const dbDir = path.dirname(this.dbPath)
    if (!fs.existsSync(dbDir)) {
      console.log(`[DatabaseService] Creating database directory: ${dbDir}`)
      fs.mkdirSync(dbDir, { recursive: true })
    }
  }

  async connect(): Promise<void> {
    if (this.db) {
      logger.debug('[DatabaseService] Already connected.')
      return Promise.resolve()
    }
    logger.debug(`[DatabaseService] Connecting to database at: ${this.dbPath}`)
    return new Promise((resolve, reject) => {
      const verboseDb = new (sqlite3.verbose().Database)(this.dbPath, (err) => {
        if (err) {
          logger.error(`Error connecting to SQLite database: ${err.message}`, {
            stack: err.stack,
          })
          reject(
            new Error(`Error connecting to SQLite database: ${err.message}`)
          )
          return
        }
        this.db = verboseDb
        logger.debug('[DatabaseService] Database connection successful.')
        resolve()
      })
    })
  }

  async close(): Promise<void> {
    logger.debug('[DatabaseService] Attempting to close database connection.')
    return new Promise((resolve, reject) => {
      if (!this.db) {
        logger.debug('[DatabaseService] No active connection to close.')
        resolve()
        return
      }
      this.db.close((err) => {
        if (err) {
          logger.error(`Error closing SQLite database: ${err.message}`, {
            stack: err.stack,
          })
          reject(new Error(`Error closing SQLite database: ${err.message}`))
          return
        }
        this.db = null
        logger.debug(
          '[DatabaseService] Database connection closed successfully.'
        )
        resolve()
      })
    })
  }

  public async runAsync(
    sql: string,
    params: any[] = []
  ): Promise<sqlite3.RunResult> {
    if (!this.db) {
      logger.error(
        '[DatabaseService] runAsync called but database is not connected.'
      )
      throw new Error('Database is not connected')
    }
    return new Promise((resolve, reject) => {
      this.db!.run(sql, params, function (err) {
        if (err) {
          logger.error(
            `Error executing SQL: ${sql} - Params: ${JSON.stringify(
              params
            )} - Error: ${err.message}`,
            { stack: err.stack }
          )
          reject(new Error(`Error executing SQL: ${err.message}`))
        } else {
          resolve(this)
        }
      })
    })
  }

  private async runSchemaFromFile(): Promise<void> {
    const schemaPath = path.join(__dirname, '..', 'config', 'schema.sql')
    logger.info(`Attempting to run schema from: ${schemaPath}`)
    if (!fs.existsSync(schemaPath)) {
      logger.error(`Schema file not found at ${schemaPath}`)
      throw new Error(`Schema file not found at ${schemaPath}`)
    }
    logger.info(`Schema file found at ${schemaPath}`)
    const schema = fs.readFileSync(schemaPath, 'utf8')
    const statements = schema
      .split(';')
      .map((statement) => statement.trim())
      .filter((statement) => statement.length > 0)
    logger.info(`Found ${statements.length} SQL statements in schema file.`)
    if (!this.db) {
      logger.error('Database is not connected in runSchemaFromFile.')
      throw new Error('Database is not connected')
    }
    try {
      logger.info('Starting transaction for schema execution.')
      await this.runAsync('BEGIN TRANSACTION;')
      for (let i = 0; i < statements.length; i++) {
        const statement = statements[i]
        logger.debug(
          `Executing schema statement #${i + 1}: ${statement.substring(
            0,
            60
          )}...`
        )
        await this.runAsync(statement)
        logger.debug(`Successfully executed statement #${i + 1}`)
      }
      logger.info('Committing transaction for schema execution.')
      await this.runAsync('COMMIT;')
      logger.info('Schema execution committed successfully.')
    } catch (error: any) {
      logger.error(
        `Error during schema execution: ${error.message}. Rolling back transaction.`,
        { stack: error.stack }
      )
      try {
        await this.runAsync('ROLLBACK;')
        logger.info('Transaction rolled back successfully.')
      } catch (rollbackError: any) {
        logger.error(`Failed to rollback transaction: ${rollbackError.message}`)
      }
      throw new Error(`Schema execution failed: ${error.message}`)
    }
  }

  async tableExists(tableName: string): Promise<boolean> {
    if (!this.db) {
      logger.error(
        '[DatabaseService] tableExists called but database is not connected.'
      )
      throw new Error('Database is not connected')
    }
    return new Promise((resolve, reject) => {
      this.db!.get(
        "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
        [tableName],
        (err, row) => {
          if (err) {
            logger.error(
              `Error checking if table ${tableName} exists: ${err.message}`
            )
            reject(err)
          } else {
            resolve(!!row)
          }
        }
      )
    })
  }

  async initializeDatabase(): Promise<void> {
    if (!this.db) {
      logger.info(
        '[DatabaseService] Connecting DB within initializeDatabase...'
      )
      await this.connect()
    } else {
      logger.debug('[DatabaseService] DB already connected for initialization.')
    }
    try {
      logger.info('[DatabaseService] Checking if tables exist...')
      const tablesExist = await this.tableExists('tasks')
      logger.info(
        `[DatabaseService] 'tasks' table exists check returned: ${tablesExist}`
      )
      if (!tablesExist) {
        logger.info(
          '[DatabaseService] Initializing database schema as tables do not exist...'
        )
        await this.runSchemaFromFile()
        logger.info(
          '[DatabaseService] Database schema initialization complete.'
        )
      } else {
        logger.info(
          '[DatabaseService] Database tables already exist. Skipping schema initialization.'
        )
      }
    } catch (error: any) {
      logger.error(`Error during database initialization: ${error.message}`, {
        stack: error.stack,
      })
      console.error('Error initializing database:', error)
      throw error
    }
  }

  async runMigrations(): Promise<void> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    try {
      // Run schema first to create tables if they don't exist
      await this.runSchemaFromFile()

      // Run migrations to update existing tables
      await this.runMigrationsFromFile()
    } catch (error) {
      console.error('Error running migrations:', error)
      throw error
    }
  }

  private async runMigrationsFromFile(): Promise<void> {
    // Use __dirname to reliably locate the file relative to the compiled JS file
    const migrationsPath = path.join(
      __dirname,
      '..',
      'config',
      'migrations.sql'
    )
    console.log(
      `[DB Service] Attempting to load migrations from: ${migrationsPath}`
    ) // Log path

    if (!fs.existsSync(migrationsPath)) {
      console.log(
        `[DB Service] Migrations file not found at ${migrationsPath}, skipping migrations.` // Adjusted log level
      )
      return
    }
    console.log(
      `[DB Service] Migrations file found at ${migrationsPath}. Reading...`
    ) // Log if found

    const migrations = fs.readFileSync(migrationsPath, 'utf8')
    const statements = migrations
      .split(';')
      .map((statement) => statement.trim())
      .filter((statement) => statement.length > 0)

    console.log(
      `[DB Service] Executing ${statements.length} statements from migrations.sql...`
    ) // Log count
    for (const statement of statements) {
      try {
        console.log(
          `[DB Service] Executing migration statement: ${statement.substring(
            0,
            100
          )}...`
        ) // Log statement (truncated)
        await this.runAsync(statement)
      } catch (error: any) {
        // Only ignore the error if it's specifically about a duplicate column
        if (error?.message?.includes('duplicate column name')) {
          console.log(
            `[DB Service] Migration statement likely already applied (duplicate column): ${statement}` // Adjusted log
          )
        } else {
          // Re-throw any other error during migration
          console.error(
            `[DB Service] Migration statement failed: ${statement}`,
            error
          ) // Adjusted log
          throw error
        }
      }
    }
    console.log(`[DB Service] Finished executing migration statements.`) // Log completion
  }

  async get(sql: string, params: any[] = []): Promise<any> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    return new Promise((resolve, reject) => {
      this.db!.get(sql, params, (err, row) => {
        if (err) {
          reject(`Error executing SQL: ${err.message}`)
          return
        }
        resolve(row)
      })
    })
  }

  async all(sql: string, params: any[] = []): Promise<any[]> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    return new Promise((resolve, reject) => {
      this.db!.all(sql, params, (err, rows) => {
        if (err) {
          reject(`Error executing SQL: ${err.message}`)
          return
        }
        resolve(rows)
      })
    })
  }

  async getTasksByFeatureId(featureId: string): Promise<Task[]> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    try {
      const rows = await this.all(
        `SELECT 
          id, title, description, status, 
          completed, effort, feature_id, parent_task_id,
          created_at, updated_at, from_review
        FROM tasks 
        WHERE feature_id = ?
        ORDER BY created_at ASC`,
        [featureId]
      )

      return rows.map((row) => ({
        ...row,
        completed: Boolean(row.completed),
        fromReview: Boolean(row.from_review),
      }))
    } catch (error) {
      console.error(`Error fetching tasks for feature ${featureId}:`, error)
      throw error
    }
  }

  async getTaskById(taskId: string): Promise<Task | null> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    try {
      const row = await this.get(
        `SELECT 
          id, title, description, status, 
          completed, effort, feature_id, parent_task_id,
          created_at, updated_at, from_review
        FROM tasks 
        WHERE id = ?`,
        [taskId]
      )

      if (!row) {
        return null
      }

      return {
        ...row,
        completed: Boolean(row.completed),
        fromReview: Boolean(row.from_review),
      }
    } catch (error) {
      console.error(`Error fetching task ${taskId}:`, error)
      throw error
    }
  }

  async addTask(task: Task): Promise<string> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    const now = Math.floor(Date.now() / 1000)
    const timestamp = task.created_at || now

    try {
      await this.runAsync(
        `INSERT INTO tasks (
          id, title, description, status, 
          completed, effort, feature_id, parent_task_id,
          created_at, updated_at, from_review
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
        [
          task.id,
          task.title || null,
          task.description || null,
          task.status,
          task.completed ? 1 : 0,
          task.effort || null,
          task.feature_id || null,
          task.parent_task_id || null,
          timestamp,
          task.updated_at || timestamp,
          task.fromReview ? 1 : 0,
        ]
      )

      return task.id
    } catch (error) {
      console.error('Error adding task:', error)
      throw error
    }
  }

  async updateTaskStatus(
    taskId: string,
    status: 'pending' | 'in_progress' | 'completed' | 'decomposed',
    completed?: boolean
  ): Promise<boolean> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    const now = Math.floor(Date.now() / 1000)

    try {
      let result

      if (completed !== undefined) {
        result = await this.runAsync(
          `UPDATE tasks 
           SET status = ?, completed = ?, updated_at = ? 
           WHERE id = ?`,
          [status, completed ? 1 : 0, now, taskId]
        )
      } else {
        result = await this.runAsync(
          `UPDATE tasks 
           SET status = ?, updated_at = ? 
           WHERE id = ?`,
          [status, now, taskId]
        )
      }

      return result.changes > 0
    } catch (error) {
      console.error(`Error updating status for task ${taskId}:`, error)
      throw error
    }
  }

  async updateTaskDetails(
    taskId: string,
    updates: TaskUpdate
  ): Promise<boolean> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    const now = Math.floor(Date.now() / 1000)

    try {
      const task = await this.getTaskById(taskId)

      if (!task) {
        return false
      }

      const updatedTask = {
        ...task,
        title: updates.title ?? task.title,
        description: updates.description ?? task.description,
        effort: updates.effort ?? task.effort,
        parent_task_id: updates.parent_task_id ?? task.parent_task_id,
        fromReview:
          updates.fromReview !== undefined
            ? updates.fromReview
            : task.fromReview,
        updated_at: now,
      }

      const result = await this.runAsync(
        `UPDATE tasks 
         SET title = ?, description = ?, effort = ?, parent_task_id = ?, updated_at = ?, from_review = ? 
         WHERE id = ?`,
        [
          updatedTask.title || null,
          updatedTask.description || null,
          updatedTask.effort || null,
          updatedTask.parent_task_id || null,
          updatedTask.updated_at,
          updatedTask.fromReview ? 1 : 0,
          taskId,
        ]
      )

      return result.changes > 0
    } catch (error) {
      console.error(`Error updating details for task ${taskId}:`, error)
      throw error
    }
  }

  async deleteTask(taskId: string): Promise<boolean> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    try {
      // Begin transaction
      await this.runAsync('BEGIN TRANSACTION')

      try {
        // Delete any task relationships first
        await this.runAsync(
          'DELETE FROM task_relationships WHERE parent_id = ? OR child_id = ?',
          [taskId, taskId]
        )

        // Finally delete the task
        const result = await this.runAsync('DELETE FROM tasks WHERE id = ?', [
          taskId,
        ])

        // Commit transaction
        await this.runAsync('COMMIT')

        return result.changes > 0
      } catch (error) {
        // Rollback in case of error
        await this.runAsync('ROLLBACK')
        throw error
      }
    } catch (error) {
      console.error(`Error deleting task ${taskId}:`, error)
      throw error
    }
  }

  // History Entry Operations

  async getHistoryByFeatureId(
    featureId: string,
    limit: number = 100
  ): Promise<HistoryEntry[]> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    try {
      const rows = await this.all(
        `SELECT 
          id, timestamp, role, content, feature_id, 
          task_id, action, details
        FROM history_entries 
        WHERE feature_id = ?
        ORDER BY timestamp DESC
        LIMIT ?`,
        [featureId, limit]
      )

      return rows.map((row) => ({
        ...row,
        content:
          typeof row.content === 'string'
            ? JSON.parse(row.content)
            : row.content,
      }))
    } catch (error) {
      console.error(`Error fetching history for feature ${featureId}:`, error)
      throw error
    }
  }

  async addHistoryEntry(entry: HistoryEntry): Promise<number> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    const now = Math.floor(Date.now() / 1000)
    const timestamp = entry.timestamp || now
    const content =
      typeof entry.content === 'object'
        ? JSON.stringify(entry.content)
        : entry.content

    try {
      const result = await this.runAsync(
        `INSERT INTO history_entries (
          timestamp, role, content, feature_id,
          task_id, action, details
        ) VALUES (?, ?, ?, ?, ?, ?, ?)`,
        [
          timestamp,
          entry.role,
          content,
          entry.feature_id,
          entry.task_id || null,
          entry.action || null,
          entry.details || null,
        ]
      )

      return result.lastID
    } catch (error) {
      console.error('Error adding history entry:', error)
      throw error
    }
  }

  async deleteHistoryByFeatureId(featureId: string): Promise<boolean> {
    if (!this.db) {
      throw new Error('Database is not connected')
    }

    try {
      const result = await this.runAsync(
        'DELETE FROM history_entries WHERE feature_id = ?',
        [featureId]
      )

      return result.changes > 0
    } catch (error) {
      console.error(`Error deleting history for feature ${featureId}:`, error)
      throw error
    }
  }

  // Feature Management

  /**
   * Creates a new feature in the database
   * @param id The feature ID
   * @param description The feature description
   * @param projectPath The project path for the feature
   * @returns The created feature
   */
  async createFeature(
    id: string,
    description: string,
    projectPath: string
  ): Promise<{ id: string; description: string; project_path: string }> {
    try {
      const now = Math.floor(Date.now() / 1000)

      await this.connect()

      await this.runAsync(
        `INSERT INTO features (id, description, project_path, created_at, updated_at)
         VALUES (?, ?, ?, ?, ?)`,
        [id, description, projectPath, now, now]
      )

      await this.close()

      return { id, description, project_path: projectPath }
    } catch (error) {
      console.error(`Error creating feature:`, error)
      throw error
    }
  }

  /**
   * Gets a feature by ID
   * @param featureId The feature ID
   * @returns The feature or null if not found
   */
  async getFeatureById(featureId: string): Promise<{
    id: string
    description: string
    project_path: string | null
    status: string
  } | null> {
    try {
      const feature = await this.get(
        `SELECT id, description, project_path, status
         FROM features
         WHERE id = ?`,
        [featureId]
      )

      return feature || null
    } catch (error) {
      console.error(`Error fetching feature ${featureId}:`, error)
      return null
    }
  }
}

export const databaseService = new DatabaseService()
export default DatabaseService

```

--------------------------------------------------------------------------------
/src/tools/reviewChanges.ts:
--------------------------------------------------------------------------------

```typescript
// src/tools/reviewChanges.ts

import { logToFile } from '../lib/logger' // Use specific log functions
import { aiService } from '../services/aiService'
import { promisify } from 'util'
import { exec } from 'child_process'
import crypto from 'crypto'
// Import the correct schema for task list output and other necessary types
import {
  Task,
  ReviewResponseWithTasksSchema, // Schema for review task output
  PlanFeatureResponseSchema, // Schema for initial plan (if used elsewhere)
  type Task as AppTask, // Rename if needed to avoid conflict with local Task type/variable
} from '../models/types'
import { z } from 'zod'
import {
  parseAndValidateJsonResponse,
  processAndFinalizePlan, // We WILL use this now
} from '../lib/llmUtils'
import {
  GIT_DIFF_MAX_BUFFER_MB,
  GEMINI_MODEL, // Make sure these are imported if needed directly
  OPENROUTER_MODEL, // Make sure these are imported if needed directly
} from '../config'
import path from 'path'
import { getCodebaseContext } from '../lib/repomixUtils'
import { addHistoryEntry, getHistoryForFeature } from '../lib/dbUtils'

const execPromise = promisify(exec)

interface ReviewChangesParams {
  featureId: string // Make featureId mandatory
  project_path?: string
}

// Use the standard response type
interface PlanFeatureStandardResponse {
  status: 'completed' | 'awaiting_clarification' | 'error'
  message: string
  featureId: string
  taskCount?: number
  firstTask?: Task | { description: string; effort: string } // Allow slightly different structure if needed
  uiUrl?: string
  data?: any // For clarification details or other metadata
}

interface ReviewChangesResult {
  content: Array<{ type: 'text'; text: string }>
  isError?: boolean
}

export async function handleReviewChanges(
  params: ReviewChangesParams
): Promise<ReviewChangesResult> {
  const { featureId, project_path } = params
  const reviewId = crypto.randomUUID() // Unique ID for this review operation

  logToFile(
    `[TaskServer] Handling review_changes request for feature ${featureId} (Review ID: ${reviewId})`
  )
  // Wrap initial history logging
  try {
    await addHistoryEntry(featureId, 'tool_call', {
      tool: 'review_changes',
      params,
      reviewId,
    })
  } catch (historyError) {
    console.error(
      `[TaskServer] Failed to add initial history entry for review: ${historyError}`
    )
    // Continue execution even if initial history fails
  }

  let targetDir = process.cwd()

  if (project_path) {
    // Basic check for path traversal characters
    if (project_path.includes('..') || project_path.includes('~')) {
      const errorMsg = `Error: Invalid project_path provided: ${project_path}. Path cannot contain '..' or '~'.`
      await logToFile(`[TaskServer] ${errorMsg}`)
      // Try to log error to history before returning
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError: true,
          message: errorMsg,
          reviewId,
          step: 'invalid_path',
        })
      } catch (historyError) {
        /* Ignore */
      }
      return { content: [{ type: 'text', text: errorMsg }], isError: true }
    }

    // Resolve the path and check it's within a reasonable base (e.g., current working directory)
    const resolvedPath = path.resolve(project_path)
    const cwd = process.cwd()

    // This is a basic check; more robust checks might compare against a known workspace root
    if (!resolvedPath.startsWith(cwd)) {
      const errorMsg = `Error: Invalid project_path provided: ${project_path}. Path must be within the current workspace.`
      await logToFile(`[TaskServer] ${errorMsg}`)
      // Try to log error to history before returning
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError: true,
          message: errorMsg,
          reviewId,
          step: 'invalid_path',
        })
      } catch (historyError) {
        /* Ignore */
      }
      return { content: [{ type: 'text', text: errorMsg }], isError: true }
    }
    targetDir = resolvedPath
  }

  try {
    let message: string | null = null
    let isError = false

    const reviewModel = aiService.getReviewModel()
    if (!reviewModel) {
      message = 'Error: Review model not initialized. Check API Key.'
      isError = true
      logToFile(`[TaskServer] ${message} (Review ID: ${reviewId})`)
      // Wrap history logging
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError,
          message,
          reviewId,
        })
      } catch (historyError) {
        console.error(
          `[TaskServer] Failed to add history entry for model init failure: ${historyError}`
        )
      }
      return { content: [{ type: 'text', text: message }], isError }
    }

    // --- Get Codebase Context --- (Keep as is)
    const { context: codebaseContext, error: contextError } =
      await getCodebaseContext(targetDir, reviewId)
    if (contextError) {
      message = contextError
      isError = true
      // Wrap history logging
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError,
          message,
          reviewId,
          step: 'context_error',
        })
      } catch (historyError) {
        console.error(
          `[TaskServer] Failed to add history entry for context error: ${historyError}`
        )
      }
      return { content: [{ type: 'text', text: message }], isError }
    }
    // --- End Codebase Context ---

    // --- Git Diff Execution --- (Keep as is)
    let gitDiff = ''
    try {
      await logToFile(
        `[TaskServer] Running git diff HEAD in directory: ${targetDir}... (reviewId: ${reviewId})`
      )
      // Execute git commands directly in the validated target directory
      const diffCmd = `git --no-pager diff HEAD`
      const lsFilesCmd = `git ls-files --others --exclude-standard`
      const execOptions = {
        cwd: targetDir, // Set current working directory for the command
        maxBuffer: GIT_DIFF_MAX_BUFFER_MB * 1024 * 1024,
      }

      const { stdout: diffStdout, stderr: diffStderr } = await execPromise(
        diffCmd,
        execOptions
      )
      if (diffStderr) {
        await logToFile(
          `[TaskServer] git diff stderr: ${diffStderr} (reviewId: ${reviewId})`
        )
      }
      let combinedDiff = diffStdout

      const { stdout: untrackedStdout } = await execPromise(
        lsFilesCmd,
        execOptions
      )
      const untrackedFiles = untrackedStdout
        .split('\n')
        .map((f: string) => f.trim())
        .filter((f: string) => f.length > 0)
      for (const file of untrackedFiles) {
        // Ensure the filename itself is not malicious (basic check)
        if (file.includes('..') || file.includes('/') || file.includes('\\')) {
          await logToFile(
            `[TaskServer] Skipping potentially unsafe untracked filename: ${file} (reviewId: ${reviewId})`
          )
          continue
        }
        // For each untracked file, get its diff
        const fileDiffCmd = `git --no-pager diff --no-index /dev/null "${file}"`
        try {
          const { stdout: fileDiff } = await execPromise(
            fileDiffCmd,
            execOptions
          )
          if (fileDiff && fileDiff.trim().length > 0) {
            combinedDiff += `\n\n${fileDiff}`
          }
        } catch (fileDiffErr) {
          await logToFile(
            `[TaskServer] Error getting diff for untracked file ${file}: ${fileDiffErr} (reviewId: ${reviewId})`
          )
        }
      }
      gitDiff = combinedDiff
      if (!gitDiff.trim()) {
        message = 'No staged or untracked changes found to review.'
        logToFile(`[TaskServer] ${message} (Review ID: ${reviewId})`)
        // Wrap history logging
        try {
          await addHistoryEntry(featureId, 'tool_response', {
            tool: 'review_changes',
            isError: false,
            message,
            reviewId,
            status: 'no_changes',
          })
        } catch (historyError) {
          console.error(
            `[TaskServer] Failed to add history entry for no_changes status: ${historyError}`
          )
        }
        return { content: [{ type: 'text', text: message }] }
      }
      logToFile(
        `[TaskServer] git diff captured (${gitDiff.length} chars). (Review ID: ${reviewId})`
      )
    } catch (error: any) {
      message = `Error running git diff: ${error.message || error}` // Assign error message
      isError = true
      logToFile(`[TaskServer] ${message} (Review ID: ${reviewId})`, error)
      // Wrap history logging
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError: true,
          message,
          reviewId,
          step: 'git_diff_error',
        })
      } catch (historyError) {
        console.error(
          `[TaskServer] Failed to add history entry for git diff error: ${historyError}`
        )
      }
      return { content: [{ type: 'text', text: message }], isError }
    }
    // --- End Git Diff ---

    // --- LLM Call to Generate Tasks from Review ---
    try {
      logToFile(
        `[TaskServer] Calling LLM for review analysis and task generation... (Review ID: ${reviewId})`
      )

      // Fetch history to get original feature request
      let originalFeatureRequest = 'Original feature request not found.'
      try {
        const history: any[] = await getHistoryForFeature(featureId, 200) // Fetch more history if needed
        const planFeatureCall = history.find(
          (entry) =>
            entry.role === 'tool_call' &&
            entry.content?.tool === 'plan_feature' &&
            entry.content?.params?.feature_description
        )
        if (planFeatureCall) {
          originalFeatureRequest =
            planFeatureCall.content.params.feature_description
          logToFile(
            `[TaskServer] Found original feature request for review context: "${originalFeatureRequest.substring(
              0,
              50
            )}..."`
          )
        } else {
          logToFile(
            `[TaskServer] Could not find original plan_feature call in history for feature ${featureId}.`
          )
        }
      } catch (historyError) {
        logToFile(
          `[TaskServer] Error fetching history to get original feature request: ${historyError}. Proceeding without it.`
        )
      }

      const contextPromptPart = codebaseContext
        ? `\n\nCodebase Context Overview:\n\`\`\`\n${codebaseContext}\n\`\`\`\n`
        : '\n\n(No overall codebase context was available.)'

      // *** REVISED Prompt: Ask for TASKS based on checklist criteria ***
      const structuredPrompt = `You are a senior software engineer performing a code review.
Original Feature Request Context: "${originalFeatureRequest}"

Review the following code changes (git diff) and consider the overall codebase context (if provided).
Your goal is to identify necessary fixes, improvements, or refactorings based on standard best practices and generate a list of actionable coding tasks for another developer to implement.

\`\`\`diff
${gitDiff}
\`\`\`
${contextPromptPart}
**Review Criteria (Generate tasks based on these):**
1.  **Functionality:** Does the change work? Are there bugs? Handle edge cases & errors?
2.  **Design:** Does it fit the architecture? Is it modular/maintainable (SOLID/DRY)? Overly complex?
3.  **Readability:** Is code clear? Are names good? Are comments needed (explaining 'why')? Style consistent?
4.  **Maintainability:** Easy to modify/debug/test? Clean dependencies?
5.  **Performance:** Obvious bottlenecks?
6.  **Security:** Potential vulnerabilities (input validation, etc.)?

**Output Format:**
Respond ONLY with a single valid JSON object matching this exact schema:
{
  "tasks": [
    {
      "description": "string // Clear, concise description of the required coding action.",
      "effort": "'low' | 'medium' | 'high' // Estimated effort level."
    }
    // ... include all actionable tasks generated from the review.
    // If NO tasks are needed, return an empty array: "tasks": []
  ]
}

Do NOT include summaries, commentary, or anything outside this JSON structure. Do not use markdown formatting.`

      let llmResponseData: { tasks: Task[] } | null = null
      let rawLLMResponse: any = null

      // Call LLM using aiService - Attempt structured output
      if ('chat' in reviewModel) {
        // OpenRouter
        const structuredResult = await aiService.callOpenRouterWithSchema(
          OPENROUTER_MODEL, // Use configured or default for review
          [{ role: 'user', content: structuredPrompt }],
          ReviewResponseWithTasksSchema, // *** USE THE CORRECT SCHEMA HERE ***
          { temperature: 0.5 } // Slightly higher temp might be ok for task generation
        )
        rawLLMResponse = structuredResult.rawResponse

        if (structuredResult.success) {
          llmResponseData = structuredResult.data as { tasks: Task[] }
          // Wrap history logging
          try {
            await addHistoryEntry(featureId, 'model', {
              tool: 'review_changes',
              reviewId,
              response: llmResponseData,
              structured: true,
            })
          } catch (historyError) {
            console.error(
              `[TaskServer] Failed to add history entry for successful structured OpenRouter response: ${historyError}`
            )
          }
        } else {
          logToFile(
            `[TaskServer] Structured review task generation failed (OpenRouter): ${structuredResult.error}. Cannot reliably generate tasks from review.`
          )
          message = `Error: AI failed to generate structured tasks based on review: ${structuredResult.error}`
          isError = true
          // Wrap history logging
          try {
            await addHistoryEntry(featureId, 'tool_response', {
              tool: 'review_changes',
              isError,
              message,
              reviewId,
              step: 'llm_structured_fail',
            })
          } catch (historyError) {
            console.error(
              `[TaskServer] Failed to add history entry for OpenRouter structured fail: ${historyError}`
            )
          }
          return { content: [{ type: 'text', text: message }], isError }
        }
      } else if ('generateContentStream' in reviewModel) {
        // Gemini
        const structuredResult = await aiService.callGeminiWithSchema(
          process.env.GEMINI_MODEL || 'gemini-1.5-flash-latest',
          structuredPrompt,
          ReviewResponseWithTasksSchema, // *** USE THE CORRECT SCHEMA HERE ***
          { temperature: 0.5 }
        )
        rawLLMResponse = structuredResult.rawResponse

        if (structuredResult.success) {
          llmResponseData = structuredResult.data as { tasks: Task[] }
          // Wrap history logging
          try {
            await addHistoryEntry(featureId, 'model', {
              tool: 'review_changes',
              reviewId,
              response: llmResponseData,
              structured: true,
            })
          } catch (historyError) {
            console.error(
              `[TaskServer] Failed to add history entry for successful structured Gemini response: ${historyError}`
            )
          }
        } else {
          logToFile(
            `[TaskServer] Structured review task generation failed (Gemini): ${structuredResult.error}. Cannot reliably generate tasks from review.`
          )
          message = `Error: AI failed to generate structured tasks based on review: ${structuredResult.error}`
          isError = true
          // Wrap history logging
          try {
            await addHistoryEntry(featureId, 'tool_response', {
              tool: 'review_changes',
              isError,
              message,
              reviewId,
              step: 'llm_structured_fail',
            })
          } catch (historyError) {
            console.error(
              `[TaskServer] Failed to add history entry for Gemini structured fail: ${historyError}`
            )
          }
          return { content: [{ type: 'text', text: message }], isError }
        }
      } else {
        message = 'Error: Review model does not support structured output.'
        isError = true
        logToFile(`[TaskServer] ${message} (Review ID: ${reviewId})`)
        // Wrap history logging
        try {
          await addHistoryEntry(featureId, 'tool_response', {
            tool: 'review_changes',
            isError,
            message,
            reviewId,
            step: 'llm_structured_fail',
          })
        } catch (historyError) {
          console.error(
            `[TaskServer] Failed to add history entry for structured fail: ${historyError}`
          )
        }
        return { content: [{ type: 'text', text: message }], isError }
      }

      // --- Process and Save Generated Tasks ---
      if (!llmResponseData || !llmResponseData.tasks) {
        message = 'Error: LLM response did not contain a valid task list.'
        isError = true
        logToFile(`[TaskServer] ${message} (Review ID: ${reviewId})`)
        // Wrap history logging
        try {
          await addHistoryEntry(featureId, 'tool_response', {
            tool: 'review_changes',
            isError,
            message,
            reviewId,
            step: 'task_processing_error',
          })
        } catch (historyError) {
          console.error(
            `[TaskServer] Failed to add history entry for task processing error: ${historyError}`
          )
        }
        return { content: [{ type: 'text', text: message }], isError }
      }

      if (llmResponseData.tasks.length === 0) {
        message =
          'Code review completed. No immediate action tasks were identified.'
        logToFile(`[TaskServer] ${message} (Review ID: ${reviewId})`)
        // Wrap history logging
        try {
          await addHistoryEntry(featureId, 'tool_response', {
            tool: 'review_changes',
            isError: false,
            message,
            reviewId,
            status: 'no_tasks_generated',
          })
        } catch (historyError) {
          console.error(
            `[TaskServer] Failed to add history entry for no_tasks_generated status: ${historyError}`
          )
        }
        return { content: [{ type: 'text', text: message }], isError: false }
      }

      // Format tasks for processing (like in planFeature)
      const rawPlanSteps = llmResponseData.tasks.map(
        (task) => `[${task.effort}] ${task.description}`
      )

      logToFile(
        `[TaskServer] Generated ${rawPlanSteps.length} tasks from review. Processing... (Review ID: ${reviewId})`
      )

      // Process these tasks (effort check, breakdown, save, notify)
      // This adds the review-generated tasks to the existing feature plan
      const finalTasks = await processAndFinalizePlan(
        rawPlanSteps,
        reviewModel, // Use the same model for potential breakdown
        featureId,
        true // Indicate tasks came from review context
      )

      const taskCount = finalTasks.length // Count tasks *added* or processed
      const firstNewTask = finalTasks[0] // Get the first task generated by *this* review

      const responseData: PlanFeatureStandardResponse = {
        status: 'completed', // Indicates review+task generation is done
        // Provide a clear message indicating tasks were *added* from review
        message: `Code review complete. Generated ${taskCount} actionable tasks based on the review. ${
          firstNewTask
            ? 'First new task: "' + firstNewTask.description + '"'
            : ''
        } Call 'get_next_task' with featureId '${featureId}' to continue implementation.`,
        featureId: featureId,
        taskCount: taskCount,
        firstTask: firstNewTask
          ? {
              description: firstNewTask.description || '',
              effort: firstNewTask.effort || 'medium',
            }
          : undefined, // Ensure effort is present
      }

      logToFile(
        `[TaskServer] Review tasks processed and saved for feature ${featureId}. (Review ID: ${reviewId})`
      )
      // Wrap history logging
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError: false,
          message: responseData.message,
          reviewId,
          responseData,
        })
      } catch (historyError) {
        console.error(
          `[TaskServer] Failed to add final success history entry: ${historyError}`
        )
      }

      return {
        content: [{ type: 'text', text: responseData.message }],
        isError: false,
      }
    } catch (error: any) {
      message = `Error occurred during review analysis API call: ${error.message}`
      isError = true
      logToFile(
        `[TaskServer] Error calling LLM review API (Review ID: ${reviewId})`,
        error
      )
      // Wrap history logging inside the catch block
      try {
        await addHistoryEntry(featureId, 'tool_response', {
          tool: 'review_changes',
          isError,
          message,
          error: error.message,
          reviewId,
        })
      } catch (historyError) {
        console.error(
          `[TaskServer] Failed to add error history entry during LLM API call failure: ${historyError}`
        )
      }
      return { content: [{ type: 'text', text: message }], isError }
    }
  } catch (error: any) {
    // Outer catch already wraps history logging and ignores errors
    const errorMsg = `Error processing review_changes request: ${error.message}`
    logToFile(`[TaskServer] ${errorMsg} (Review ID: ${reviewId})`, error)
    try {
      await addHistoryEntry(featureId, 'tool_response', {
        tool: 'review_changes',
        isError: true,
        message: errorMsg,
        reviewId,
        step: 'preprocessing_error',
      })
    } catch (historyError) {
      /* Ignore */
    }
    return { content: [{ type: 'text', text: errorMsg }], isError: true }
  }
}

```
Page 1/2FirstPrevNextLast