//! `tools/list` MCP method — returns the static schema for every tool the server exposes.

use serde_json::{Value, json};

use super::JsonRpcResponse;

pub(super) fn handle_tools_list(id: Option<Value>) -> JsonRpcResponse {
    JsonRpcResponse::success(
        id,
        json!({
            "tools": [
                {
                    "name": "create_story",
                    "description": "Create a new story file with front matter in upcoming/. Returns the story_id.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Human-readable story name"
                            },
                            "user_story": {
                                "type": "string",
                                "description": "Optional user story text (As a..., I want..., so that...)"
                            },
                            "description": {
                                "type": "string",
                                "description": "Optional description / background context for the story"
                            },
                            "acceptance_criteria": {
                                "type": "array",
                                "items": { "type": "string" },
                                "minItems": 1,
                                "description": "List of acceptance criteria (at least one required)"
                            },
                            "depends_on": {
                                "type": "array",
                                "items": { "type": "integer" },
                                "description": "Optional list of story IDs this story depends on; written as a YAML inline sequence in front matter"
                            },
                            "commit": {
                                "type": "boolean",
                                "description": "If true, git-add and git-commit the new story file to the current branch"
                            }
                        },
                        "required": ["name", "acceptance_criteria"]
                    }
                },
                {
                    "name": "validate_stories",
                    "description": "Validate front matter on all current and upcoming story files.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "list_upcoming",
                    "description": "List all upcoming stories with their names and any parsing errors.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "get_story_todos",
                    "description": "Get unchecked acceptance criteria (todos) for a story file in current/.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "record_tests",
                    "description": "Record test results for a story. Only one failing test at a time is allowed.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier"
                            },
                            "unit": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "name": { "type": "string" },
                                        "status": { "type": "string", "enum": ["pass", "fail"] },
                                        "details": { "type": "string" }
                                    },
                                    "required": ["name", "status"]
                                },
                                "description": "Unit test results"
                            },
                            "integration": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "name": { "type": "string" },
                                        "status": { "type": "string", "enum": ["pass", "fail"] },
                                        "details": { "type": "string" }
                                    },
                                    "required": ["name", "status"]
                                },
                                "description": "Integration test results"
                            }
                        },
                        "required": ["story_id", "unit", "integration"]
                    }
                },
                {
                    "name": "ensure_acceptance",
                    "description": "Check whether a story can be accepted. Returns acceptance status with reasons if blocked.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "start_agent",
                    "description": "Start an agent for a story. Creates a worktree, runs setup, and spawns the agent process.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '28_my_story')"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Agent name from project.toml config. If omitted, uses the first coder agent (stage = \"coder\"). Supervisor must be requested explicitly by name."
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "stop_agent",
                    "description": "Stop a running agent. Worktree is preserved for inspection.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Agent name to stop"
                            }
                        },
                        "required": ["story_id", "agent_name"]
                    }
                },
                {
                    "name": "list_agents",
                    "description": "List all agents with their current status, story assignment, and worktree path.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "get_agent_config",
                    "description": "Get the configured agent roster from project.toml (names, roles, models, allowed tools, limits).",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "reload_agent_config",
                    "description": "Reload project.toml and return the updated agent roster.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "get_agent_output",
                    "description": "Read agent session logs from disk as a human-readable timeline. Stitches all sessions for the story together in chronological order — text output, tool calls, tool results, errors. Works for both running and completed agents. If agent_name is omitted, returns logs from every agent that worked on the story. If the named agent is currently running, live buffered events are appended.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '42_story_my_feature')"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Optional: filter to a specific agent (e.g. 'mergemaster', 'coder-1'). Omit to see all agents."
                            },
                            "lines": {
                                "type": "integer",
                                "description": "Optional: return only the last N lines (tail). Useful for large logs."
                            },
                            "filter": {
                                "type": "string",
                                "description": "Optional: return only lines containing this substring (e.g. 'ERROR', 'TOOL:', a function name)."
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "wait_for_agent",
                    "description": "Block until the agent reaches a terminal state (completed, failed, stopped). Returns final status and summary including session_id, worktree_path, and any commits made. Use this instead of polling get_agent_output when you want to fire-and-forget and be notified on completion.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Agent name to wait for"
                            },
                            "timeout_ms": {
                                "type": "integer",
                                "description": "Maximum time to wait in milliseconds (default: 300000 = 5 minutes)"
                            }
                        },
                        "required": ["story_id", "agent_name"]
                    }
                },
                {
                    "name": "get_agent_remaining_turns_and_budget",
                    "description": "Get remaining turns and budget for a running agent. Returns turns used, max turns, remaining turns, budget used (from completed sessions), max budget, and remaining budget. Only works for agents in running or pending state.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '42_story_my_feature')"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Agent name (e.g. 'coder-1', 'mergemaster', 'qa')"
                            }
                        },
                        "required": ["story_id", "agent_name"]
                    }
                },
                {
                    "name": "create_worktree",
                    "description": "Create a git worktree for a story under .huskies/worktrees/{story_id} with deterministic naming. Writes .mcp.json and runs component setup. Returns the worktree path.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '42_my_story')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "list_worktrees",
                    "description": "List all worktrees under .huskies/worktrees/ for the current project.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "remove_worktree",
                    "description": "Remove a git worktree and its feature branch for a story.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "get_editor_command",
                    "description": "Get the open-in-editor command for a worktree. Returns a ready-to-paste shell command like 'zed /path/to/worktree'. Requires the editor preference to be configured via PUT /api/settings/editor.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory"
                            }
                        },
                        "required": ["worktree_path"]
                    }
                },
                {
                    "name": "accept_story",
                    "description": "Accept a story: moves it from current/ to done/ and auto-commits to master.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "check_criterion",
                    "description": "Check off an acceptance criterion (- [ ] → - [x]) by 0-based index among unchecked items, then auto-commit to master. Use get_story_todos to see the current list of unchecked criteria.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            },
                            "criterion_index": {
                                "type": "integer",
                                "description": "0-based index of the unchecked criterion to check off"
                            }
                        },
                        "required": ["story_id", "criterion_index"]
                    }
                },
                {
                    "name": "edit_criterion",
                    "description": "Update the text of an existing acceptance criterion in place, preserving its checked/unchecked state. Uses a 0-based index counting all criteria (both checked and unchecked).",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            },
                            "criterion_index": {
                                "type": "integer",
                                "description": "0-based index of the criterion to edit (counts all criteria)"
                            },
                            "new_text": {
                                "type": "string",
                                "description": "New text for the criterion (without the '- [ ] ' or '- [x] ' prefix)"
                            }
                        },
                        "required": ["story_id", "criterion_index", "new_text"]
                    }
                },
                {
                    "name": "add_criterion",
                    "description": "Add an acceptance criterion to an existing story file. Appends '- [ ] {criterion}' after the last existing criterion in the '## Acceptance Criteria' section. Auto-commits via the filesystem watcher.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            },
                            "criterion": {
                                "type": "string",
                                "description": "The acceptance criterion text to add (without the '- [ ] ' prefix)"
                            }
                        },
                        "required": ["story_id", "criterion"]
                    }
                },
                {
                    "name": "remove_criterion",
                    "description": "Remove an acceptance criterion from a story by its 0-based index (counting all criteria, both checked and unchecked). Returns an error if the index is out of range. Auto-commits via the filesystem watcher.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            },
                            "criterion_index": {
                                "type": "integer",
                                "description": "0-based index of the criterion to remove (counts all criteria)"
                            }
                        },
                        "required": ["story_id", "criterion_index"]
                    }
                },
                {
                    "name": "update_story",
                    "description": "Update an existing story file. Can replace the '## User Story' and/or '## Description' section content, and/or set YAML front matter fields (e.g. agent, qa). Auto-commits via the filesystem watcher.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            },
                            "user_story": {
                                "type": "string",
                                "description": "New user story text to replace the '## User Story' section content"
                            },
                            "description": {
                                "type": "string",
                                "description": "New description text to replace the '## Description' section content"
                            },
                            "agent": {
                                "type": "string",
                                "description": "Set or change the 'agent' YAML front matter field"
                            },
                            "front_matter": {
                                "type": "object",
                                "description": "Arbitrary YAML front matter key-value pairs to set or update. Values may be strings, booleans, integers, numbers, or arrays (e.g. [490, 491]).",
                                "additionalProperties": {
                                    "oneOf": [
                                        {"type": "string"},
                                        {"type": "boolean"},
                                        {"type": "integer"},
                                        {"type": "number"},
                                        {"type": "array"}
                                    ]
                                }
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "create_spike",
                    "description": "Create a spike file in .huskies/work/1_backlog/ with a deterministic filename and YAML front matter. Returns the spike_id.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Human-readable spike name"
                            },
                            "description": {
                                "type": "string",
                                "description": "Optional description / question the spike aims to answer"
                            },
                            "acceptance_criteria": {
                                "type": "array",
                                "items": { "type": "string" },
                                "minItems": 1,
                                "description": "List of acceptance criteria (at least one required)"
                            }
                        },
                        "required": ["name", "acceptance_criteria"]
                    }
                },
                {
                    "name": "create_bug",
                    "description": "Create a bug file in work/1_backlog/ with a deterministic filename and auto-commit to master. Returns the bug_id.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Short human-readable bug name"
                            },
                            "description": {
                                "type": "string",
                                "description": "Description of the bug"
                            },
                            "steps_to_reproduce": {
                                "type": "string",
                                "description": "Steps to reproduce the bug"
                            },
                            "actual_result": {
                                "type": "string",
                                "description": "What actually happens"
                            },
                            "expected_result": {
                                "type": "string",
                                "description": "What should happen"
                            },
                            "acceptance_criteria": {
                                "type": "array",
                                "items": { "type": "string" },
                                "minItems": 1,
                                "description": "List of acceptance criteria for the fix (at least one required)"
                            },
                            "depends_on": {
                                "type": "array",
                                "items": { "type": "integer" },
                                "description": "Optional list of story numbers this bug depends on (e.g. [42, 43]). Persisted as depends_on in YAML front matter."
                            }
                        },
                        "required": ["name", "description", "steps_to_reproduce", "actual_result", "expected_result", "acceptance_criteria"]
                    }
                },
                {
                    "name": "list_bugs",
                    "description": "List all open bugs in work/1_backlog/ matching the _bug_ naming convention.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "create_refactor",
                    "description": "Create a refactor work item in work/1_backlog/ with a deterministic filename and YAML front matter. Returns the refactor_id.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Short human-readable refactor name"
                            },
                            "description": {
                                "type": "string",
                                "description": "Optional description of the desired state after refactoring"
                            },
                            "acceptance_criteria": {
                                "type": "array",
                                "items": { "type": "string" },
                                "minItems": 1,
                                "description": "List of acceptance criteria (at least one required)"
                            },
                            "depends_on": {
                                "type": "array",
                                "items": { "type": "integer" },
                                "description": "Optional list of story numbers this refactor depends on (e.g. [42, 43]). Persisted as depends_on in YAML front matter."
                            }
                        },
                        "required": ["name", "acceptance_criteria"]
                    }
                },
                {
                    "name": "list_refactors",
                    "description": "List all open refactors in work/1_backlog/ matching the _refactor_ naming convention.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "close_bug",
                    "description": "Archive a bug from work/2_current/ or work/1_backlog/ to work/5_done/ and auto-commit to master.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "bug_id": {
                                "type": "string",
                                "description": "Bug identifier (e.g. 'bug-3-login_crash')"
                            }
                        },
                        "required": ["bug_id"]
                    }
                },
                {
                    "name": "merge_agent_work",
                    "description": "Run the mergemaster pipeline for a completed story. Blocks until the merge completes or fails, then returns the full result — no polling needed. The pipeline squash-merges the feature branch into master, runs quality gates, moves the story to done, and cleans up.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '52_story_mergemaster_agent_role')"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Optional: name of the coder agent whose work is being merged (for logging)"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "get_merge_status",
                    "description": "Check the cached result of a merge_agent_work job. Returns the full merge report immediately — no polling needed. Useful if merge_agent_work already returned but you need the result again.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (same as passed to merge_agent_work)"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "move_story_to_merge",
                    "description": "Move a story or bug from work/2_current/ to work/4_merge/ to queue it for the mergemaster pipeline and automatically spawn the mergemaster agent to squash-merge, run quality gates, and archive.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '28_my_story')"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Agent name to use for merging (defaults to 'mergemaster')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "report_merge_failure",
                    "description": "Report that a merge failed for a story. Leaves the story in work/4_merge/ and logs the failure reason. Use this when merge_agent_work returns success=false instead of manually moving the story file.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '52_story_mergemaster_agent_role')"
                            },
                            "reason": {
                                "type": "string",
                                "description": "Human-readable explanation of why the merge failed"
                            }
                        },
                        "required": ["story_id", "reason"]
                    }
                },
                {
                    "name": "request_qa",
                    "description": "Trigger QA review of a completed story worktree: moves the item from work/2_current/ to work/3_qa/ and starts the qa agent to run quality gates, tests, and generate a manual testing plan.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '53_story_qa_agent_role')"
                            },
                            "agent_name": {
                                "type": "string",
                                "description": "Agent name to use for QA (defaults to 'qa')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "approve_qa",
                    "description": "Approve a story that passed machine QA and is awaiting human review. Moves the story from work/3_qa/ to work/4_merge/ and starts the mergemaster agent.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '247_story_human_qa_gate')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "reject_qa",
                    "description": "Reject a story during human QA review. Moves the story from work/3_qa/ back to work/2_current/ with rejection notes so the coder agent can fix the issues.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (e.g. '247_story_human_qa_gate')"
                            },
                            "notes": {
                                "type": "string",
                                "description": "Explanation of what is broken or needs fixing"
                            }
                        },
                        "required": ["story_id", "notes"]
                    }
                },
                {
                    "name": "launch_qa_app",
                    "description": "Launch the app from a story's worktree for manual QA testing. Automatically assigns a free port, writes it to .huskies_port, and starts the backend server. Only one QA app instance runs at a time.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier whose worktree app to launch"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "get_pipeline_status",
                    "description": "Return a structured snapshot of the full work item pipeline. Includes all active stages (current, qa, merge, done) with each item's stage, name, and assigned agent. Also includes upcoming backlog items.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "get_server_logs",
                    "description": "Return recent server log lines captured in the in-process ring buffer. Useful for diagnosing runtime behaviour such as WebSocket events, MCP call flow, and filesystem watcher activity.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "lines": {
                                "type": "integer",
                                "description": "Number of recent lines to return (default 100, max 1000)"
                            },
                            "filter": {
                                "type": "string",
                                "description": "Optional substring filter (e.g. 'watcher', 'mcp', 'permission')"
                            },
                            "severity": {
                                "type": "string",
                                "description": "Filter by severity level: ERROR, WARN, or INFO. Returns only entries at that level."
                            }
                        }
                    }
                },
                {
                    "name": "get_version",
                    "description": "Return the server version, build hash, and running port.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "rebuild_and_restart",
                    "description": "Rebuild the server binary from source and re-exec with the new binary. Gracefully stops all running agents before restart. If the build fails, the server stays up and returns the build error.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "prompt_permission",
                    "description": "Present a permission request to the user via the web UI. Used by Claude Code's --permission-prompt-tool to delegate permission decisions to the frontend dialog. Returns on approval; returns an error on denial.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "tool_name": {
                                "type": "string",
                                "description": "The tool requesting permission (e.g. 'Bash', 'Write')"
                            },
                            "input": {
                                "type": "object",
                                "description": "The tool's input arguments"
                            }
                        },
                        "required": ["tool_name", "input"]
                    }
                },
                {
                    "name": "get_token_usage",
                    "description": "Return per-agent token usage records from the persistent log. Shows input tokens, output tokens, cache tokens, and cost in USD for each agent session. Optionally filter by story_id.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Optional: filter records to a specific story (e.g. '42_my_story')"
                            }
                        }
                    }
                },
                {
                    "name": "delete_story",
                    "description": "Delete a work item from the pipeline entirely. Stops any running agent, removes the worktree, and deletes the story file. Use only for removing obsolete or duplicate items.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Work item identifier (filename stem, e.g. '28_story_my_feature')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "purge_story",
                    "description": "Write a CRDT tombstone op for a story (story 521). Marks the in-memory CRDT item as deleted, persists the tombstone to crdt_ops so it survives restart, and drops the in-memory content store entry. Does NOT touch running agents, worktrees, the pipeline_items shadow table, timers.json, or filesystem shadows — compose with stop_agent / remove_worktree / etc. for a full cleanup. Use this when a story has gone zombie in the running server's in-memory state and direct sqlite deletes alone are not enough to clear it.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Work item identifier (filename stem, e.g. '28_story_my_feature')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "dump_crdt",
                    "description": "DEBUG TOOL: Dump the raw in-memory CRDT state. Returns every item the running server knows about, including tombstoned (deleted) entries, with internal op metadata (content_index, is_deleted, stage, etc.). Use this when diagnosing CRDT/state divergence — NOT for normal pipeline introspection (use get_pipeline_status for that). Optional story_id filter returns a single item.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Optional: restrict output to this single work item identifier (filename stem, e.g. '42_story_my_feature')"
                            }
                        },
                        "required": []
                    }
                },
                {
                    "name": "move_story",
                    "description": "Move a work item (story, bug, spike, or refactor) to an arbitrary pipeline stage. Prefer dedicated tools when available: use accept_story to mark items done, move_story_to_merge to queue for merging, or request_qa to trigger QA review. Use move_story only for arbitrary moves that lack a dedicated tool — for example, moving a story back to backlog or recovering a ghost story by moving it back to current.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Work item identifier (filename stem, e.g. '28_story_my_feature')"
                            },
                            "target_stage": {
                                "type": "string",
                                "enum": ["backlog", "current", "qa", "merge", "done"],
                                "description": "Target pipeline stage: backlog (1_backlog), current (2_current), qa (3_qa), merge (4_merge), done (5_done)"
                            }
                        },
                        "required": ["story_id", "target_stage"]
                    }
                },
                {
                    "name": "unblock_story",
                    "description": "Clear the blocked flag and reset retry_count to 0 on a work item. Use this when an agent is stuck and needs to be restarted from a clean state.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Work item identifier (filename stem, e.g. '42_story_my_feature')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "run_command",
                    "description": "Execute a shell command in an agent's worktree directory. The working_dir must be inside .huskies/worktrees/. Returns stdout, stderr, exit_code, and timed_out. Supports SSE streaming (send Accept: text/event-stream) for long-running commands. Dangerous commands (rm -rf /, sudo, etc.) are blocked.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "command": {
                                "type": "string",
                                "description": "The bash command to execute (passed to bash -c)"
                            },
                            "working_dir": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory to run the command in. Must be inside .huskies/worktrees/."
                            },
                            "timeout": {
                                "type": "integer",
                                "description": "Timeout in seconds (default: 120, max: 600)"
                            }
                        },
                        "required": ["command", "working_dir"]
                    }
                },
                {
                    "name": "run_tests",
                    "description": "Start the project's test suite (script/test) as a background job. Returns immediately with {\"status\": \"started\"}. Poll get_test_result with the same worktree_path to check for completion. If the previous run already finished, returns the result inline.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Optional absolute path to a worktree to run tests in. Must be inside .huskies/worktrees/. Defaults to the project root."
                            }
                        },
                        "required": []
                    }
                },
                {
                    "name": "get_test_result",
                    "description": "Check on a running test job started by run_tests. Returns {\"status\": \"running\", \"elapsed_secs\": N} if still in progress, or the full test result (passed, exit_code, test counts, output) if finished.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Optional absolute path to the worktree. Must match the worktree_path used in run_tests."
                            }
                        },
                        "required": []
                    }
                },
                {
                    "name": "run_build",
                    "description": "Run the project's build script (script/build) in the given worktree and return the result as truncated JSON with passed, exit_code, and output fields.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Optional absolute path to a worktree to run the build in. Must be inside .huskies/worktrees/. Defaults to the project root."
                            }
                        },
                        "required": []
                    }
                },
                {
                    "name": "run_lint",
                    "description": "Run the project's lint script (script/lint) in the given worktree and return the result as truncated JSON with passed, exit_code, and output fields.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Optional absolute path to a worktree to run lint in. Must be inside .huskies/worktrees/. Defaults to the project root."
                            }
                        },
                        "required": []
                    }
                },
                {
                    "name": "git_status",
                    "description": "Return the working tree status of an agent's worktree (staged, unstaged, and untracked files). The worktree_path must be inside .huskies/worktrees/. Push and remote operations are not available.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory. Must be inside .huskies/worktrees/."
                            }
                        },
                        "required": ["worktree_path"]
                    }
                },
                {
                    "name": "git_diff",
                    "description": "Return diff output for an agent's worktree. Supports unstaged (default), staged, or a commit range. The worktree_path must be inside .huskies/worktrees/.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory. Must be inside .huskies/worktrees/."
                            },
                            "staged": {
                                "type": "boolean",
                                "description": "If true, show staged diff (--staged). Default: false."
                            },
                            "commit_range": {
                                "type": "string",
                                "description": "Optional commit range (e.g. 'HEAD~3..HEAD', 'abc123..def456')."
                            }
                        },
                        "required": ["worktree_path"]
                    }
                },
                {
                    "name": "git_add",
                    "description": "Stage files by path in an agent's worktree. The worktree_path must be inside .huskies/worktrees/.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory. Must be inside .huskies/worktrees/."
                            },
                            "paths": {
                                "type": "array",
                                "items": { "type": "string" },
                                "description": "List of file paths to stage (relative to worktree_path)."
                            }
                        },
                        "required": ["worktree_path", "paths"]
                    }
                },
                {
                    "name": "git_commit",
                    "description": "Commit staged changes in an agent's worktree with the given message. The worktree_path must be inside .huskies/worktrees/. Push and remote operations are not available.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory. Must be inside .huskies/worktrees/."
                            },
                            "message": {
                                "type": "string",
                                "description": "Commit message."
                            }
                        },
                        "required": ["worktree_path", "message"]
                    }
                },
                {
                    "name": "git_log",
                    "description": "Return commit history for an agent's worktree with configurable count and format. The worktree_path must be inside .huskies/worktrees/.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "worktree_path": {
                                "type": "string",
                                "description": "Absolute path to the worktree directory. Must be inside .huskies/worktrees/."
                            },
                            "count": {
                                "type": "integer",
                                "description": "Number of commits to return (default: 10, max: 500)."
                            },
                            "format": {
                                "type": "string",
                                "description": "git pretty-format string (default: '%H%x09%s%x09%an%x09%ai')."
                            }
                        },
                        "required": ["worktree_path"]
                    }
                },
                {
                    "name": "status",
                    "description": "Get a full triage dump for an in-progress story: front matter, AC checklist, active worktree/branch, git diff --stat since master, last 5 commits, and last 20 lines of the most recent agent log. Returns a clear error if the story is not in work/2_current/.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "story_id": {
                                "type": "string",
                                "description": "Story identifier (filename stem, e.g. '42_story_my_feature')"
                            }
                        },
                        "required": ["story_id"]
                    }
                },
                {
                    "name": "loc_file",
                    "description": "Return the line count for a specific file. Path is resolved relative to the project root. Returns an error if the file does not exist.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "file_path": {
                                "type": "string",
                                "description": "Path to the file, relative to the project root (e.g. 'server/src/main.rs')"
                            }
                        },
                        "required": ["file_path"]
                    }
                },
                {
                    "name": "wizard_status",
                    "description": "Return the current setup wizard state: which step is active, and which are done/skipped/pending. Use this to inspect progress before calling wizard_generate, wizard_confirm, wizard_skip, or wizard_retry.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "wizard_generate",
                    "description": "Drive content generation for the current wizard step. Call with no arguments to mark the step as 'generating' and receive a hint about what to produce. Call again with a 'content' argument (the full file body you generated) to stage it for review. Content is NOT written to disk until wizard_confirm is called.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {
                            "content": {
                                "type": "string",
                                "description": "The generated file content to stage for the current step. Omit to receive a generation hint and mark the step as generating."
                            }
                        }
                    }
                },
                {
                    "name": "wizard_confirm",
                    "description": "Confirm the current wizard step: writes any staged content to disk (only if the target file does not already exist) and advances to the next step. Existing files are never overwritten.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "wizard_skip",
                    "description": "Skip the current wizard step without writing any file. Use when a step does not apply to this project.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                },
                {
                    "name": "wizard_retry",
                    "description": "Discard any staged content for the current wizard step and reset it to pending so it can be regenerated. Use when the generated content needs improvement.",
                    "inputSchema": {
                        "type": "object",
                        "properties": {}
                    }
                }
            ]
        }),
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tools_list_returns_all_tools() {
        let resp = handle_tools_list(Some(json!(2)));
        let result = resp.result.unwrap();
        let tools = result["tools"].as_array().unwrap();
        let names: Vec<&str> = tools.iter().map(|t| t["name"].as_str().unwrap()).collect();
        assert!(names.contains(&"create_story"));
        assert!(names.contains(&"validate_stories"));
        assert!(names.contains(&"list_upcoming"));
        assert!(names.contains(&"get_story_todos"));
        assert!(names.contains(&"record_tests"));
        assert!(names.contains(&"ensure_acceptance"));
        assert!(names.contains(&"start_agent"));
        assert!(names.contains(&"stop_agent"));
        assert!(names.contains(&"list_agents"));
        assert!(names.contains(&"get_agent_config"));
        assert!(names.contains(&"reload_agent_config"));
        assert!(names.contains(&"get_agent_output"));
        assert!(names.contains(&"wait_for_agent"));
        assert!(names.contains(&"get_agent_remaining_turns_and_budget"));
        assert!(names.contains(&"create_worktree"));
        assert!(names.contains(&"list_worktrees"));
        assert!(names.contains(&"remove_worktree"));
        assert!(names.contains(&"get_editor_command"));
        assert!(!names.contains(&"report_completion"));
        assert!(names.contains(&"accept_story"));
        assert!(names.contains(&"check_criterion"));
        assert!(names.contains(&"add_criterion"));
        assert!(names.contains(&"update_story"));
        assert!(names.contains(&"create_spike"));
        assert!(names.contains(&"create_bug"));
        assert!(names.contains(&"list_bugs"));
        assert!(names.contains(&"close_bug"));
        assert!(names.contains(&"create_refactor"));
        assert!(names.contains(&"list_refactors"));
        assert!(names.contains(&"merge_agent_work"));
        assert!(names.contains(&"get_merge_status"));
        assert!(names.contains(&"move_story_to_merge"));
        assert!(names.contains(&"report_merge_failure"));
        assert!(names.contains(&"request_qa"));
        assert!(names.contains(&"approve_qa"));
        assert!(names.contains(&"reject_qa"));
        assert!(names.contains(&"launch_qa_app"));
        assert!(names.contains(&"get_server_logs"));
        assert!(names.contains(&"prompt_permission"));
        assert!(names.contains(&"get_pipeline_status"));
        assert!(names.contains(&"rebuild_and_restart"));
        assert!(names.contains(&"get_token_usage"));
        assert!(names.contains(&"move_story"));
        assert!(names.contains(&"unblock_story"));
        assert!(names.contains(&"delete_story"));
        assert!(names.contains(&"run_command"));
        assert!(names.contains(&"run_tests"));
        assert!(names.contains(&"get_test_result"));
        assert!(names.contains(&"run_build"));
        assert!(names.contains(&"run_lint"));
        assert!(names.contains(&"git_status"));
        assert!(names.contains(&"git_diff"));
        assert!(names.contains(&"git_add"));
        assert!(names.contains(&"git_commit"));
        assert!(names.contains(&"git_log"));
        assert!(names.contains(&"status"));
        assert!(names.contains(&"loc_file"));
        assert!(names.contains(&"dump_crdt"));
        assert!(names.contains(&"get_version"));
        assert!(names.contains(&"remove_criterion"));
        assert_eq!(tools.len(), 66);
    }

    #[test]
    fn tools_list_schemas_have_required_fields() {
        let resp = handle_tools_list(Some(json!(1)));
        let tools = resp.result.unwrap()["tools"].as_array().unwrap().clone();
        for tool in &tools {
            assert!(tool["name"].is_string(), "tool missing name");
            assert!(tool["description"].is_string(), "tool missing description");
            assert!(tool["inputSchema"].is_object(), "tool missing inputSchema");
            assert_eq!(tool["inputSchema"]["type"], "object");
        }
    }
}
