diff --git a/.gitignore b/.gitignore index bce9863..e641279 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ dist/ # Evaluation results .evaluated-commits/ + +# Test temp files +.test-temp/ results.json report.html index.html @@ -13,6 +16,12 @@ index.html # Claude Code configuration .claude/ +#Cursor +.Cursor + +#Tests +coverage + # IDE configuration .vscode/* !.vscode/extensions.json diff --git a/README.md b/README.md index 18c5729..59f5d7e 100644 --- a/README.md +++ b/README.md @@ -1062,6 +1062,152 @@ codewave evaluate --commit HEAD --- +## MCP Integration with Claude Desktop + +CodeWave is available as an **MCP (Model Context Protocol) server**, allowing you to evaluate commits and analyze code quality directly within **Claude Desktop** through natural conversation. + +### What You Can Do + +- **Evaluate commits conversationally**: Ask Claude to evaluate your latest commits +- **Get insights without commands**: No need to remember CLI syntax +- **Generate OKRs**: Create data-driven development goals based on commit history +- **Compare quality**: Track improvements between commits +- **Analyze teams**: Get team-wide code quality statistics +- **Export reports**: Save evaluations to HTML/Markdown/JSON formats + +### Quick Start + +```bash +# 1. Install CodeWave globally +npm install -g @techdebtgpt/codewave + +# 2. Configure CodeWave +codewave config --init + +# 3. Setup Claude Desktop +# See docs/MCP_SETUP.md for detailed instructions +``` + +### Example Conversation + +``` +You: "Evaluate my last commit and tell me if there are any concerns" + +Claude: [Uses CodeWave MCP to evaluate] + "I've evaluated commit abc123. Overall score: 8.5/10. + + The code quality is excellent (9.0/10), but there are two concerns: + 1. Missing rate limiting on the login endpoint + 2. Password validation could be stronger + + I recommend adding rate limiting before pushing to production..." +``` + +### Available Tools + +- **10 production-ready tools** for evaluation, analytics, and reporting +- **Support for inline configuration** (no config file needed) +- **Three analysis modes**: fast, normal, and deep +- **Batch operations**: Evaluate up to 10 commits at once +- **No API calls** for analytics tools (instant results) + +### Key Features + +| Feature | Description | +|---------|-------------| +| Natural language interface | Chat with Claude instead of remembering commands | +| Conversational workflows | Combine multiple tools in one conversation | +| Context awareness | Claude remembers previous results in the conversation | +| Intelligent interpretation | Claude explains results in plain language | +| Follow-up questions | Ask for clarification or deeper analysis | + +### Available MCP Tools + +| Tool | Purpose | Makes API Calls? | +|------|---------|------------------| +| `evaluate_commit` | Evaluate single commit | Yes | +| `batch_evaluate` | Evaluate multiple commits | Yes | +| `generate_okr` | Generate developer OKRs | Yes | +| `check_config` | Verify configuration | No | +| `list_evaluations` | Browse evaluation history | No | +| `get_report` | Get detailed commit report | No | +| `get_author_stats` | Analyze developer stats | No | +| `compare_commits` | Compare two commits | No | +| `get_team_stats` | Team-wide statistics | No | +| `export_report` | Export to file | No | + +### Documentation + +- **[MCP Setup Guide](./docs/MCP_SETUP.md)** - Installation and Claude Desktop configuration +- **[MCP Tools Reference](./docs/MCP_TOOLS_REFERENCE.md)** - Complete tool documentation with examples +- **[MCP Examples](./docs/MCP_EXAMPLES.md)** - Practical workflows and example conversations + +### Requirements + +- **Claude Desktop** app installed ([download](https://claude.ai/download)) +- **Node.js** ≥18.0.0 +- **CodeWave** globally installed +- **Valid configuration** with LLM API key + +### How It Works + +``` +┌─────────────────┐ +│ Claude Desktop │ +│ (Your Chat) │ +└────────┬────────┘ + │ + │ Natural language request + │ "Evaluate my last commit" + │ + ▼ +┌─────────────────┐ +│ MCP Protocol │ +│ (JSON-RPC) │ +└────────┬────────┘ + │ + │ Tool call: evaluate_commit + │ + ▼ +┌─────────────────┐ +│ CodeWave Server │ +│ Multi-Agent AI │ +└────────┬────────┘ + │ + │ Results + │ + ▼ +┌─────────────────┐ +│ Claude Desktop │ +│ (Interprets & │ +│ Explains) │ +└─────────────────┘ +``` + +### Getting Started + +1. **Complete setup**: Follow the [MCP Setup Guide](./docs/MCP_SETUP.md) +2. **Try examples**: See [MCP Examples](./docs/MCP_EXAMPLES.md) for workflows +3. **Learn tools**: Reference [MCP Tools Reference](./docs/MCP_TOOLS_REFERENCE.md) as needed + +### MCP vs CLI + +Both interfaces use the same CodeWave engine. Choose based on your workflow: + +| Use Case | Recommended Interface | +|----------|----------------------| +| Interactive exploration | **MCP (Claude Desktop)** | +| Quick quality checks | **MCP (Claude Desktop)** | +| Learning patterns | **MCP (Claude Desktop)** | +| Performance reviews | **MCP (Claude Desktop)** | +| CI/CD automation | **CLI** | +| Scripting/batch jobs | **CLI** | +| Custom integrations | **CLI + API** | + +See [docs/MCP_SETUP.md](./docs/MCP_SETUP.md) for complete setup instructions. + +--- + ## Examples ### Example 1: Evaluate Latest 5 Commits diff --git a/cli/mcp-server.ts b/cli/mcp-server.ts new file mode 100644 index 0000000..601b327 --- /dev/null +++ b/cli/mcp-server.ts @@ -0,0 +1,17 @@ +#!/usr/bin/env node + +/** + * CLI Entry Point for CodeWave MCP Server + * + * Usage: + * npx codewave-mcp + * node dist/cli/mcp-server.js + */ + +import { startServer } from '../src/mcp-server/index'; + +// Start the MCP server +startServer().catch((error) => { + console.error('Failed to start CodeWave MCP server:', error); + process.exit(1); +}); diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index f6cf848..0cff1d1 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -777,6 +777,133 @@ cp ~/.codewave/config.json.backup ~/.codewave/config.json --- +## MCP Configuration + +When using CodeWave as an MCP server with Claude Desktop or Cursor, the configuration works the same way as the CLI. + +### Configuration Methods + +**1. File-based configuration (recommended):** + +- Use `.codewave.config.json` in your repository root or home directory +- Run `codewave config --init` to create interactively +- MCP server will automatically detect and use this file +- Same configuration file used by both CLI and MCP + +**2. Inline configuration:** + +- Pass `provider`, `api_key`, and `model` directly in MCP tool calls +- Useful when no config file exists or for testing different providers +- All three parameters are required together +- Example (in Claude Desktop conversation): + +``` +"Evaluate HEAD with Google Gemini" +[Claude will prompt for api_key if needed] +``` + +Or you can provide all inline parameters: + +```json +{ + "commit_hash": "HEAD", + "provider": "google", + "api_key": "your-google-api-key", + "model": "gemini-2.0-flash-exp" +} +``` + +### Verifying Configuration + +Use the `check_config` MCP tool to verify your configuration: + +**In Claude Desktop:** +``` +"Check my CodeWave configuration" +``` + +This will return: +- **Provider**: anthropic, openai, google, etc. +- **Model**: claude-3-5-sonnet-20241022, gpt-4, etc. +- **API Key Status**: Present (✓) or Missing (✗) - never shows actual key +- **Enabled Agents**: BA, DA, DR, SA, QA +- **Configuration Path**: Where the config file is located + +### API Key Security + +**Important security notes:** + +1. **Never commit** `.codewave.config.json` to version control + ```bash + echo ".codewave.config.json" >> .gitignore + ``` + +2. **API keys are not logged**: Only the last 4 characters are shown in logs for debugging + +3. **Inline keys are not stored**: Keys passed inline in MCP calls are used once and discarded + +4. **Claude Desktop isolation**: Your API keys never leave your machine - MCP runs locally + +### Configuration Priority + +When both file and inline config are provided: + +1. **Inline parameters** (provider/api_key/model) take highest priority +2. **File configuration** used if no inline params provided +3. **Error** if neither is available + +**Example scenarios:** + +| Scenario | File Config | Inline Config | Result | +|----------|-------------|---------------|--------| +| Both present | ✓ Anthropic | ✓ Google | Uses **Google** (inline wins) | +| Only file | ✓ Anthropic | ✗ None | Uses **Anthropic** | +| Only inline | ✗ None | ✓ Google | Uses **Google** | +| Neither | ✗ None | ✗ None | **Error** - run config --init | + +### Claude Desktop Setup + +For complete Claude Desktop setup instructions, see: +- **[MCP_SETUP.md](./MCP_SETUP.md)** - Installation and configuration guide +- **[MCP_TOOLS_REFERENCE.md](./MCP_TOOLS_REFERENCE.md)** - Tool documentation + +### Example: Using Different Providers + +You can easily test different LLM providers without changing your config file: + +**With file config (Anthropic):** +``` +"Evaluate my last commit" +[Uses Claude from config file] +``` + +**Override with Google inline:** +``` +"Evaluate HEAD using Google Gemini with my API key" +[Claude will use inline Google config] +``` + +**Back to file config:** +``` +"Evaluate HEAD~1" +[Uses Claude from config file again] +``` + +### Environment Variables + +MCP server also supports environment variables for API keys: + +```bash +# Set in shell before starting Claude Desktop +export ANTHROPIC_API_KEY="your-anthropic-key" +export OPENAI_API_KEY="your-openai-key" +export GOOGLE_API_KEY="your-google-key" +``` + +Priority: **Inline > Config File > Environment Variables** + +--- + ## Troubleshooting ### "API Key not found" diff --git a/docs/INDEX.md b/docs/INDEX.md index a6abc92..9771034 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -104,6 +104,32 @@ Complete guide to all CodeWave documentation. - Advanced usage patterns - Analyzing discussion quality +### MCP Integration (Claude Desktop / Cursor) + +- **[MCP_SETUP.md](./MCP_SETUP.md)** (~450 lines) + - What is MCP and Model Context Protocol + - Prerequisites and installation + - Claude Desktop configuration + - Step-by-step setup guide + - Verification and troubleshooting + - Connection issues and debugging + +- **[MCP_TOOLS_REFERENCE.md](./MCP_TOOLS_REFERENCE.md)** (~950 lines) + - Complete reference for all 10 MCP tools + - Tool parameters and validation rules + - Example requests and responses + - Error handling and error codes + - Response format specifications + - Performance characteristics + +- **[MCP_EXAMPLES.md](./MCP_EXAMPLES.md)** (~680 lines) + - Getting started workflow (5 minutes) + - Example Claude Desktop conversations + - Step-by-step workflow scenarios + - Advanced usage patterns + - Tips and tricks for better prompts + - CI/CD integration examples + ### Developers - **[API.md](./API.md)** (716 lines) @@ -142,7 +168,10 @@ Complete guide to all CodeWave documentation. | EXAMPLES.md | 688 | Practical Use Cases | Users | | API.md | 716 | Programmatic Access | Developers | | TROUBLESHOOTING.md | 824 | Support & Fixes | Everyone | -| **TOTAL** | **8,245** | | | +| MCP_SETUP.md | 450 | MCP Installation & Setup | Claude Users | +| MCP_TOOLS_REFERENCE.md| 950 | MCP Tool Reference | Claude Users | +| MCP_EXAMPLES.md | 680 | MCP Use Cases | Claude Users | +| **TOTAL** | **10,325**| | | ## Quick Navigation @@ -187,6 +216,9 @@ Complete guide to all CodeWave documentation. **Track model usage and costs** → [CONFIGURATION.md](./CONFIGURATION.md) - Model Display and Cost Tracking sections +**Use CodeWave in Claude Desktop** +→ [MCP_SETUP.md](./MCP_SETUP.md) - Configure MCP integration + ## Command Cheat Sheet ### Essential Commands @@ -287,6 +319,13 @@ START HERE 3. Enable verbose: `codewave evaluate HEAD --verbose` 4. Collect debug info and report issue +### Path 5: Claude Desktop Integration (30 min - 1 hour) + +1. Read [README.md](../README.md) - "MCP Integration" section (5 min) +2. Follow [MCP_SETUP.md](./MCP_SETUP.md) - Setup Claude Desktop (10 min) +3. Try examples from [MCP_EXAMPLES.md](./MCP_EXAMPLES.md) - Practice (15 min) +4. Bookmark [MCP_TOOLS_REFERENCE.md](./MCP_TOOLS_REFERENCE.md) - Reference + ## Search Tips ### By Error Message @@ -325,6 +364,13 @@ Search [CLI.md](./CLI.md) or [EXAMPLES.md](./EXAMPLES.md) - **LangGraph**: https://www.langchain.com/langgraph - **Commander.js**: https://github.com/tj/commander.js +### MCP Resources + +- **MCP Specification**: https://modelcontextprotocol.io +- **MCP Inspector**: https://github.com/modelcontextprotocol/inspector +- **Claude Desktop**: https://claude.ai/download +- **MCP SDK**: https://github.com/modelcontextprotocol/sdk + ## Feedback & Contributions - Report Issues: https://github.com/techdebtgpt/codewave/issues/new diff --git a/docs/MCP_EXAMPLES.md b/docs/MCP_EXAMPLES.md new file mode 100644 index 0000000..2043d2a --- /dev/null +++ b/docs/MCP_EXAMPLES.md @@ -0,0 +1,840 @@ +# CodeWave MCP Examples + +Practical examples and workflows for using CodeWave with Claude Desktop. + +## Table of Contents + +- [Getting Started (5 Minutes)](#getting-started-5-minutes) +- [Example Conversations](#example-conversations) + - [Scenario 1: Quick Commit Check](#scenario-1-quick-commit-check) + - [Scenario 2: Building Evaluation History](#scenario-2-building-evaluation-history) + - [Scenario 3: Author Analysis](#scenario-3-author-analysis) + - [Scenario 4: Generate OKRs](#scenario-4-generate-okrs) + - [Scenario 5: Compare Commits](#scenario-5-compare-commits) +- [Step-by-Step Workflows](#step-by-step-workflows) + - [Workflow 1: Single Commit Deep Dive](#workflow-1-single-commit-deep-dive) + - [Workflow 2: Building Evaluation History](#workflow-2-building-evaluation-history) + - [Workflow 3: Author Analysis](#workflow-3-author-analysis) + - [Workflow 4: Team Analysis](#workflow-4-team-analysis) + - [Workflow 5: CI/CD Integration](#workflow-5-cicd-integration) +- [Advanced Usage Patterns](#advanced-usage-patterns) +- [Tips and Tricks](#tips-and-tricks) + +--- + +## Getting Started (5 Minutes) + +This quick guide gets you evaluating commits in Claude Desktop. + +### Prerequisites + +1. **Setup complete**: Follow [MCP_SETUP.md](./MCP_SETUP.md) first +2. **In a git repository**: Navigate to a repo with commits +3. **Configuration ready**: Run `codewave config --init` if needed + +### Your First Evaluation + +Open Claude Desktop and start a new conversation: + +``` +You: "Check my CodeWave configuration" +``` + +**Claude will:** +- Use the `check_config` tool +- Show your provider, model, and API key status +- Confirm everything is ready + +Then ask: + +``` +You: "Evaluate my last commit" +``` + +**Claude will:** +- Use the `evaluate_commit` tool with `commit_hash: HEAD` +- Wait 30-60 seconds for evaluation +- Show you: + - Commit hash and message + - Overall quality score (0-10) + - Breakdown of metrics + - Agent summaries + - Concerns and recommendations + +**Example response from Claude:** + +> I've evaluated your commit `1fac813` ("feat: Add user authentication module"). +> +> **Overall Score: 8.5/10** +> +> Here's the breakdown: +> - Code Quality: 9.0/10 - Excellent structure and readability +> - Complexity: 7.5/10 - Some complex authentication logic +> - Technical Debt: 8.0/10 - Minimal debt introduced +> - Test Coverage: 8.0/10 - Good test coverage +> - Functional Impact: 9.0/10 - Critical security feature +> +> **Concerns identified:** +> 1. Missing rate limiting on login endpoint +> 2. Password validation could be stronger +> +> **Recommendations:** +> 1. Implement rate limiting (max 5 attempts/min) +> 2. Add password complexity requirements + +### What to Do Next + +- **Save your evaluation**: Ask "Evaluate my last 5 commits and save them" to build history +- **Analyze patterns**: Ask "Show me my code quality statistics" +- **Get detailed report**: Ask "Show me a detailed report for commit abc123" +- **Generate OKRs**: Ask "Generate OKRs for me based on my commits" + +--- + +## Example Conversations + +Real conversations showing how to use CodeWave tools naturally with Claude Desktop. + +### Scenario 1: Quick Commit Check + +**Goal**: Quickly evaluate recent work before pushing. + +``` +You: "I just finished implementing user authentication. Can you evaluate + my last commit and tell me if it's ready to push?" + +Claude: "I'll evaluate your latest commit for you." + +[Calls evaluate_commit with commit_hash: HEAD] + +Claude: "I've reviewed commit 1fac813 'feat: Add user authentication module'. + +Overall, this is solid work with a score of 8.5/10. Here's what stands out: + +**Strengths:** +- Excellent code structure and readability (9.0/10) +- Good test coverage (8.0/10) +- Critical functionality properly implemented + +**Areas to address before pushing:** +1. **Security**: Missing rate limiting on the login endpoint - this is a + critical security concern for production +2. **Password validation**: Current regex allows weak passwords + +**My recommendation:** +Add rate limiting and strengthen password validation before pushing. +These are security-critical features that should be in place from day one. + +Would you like me to suggest specific implementations for these fixes?" + +You: "Yes, please suggest how to implement rate limiting" + +Claude: [Provides implementation suggestions...] +``` + +**Key Points:** +- Natural conversation flow +- Claude interprets results and provides context +- Can ask follow-up questions +- Gets actionable advice, not just metrics + +--- + +### Scenario 2: Building Evaluation History + +**Goal**: Create evaluation history for a project. + +``` +You: "I want to build up evaluation history for our project. Can you + evaluate my last 10 commits and save them?" + +Claude: "I'll evaluate your last 10 commits and save the results for + future analysis." + +[Calls batch_evaluate with commits: "last:10", save: true] + +Claude: "I've completed evaluating your last 10 commits. Here's the summary: + +**Results:** +- Successfully evaluated: 10/10 commits +- Average quality score: 8.2/10 +- Date range: Jan 15 - Jan 28, 2024 +- Total evaluation time: 8 minutes + +**Quality Distribution:** +- Excellent (9.0+): 2 commits +- Good (8.0-8.9): 5 commits +- Fair (7.0-7.9): 3 commits +- Needs improvement (<7.0): 0 commits + +**Highest scoring commit:** +- abc123 (9.2/10): 'feat: Add comprehensive test suite' + +**Trends observed:** +Your code quality has been improving over this period. Recent commits +show better test coverage and lower complexity. + +All results are now saved to .evaluated-commits/ and you can use tools +like get_author_stats and generate_okr for deeper analysis." + +You: "Great! Can you show me my overall statistics?" + +Claude: [Calls get_author_stats...] +``` + +**Key Points:** +- Batch operations save time +- Results automatically saved for later use +- Claude provides interpretive summary +- Can immediately follow up with analytics + +--- + +### Scenario 3: Author Analysis + +**Goal**: Understand a developer's performance patterns. + +``` +You: "Can you analyze John's code quality statistics?" + +Claude: "I'll analyze John's commit history and show you his performance + statistics." + +[Calls get_author_stats with author: "john"] + +Claude: "Here's John's code quality analysis based on 25 evaluated commits + from Jan 1-28, 2024: + +**Overall Performance:** +- Average Quality Score: 8.2/10 +- Trend: **Improving** ↗ +- Total Commits Analyzed: 25 + +**Strengths:** +1. Consistently high code quality (avg 8.5/10) +2. Strong functional impact (avg 8.7/10) +3. Good technical debt management + +**Areas for Improvement:** +1. Test coverage could be improved (avg 7.5/10) +2. Occasional complexity spikes in large features + +**Top Commits:** +1. abc123 (9.2): 'feat: Add user dashboard with tests' +2. def456 (9.0): 'refactor: Simplify authentication flow' +3. ghi789 (8.9): 'feat: Implement caching layer' + +**Trend Analysis:** +John's recent commits (last 12) average 8.6/10 compared to 7.9/10 for +earlier commits, showing clear improvement. The main driver is better +test coverage in recent work. + +**Recommendation:** +John is doing well overall and trending positively. Focus areas for growth +would be maintaining test coverage across all features and breaking down +complex features into smaller, more manageable commits." + +You: "This is great for his upcoming performance review. Can you generate + personalized OKRs for him?" + +Claude: [Calls generate_okr with author: "john"...] +``` + +**Key Points:** +- Comprehensive performance overview +- Identifies specific patterns +- Trend analysis shows improvement +- Natural follow-up to OKR generation + +--- + +### Scenario 4: Generate OKRs + +**Goal**: Create data-driven development goals. + +``` +You: "Generate personalized OKRs for Sarah based on her commit history" + +Claude: "I'll analyze Sarah's commits and generate personalized OKRs + and an action plan." + +[Calls generate_okr with author: "sarah", save: true] + +Claude: "I've generated personalized OKRs for Sarah based on 18 evaluated + commits. Here's the development plan: + +**Strong Points:** +- Exceptional test coverage (avg 9.2/10) +- Clean, maintainable code +- Good documentation practices + +**Areas for Growth:** +- Occasional high complexity in data processing +- Could improve architectural planning for large features + +**3-Month OKR: Master Complex System Design** + +Objective: Develop skills to handle complex features with low complexity scores + +Key Results: +1. Reduce average complexity score from 7.5 to 8.5+ +2. Complete 'Refactoring' and 'Clean Architecture' courses +3. Break down all features >300 lines into smaller modules +4. Get 2+ architectural design reviews from senior engineers + +**6-Month OKR: Become Technical Leader in Testing** + +Objective: Leverage testing expertise to mentor team + +Key Results: +1. Mentor 2-3 team members on testing practices +2. Lead testing strategy initiative for the team +3. Achieve consistent 9.5+ test coverage scores +4. Deliver testing best practices workshop + +**12-Month OKR: Senior Engineer Excellence** + +Objective: Demonstrate senior-level technical leadership + +Key Results: +1. Maintain 9.0+ overall quality across all commits +2. Lead 2+ major architectural initiatives +3. Mentor 5+ junior/mid-level developers +4. Contribute to engineering standards documentation + +**Action Plan (Prioritized):** + +HIGH PRIORITY (Next 2 weeks): +- Enroll in 'Refactoring' course (Martin Fowler) +- Schedule architectural design review session with tech lead +- Resources: Refactoring book, team design review schedule + +MEDIUM PRIORITY (Next month): +- Start breaking down large features (identify 3 candidates) +- Begin mentoring relationship with 2 team members +- Resources: Mentorship program guidelines + +LOW PRIORITY (Next quarter): +- Prepare testing workshop outline +- Document personal best practices +- Resources: Internal wiki, past workshop examples + +I've saved this OKR plan to .evaluated-commits/.okrs/sarah-2024-01-28.json +for future reference." + +You: "Excellent! Can you help me schedule these milestones?" + +Claude: [Provides scheduling suggestions...] +``` + +**Key Points:** +- Data-driven, personalized goals +- Builds on strengths, addresses weaknesses +- Actionable timeline with resources +- Saved for performance tracking + +--- + +### Scenario 5: Compare Commits + +**Goal**: Validate that refactoring improved code quality. + +``` +You: "I just refactored the authentication module. Can you compare commit + 1fac813 (before) with commit abc123 (after) to see if quality improved?" + +Claude: "I'll compare those two commits to show how the refactoring + affected code quality." + +[Calls compare_commits with commit_a: "1fac813", commit_b: "abc123"] + +Claude: "Here's the comparison of your refactoring work: + +**Baseline (1fac813): 'Initial authentication implementation'** +- Overall Score: 7.5/10 +- Date: Jan 20, 2024 + +**After Refactoring (abc123): 'Refactor authentication with tests'** +- Overall Score: 8.8/10 +- Date: Jan 28, 2024 + +**Changes:** + +✅ **Significant Improvements:** +1. Overall Score: +1.3 points (7.5 → 8.8) ⬆ +2. Test Coverage: +3.0 points (6.0 → 9.0) ⬆⬆ +3. Complexity: +1.0 points (7.5 → 8.5) ⬆ +4. Technical Debt: +0.8 points (7.5 → 8.3) ⬆ + +⚖️ **No Change:** +- Functional Impact: 9.0/10 (maintained) + +**Summary:** +Your refactoring was highly successful! The biggest improvement was in +test coverage (+3.0 points), which is exactly what good refactoring +achieves. You also managed to reduce complexity while maintaining all +functionality. + +**Key Achievements:** +- Added comprehensive tests without changing behavior +- Simplified complex authentication logic +- Reduced technical debt +- Improved maintainability + +This is a great example of refactoring done right. The code is now +significantly more maintainable and testable." + +You: "Thanks! Should I merge this to main?" + +Claude: "Absolutely! This is a clear improvement across all metrics..." +``` + +**Key Points:** +- Validates refactoring efforts +- Shows specific improvements +- Identifies what worked well +- Provides confidence to merge + +--- + +## Step-by-Step Workflows + +Detailed workflows for common use cases. + +### Workflow 1: Single Commit Deep Dive + +**Goal**: Get comprehensive analysis of a specific commit. + +**Steps:** + +1. **Verify configuration**: + ``` + "Check my CodeWave configuration" + ``` + +2. **Evaluate with save enabled**: + ``` + "Evaluate commit 1fac813 with deep analysis and save the results" + ``` + + Claude will call: `evaluate_commit` with `depth: "deep"`, `save: true` + +3. **Get detailed report**: + ``` + "Show me the full detailed report for commit 1fac813" + ``` + + Claude will call: `get_report` with `format: "full"` + +4. **Export for sharing**: + ``` + "Export that evaluation to HTML at ./reports/auth-module-review.html" + ``` + + Claude will call: `export_report` with `format: "html"` + +**Use Case**: Code review preparation, documentation, important feature evaluation + +--- + +### Workflow 2: Building Evaluation History + +**Goal**: Create evaluation baseline for a project. + +**Steps:** + +1. **Evaluate recent commits**: + ``` + "Evaluate my last 20 commits and save them all" + ``` + + Claude will call: `batch_evaluate` with `commits: "last:20"`, `save: true` + +2. **Verify evaluations were saved**: + ``` + "List all my evaluations" + ``` + + Claude will call: `list_evaluations` + +3. **Filter by specific author**: + ``` + "Show me only evaluations by John from the last month" + ``` + + Claude will call: `list_evaluations` with `author: "john"`, `since: "[date]"` + +4. **Get individual reports**: + ``` + "Show me the detailed report for the highest scoring commit" + ``` + + Claude will call: `get_report` with the specific commit hash + +**Use Case**: Project onboarding, establishing quality baseline, historical analysis + +--- + +### Workflow 3: Author Analysis + +**Goal**: Comprehensive developer performance review. + +**Steps:** + +1. **Ensure author has evaluated commits**: + ``` + "List all evaluations by Sarah" + ``` + + If empty, evaluate their commits first: + ``` + "Evaluate the last 15 commits by Sarah and save them" + ``` + +2. **Get author statistics**: + ``` + "Show me Sarah's code quality statistics" + ``` + + Claude will call: `get_author_stats` with `author: "sarah"` + +3. **Review strengths and weaknesses**: + Ask Claude to elaborate on specific findings: + ``` + "What does the 'improving' trend mean for Sarah?" + ``` + ``` + "Can you explain Sarah's test coverage weakness in more detail?" + ``` + +4. **Generate personalized OKRs**: + ``` + "Generate development goals and OKRs for Sarah" + ``` + + Claude will call: `generate_okr` with `author: "sarah"` + +5. **Export OKRs for review meeting**: + ``` + "Can you summarize Sarah's OKRs in a format I can use for her review?" + ``` + +**Use Case**: Performance reviews, 1:1 meetings, career development planning + +--- + +### Workflow 4: Team Analysis + +**Goal**: Understand team-wide code quality patterns. + +**Steps:** + +1. **Ensure team has evaluated commits**: + ``` + "Evaluate the last 10 commits from each team member and save them" + ``` + +2. **Get team-wide statistics**: + ``` + "Show me team-wide code quality statistics" + ``` + + Claude will call: `get_team_stats` + +3. **Identify high performers**: + ``` + "Who are our top performers by code quality?" + ``` + +4. **Identify areas needing support**: + ``` + "Which team members need coaching or support?" + ``` + +5. **Compare team members**: + ``` + "Compare John's statistics with Sarah's" + ``` + + Claude will call `get_author_stats` for both and provide comparison + +6. **Generate team OKRs**: + ``` + "Based on team stats, what should our team-wide OKRs be?" + ``` + + Claude will synthesize insights and suggest goals + +**Use Case**: Sprint retrospectives, team planning, resource allocation + +--- + +### Workflow 5: CI/CD Integration + +**Goal**: Automate quality checks in deployment pipeline. + +**Pattern for Manual Testing:** + +``` +"Evaluate the commits in pull request #123 and tell me if they meet + our quality standards (8.0+ overall)" +``` + +Claude will: +1. Evaluate the commits (using fast mode for speed) +2. Check if they meet threshold +3. Provide recommendation + +**Example conversation:** + +``` +You: "Evaluate commits abc123, def456, and ghi789 from PR #123 using + fast mode and tell me if they're all above 8.0 quality" + +Claude: [Evaluates 3 commits in fast mode] + +Claude: "I've evaluated all 3 commits from PR #123: + +**Results:** +1. abc123: 8.3/10 ✅ Meets threshold +2. def456: 7.5/10 ❌ Below threshold +3. ghi789: 8.8/10 ✅ Meets threshold + +**Overall:** 2/3 commits meet the 8.0+ requirement. + +**Issue with def456:** +- Test coverage: 6.5/10 +- Complexity: 7.2/10 +- Missing tests for error handling + +**Recommendation:** +Request changes on def456. The commit needs additional tests before +merge. The complexity is acceptable, but untested error paths are +a risk. + +Would you like me to provide specific test cases that should be added?" + +You: "Yes, please suggest test cases" + +Claude: [Provides specific test recommendations...] +``` + +**Use Case**: Pre-merge quality gates, automated reviews, quality enforcement + +--- + +## Advanced Usage Patterns + +### Using Inline Configuration + +When working in an environment without `.codewave.config.json`: + +``` +You: "Evaluate HEAD using Google Gemini (I'll provide the API key)" + +Claude: [Prompts for parameters or you provide them] + +"Use provider: google, api_key: [your-key], model: gemini-2.0-flash-exp" +``` + +### Analysis Depth Selection + +**Fast mode** for quick checks: +``` +"Quickly evaluate my last commit using fast mode" +``` + +**Deep mode** for important reviews: +``` +"I need a thorough analysis of commit abc123 using deep mode" +``` + +### Filtering Evaluations by Date + +**Recent work only**: +``` +"Show me evaluations from the last week" +``` + +Claude will calculate date and use `since` parameter. + +**Specific date range**: +``` +"Show me all evaluations from January 2024" +``` + +### Partial Hash Matching + +You don't need full commit hashes: + +``` +"Show me the report for commit 1fac" +``` + +CodeWave will match the partial hash automatically. + +### Combining Tools in Conversation + +Natural multi-tool workflows: + +``` +You: "Evaluate my last 5 commits, find the best one, and export it to HTML" + +Claude: +1. [Calls batch_evaluate with commits: "last:5"] +2. Identifies highest scoring commit +3. [Calls export_report for that commit] +4. Provides summary with exported file path +``` + +--- + +## Tips and Tricks + +### Ask for Specific Insights + +Instead of: "Evaluate this commit" + +Try: +- "Evaluate this commit and focus on security concerns" +- "Evaluate this commit and tell me about test coverage" +- "Evaluate this commit and compare it to my average" + +Claude will emphasize relevant aspects. + +### Request Trending Analysis + +``` +"Show me my code quality trend over the last month" +``` + +``` +"Compare my first 10 commits with my last 10 commits" +``` + +### Find Specific Patterns + +``` +"Show me all my commits with test coverage below 7.0" +``` + +``` +"Find commits with high technical debt scores" +``` + +Claude will filter evaluation results accordingly. + +### Get Improvement Recommendations + +``` +"Based on my recent commits, what should I focus on improving?" +``` + +``` +"What's the fastest way to improve my overall quality score?" +``` + +### Track Progress Over Time + +``` +"Compare my average quality score this month vs last month" +``` + +``` +"Am I improving in test coverage? Show me the trend" +``` + +### Prepare for Code Reviews + +``` +"Evaluate all my commits from the current sprint and tell me which ones + might need discussion in code review" +``` + +### Competitive Team Analysis + +``` +"Who has the highest average quality score on the team?" +``` + +``` +"Show me how I rank compared to my teammates" +``` + +### Export for Stakeholders + +``` +"Export my top 5 commits to HTML reports for my manager" +``` + +### Save Time with Defaults + +Claude remembers context, so you can say: +``` +"Evaluate my last commit" +[Results shown] + +"Now evaluate the one before that" +[Claude understands "that" = HEAD~1] + +"Compare those two" +[Claude remembers both commits] +``` + +--- + +## Best Practices + +### 1. Build History First + +Always start by building evaluation history: +``` +"Evaluate my last 20 commits and save them" +``` + +This unlocks all analytics tools. + +### 2. Use Appropriate Depth + +- **Fast**: CI/CD, quick checks, large batches +- **Normal**: Regular evaluation, most use cases +- **Deep**: Important features, pre-merge reviews + +### 3. Save Strategically + +Save evaluations you might need later: +- Important commits +- Baseline evaluations +- Batch evaluations +- Pre-merge reviews + +### 4. Regular Check-ins + +Make it a habit: +``` +"Evaluate my commits from today and show me the summary" +``` + +### 5. Use Natural Language + +Don't worry about exact tool names or parameters. Ask naturally: +``` +✅ "Show me my code quality stats" +❌ "Call get_author_stats with author: me" +``` + +Claude handles the tool mapping. + +--- + +## See Also + +- [MCP Setup Guide](./MCP_SETUP.md) - Installation and configuration +- [MCP Tools Reference](./MCP_TOOLS_REFERENCE.md) - Complete tool documentation +- [Configuration Guide](./CONFIGURATION.md) - Detailed configuration options +- [Quick Reference](./QUICK_REFERENCE.md) - Fast command lookup + +--- + +**Last Updated**: 2026-01-28 +**CodeWave Version**: 0.1.11+ diff --git a/docs/MCP_SETUP.md b/docs/MCP_SETUP.md new file mode 100644 index 0000000..a1be363 --- /dev/null +++ b/docs/MCP_SETUP.md @@ -0,0 +1,546 @@ +# CodeWave MCP Setup Guide + +Complete guide to setting up CodeWave as an MCP (Model Context Protocol) server for Claude Desktop. + +## Table of Contents + +- [What is MCP?](#what-is-mcp) +- [Prerequisites](#prerequisites) +- [Installation](#installation) + - [Step 1: Install CodeWave](#step-1-install-codewave) + - [Step 2: Configure CodeWave](#step-2-configure-codewave) + - [Step 3: Setup Claude Desktop](#step-3-setup-claude-desktop) + - [Step 4: Restart and Verify](#step-4-restart-and-verify) +- [Quick Verification](#quick-verification) +- [Troubleshooting](#troubleshooting) +- [Next Steps](#next-steps) + +--- + +## What is MCP? + +**MCP (Model Context Protocol)** is an open standard developed by Anthropic that allows AI applications like Claude Desktop to connect to external tools and data sources. + +### Why Use CodeWave with Claude Desktop? + +Instead of running command-line evaluations, you can now: + +- **Chat naturally** with Claude to evaluate commits +- **Ask questions** about code quality without memorizing commands +- **Get insights** through conversation instead of reading raw JSON +- **Analyze patterns** across commits with natural language queries +- **Generate reports** on demand without leaving your chat window + +### How It Works + +``` +You: "Evaluate my last commit and tell me if it's good" + ↓ +Claude Desktop → CodeWave MCP Server → Multi-Agent Evaluation → Results + ↓ +Claude: "I've analyzed commit abc123. Overall score: 8.5/10..." +``` + +**Benefits over CLI:** +- No command syntax to remember +- Conversational interface +- Claude interprets results for you +- Can ask follow-up questions +- Combines tools in intelligent ways + +--- + +## Prerequisites + +Before setting up CodeWave MCP, ensure you have: + +### 1. Node.js ≥18.0.0 + +Check your version: +```bash +node --version +``` + +If you need to install or update Node.js: +- **macOS**: `brew install node` or download from [nodejs.org](https://nodejs.org) +- **Windows**: Download installer from [nodejs.org](https://nodejs.org) +- **Linux**: Use your package manager (apt, yum, pacman) + +### 2. Claude Desktop App + +Download and install from: +- **Official site**: [claude.ai/download](https://claude.ai/download) +- **macOS**: Available on the Mac App Store +- **Windows**: Direct download from Anthropic +- **Linux**: AppImage available + +### 3. Git Repository + +CodeWave evaluates git commits, so you need: +- A git repository to analyze +- Commits to evaluate +- Access to commit history + +### 4. LLM API Key + +CodeWave uses AI models for evaluation. You need an API key from one of: +- **Anthropic** (Claude): [console.anthropic.com](https://console.anthropic.com) +- **OpenAI** (GPT): [platform.openai.com](https://platform.openai.com) +- **Google** (Gemini): [ai.google.dev](https://ai.google.dev) +- **Groq** (Fast inference): [console.groq.com](https://console.groq.com) +- **Ollama** (Local, free): [ollama.ai](https://ollama.ai) - No API key needed +- **xAI** (Grok): [x.ai](https://x.ai) +- **LM Studio** (Local, free): [lmstudio.ai](https://lmstudio.ai) - No API key needed + +--- + +## Installation + +### Step 1: Install CodeWave + +#### Option A: Global Installation (Recommended) + +```bash +npm install -g @techdebtgpt/codewave +``` + +Verify installation: +```bash +codewave --version +``` + +#### Option B: Local Development + +If you're developing or testing: + +```bash +git clone https://github.com/techdebtgpt/codewave.git +cd codewave +npm install +npm run build +``` + +The MCP server binary will be at: `./dist/cli/mcp-server.js` + +--- + +### Step 2: Configure CodeWave + +CodeWave needs configuration before it can evaluate commits. Run the interactive setup: + +```bash +codewave config --init +``` + +This wizard will guide you through: + +1. **Choose LLM Provider**: anthropic, openai, google, groq, ollama, xai, lm-studio +2. **Enter API Key**: Your provider's API key (not needed for ollama/lm-studio) +3. **Select Model**: Recommended models for your provider +4. **Enable Agents**: Which analysis agents to use (default: all) +5. **Set Defaults**: Batch size, output directory, report preferences + +Configuration is saved to `.codewave.config.json` in your home directory or repository root. + +**Verify configuration:** +```bash +codewave config show +``` + +--- + +### Step 3: Setup Claude Desktop + +Now configure Claude Desktop to use CodeWave as an MCP server. + +#### Find Claude Desktop Config File + +The configuration file location depends on your operating system: + +**macOS:** +``` +~/Library/Application Support/Claude/claude_desktop_config.json +``` + +**Windows:** +``` +%APPDATA%/Claude/claude_desktop_config.json +``` + +Or typically: +``` +C:\Users\YourUsername\AppData\Roaming\Claude\claude_desktop_config.json +``` + +**Linux:** +``` +~/.config/Claude/claude_desktop_config.json +``` + +#### Create or Edit Configuration + +Open the `claude_desktop_config.json` file in a text editor. If it doesn't exist, create it. + +**For global npm installation:** + +First, find where CodeWave is installed: +```bash +npm root -g +``` + +This shows your global node_modules path (e.g., `/usr/local/lib/node_modules`) + +Then add to `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "codewave": { + "command": "node", + "args": [ + "/usr/local/lib/node_modules/@techdebtgpt/codewave/dist/cli/mcp-server.js" + ] + } + } +} +``` + +Replace `/usr/local/lib/node_modules` with your actual path from `npm root -g`. + +**For local development:** + +```json +{ + "mcpServers": { + "codewave": { + "command": "node", + "args": [ + "/absolute/path/to/codewave/dist/cli/mcp-server.js" + ] + } + } +} +``` + +Replace `/absolute/path/to/codewave` with the actual path to your cloned repository. + +**Important Notes:** +- Use **absolute paths** only (no `~` or relative paths) +- On Windows, use forward slashes `/` or escaped backslashes `\\` +- The JSON must be valid (trailing commas not allowed) + +**Example for Windows:** +```json +{ + "mcpServers": { + "codewave": { + "command": "node", + "args": [ + "C:/Users/John/AppData/Roaming/npm/node_modules/@techdebtgpt/codewave/dist/cli/mcp-server.js" + ] + } + } +} +``` + +#### If You Have Other MCP Servers + +If you already have other MCP servers configured, just add CodeWave: + +```json +{ + "mcpServers": { + "existing-server": { + "command": "...", + "args": ["..."] + }, + "codewave": { + "command": "node", + "args": [ + "/path/to/codewave/dist/cli/mcp-server.js" + ] + } + } +} +``` + +--- + +### Step 4: Restart and Verify + +1. **Save** the `claude_desktop_config.json` file +2. **Completely quit** Claude Desktop (not just close the window): + - **macOS**: Cmd+Q or right-click dock icon → Quit + - **Windows**: Right-click system tray icon → Exit + - **Linux**: Close all windows and quit from system tray +3. **Restart** Claude Desktop +4. **Check for CodeWave tools**: + - Look for the MCP icon or tools panel in Claude Desktop + - CodeWave tools should be visible in the available tools list + +--- + +## Quick Verification + +Test that CodeWave MCP is working correctly. + +### Test 1: Check Configuration + +Start a new conversation in Claude Desktop and ask: + +``` +Check my CodeWave configuration +``` + +**Expected response:** +Claude will use the `check_config` tool and show: +- Provider: anthropic/openai/google/etc. +- Model: claude-3-5-sonnet/gpt-4/gemini-2.0-flash/etc. +- API Key: Present (✓) or Missing (✗) +- Enabled Agents: BA, DA, DR, SA, QA + +### Test 2: List Evaluations + +If you have previous evaluations: + +``` +Show me my past CodeWave evaluations +``` + +**Expected response:** +Claude will use the `list_evaluations` tool and show a list of evaluated commits with metrics. + +### Test 3: Evaluate a Commit + +Navigate to a git repository and ask: + +``` +Evaluate my last commit +``` + +**Expected response:** +Claude will use the `evaluate_commit` tool, which typically takes 30-60 seconds, and then show: +- Commit hash and message +- Overall quality score (0-10) +- Metrics breakdown (code quality, complexity, technical debt, etc.) +- Agent summaries +- Concerns and recommendations + +--- + +## Troubleshooting + +### Tools Not Appearing in Claude Desktop + +**Symptoms:** +- CodeWave tools don't show in the tools list +- Claude says "I don't have access to CodeWave tools" + +**Solutions:** + +1. **Verify config file location**: + ```bash + # macOS/Linux + ls -la ~/Library/Application\ Support/Claude/claude_desktop_config.json + + # Windows + dir %APPDATA%\Claude\claude_desktop_config.json + ``` + +2. **Check JSON syntax**: + - Use a JSON validator: [jsonlint.com](https://jsonlint.com) + - No trailing commas + - All quotes must be double quotes `"` + - Paths must use forward slashes `/` or escaped backslashes `\\` + +3. **Verify absolute path**: + ```bash + # Test the path directly + node /absolute/path/to/codewave/dist/cli/mcp-server.js + ``` + + If this fails, the path is incorrect. + +4. **Check permissions**: + ```bash + # macOS/Linux - make sure file is readable + chmod +r /path/to/codewave/dist/cli/mcp-server.js + ``` + +5. **Restart Claude Desktop completely**: + - Don't just close windows - fully quit the application + - Wait 5 seconds + - Reopen Claude Desktop + +### "Server Not Responding" Errors + +**Symptoms:** +- Tools appear but fail when used +- "MCP server error" messages +- Timeouts + +**Solutions:** + +1. **Check CodeWave configuration**: + ```bash + codewave config show + ``` + + Verify API key is set and valid. + +2. **Test MCP server directly**: + ```bash + node /path/to/codewave/dist/cli/mcp-server.js + ``` + + It should start without errors and wait for input. + +3. **Check Node.js version**: + ```bash + node --version + ``` + + Must be ≥18.0.0 + +4. **Rebuild if using local development**: + ```bash + cd /path/to/codewave + npm run build + ``` + +### Path Issues (Relative vs Absolute) + +**Symptoms:** +- "Cannot find module" errors +- Path not found + +**Solutions:** + +1. **Always use absolute paths** in `claude_desktop_config.json` +2. **Don't use `~` for home directory** - expand to full path +3. **Find npm global modules**: + ```bash + npm root -g + ``` + + Then add `/node_modules/@techdebtgpt/codewave/dist/cli/mcp-server.js` + +4. **Windows paths**: + - Use forward slashes: `C:/Users/...` + - Or escaped backslashes: `C:\\Users\\...` + - Don't mix both + +### Permission Issues + +**Symptoms:** +- "Permission denied" errors +- "EACCES" errors + +**Solutions:** + +1. **macOS/Linux - Check file permissions**: + ```bash + ls -la /path/to/codewave/dist/cli/mcp-server.js + ``` + + Should be readable (r-- at minimum) + +2. **Fix permissions**: + ```bash + chmod +r /path/to/codewave/dist/cli/mcp-server.js + ``` + +3. **Check directory permissions**: + ```bash + ls -la /path/to/codewave/dist/cli/ + ``` + +### Debugging with MCP Inspector + +For advanced debugging, use the MCP Inspector tool: + +```bash +# Install MCP Inspector +npm install -g @modelcontextprotocol/inspector + +# Run CodeWave with inspector +npx @modelcontextprotocol/inspector node /path/to/codewave/dist/cli/mcp-server.js +``` + +This opens a web UI where you can: +- Test tool calls manually +- See request/response JSON +- Debug parameter validation +- Check error messages + +### Checking Logs + +Claude Desktop logs MCP server errors. Check: + +**macOS:** +```bash +tail -f ~/Library/Logs/Claude/mcp*.log +``` + +**Windows:** +``` +%APPDATA%\Claude\logs\ +``` + +**Linux:** +```bash +tail -f ~/.config/Claude/logs/mcp*.log +``` + +--- + +## Next Steps + +Now that CodeWave MCP is set up, you can: + +1. **Learn about available tools**: See [MCP_TOOLS_REFERENCE.md](./MCP_TOOLS_REFERENCE.md) + - 10 tools for evaluation, analytics, and reporting + - Complete parameter reference + - Example requests and responses + +2. **Try practical examples**: See [MCP_EXAMPLES.md](./MCP_EXAMPLES.md) + - Example conversations with Claude Desktop + - Step-by-step workflows + - Tips and tricks + +3. **Understand MCP configuration**: See [CONFIGURATION.md](./CONFIGURATION.md) + - File-based vs inline configuration + - API key security + - Configuration priority + +4. **Quick reference**: See [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) + - Most common tool calls + - Setup checklist + - MCP vs CLI comparison + +--- + +## Additional Resources + +### Official Documentation + +- **MCP Specification**: [modelcontextprotocol.io](https://modelcontextprotocol.io) +- **MCP GitHub**: [github.com/modelcontextprotocol](https://github.com/modelcontextprotocol) +- **Claude Desktop**: [claude.ai/download](https://claude.ai/download) + +### CodeWave Documentation + +- **Main README**: [README.md](../README.md) +- **CLI Reference**: [CLI.md](./CLI.md) +- **Architecture**: [ARCHITECTURE.md](./ARCHITECTURE.md) +- **Troubleshooting**: [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) + +### Getting Help + +- **Issues**: [github.com/techdebtgpt/codewave/issues](https://github.com/techdebtgpt/codewave/issues) +- **Discussions**: [github.com/techdebtgpt/codewave/discussions](https://github.com/techdebtgpt/codewave/discussions) + +--- + +**Last Updated**: 2026-01-28 +**CodeWave Version**: 0.1.11+ diff --git a/docs/MCP_TOOLS_REFERENCE.md b/docs/MCP_TOOLS_REFERENCE.md new file mode 100644 index 0000000..7e98bf8 --- /dev/null +++ b/docs/MCP_TOOLS_REFERENCE.md @@ -0,0 +1,1360 @@ +# CodeWave MCP Tools Reference + +Complete reference for all CodeWave MCP tools available in Claude Desktop. + +## Table of Contents + +- [Overview](#overview) +- [Tool Index](#tool-index) +- [Phase 0: Core Evaluation](#phase-0-core-evaluation) + - [evaluate_commit](#evaluate_commit) +- [Phase 1: Configuration & Data Retrieval](#phase-1-configuration--data-retrieval) + - [check_config](#check_config) + - [list_evaluations](#list_evaluations) + - [get_report](#get_report) +- [Phase 2: Analytics & OKR Generation](#phase-2-analytics--okr-generation) + - [get_author_stats](#get_author_stats) + - [generate_okr](#generate_okr) +- [Phase 3: Batch Operations & Export](#phase-3-batch-operations--export) + - [batch_evaluate](#batch_evaluate) + - [compare_commits](#compare_commits) + - [get_team_stats](#get_team_stats) + - [export_report](#export_report) +- [Error Handling](#error-handling) +- [Response Formats](#response-formats) + +--- + +## Overview + +CodeWave provides **10 production-ready MCP tools** organized into 4 phases: + +| Tool | Phase | Purpose | Makes API Calls? | Requires Saved Evaluations? | +|------|-------|---------|------------------|----------------------------| +| `evaluate_commit` | 0 | Core evaluation using multi-agent AI | ✅ Yes | ❌ No | +| `check_config` | 1 | Verify CodeWave configuration | ❌ No | ❌ No | +| `list_evaluations` | 1 | Browse evaluation history | ❌ No | ✅ Yes | +| `get_report` | 1 | Get detailed commit report | ❌ No | ✅ Yes | +| `get_author_stats` | 2 | Analyze developer statistics | ❌ No | ✅ Yes | +| `generate_okr` | 2 | AI-powered OKR generation | ✅ Yes | ✅ Yes | +| `batch_evaluate` | 3 | Evaluate multiple commits | ✅ Yes | ❌ No | +| `compare_commits` | 3 | Compare two commit metrics | ❌ No | ✅ Yes | +| `get_team_stats` | 3 | Aggregate team statistics | ❌ No | ✅ Yes | +| `export_report` | 3 | Export report to file | ❌ No | ✅ Yes | + +**Key Concepts:** + +- **Makes API Calls**: Tools that call LLM APIs cost money and take longer (30-60 seconds) +- **Requires Saved Evaluations**: Tools that need evaluations saved with `save: true` +- **Phase 0**: Foundation - core evaluation functionality +- **Phase 1**: Read-only operations on config and saved data +- **Phase 2**: Analytics and AI-powered insights +- **Phase 3**: Batch operations and export functionality + +--- + +## Tool Index + +Quick navigation to tool documentation: + +**Core:** +- [evaluate_commit](#evaluate_commit) - Evaluate a single commit with multi-agent AI + +**Configuration:** +- [check_config](#check_config) - Check if configuration exists and is valid + +**Data Retrieval:** +- [list_evaluations](#list_evaluations) - List past evaluations with filtering +- [get_report](#get_report) - Get detailed report for a commit + +**Analytics:** +- [get_author_stats](#get_author_stats) - Get developer statistics +- [generate_okr](#generate_okr) - Generate OKRs with AI + +**Batch Operations:** +- [batch_evaluate](#batch_evaluate) - Evaluate multiple commits +- [compare_commits](#compare_commits) - Compare metrics between commits +- [get_team_stats](#get_team_stats) - Get team-wide statistics +- [export_report](#export_report) - Export report to HTML/MD/JSON + +--- + +## Phase 0: Core Evaluation + +### evaluate_commit + +Evaluate a git commit using CodeWave's multi-agent AI analysis system. + +**Purpose:** +- Get comprehensive code quality assessment +- Identify technical debt and complexity issues +- Understand functional impact and test coverage +- Receive actionable recommendations from 5 specialized AI agents + +**When to use:** +- You want to evaluate your latest work +- You need detailed analysis of a specific commit +- You're preparing for code review +- You want to track code quality over time + +**⏱️ Typical Execution Time:** 30-60 seconds (varies by commit size and depth mode) + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `commit_hash` | string | ✅ Yes | - | Git commit hash (SHA, short SHA, or HEAD) | +| `repo_path` | string | ❌ No | current directory | Path to git repository | +| `depth` | string | ❌ No | `normal` | Analysis depth: `fast`, `normal`, or `deep` | +| `save` | boolean | ❌ No | `false` | Save results to `.evaluated-commits/` folder | +| `provider` | string | ❌ No | from config | LLM provider (anthropic, openai, google, groq, ollama, xai, lm-studio) | +| `api_key` | string | ❌ No | from config | API key for LLM provider | +| `model` | string | ❌ No | from config | Model name (e.g., claude-3-5-sonnet-20241022) | + +**Important Notes:** +- Inline config requires **all three** parameters: `provider`, `api_key`, `model` +- Use `save: true` if you want to use analytics tools later (`get_author_stats`, `generate_okr`) +- `HEAD` evaluates the most recent commit +- Short commit hashes (7+ characters) are supported + +#### Depth Modes + +| Mode | Speed | Detail Level | Use Case | +|------|-------|--------------|----------| +| `fast` | ~20-30s | Quick overview | Quick checks, CI/CD | +| `normal` | ~30-45s | Standard analysis | Regular evaluation | +| `deep` | ~45-60s | Thorough review | Pre-merge, important changes | + +#### Example Requests + +**1. Basic evaluation (using config file):** +```json +{ + "commit_hash": "HEAD" +} +``` + +**2. Evaluation with save and depth:** +```json +{ + "commit_hash": "HEAD", + "depth": "deep", + "save": true +} +``` + +**3. Specific commit evaluation:** +```json +{ + "commit_hash": "1fac813", + "save": true +} +``` + +**4. Inline configuration (no config file):** +```json +{ + "commit_hash": "HEAD", + "provider": "google", + "api_key": "your-google-api-key", + "model": "gemini-2.0-flash-exp", + "save": true +} +``` + +**5. Different repository:** +```json +{ + "commit_hash": "HEAD", + "repo_path": "/path/to/other/repo" +} +``` + +#### Response Format + +The response includes: + +```typescript +{ + commitHash: string; // Full commit SHA + commitMessage: string; // Commit message + author: string; // Commit author + timestamp: string; // ISO 8601 timestamp + + metrics: { + overallScore: number; // 0-10 overall quality score + codeQuality: number; // 0-10 code quality score + complexity: number; // 0-10 complexity score + technicalDebt: number; // 0-10 technical debt score + testCoverage: number; // 0-10 test coverage score + functionalImpact: number; // 0-10 functional impact score + }; + + agentSummaries: { + [agentName: string]: { + initialAssessment: string; + concerns: string[]; + finalVerdict: string; + }; + }; + + concerns: string[]; // Critical issues identified + recommendations: string[]; // Actionable recommendations + + // If save: true was used + savedTo?: string; // Path where results were saved +} +``` + +#### Example Response + +```json +{ + "commitHash": "1fac813abc...", + "commitMessage": "feat: Add user authentication module", + "author": "John Doe", + "timestamp": "2024-01-28T10:30:00Z", + "metrics": { + "overallScore": 8.5, + "codeQuality": 9.0, + "complexity": 7.5, + "technicalDebt": 8.0, + "testCoverage": 8.0, + "functionalImpact": 9.0 + }, + "agentSummaries": { + "Business Analyst": { + "initialAssessment": "Authentication is a critical security feature...", + "concerns": ["Missing rate limiting", "No password complexity rules"], + "finalVerdict": "Solid foundation, needs security enhancements" + } + }, + "concerns": [ + "Missing rate limiting on login endpoint", + "Password validation could be stronger" + ], + "recommendations": [ + "Implement rate limiting (max 5 attempts/min)", + "Add password complexity requirements" + ], + "savedTo": ".evaluated-commits/1fac813abc-2024-01-28/" +} +``` + +--- + +## Phase 1: Configuration & Data Retrieval + +### check_config + +Check if CodeWave configuration exists and is valid. + +**Purpose:** +- Verify configuration before running evaluations +- Diagnose configuration issues +- Check API key status +- See which agents are enabled + +**When to use:** +- Before your first evaluation +- Troubleshooting setup issues +- Verifying API key is set correctly +- Checking which provider/model is configured + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `repo_path` | string | ❌ No | current directory | Path to repository | + +#### Example Requests + +**1. Check current directory config:** +```json +{} +``` + +**2. Check specific repository:** +```json +{ + "repo_path": "/path/to/repo" +} +``` + +#### Response Format + +```typescript +{ + configExists: boolean; + configPath?: string; + provider?: string; // e.g., "anthropic" + model?: string; // e.g., "claude-3-5-sonnet-20241022" + apiKeyPresent: boolean; // true if API key is set (never shows actual key) + enabledAgents?: string[]; // e.g., ["BA", "DA", "DR", "SA", "QA"] + error?: string; // If config invalid or missing +} +``` + +#### Example Response + +```json +{ + "configExists": true, + "configPath": "/Users/john/projects/myapp/.codewave.config.json", + "provider": "anthropic", + "model": "claude-3-5-sonnet-20241022", + "apiKeyPresent": true, + "enabledAgents": ["BA", "DA", "DR", "SA", "QA"] +} +``` + +--- + +### list_evaluations + +List all past commit evaluations with optional filtering. + +**Purpose:** +- Browse evaluation history +- Find commits evaluated by specific authors +- Filter evaluations by date range +- Get quick overview of evaluated commits + +**When to use:** +- You want to see what's been evaluated +- You're looking for a specific commit +- You want to filter by author or date +- You need commit hashes for other tools + +**⚠️ Requirement:** Evaluations must have been saved with `save: true` + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `repo_path` | string | ❌ No | current directory | Path to repository | +| `author` | string | ❌ No | - | Filter by author (case-insensitive partial match) | +| `since` | string | ❌ No | - | Only evaluations after this date (ISO format: YYYY-MM-DD) | +| `limit` | number | ❌ No | `20` | Maximum results (max: 100) | + +#### Example Requests + +**1. List recent evaluations:** +```json +{ + "limit": 10 +} +``` + +**2. Filter by author:** +```json +{ + "author": "john", + "limit": 20 +} +``` + +**3. Filter by date:** +```json +{ + "since": "2024-01-01", + "limit": 50 +} +``` + +**4. Combined filters:** +```json +{ + "author": "sarah", + "since": "2024-01-15", + "limit": 30 +} +``` + +#### Response Format + +```typescript +{ + totalFound: number; + evaluations: Array<{ + commitHash: string; + shortHash: string; // First 7 characters + author: string; + date: string; // ISO 8601 + message: string; + overallScore: number; // 0-10 + codeQuality: number; + complexity: number; + technicalDebt: number; + testCoverage: number; + functionalImpact: number; + }>; +} +``` + +#### Example Response + +```json +{ + "totalFound": 15, + "evaluations": [ + { + "commitHash": "1fac813abc123...", + "shortHash": "1fac813", + "author": "John Doe", + "date": "2024-01-28T10:30:00Z", + "message": "feat: Add user authentication", + "overallScore": 8.5, + "codeQuality": 9.0, + "complexity": 7.5, + "technicalDebt": 8.0, + "testCoverage": 8.0, + "functionalImpact": 9.0 + } + ] +} +``` + +--- + +### get_report + +Get detailed evaluation report for a specific commit. + +**Purpose:** +- Retrieve full evaluation data +- Review agent summaries and discussions +- Access concerns and recommendations +- Export data for analysis + +**When to use:** +- You need detailed information about a commit +- You want to see agent reasoning +- You need to review concerns +- You want full metrics and summaries + +**⚠️ Requirement:** Commit must have been evaluated with `save: true` + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `commit_hash` | string | ✅ Yes | - | Commit hash (supports partial match: 4-40 characters) | +| `repo_path` | string | ❌ No | current directory | Path to repository | +| `format` | string | ❌ No | `summary` | Detail level: `metrics_only`, `summary`, or `full` | + +#### Format Modes + +| Mode | Includes | Use Case | +|------|----------|----------| +| `metrics_only` | Just numeric scores | Quick comparison, data analysis | +| `summary` | Metrics + agent summaries + concerns | Standard use case | +| `full` | Everything (including raw conversation data) | Deep analysis, debugging | + +#### Example Requests + +**1. Get summary report:** +```json +{ + "commit_hash": "1fac813" +} +``` + +**2. Get full report:** +```json +{ + "commit_hash": "1fac813", + "format": "full" +} +``` + +**3. Get metrics only:** +```json +{ + "commit_hash": "HEAD~1", + "format": "metrics_only" +} +``` + +#### Response Format + +See [evaluate_commit Response Format](#response-format) for full structure. + +--- + +## Phase 2: Analytics & OKR Generation + +### get_author_stats + +Get aggregated code quality statistics for a specific developer. + +**Purpose:** +- Analyze developer performance over time +- Identify strengths and weaknesses +- Track improvement trends +- Generate data-driven insights + +**When to use:** +- Performance reviews +- Developer coaching +- Team retrospectives +- Identifying training needs + +**⚠️ Requirement:** Author must have evaluated commits with `save: true` + +**⏱️ Execution Time:** < 1 second (no API calls) + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `author` | string | ✅ Yes | - | Author name (case-insensitive partial match) | +| `repo_path` | string | ❌ No | current directory | Path to repository | +| `since` | string | ❌ No | - | Only include evaluations after this date (YYYY-MM-DD) | +| `limit` | number | ❌ No | `50` | Maximum commits to analyze | + +#### Example Requests + +**1. Get author stats:** +```json +{ + "author": "john" +} +``` + +**2. With date filter:** +```json +{ + "author": "sarah", + "since": "2024-01-01" +} +``` + +**3. Limited analysis:** +```json +{ + "author": "john", + "limit": 20 +} +``` + +#### Response Format + +```typescript +{ + author: string; + commitsAnalyzed: number; + dateRange: { + start: string; + end: string; + }; + + averageMetrics: { + overallScore: number; + codeQuality: number; + complexity: number; + technicalDebt: number; + testCoverage: number; + functionalImpact: number; + }; + + trend: "improving" | "declining" | "stable"; + + strengths: string[]; // Top 3 strengths + weaknesses: string[]; // Top 3 weaknesses + + topCommits: Array<{ // Best 5 commits + hash: string; + score: number; + message: string; + }>; +} +``` + +#### Trend Calculation + +- **Improving**: Recent commits (last 50%) score higher than older commits +- **Declining**: Recent commits score lower than older commits +- **Stable**: No significant change (< 0.5 point difference) +- Requires at least 4 commits for trend analysis + +#### Example Response + +```json +{ + "author": "John Doe", + "commitsAnalyzed": 25, + "dateRange": { + "start": "2024-01-01", + "end": "2024-01-28" + }, + "averageMetrics": { + "overallScore": 8.2, + "codeQuality": 8.5, + "complexity": 7.8, + "technicalDebt": 8.0, + "testCoverage": 7.5, + "functionalImpact": 8.7 + }, + "trend": "improving", + "strengths": [ + "Consistently high code quality scores (avg 8.5/10)", + "Strong functional impact (avg 8.7/10)", + "Good technical debt management" + ], + "weaknesses": [ + "Test coverage could be improved (avg 7.5/10)", + "Occasional complexity spikes in large features" + ], + "topCommits": [ + { + "hash": "abc123", + "score": 9.2, + "message": "feat: Add user dashboard with tests" + } + ] +} +``` + +--- + +### generate_okr + +Generate personalized OKRs (Objectives and Key Results) and improvement action plan for a developer using AI. + +**Purpose:** +- Create data-driven growth objectives +- Identify specific areas for improvement +- Generate actionable 3/6/12-month goals +- Provide personalized development roadmap + +**When to use:** +- Quarterly planning +- Performance reviews +- Career development planning +- Team goal setting + +**⚠️ Requirements:** +- Author must have evaluated commits with `save: true` +- Valid configuration with API key (or use inline config) + +**⏱️ Typical Execution Time:** 10-30 seconds (makes API calls) + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `author` | string | ✅ Yes | - | Author name to generate OKRs for | +| `repo_path` | string | ❌ No | current directory | Path to repository | +| `since` | string | ❌ No | - | Only include evaluations after this date | +| `save` | boolean | ❌ No | `true` | Save OKRs to `.evaluated-commits/.okrs/` folder | +| `provider` | string | ❌ No | from config | LLM provider (for inline config) | +| `api_key` | string | ❌ No | from config | API key (for inline config) | +| `model` | string | ❌ No | from config | Model name (for inline config) | + +#### Example Requests + +**1. Generate OKRs (using config):** +```json +{ + "author": "john" +} +``` + +**2. With date filter and no save:** +```json +{ + "author": "sarah", + "since": "2024-01-01", + "save": false +} +``` + +**3. Inline configuration:** +```json +{ + "author": "john", + "provider": "anthropic", + "api_key": "your-api-key", + "model": "claude-3-5-sonnet-20241022" +} +``` + +#### Response Format + +```typescript +{ + author: string; + generatedAt: string; // ISO 8601 timestamp + basedOnCommits: number; + + strongPoints: string[]; // Top strengths + weakPoints: string[]; // Areas for improvement + knowledgeGaps: string[]; // Learning opportunities + + okr3Month: { + objective: string; + keyResults: string[]; // 3-4 measurable key results + }; + + okr6Month: { + objective: string; + keyResults: string[]; + }; + + okr12Month: { + objective: string; + keyResults: string[]; + }; + + actionPlan: Array<{ + priority: "high" | "medium" | "low"; + action: string; + timeline: string; + resources: string[]; + }>; + + savedTo?: string; // If save: true +} +``` + +#### Example Response + +```json +{ + "author": "John Doe", + "generatedAt": "2024-01-28T15:00:00Z", + "basedOnCommits": 25, + "strongPoints": [ + "Excellent code quality and maintainability", + "Strong architectural thinking", + "Good documentation practices" + ], + "weakPoints": [ + "Test coverage needs improvement", + "Occasionally high complexity in large features" + ], + "knowledgeGaps": [ + "Test-driven development practices", + "Refactoring techniques for complex code" + ], + "okr3Month": { + "objective": "Establish comprehensive testing practices", + "keyResults": [ + "Achieve 80%+ test coverage on all new code", + "Complete TDD fundamentals course", + "Write tests before implementation for all new features" + ] + }, + "okr6Month": { + "objective": "Master code complexity management", + "keyResults": [ + "Reduce average complexity score from 7.8 to 8.5+", + "Complete refactoring patterns training", + "Break down all features into smaller components (<200 lines)" + ] + }, + "okr12Month": { + "objective": "Become technical leader in testing and clean code", + "keyResults": [ + "Mentor 2-3 team members on testing practices", + "Achieve consistent 9.0+ overall quality scores", + "Lead testing strategy initiative for team" + ] + }, + "actionPlan": [ + { + "priority": "high", + "action": "Complete comprehensive TDD course", + "timeline": "Next 2 weeks", + "resources": ["Test-Driven Development by Example (Kent Beck)", "Udemy TDD course"] + } + ], + "savedTo": ".evaluated-commits/.okrs/john-doe-2024-01-28.json" +} +``` + +--- + +## Phase 3: Batch Operations & Export + +### batch_evaluate + +Evaluate multiple commits in a single call. + +**Purpose:** +- Build evaluation history quickly +- Evaluate recent work batch +- Analyze multiple commits for trends +- Efficient API usage for multiple evaluations + +**When to use:** +- Setting up CodeWave for existing project +- Analyzing sprint or milestone +- Building historical data +- Evaluating recent work batch + +**⏱️ Execution Time:** ~30-60 seconds per commit (runs sequentially) + +**⚠️ Limits:** Maximum 10 commits per batch + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `commits` | string | ✅ Yes | - | "last:N" or comma-separated hashes (e.g., "abc123,def456,HEAD") | +| `repo_path` | string | ❌ No | current directory | Path to git repository | +| `depth` | string | ❌ No | `normal` | Analysis depth: `fast`, `normal`, or `deep` | +| `save` | boolean | ❌ No | `true` | Save evaluation results | +| `provider` | string | ❌ No | from config | LLM provider (for inline config) | +| `api_key` | string | ❌ No | from config | API key (for inline config) | +| `model` | string | ❌ No | from config | Model name (for inline config) | + +#### Batch Syntax + +**Option 1: "last:N" syntax** +```json +{ + "commits": "last:5" +} +``` +Evaluates the last 5 commits from HEAD. + +**Option 2: Comma-separated hashes** +```json +{ + "commits": "abc123,def456,HEAD,HEAD~1" +} +``` + +#### Example Requests + +**1. Evaluate last 5 commits:** +```json +{ + "commits": "last:5" +} +``` + +**2. Specific commits with fast mode:** +```json +{ + "commits": "1fac813,abc123,def456", + "depth": "fast" +} +``` + +**3. Maximum batch:** +```json +{ + "commits": "last:10", + "depth": "fast", + "save": true +} +``` + +#### Response Format + +```typescript +{ + totalCommits: number; + successful: number; + failed: number; + + results: Array<{ + commitHash: string; + success: boolean; + error?: string; + metrics?: { + overallScore: number; + codeQuality: number; + complexity: number; + technicalDebt: number; + testCoverage: number; + functionalImpact: number; + }; + }>; + + summary: { + averageScore: number; + highestScore: number; + lowestScore: number; + totalDuration: number; // seconds + }; +} +``` + +--- + +### compare_commits + +Compare metrics between two commits to show improvement or regression. + +**Purpose:** +- Track code quality changes +- Compare before/after refactoring +- Identify improvements or regressions +- Validate that changes improved quality + +**When to use:** +- After refactoring +- Comparing feature branches +- Tracking quality trends +- Code review validation + +**⚠️ Requirement:** Both commits must have been evaluated with `save: true` + +**⏱️ Execution Time:** < 1 second (no API calls) + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `commit_a` | string | ✅ Yes | - | First commit (baseline) - supports partial match | +| `commit_b` | string | ✅ Yes | - | Second commit (to compare) - supports partial match | +| `repo_path` | string | ❌ No | current directory | Path to repository | + +#### Example Requests + +**1. Compare two commits:** +```json +{ + "commit_a": "1fac813", + "commit_b": "abc123" +} +``` + +**2. Compare HEAD with previous:** +```json +{ + "commit_a": "HEAD~1", + "commit_b": "HEAD" +} +``` + +#### Response Format + +```typescript +{ + commitA: { + hash: string; + message: string; + author: string; + date: string; + }; + + commitB: { + hash: string; + message: string; + author: string; + date: string; + }; + + comparison: { + overallScore: { + commitA: number; + commitB: number; + diff: number; // Positive = improvement + change: "improved" | "declined" | "unchanged"; + }; + codeQuality: { /* same structure */ }; + complexity: { /* same structure */ }; + technicalDebt: { /* same structure */ }; + testCoverage: { /* same structure */ }; + functionalImpact: { /* same structure */ }; + }; + + summary: string; // Human-readable summary +} +``` + +#### Example Response + +```json +{ + "commitA": { + "hash": "1fac813", + "message": "Initial implementation", + "author": "John Doe", + "date": "2024-01-20" + }, + "commitB": { + "hash": "abc123", + "message": "Refactor with tests", + "author": "John Doe", + "date": "2024-01-28" + }, + "comparison": { + "overallScore": { + "commitA": 7.5, + "commitB": 8.8, + "diff": +1.3, + "change": "improved" + }, + "testCoverage": { + "commitA": 6.0, + "commitB": 9.0, + "diff": +3.0, + "change": "improved" + } + }, + "summary": "Commit B shows significant improvement (+1.3 overall). Notable gains in test coverage (+3.0) and complexity management." +} +``` + +--- + +### get_team_stats + +Get aggregated code quality statistics for all authors in a repository. + +**Purpose:** +- Team-wide performance overview +- Identify high/low performers +- Track team quality trends +- Resource allocation planning + +**When to use:** +- Team retrospectives +- Sprint reviews +- Identifying training needs +- Resource planning + +**⚠️ Requirement:** Team members must have evaluated commits with `save: true` + +**⏱️ Execution Time:** < 1 second (no API calls) + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `repo_path` | string | ❌ No | current directory | Path to repository | +| `since` | string | ❌ No | - | Only include evaluations after this date (YYYY-MM-DD) | +| `limit` | number | ❌ No | `10` | Maximum number of authors to return | + +#### Example Requests + +**1. Get team stats:** +```json +{} +``` + +**2. With date filter:** +```json +{ + "since": "2024-01-01" +} +``` + +**3. Top 5 contributors:** +```json +{ + "limit": 5 +} +``` + +#### Response Format + +```typescript +{ + totalAuthors: number; + totalCommitsAnalyzed: number; + dateRange: { + start: string; + end: string; + }; + + teamAverages: { + overallScore: number; + codeQuality: number; + complexity: number; + technicalDebt: number; + testCoverage: number; + functionalImpact: number; + }; + + authors: Array<{ + author: string; + commitCount: number; + averageScore: number; + trend: "improving" | "declining" | "stable"; + metrics: { /* same structure as teamAverages */ }; + }>; + + rankings: { + byCommitCount: string[]; // Author names sorted by commit count + byQuality: string[]; // Author names sorted by quality score + }; +} +``` + +#### Example Response + +```json +{ + "totalAuthors": 5, + "totalCommitsAnalyzed": 87, + "dateRange": { + "start": "2024-01-01", + "end": "2024-01-28" + }, + "teamAverages": { + "overallScore": 8.1, + "codeQuality": 8.3, + "complexity": 7.9, + "technicalDebt": 8.0, + "testCoverage": 7.8, + "functionalImpact": 8.4 + }, + "authors": [ + { + "author": "John Doe", + "commitCount": 25, + "averageScore": 8.5, + "trend": "improving", + "metrics": { + "overallScore": 8.5, + "codeQuality": 8.8, + "complexity": 8.2, + "technicalDebt": 8.3, + "testCoverage": 8.0, + "functionalImpact": 8.9 + } + } + ], + "rankings": { + "byCommitCount": ["John Doe", "Sarah Smith", "Mike Johnson"], + "byQuality": ["Sarah Smith", "John Doe", "Mike Johnson"] + } +} +``` + +--- + +### export_report + +Export evaluation report to a file in HTML, Markdown, or JSON format. + +**Purpose:** +- Save reports for later review +- Share reports with team +- Archive evaluation data +- Integrate with external tools + +**When to use:** +- Creating documentation +- Sharing with non-technical stakeholders +- Archiving important evaluations +- Integrating with CI/CD pipelines + +**⚠️ Requirement:** Commit must have been evaluated with `save: true` + +**⏱️ Execution Time:** < 1 second (no API calls) + +#### Parameters + +| Parameter | Type | Required | Default | Description | +|-----------|------|----------|---------|-------------| +| `commit_hash` | string | ✅ Yes | - | Commit hash to export (supports partial match) | +| `output_path` | string | ✅ Yes | - | File path where report will be saved | +| `format` | string | ❌ No | `html` | Output format: `html`, `md`, or `json` | +| `repo_path` | string | ❌ No | current directory | Path to repository | + +#### Export Formats + +| Format | Extension | Description | Use Case | +|--------|-----------|-------------|----------| +| `html` | .html | Interactive HTML with styling | Sharing with stakeholders, web viewing | +| `md` | .md | Markdown format | Documentation, GitHub/GitLab | +| `json` | .json | Structured JSON data | Programmatic access, data analysis | + +#### Example Requests + +**1. Export to HTML:** +```json +{ + "commit_hash": "1fac813", + "output_path": "/tmp/report.html" +} +``` + +**2. Export to Markdown:** +```json +{ + "commit_hash": "HEAD", + "output_path": "./docs/evaluation.md", + "format": "md" +} +``` + +**3. Export to JSON:** +```json +{ + "commit_hash": "abc123", + "output_path": "./reports/commit-abc123.json", + "format": "json" +} +``` + +#### Response Format + +```typescript +{ + success: boolean; + outputPath: string; + format: string; + fileSize: number; // bytes + commitHash: string; +} +``` + +#### Example Response + +```json +{ + "success": true, + "outputPath": "/tmp/report.html", + "format": "html", + "fileSize": 45678, + "commitHash": "1fac813abc123..." +} +``` + +--- + +## Error Handling + +CodeWave MCP tools use a sophisticated error classification system. + +### Error Types + +| Error Type | Cause | How to Fix | +|------------|-------|------------| +| `API_KEY_ERROR` | Invalid or missing API key | Check configuration, verify API key is valid | +| `RATE_LIMIT_ERROR` | Too many API requests | Wait and retry, or use different provider | +| `NETWORK_ERROR` | Network connectivity issues | Check internet connection, verify provider URL | +| `MODEL_NOT_FOUND` | Invalid model name | Check model name spelling, verify provider supports model | +| `GIT_ERROR` | Git operation failed | Verify commit hash, check repository status | +| `TOKEN_LIMIT_ERROR` | Input too large for model | Use smaller commits, or switch to model with larger context | +| `UNKNOWN_ERROR` | Unexpected error | Check logs, report issue if persistent | + +### Error Response Format + +When a tool fails, it returns: + +```typescript +{ + isError: true; + errorType: string; // One of the error types above + message: string; // Human-readable error message + suggestion: string; // Actionable suggestion to fix + details?: any; // Additional context (if available) +} +``` + +### Example Error Response + +```json +{ + "isError": true, + "errorType": "API_KEY_ERROR", + "message": "API key is invalid or missing", + "suggestion": "Run 'codewave config --init' to set up your API key, or pass provider/api_key/model inline", + "details": { + "provider": "anthropic", + "keyLength": 0 + } +} +``` + +--- + +## Response Formats + +### Success Response + +All successful tool calls return: + +```typescript +{ + content: Array<{ + type: "text"; + text: string; // JSON-encoded result + }>; + isError?: false; +} +``` + +The `text` field contains JSON with the tool-specific data. + +### Error Response + +Failed tool calls return: + +```typescript +{ + content: Array<{ + type: "text"; + text: string; // JSON-encoded error + }>; + isError: true; +} +``` + +--- + +## Additional Notes + +### Saving Evaluations + +Several tools require evaluations to be saved with `save: true`: +- `list_evaluations` +- `get_report` +- `get_author_stats` +- `generate_okr` +- `compare_commits` +- `get_team_stats` +- `export_report` + +**Where evaluations are saved:** +``` +.evaluated-commits/ +├── 1fac813abc-2024-01-28/ +│ ├── results.json +│ ├── report.html +│ └── conversation-transcript.md +├── abc123def-2024-01-27/ +│ └── ... +└── index.json # Fast lookup index +``` + +### API Costs + +Tools that make API calls (and cost money): +- `evaluate_commit`: 1 evaluation = ~5-10 API calls +- `batch_evaluate`: N evaluations = ~(5-10 * N) API calls +- `generate_okr`: 1 OKR generation = ~3-5 API calls + +All other tools read from saved data and make no API calls. + +### Performance Tips + +1. **Use `fast` mode** for quick checks and CI/CD +2. **Save evaluations** (`save: true`) to enable analytics +3. **Batch evaluate** instead of individual calls when possible +4. **Filter by date** (`since` parameter) to reduce processing time +5. **Use partial hashes** (7+ characters) for faster lookups + +--- + +## See Also + +- [MCP Setup Guide](./MCP_SETUP.md) - Installation and configuration +- [MCP Examples](./MCP_EXAMPLES.md) - Practical workflows and examples +- [Configuration Guide](./CONFIGURATION.md) - Detailed configuration options +- [Quick Reference](./QUICK_REFERENCE.md) - Fast command lookup + +--- + +**Last Updated**: 2026-01-28 +**CodeWave Version**: 0.1.11+ diff --git a/docs/QUICK_REFERENCE.md b/docs/QUICK_REFERENCE.md index aeeab54..8720614 100644 --- a/docs/QUICK_REFERENCE.md +++ b/docs/QUICK_REFERENCE.md @@ -353,6 +353,109 @@ codewave evaluate abc1234 --- +## MCP Quick Reference (Claude Desktop) + +### Setup Checklist + +- [ ] Install CodeWave: `npm install -g @techdebtgpt/codewave` +- [ ] Configure: `codewave config --init` +- [ ] Add to `claude_desktop_config.json` (see [MCP_SETUP.md](./MCP_SETUP.md)) +- [ ] Restart Claude Desktop +- [ ] Verify: Ask Claude "Check my CodeWave configuration" + +### Most Common MCP Tool Calls + +| What You Want | Ask Claude | Tool Used | +|---------------|------------|-----------| +| Evaluate last commit | "Evaluate my last commit" | `evaluate_commit` | +| Check config | "Check my CodeWave config" | `check_config` | +| List past evaluations | "Show my past evaluations" | `list_evaluations` | +| Get detailed report | "Show report for commit abc123" | `get_report` | +| Author statistics | "Show John's code quality stats" | `get_author_stats` | +| Generate OKRs | "Generate OKRs for Sarah" | `generate_okr` | +| Evaluate multiple | "Evaluate my last 5 commits" | `batch_evaluate` | +| Compare commits | "Compare abc123 with def456" | `compare_commits` | +| Team statistics | "Show team code quality stats" | `get_team_stats` | +| Export report | "Export commit abc123 to HTML" | `export_report` | + +### MCP vs CLI Comparison + +| Feature | MCP (Claude Desktop) | CLI | +|---------|---------------------|-----| +| **Interface** | Natural conversation | Command-line | +| **Setup** | claude_desktop_config.json | Install globally | +| **Configuration** | Same .codewave.config.json | Same .codewave.config.json | +| **Tools** | 10 tools via prompts | evaluate, batch-evaluate, config commands | +| **Output** | Claude interprets results | JSON + HTML reports | +| **Use Case** | Interactive exploration | Automation, CI/CD | +| **Learning Curve** | Low (natural language) | Medium (command syntax) | +| **Speed** | Same evaluation time | Same evaluation time | +| **Cost** | Same API costs | Same API costs | + +### Quick Start Example + +``` +1. You: "Check my CodeWave configuration" + Claude: [Shows config status] + +2. You: "Evaluate my last commit" + Claude: [Evaluates and shows results - takes 30-60 seconds] + +3. You: "That looks good. Evaluate my last 5 commits and save them" + Claude: [Batch evaluates and saves] + +4. You: "Show me my code quality statistics" + Claude: [Shows your trends and patterns] +``` + +### Common Patterns + +**Quick commit check:** +``` +"Evaluate HEAD and tell me if it's ready to merge" +``` + +**Build evaluation history:** +``` +"Evaluate my last 10 commits and save them" +``` + +**Analyze performance:** +``` +"Show me my code quality trend over the last month" +``` + +**Compare changes:** +``` +"Compare my refactoring commit abc123 with the original def456" +``` + +**Team review:** +``` +"Show me which team members have the highest quality scores" +``` + +### Tips for Better Prompts + +✅ **Good prompts:** +- "Evaluate my last commit with deep analysis" +- "Show John's test coverage trend" +- "Find commits with high technical debt" +- "Compare this week's commits with last week's" + +❌ **Less effective prompts:** +- "Evaluate" (too vague - which commit?) +- "Stats" (which stats for whom?) +- "Compare commits" (which commits?) + +### Documentation Links + +- **[MCP_SETUP.md](./MCP_SETUP.md)** - Installation and configuration +- **[MCP_TOOLS_REFERENCE.md](./MCP_TOOLS_REFERENCE.md)** - Complete tool reference +- **[MCP_EXAMPLES.md](./MCP_EXAMPLES.md)** - Practical workflows + +--- + ## Troubleshooting Quick Fixes ### Error: "API Key not found" diff --git a/package-lock.json b/package-lock.json index eb5ffe8..2e78372 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@langchain/langgraph": "^1.0.2", "@langchain/ollama": "^1.0.3", "@langchain/openai": "^1.1.3", + "@modelcontextprotocol/sdk": "^1.25.3", "@types/cli-progress": "^3.11.6", "@types/inquirer": "^9.0.9", "chalk": "^4.1.2", @@ -30,19 +31,22 @@ "table": "^6.9.0" }, "bin": { - "codewave": "dist/cli/index.js" + "codewave": "dist/cli/index.js", + "codewave-mcp": "dist/cli/mcp-server.js" }, "devDependencies": { "@types/glob": "^8.1.0", "@types/lodash": "^4.17.21", - "@types/node": "^20.19.24", + "@types/node": "^20.19.27", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", + "@vitest/coverage-v8": "^4.0.17", "eslint": "^8.0.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.0.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.0.17" }, "engines": { "node": ">=18.0.0", @@ -69,6 +73,42 @@ } } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -78,12 +118,478 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@cfworker/json-schema": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -180,6 +686,18 @@ "node": ">=18.0.0" } }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -263,6 +781,34 @@ } } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@langchain/anthropic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-1.2.0.tgz", @@ -325,7 +871,6 @@ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.1.tgz", "integrity": "sha512-vdUoj2CVbb+0Qszi8llP34vdUCfP7bfA9VoFr4Se1pFGu7VAPnk8lBnRat9IvqSxMfTvOHJSd7Rn6TUPjzKsnA==", "license": "MIT", - "peer": true, "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", @@ -527,6 +1072,67 @@ "@langchain/core": "^1.0.0" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz", + "integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -578,6 +1184,374 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/cli-progress": { "version": "3.11.6", "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", @@ -587,6 +1561,20 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/glob": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", @@ -623,9 +1611,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -702,7 +1690,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -863,6 +1850,148 @@ "dev": true, "license": "ISC" }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.17.tgz", + "integrity": "sha512-/6zU2FLGg0jsd+ePZcwHRy3+WpNTBBhDY56P4JTRqUN/Dp6CvOEa9HrikcQ4KfV2b2kAHUFB4dl1SuocWXSFEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.17", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.17", + "vitest": "4.0.17" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.17.tgz", + "integrity": "sha512-mEoqP3RqhKlbmUmntNDDCJeTDavDR+fVYkSOw8qRwJFaW/0/5zA9zFeTrHqNtcmwh6j26yMmwx2PqUDPzt5ZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.17.tgz", + "integrity": "sha512-+ZtQhLA3lDh1tI2wxe3yMsGzbp7uuJSWBM1iTIKCbppWTSBN09PUC+L+fyNlQApQoR+Ps8twt2pbSSXg2fQVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.17", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.17.tgz", + "integrity": "sha512-Ah3VAYmjcEdHg6+MwFE17qyLqBHZ+ni2ScKCiW2XrlSBV4H3Z7vYfPfz7CWQ33gyu76oc0Ai36+kgLU3rfF4nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.17.tgz", + "integrity": "sha512-JmuQyf8aMWoo/LmNFppdpkfRVHJcsgzkbCA+/Bk7VfNH7RE6Ut2qxegeyx2j3ojtJtKIbIGy3h+KxGfYfk28YQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.17.tgz", + "integrity": "sha512-npPelD7oyL+YQM2gbIYvlavlMVWUfNNGZPcu0aEUQXt7FXTuqhmgiYupPnAanhKvyP6Srs2pIbWo30K0RbDtRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.17", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.17.tgz", + "integrity": "sha512-I1bQo8QaP6tZlTomQNWKJE6ym4SHf3oLS7ceNjozxxgzavRAgZDc06T7kD8gb9bXKEgcLNt00Z+kZO6KaJ62Ew==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.17.tgz", + "integrity": "sha512-RG6iy+IzQpa9SB8HAFHJ9Y+pTzI+h8553MrciN9eC6TFBErqrQaTas4vG+MVj8S4uKk8uTT2p0vgZPnTdxd96w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.17", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -875,13 +2004,50 @@ "node": ">=6.5" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -928,6 +2094,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -988,8 +2193,30 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" } }, "node_modules/astral-regex": { @@ -1045,6 +2272,30 @@ "readable-stream": "^3.4.0" } }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -1092,6 +2343,15 @@ "ieee754": "^1.1.13" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1105,6 +2365,22 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1127,6 +2403,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1273,11 +2559,63 @@ "simple-wcswidth": "^1.1.2" } }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1292,7 +2630,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1343,6 +2680,15 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1395,12 +2741,27 @@ "node": ">= 0.4" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1419,6 +2780,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1446,6 +2814,54 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1466,7 +2882,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -1523,7 +2938,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -1670,6 +3084,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1680,6 +3104,15 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -1695,6 +3128,120 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1828,6 +3375,27 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1902,6 +3470,24 @@ "node": ">= 12.20" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1909,6 +3495,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2169,6 +3770,43 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz", + "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -2295,6 +3933,15 @@ "node": ">=12.0.0" } }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2368,6 +4015,12 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2384,9 +4037,56 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tiktoken": { "version": "1.0.21", "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.21.tgz", @@ -2396,6 +4096,13 @@ "base64-js": "^1.5.1" } }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2435,6 +4142,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -2579,6 +4292,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2588,6 +4339,27 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -2688,6 +4460,25 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "license": "ISC" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2695,6 +4486,15 @@ "dev": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -2741,6 +4541,38 @@ } } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/ollama": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.6.3.tgz", @@ -2750,11 +4582,22 @@ "whatwg-fetch": "^3.6.20" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -2968,6 +4811,15 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2992,12 +4844,21 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3008,6 +4869,20 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3021,6 +4896,44 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3037,7 +4950,6 @@ "integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3061,6 +4973,19 @@ "node": ">=6.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3071,6 +4996,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3092,6 +5032,30 @@ ], "license": "MIT" }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3175,6 +5139,67 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -3255,29 +5280,182 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { - "shebang-regex": "^3.0.0" + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -3341,6 +5519,39 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3468,6 +5679,81 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3481,6 +5767,15 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -3538,13 +5833,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3572,6 +5905,15 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3601,6 +5943,212 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.17.tgz", + "integrity": "sha512-FQMeF0DJdWY0iOnbv466n/0BudNdKj1l5jYgl5JVTwjSsZSlqyXFt/9+1sEyhR6CLowbZpV7O1sCHrzBhucKKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.17", + "@vitest/mocker": "4.0.17", + "@vitest/pretty-format": "4.0.17", + "@vitest/runner": "4.0.17", + "@vitest/snapshot": "4.0.17", + "@vitest/spy": "4.0.17", + "@vitest/utils": "4.0.17", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.17", + "@vitest/browser-preview": "4.0.17", + "@vitest/browser-webdriverio": "4.0.17", + "@vitest/ui": "4.0.17", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -3645,7 +6193,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -3657,6 +6204,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -3706,7 +6270,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -3741,10 +6304,18 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/package.json b/package.json index c0f4193..9ac6176 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "lint:fix": "eslint . --ext .ts --fix", "prettier": "prettier --write '**/*.{ts,js,json,md}'", "prettier:check": "prettier --check '**/*.{ts,js,json,md}'", - "test": "echo 'Tests not configured yet. Run: npm run build && npm run lint'", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", "docs": "echo 'CodeWave Documentation' && echo '' && echo 'Start at: docs/INDEX.md' && echo 'Main docs: README.md' && echo '' && ls -lah docs/*.md | awk '{print $9, \"(\" $5 \")\"}'", "docs:readme": "echo 'Main Documentation: README.md'", "docs:quick-ref": "cat docs/QUICK_REFERENCE.md | head -50", @@ -26,20 +28,23 @@ "batch": "node ./dist/cli/index.js batch --count 3" }, "bin": { - "codewave": "./dist/cli/index.js" + "codewave": "./dist/cli/index.js", + "codewave-mcp": "./dist/cli/mcp-server.js" }, "type": "commonjs", "devDependencies": { "@types/glob": "^8.1.0", "@types/lodash": "^4.17.21", - "@types/node": "^20.19.24", + "@types/node": "^20.19.27", "@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/parser": "^7.0.0", + "@vitest/coverage-v8": "^4.0.17", "eslint": "^8.0.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "prettier": "^3.0.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.0.17" }, "keywords": [ "@techdebtgpt/codewave", @@ -97,6 +102,7 @@ "@langchain/langgraph": "^1.0.2", "@langchain/ollama": "^1.0.3", "@langchain/openai": "^1.1.3", + "@modelcontextprotocol/sdk": "^1.25.3", "@types/cli-progress": "^3.11.6", "@types/inquirer": "^9.0.9", "chalk": "^4.1.2", diff --git a/src/mcp-server/__tests__/index.test.ts b/src/mcp-server/__tests__/index.test.ts new file mode 100644 index 0000000..3e635d4 --- /dev/null +++ b/src/mcp-server/__tests__/index.test.ts @@ -0,0 +1,356 @@ +/** + * Tests for MCP Server initialization (index.ts) + * + * Tests the MCP server setup, request handlers, and error handling. + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + +// ============================================================================ +// Mock Setup - Must be before imports +// ============================================================================ + +// Store registered handlers so we can test them +const registeredHandlers: Map Promise> = new Map(); + +const mockServer = { + setRequestHandler: vi.fn((schema: unknown, handler: (request: unknown) => Promise) => { + registeredHandlers.set(schema, handler); + }), + connect: vi.fn().mockResolvedValue(undefined), +}; + +const mockStdioTransport = {}; + +vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({ + Server: class { + constructor() { + return mockServer; + } + }, +})); + +vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ + StdioServerTransport: class { + constructor() { + return mockStdioTransport; + } + }, +})); + +// Mock tool registry and handlers +const mockGetAllTools = vi.fn(); +const mockGetToolHandler = vi.fn(); + +vi.mock('../tools/tool-registry', () => ({ + getAllTools: mockGetAllTools, +})); + +vi.mock('../tools/handlers', () => ({ + getToolHandler: mockGetToolHandler, +})); + +// Import after mocks +import { + CallToolRequestSchema, + InitializeRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; + +// ============================================================================ +// Test Setup +// ============================================================================ + +describe('MCP Server (index.ts)', () => { + beforeEach(() => { + vi.clearAllMocks(); + registeredHandlers.clear(); + + // Re-import the module to trigger handler registration + vi.resetModules(); + }); + + afterEach(() => { + vi.resetModules(); + }); + + // Helper to load the server module and get handlers + async function loadServerAndGetHandlers() { + // Import the module (this triggers handler registration) + await import('../index.js'); + + return { + initializeHandler: registeredHandlers.get(InitializeRequestSchema), + listToolsHandler: registeredHandlers.get(ListToolsRequestSchema), + callToolHandler: registeredHandlers.get(CallToolRequestSchema), + }; + } + + // ============================================================================ + // Server Initialization Tests + // ============================================================================ + + describe('Server Setup', () => { + it('should register all three request handlers', async () => { + // Act + await loadServerAndGetHandlers(); + + // Assert + expect(mockServer.setRequestHandler).toHaveBeenCalledTimes(3); + expect(registeredHandlers.has(InitializeRequestSchema)).toBe(true); + expect(registeredHandlers.has(ListToolsRequestSchema)).toBe(true); + expect(registeredHandlers.has(CallToolRequestSchema)).toBe(true); + }); + }); + + // ============================================================================ + // Initialize Handler Tests + // ============================================================================ + + describe('InitializeRequestSchema Handler', () => { + it('should return protocol version and server info', async () => { + // Arrange + const { initializeHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await initializeHandler!({}); + + // Assert + expect(result).toEqual({ + protocolVersion: '2025-06-18', + capabilities: { + tools: {}, + }, + serverInfo: { + name: 'codewave-mcp-server', + version: '0.1.11', + }, + }); + }); + }); + + // ============================================================================ + // ListTools Handler Tests + // ============================================================================ + + describe('ListToolsRequestSchema Handler', () => { + it('should return all tools from registry', async () => { + // Arrange + const mockTools = [ + { name: 'evaluate_commit', description: 'Evaluate a commit' }, + { name: 'check_config', description: 'Check config' }, + ]; + mockGetAllTools.mockReturnValue(mockTools); + const { listToolsHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await listToolsHandler!({}); + + // Assert + expect(mockGetAllTools).toHaveBeenCalled(); + expect(result).toEqual({ tools: mockTools }); + }); + + it('should return empty array when no tools registered', async () => { + // Arrange + mockGetAllTools.mockReturnValue([]); + const { listToolsHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await listToolsHandler!({}); + + // Assert + expect(result).toEqual({ tools: [] }); + }); + }); + + // ============================================================================ + // CallTool Handler Tests + // ============================================================================ + + describe('CallToolRequestSchema Handler', () => { + it('should call the correct handler for a known tool', async () => { + // Arrange + const mockHandler = vi.fn().mockResolvedValue({ + content: [{ type: 'text', text: '{"success": true}' }], + }); + mockGetToolHandler.mockReturnValue(mockHandler); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await callToolHandler!({ + params: { + name: 'evaluate_commit', + arguments: { commit_hash: 'abc123' }, + }, + }); + + // Assert + expect(mockGetToolHandler).toHaveBeenCalledWith('evaluate_commit'); + expect(mockHandler).toHaveBeenCalledWith( + { commit_hash: 'abc123' }, + expect.objectContaining({ + projectPath: expect.any(String), + config: null, + logger: expect.any(Object), + }) + ); + expect(result).toEqual({ + content: [{ type: 'text', text: '{"success": true}' }], + isError: undefined, + }); + }); + + it('should return error for unknown tool', async () => { + // Arrange + mockGetToolHandler.mockReturnValue(undefined); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await callToolHandler!({ + params: { + name: 'unknown_tool', + arguments: {}, + }, + }); + + // Assert + expect(result).toEqual({ + content: [{ type: 'text', text: 'Unknown tool: unknown_tool' }], + isError: true, + }); + }); + + it('should handle handler errors gracefully', async () => { + // Arrange + const mockHandler = vi.fn().mockRejectedValue(new Error('Handler failed')); + mockGetToolHandler.mockReturnValue(mockHandler); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await callToolHandler!({ + params: { + name: 'evaluate_commit', + arguments: {}, + }, + }); + + // Assert + expect(result).toEqual({ + content: [{ type: 'text', text: 'Error: Handler failed' }], + isError: true, + }); + }); + + it('should handle non-Error exceptions gracefully', async () => { + // Arrange + const mockHandler = vi.fn().mockRejectedValue('String error'); + mockGetToolHandler.mockReturnValue(mockHandler); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await callToolHandler!({ + params: { + name: 'evaluate_commit', + arguments: {}, + }, + }); + + // Assert + expect(result).toEqual({ + content: [{ type: 'text', text: 'Error: String error' }], + isError: true, + }); + }); + + it('should pass isError from handler result', async () => { + // Arrange + const mockHandler = vi.fn().mockResolvedValue({ + content: [{ type: 'text', text: 'Validation error' }], + isError: true, + }); + mockGetToolHandler.mockReturnValue(mockHandler); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + const result = await callToolHandler!({ + params: { + name: 'evaluate_commit', + arguments: {}, + }, + }); + + // Assert + expect(result).toEqual({ + content: [{ type: 'text', text: 'Validation error' }], + isError: true, + }); + }); + + it('should use empty object when arguments not provided', async () => { + // Arrange + const mockHandler = vi.fn().mockResolvedValue({ + content: [{ type: 'text', text: 'OK' }], + }); + mockGetToolHandler.mockReturnValue(mockHandler); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + await callToolHandler!({ + params: { + name: 'check_config', + // No arguments provided + }, + }); + + // Assert + expect(mockHandler).toHaveBeenCalledWith( + {}, + expect.any(Object) + ); + }); + + it('should provide correct context to handler', async () => { + // Arrange + const mockHandler = vi.fn().mockResolvedValue({ + content: [{ type: 'text', text: 'OK' }], + }); + mockGetToolHandler.mockReturnValue(mockHandler); + const { callToolHandler } = await loadServerAndGetHandlers(); + + // Act + await callToolHandler!({ + params: { + name: 'evaluate_commit', + arguments: {}, + }, + }); + + // Assert + const context = mockHandler.mock.calls[0][1]; + expect(context).toHaveProperty('projectPath'); + expect(context).toHaveProperty('config', null); + expect(context).toHaveProperty('logger'); + expect(context.logger).toHaveProperty('info'); + expect(context.logger).toHaveProperty('error'); + expect(context.logger).toHaveProperty('warn'); + }); + }); + + // ============================================================================ + // startServer Tests + // ============================================================================ + + describe('startServer', () => { + it('should create StdioServerTransport and connect', async () => { + // Arrange + const { startServer } = await import('../index.js'); + + // Act + await startServer(); + + // Assert + expect(mockServer.connect).toHaveBeenCalledWith(mockStdioTransport); + }); + }); +}); diff --git a/src/mcp-server/index.ts b/src/mcp-server/index.ts new file mode 100644 index 0000000..3b8a842 --- /dev/null +++ b/src/mcp-server/index.ts @@ -0,0 +1,135 @@ +#!/usr/bin/env node + +/** + * MCP Server for CodeWave + * Exposes commit evaluation functionality to AI clients (Claude Desktop, etc.) + */ + +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { + CallToolRequestSchema, + InitializeRequestSchema, + ListToolsRequestSchema, +} from '@modelcontextprotocol/sdk/types.js'; +import { getAllTools } from './tools/tool-registry'; +import { getToolHandler } from './tools/handlers'; +import { ToolContext } from './types'; + +/** + * Simple logger for MCP server + */ +const logger = { + info: (message: string) => console.error(`[INFO] ${message}`), + error: (message: string, error?: unknown) => + console.error(`[ERROR] ${message}`, error || ''), + warn: (message: string) => console.error(`[WARN] ${message}`), +}; + +/** + * Create MCP Server instance + */ +const server = new Server( + { + name: 'codewave-mcp-server', + version: '0.1.11', + }, + { + capabilities: { + tools: {}, + }, + }, +); + +/** + * Handle initialize request + */ +server.setRequestHandler(InitializeRequestSchema, async () => { + logger.info('Initialize request received'); + return { + protocolVersion: '2025-06-18', + capabilities: { + tools: {}, + }, + serverInfo: { + name: 'codewave-mcp-server', + version: '0.1.11', + }, + }; +}); + +/** + * List available tools + */ +server.setRequestHandler(ListToolsRequestSchema, async () => { + logger.info('ListTools request received'); + return { + tools: getAllTools(), + }; +}); + +/** + * Handle tool calls + * Routes to appropriate handler with context + */ +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args = {} } = request.params; + const projectPath = process.cwd(); + + logger.info(`CallTool request received: ${name}`); + + try { + // Get the handler for this tool + const handler = getToolHandler(name); + if (!handler) { + return { + content: [ + { + type: 'text', + text: `Unknown tool: ${name}`, + }, + ], + isError: true, + }; + } + + // Create context for handler + const context: ToolContext = { + projectPath, + config: null, + logger, + }; + + // Call handler with context + const result = await handler(args, context); + + return { + content: result.content, + isError: result.isError, + }; + } catch (error) { + logger.error(`Tool ${name} failed`, error); + + return { + content: [ + { + type: 'text', + text: `Error: ${error instanceof Error ? error.message : String(error)}`, + }, + ], + isError: true, + }; + } +}); + +/** + * Start the MCP server on stdio transport + */ +export async function startServer(): Promise { + const transport = new StdioServerTransport(); + logger.info('CodeWave MCP Server starting...'); + await server.connect(transport); + // Note: connect() never returns - server runs indefinitely +} + +// Note: Server is started via cli/mcp-server.ts entry point diff --git a/src/mcp-server/tools/handlers.ts b/src/mcp-server/tools/handlers.ts new file mode 100644 index 0000000..d28a2c2 --- /dev/null +++ b/src/mcp-server/tools/handlers.ts @@ -0,0 +1,3184 @@ +/** + * Tool Handlers - All tool implementations + * Enhanced with comprehensive error handling and logging + */ + +import fs from 'fs'; +import path from 'path'; +import { spawnSync } from 'child_process'; +import { ContextualToolHandler, ToolResponse } from '../types'; +import { loadConfig, configExists } from '../../config/config-loader'; +import { DEFAULT_CONFIG } from '../../config/default-config'; +import { AppConfig } from '../../config/config.interface'; +import { createAgentRegistry, getEvaluationRoot, saveEvaluationReports, createEvaluationDirectory } from '../../../cli/utils/shared.utils'; +import { CommitEvaluationOrchestrator } from '../../orchestrator/commit-evaluation-orchestrator'; +import { getCommitDiff, extractCommitHash, generateDiffHash } from '../../../cli/utils/git-utils'; +import { parseCommitStats } from '../../common/utils/commit-utils'; +import { AuthorStatsAggregatorService } from '../../services/author-stats-aggregator.service'; +import { OkrAgentService } from '../../services/okr-agent.service'; +import { OkrOrchestrator } from '../../orchestrator/okr-orchestrator'; +import { generateEnhancedHtmlReport } from '../../formatters/html-report-formatter-enhanced'; +import { generateConversationTranscript } from '../../formatters/conversation-transcript-formatter'; +import { AgentResult } from '../../agents/agent.interface'; +import pLimit from 'p-limit'; + +// ============================================================================ +// CONSTANTS: List Evaluations +// ============================================================================ + +const DEFAULT_EVALUATION_LIMIT = 20; +const MAX_EVALUATION_LIMIT = 100; +const DEFAULT_AUTHOR_STATS_LIMIT = 50; + +// ============================================================================ +// CONSTANTS +// ============================================================================ + +const VALID_PROVIDERS = ['openai', 'anthropic', 'groq', 'google', 'ollama', 'xai', 'lm-studio'] as const; +const VALID_DEPTHS = ['fast', 'normal', 'deep'] as const; +const VALID_FORMATS = ['full', 'summary', 'metrics_only'] as const; +const VALID_EXPORT_FORMATS = ['html', 'md', 'json'] as const; +const COMMIT_HASH_PATTERN = /^[a-fA-F0-9]{4,40}$|^HEAD(~\d+)?(\^+)?$/; + +// ============================================================================ +// CONSOLE SUPPRESSION FOR MCP +// ============================================================================ +// MCP uses stdio for JSON-RPC communication. Any console output to stdout +// interferes with the protocol and causes JSON parsing errors. +// This wrapper suppresses console output during long-running operations +// like evaluation and OKR generation. + +interface ConsoleMethods { + log: typeof console.log; + info: typeof console.info; + warn: typeof console.warn; + error: typeof console.error; +} + +/** + * Suppress console output during an async operation. + * This prevents emoji progress messages (🚀, 📦, ✅) from interfering + * with MCP stdio JSON-RPC protocol. + * + * Note: This only affects MCP handlers. CLI uses a separate code path + * and is NOT affected by this suppression. + * + * @param fn - Async function to execute with suppressed console + * @returns The result of the async function + */ +export async function withSuppressedConsole(fn: () => Promise): Promise { + // Save original console methods + const original: ConsoleMethods = { + log: console.log, + info: console.info, + warn: console.warn, + error: console.error, + }; + + // Replace with no-ops (suppress output to stdout) + // eslint-disable-next-line @typescript-eslint/no-empty-function + const noop = () => {}; + console.log = noop; + console.info = noop; + console.warn = noop; + // Keep console.error but redirect to stderr (doesn't interfere with MCP) + // No prefix needed - messages already contain [INFO], [WARN], or [ERROR] from logger + console.error = (...args: unknown[]) => { + process.stderr.write(`${args.map(String).join(' ')}\n`); + }; + + try { + return await fn(); + } finally { + // Always restore original console methods + console.log = original.log; + console.info = original.info; + console.warn = original.warn; + console.error = original.error; + } +} + +// ============================================================================ +// HELPER TYPES +// ============================================================================ + +export interface ValidationError { + field: string; + message: string; + value?: unknown; +} + +interface EvaluationTiming { + startTime: number; + configLoadedAt?: number; + diffFetchedAt?: number; + evaluationStartedAt?: number; + evaluationEndedAt?: number; + totalDurationMs?: number; +} + +// ============================================================================ +// HELPER FUNCTIONS: Error Response Builders +// ============================================================================ + +/** + * Create a standardized error response + */ +export function createErrorResponse( + error: string, + message: string, + details?: Record, +): ToolResponse { + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + error, + message, + timestamp: new Date().toISOString(), + ...details, + }, + null, + 2, + ), + }, + ], + isError: true, + }; +} + +/** + * Create a validation error response with multiple errors + */ +export function createValidationErrorResponse(errors: ValidationError[]): ToolResponse { + return createErrorResponse('Validation failed', 'One or more input parameters are invalid', { + validationErrors: errors, + hint: 'Please check the parameter values and try again.', + }); +} + +// ============================================================================ +// HELPER FUNCTIONS: Validation +// ============================================================================ + +/** + * Validate all input parameters + */ +export function validateInputs(args: { + commit_hash: string; + repo_path?: string; + depth?: string; + provider?: string; + api_key?: string; + model?: string; +}): ValidationError[] { + const errors: ValidationError[] = []; + + // Validate commit_hash (required) + if (!args.commit_hash) { + errors.push({ + field: 'commit_hash', + message: 'commit_hash is required', + }); + } else if (!COMMIT_HASH_PATTERN.test(args.commit_hash)) { + errors.push({ + field: 'commit_hash', + message: 'Invalid commit hash format. Expected a git SHA (4-40 hex chars) or HEAD reference.', + value: args.commit_hash, + }); + } + + // Validate depth (optional, but must be valid if provided) + if (args.depth && !VALID_DEPTHS.includes(args.depth as typeof VALID_DEPTHS[number])) { + errors.push({ + field: 'depth', + message: `Invalid depth value. Must be one of: ${VALID_DEPTHS.join(', ')}`, + value: args.depth, + }); + } + + // Validate provider (optional, but must be valid if provided) + if (args.provider && !VALID_PROVIDERS.includes(args.provider as typeof VALID_PROVIDERS[number])) { + errors.push({ + field: 'provider', + message: `Invalid provider. Must be one of: ${VALID_PROVIDERS.join(', ')}`, + value: args.provider, + }); + } + + // Validate inline config completeness (if any inline param is provided, all must be) + const hasProvider = !!args.provider; + const hasApiKey = !!args.api_key; + const hasModel = !!args.model; + const inlineParamCount = [hasProvider, hasApiKey, hasModel].filter(Boolean).length; + + if (inlineParamCount > 0 && inlineParamCount < 3) { + const missing: string[] = []; + if (!hasProvider) missing.push('provider'); + if (!hasApiKey) missing.push('api_key'); + if (!hasModel) missing.push('model'); + errors.push({ + field: 'inline_config', + message: `Incomplete inline config. If using inline parameters, all three are required: provider, api_key, model. Missing: ${missing.join(', ')}`, + }); + } + + // Validate api_key is not empty string + if (hasApiKey && args.api_key!.trim().length === 0) { + errors.push({ + field: 'api_key', + message: 'api_key cannot be empty', + }); + } + + // Validate model is not empty string + if (hasModel && args.model!.trim().length === 0) { + errors.push({ + field: 'model', + message: 'model cannot be empty', + }); + } + + return errors; +} + +/** + * Check if a path is a valid git repository + */ +export function isGitRepository(repoPath: string): { valid: boolean; error?: string } { + try { + const gitDir = path.join(repoPath, '.git'); + if (!fs.existsSync(repoPath)) { + return { valid: false, error: `Path does not exist: ${repoPath}` }; + } + if (!fs.statSync(repoPath).isDirectory()) { + return { valid: false, error: `Path is not a directory: ${repoPath}` }; + } + if (!fs.existsSync(gitDir)) { + return { valid: false, error: `Not a git repository (no .git folder): ${repoPath}` }; + } + return { valid: true }; + } catch (err) { + return { + valid: false, + error: `Failed to check git repository: ${err instanceof Error ? err.message : String(err)}`, + }; + } +} + +// ============================================================================ +// HELPER FUNCTIONS: Config Building +// ============================================================================ + +/** + * Build a config from inline parameters (when no config file exists) + */ +export function buildInlineConfig( + provider: string, + apiKey: string, + model: string, + depth: 'fast' | 'normal' | 'deep' = 'normal', +): AppConfig { + return { + apiKeys: { + ...DEFAULT_CONFIG.apiKeys, + [provider]: apiKey, + }, + llm: { + provider: provider as AppConfig['llm']['provider'], + model: model, + temperature: DEFAULT_CONFIG.llm.temperature, + maxTokens: DEFAULT_CONFIG.llm.maxTokens, + baseUrl: DEFAULT_CONFIG.llm.baseUrl, + }, + agents: { + ...DEFAULT_CONFIG.agents, + depthMode: depth, + }, + output: { ...DEFAULT_CONFIG.output }, + tracing: { ...DEFAULT_CONFIG.tracing }, + }; +} + +// ============================================================================ +// HELPER FUNCTIONS: Error Classification +// ============================================================================ + +/** + * Classify and format errors from the evaluation process + */ +export function classifyError(error: unknown): { + type: string; + message: string; + details?: Record; + suggestion?: string; +} { + const errorMessage = error instanceof Error ? error.message : String(error); + const errorString = errorMessage.toLowerCase(); + + // API Key errors + if (errorString.includes('api key') || errorString.includes('apikey') || errorString.includes('unauthorized') || errorString.includes('401')) { + return { + type: 'API_KEY_ERROR', + message: 'Invalid or missing API key', + details: { originalError: errorMessage }, + suggestion: 'Check that your API key is correct and has not expired.', + }; + } + + // Rate limit errors + if (errorString.includes('rate limit') || errorString.includes('429') || errorString.includes('too many requests')) { + return { + type: 'RATE_LIMIT_ERROR', + message: 'API rate limit exceeded', + details: { originalError: errorMessage }, + suggestion: 'Wait a few minutes before retrying, or consider using a different API key.', + }; + } + + // Network errors + if (errorString.includes('network') || errorString.includes('econnrefused') || errorString.includes('enotfound') || errorString.includes('timeout')) { + return { + type: 'NETWORK_ERROR', + message: 'Network connection failed', + details: { originalError: errorMessage }, + suggestion: 'Check your internet connection and try again.', + }; + } + + // Model not found + if (errorString.includes('model') && (errorString.includes('not found') || errorString.includes('does not exist'))) { + return { + type: 'MODEL_NOT_FOUND', + message: 'The specified model was not found', + details: { originalError: errorMessage }, + suggestion: 'Check that the model name is correct for your provider.', + }; + } + + // Git errors + if (errorString.includes('git') || errorString.includes('commit') || errorString.includes('repository')) { + return { + type: 'GIT_ERROR', + message: 'Git operation failed', + details: { originalError: errorMessage }, + suggestion: 'Ensure the commit hash exists and the repository is valid.', + }; + } + + // Token/context limit errors + if (errorString.includes('token') || errorString.includes('context length') || errorString.includes('too long')) { + return { + type: 'TOKEN_LIMIT_ERROR', + message: 'Input too large for the model', + details: { originalError: errorMessage }, + suggestion: 'The commit diff may be too large. Try evaluating a smaller commit.', + }; + } + + // Default: unknown error + return { + type: 'UNKNOWN_ERROR', + message: errorMessage, + details: error instanceof Error ? { stack: error.stack } : undefined, + suggestion: 'An unexpected error occurred. Check the error details for more information.', + }; +} + +// ============================================================================ +// HELPER FUNCTIONS: Evaluation Directory Lookup +// ============================================================================ + +type EvalDirResult = + | { found: true; dir: string } + | { found: false; error: 'not_found' } + | { found: false; error: 'ambiguous'; matches: string[] }; + +/** + * Find evaluation directory by commit hash with exact match priority. + * This prevents ambiguity when one directory name is a prefix of another. + * E.g., "1fac813" should match "1fac813" exactly, not report ambiguity with "1fac8137" + */ +export function findEvaluationDirectory(evalRoot: string, hash: string): EvalDirResult { + const dirs = fs.readdirSync(evalRoot).filter((d) => { + // Only include directories that look like commit hashes (not .okrs, index.json, etc.) + return fs.statSync(path.join(evalRoot, d)).isDirectory() && !d.startsWith('.'); + }); + const hashLower = hash.toLowerCase(); + + // Priority 1: Exact match + const exactMatch = dirs.find((dir) => dir.toLowerCase() === hashLower); + if (exactMatch) { + return { found: true, dir: exactMatch }; + } + + // Priority 2: Partial match (startsWith) + const matchingDirs = dirs.filter((dir) => dir.toLowerCase().startsWith(hashLower)); + + if (matchingDirs.length === 0) { + return { found: false, error: 'not_found' }; + } + + if (matchingDirs.length > 1) { + return { found: false, error: 'ambiguous', matches: matchingDirs.slice(0, 5) }; + } + + return { found: true, dir: matchingDirs[0] }; +} + +// ============================================================================ +// MAIN HANDLER +// ============================================================================ + +/** + * Handler: evaluate_commit + * Evaluates a git commit using CodeWave's multi-agent AI analysis + */ +export const handleEvaluateCommit: ContextualToolHandler = async ( + args, + context, +): Promise => { + // Initialize timing + const timing: EvaluationTiming = { + startTime: Date.now(), + }; + + // Extract parameters + const { commit_hash, repo_path, depth = 'normal', provider, api_key, model, save = false } = args as { + commit_hash: string; + repo_path?: string; + depth?: 'fast' | 'normal' | 'deep'; + provider?: string; + api_key?: string; + model?: string; + save?: boolean; + }; + + const projectPath = repo_path || context.projectPath; + const configFilePath = path.join(projectPath, '.codewave.config.json'); + + // Log start with all parameters (mask api_key for security) + context.logger.info('=== evaluate_commit START ==='); + context.logger.info(`Parameters: commit_hash=${commit_hash}, repo_path=${projectPath}, depth=${depth}`); + if (provider) { + context.logger.info(`Inline config: provider=${provider}, model=${model}, api_key=${api_key ? '***' + api_key.slice(-4) : 'not provided'}`); + } + + try { + // ======================================================================== + // STEP 1: Input Validation + // ======================================================================== + context.logger.info('Step 1: Validating inputs...'); + const validationErrors = validateInputs({ commit_hash, repo_path, depth, provider, api_key, model }); + + if (validationErrors.length > 0) { + context.logger.error(`Validation failed with ${validationErrors.length} error(s)`); + validationErrors.forEach((err) => context.logger.error(` - ${err.field}: ${err.message}`)); + return createValidationErrorResponse(validationErrors); + } + context.logger.info('Input validation passed'); + + // ======================================================================== + // STEP 2: Git Repository Validation + // ======================================================================== + context.logger.info('Step 2: Validating git repository...'); + const gitCheck = isGitRepository(projectPath); + + if (!gitCheck.valid) { + context.logger.error(`Git validation failed: ${gitCheck.error}`); + return createErrorResponse('Invalid repository', gitCheck.error || 'Not a valid git repository', { + repo_path: projectPath, + hint: 'Ensure the path points to a valid git repository with a .git folder.', + }); + } + context.logger.info(`Git repository validated: ${projectPath}`); + + // ======================================================================== + // STEP 3: Configuration Loading + // ======================================================================== + context.logger.info('Step 3: Loading configuration...'); + let config: AppConfig; + let configSource: 'inline' | 'file'; + + const hasInlineConfig = provider && api_key && model; + const hasConfigFile = configExists(configFilePath); + + if (hasInlineConfig) { + configSource = 'inline'; + context.logger.info(`Using INLINE config: provider=${provider}, model=${model}`); + config = buildInlineConfig(provider, api_key, model, depth as 'fast' | 'normal' | 'deep'); + } else if (hasConfigFile) { + configSource = 'file'; + context.logger.info(`Using CONFIG FILE: ${configFilePath}`); + + const loadedConfig = loadConfig(configFilePath); + if (!loadedConfig) { + context.logger.error('Failed to parse config file'); + return createErrorResponse('Configuration load failed', 'Failed to load .codewave.config.json. Please check the file format.', { + configPath: configFilePath, + hint: 'Ensure the config file is valid JSON and follows the expected schema.', + }); + } + config = loadedConfig; + config.agents.depthMode = depth as 'fast' | 'normal' | 'deep'; + context.logger.info(`Config loaded: provider=${config.llm.provider}, model=${config.llm.model}`); + } else { + context.logger.error('No configuration available'); + return createErrorResponse( + 'Configuration required', + 'No .codewave.config.json found and no inline config provided.', + { + projectPath, + hint: 'Either create a config file with "codewave config --init" or provide provider, api_key, and model parameters.', + }, + ); + } + + timing.configLoadedAt = Date.now(); + context.logger.info(`Config loaded in ${timing.configLoadedAt - timing.startTime}ms`); + + // ======================================================================== + // STEP 4: API Key Verification + // ======================================================================== + context.logger.info('Step 4: Verifying API key...'); + const activeProvider = config.llm.provider; + const activeApiKey = config.apiKeys[activeProvider]; + + if (!activeApiKey) { + context.logger.error(`No API key found for provider: ${activeProvider}`); + return createErrorResponse('API key not configured', `No API key found for provider: ${activeProvider}`, { + provider: activeProvider, + configSource, + hint: configSource === 'file' + ? `Add the API key to your .codewave.config.json under apiKeys.${activeProvider}` + : 'Provide a valid api_key parameter.', + }); + } + + // Warn if API key looks suspicious (too short, placeholder, etc.) + if (activeApiKey.length < 10) { + context.logger.warn(`API key for ${activeProvider} seems unusually short (${activeApiKey.length} chars)`); + } + if (activeApiKey.includes('your_') || activeApiKey.includes('xxx') || activeApiKey.includes('placeholder')) { + context.logger.warn(`API key for ${activeProvider} appears to be a placeholder`); + } + + context.logger.info(`API key verified for provider: ${activeProvider} (key ending in ...${activeApiKey.slice(-4)})`); + + // ======================================================================== + // STEP 5: Fetch Commit Diff + // ======================================================================== + context.logger.info(`Step 5: Fetching diff for commit: ${commit_hash}...`); + + let diff: string; + try { + diff = getCommitDiff(commit_hash, projectPath); + } catch (gitError) { + context.logger.error(`Git diff fetch failed: ${gitError}`); + return createErrorResponse('Git operation failed', `Failed to get diff for commit: ${commit_hash}`, { + commit_hash, + repo_path: projectPath, + originalError: gitError instanceof Error ? gitError.message : String(gitError), + hint: 'Ensure the commit hash exists in the repository. Use "git log" to verify.', + }); + } + + if (!diff || diff.trim().length === 0) { + context.logger.warn(`Empty diff returned for commit: ${commit_hash}`); + return createErrorResponse('No diff found', `Commit ${commit_hash} has no changes or does not exist.`, { + commit_hash, + hint: 'This might be an empty commit or a merge commit. Try a different commit hash.', + }); + } + + timing.diffFetchedAt = Date.now(); + const diffLines = diff.split('\n').length; + const diffSize = diff.length; + context.logger.info(`Diff fetched in ${timing.diffFetchedAt - (timing.configLoadedAt || timing.startTime)}ms`); + context.logger.info(`Diff stats: ${diffLines} lines, ${diffSize} bytes`); + + // Warn if diff is very large + if (diffSize > 100000) { + context.logger.warn(`Large diff detected (${diffSize} bytes). Evaluation may take longer or hit token limits.`); + } + + // ======================================================================== + // STEP 6: Resolve Commit Hash + // ======================================================================== + context.logger.info('Step 6: Resolving commit hash...'); + let resolvedHash = extractCommitHash(diff); + if (!resolvedHash) { + resolvedHash = generateDiffHash(diff); + context.logger.info(`Generated hash from diff: ${resolvedHash}`); + } else { + context.logger.info(`Extracted commit hash: ${resolvedHash}`); + } + + // ======================================================================== + // STEP 7: Create Agent Registry & Orchestrator + // ======================================================================== + context.logger.info('Step 7: Creating agent registry and orchestrator...'); + context.logger.info(`Enabled agents (${config.agents.enabled.length}): ${config.agents.enabled.join(', ')}`); + + let agentRegistry; + let orchestrator; + try { + agentRegistry = createAgentRegistry(config); + orchestrator = new CommitEvaluationOrchestrator(agentRegistry, config); + context.logger.info('Orchestrator created successfully'); + } catch (registryError) { + context.logger.error(`Failed to create agent registry: ${registryError}`); + return createErrorResponse('Agent initialization failed', 'Failed to initialize the evaluation agents.', { + originalError: registryError instanceof Error ? registryError.message : String(registryError), + hint: 'This might be a configuration issue. Check your agent settings.', + }); + } + + // ======================================================================== + // STEP 8: Run Evaluation + // ======================================================================== + context.logger.info('Step 8: Starting evaluation...'); + context.logger.info(`Evaluation settings: depth=${depth}, provider=${activeProvider}, model=${config.llm.model}`); + + timing.evaluationStartedAt = Date.now(); + + const evalContext = { + commitDiff: diff, + filesChanged: [], + commitHash: resolvedHash, + config, + }; + + let evaluationResult; + try { + // Suppress console output during evaluation to prevent emoji progress + // messages from interfering with MCP stdio JSON-RPC protocol + evaluationResult = await withSuppressedConsole(() => + orchestrator.evaluateCommit(evalContext, { + streaming: false, + threadId: `mcp-eval-${Date.now()}`, + }) + ); + } catch (evalError) { + timing.evaluationEndedAt = Date.now(); + const evalDuration = timing.evaluationEndedAt - timing.evaluationStartedAt; + context.logger.error(`Evaluation failed after ${evalDuration}ms: ${evalError}`); + + const classified = classifyError(evalError); + return createErrorResponse(classified.type, classified.message, { + ...classified.details, + commit_hash, + provider: activeProvider, + model: config.llm.model, + evaluationDurationMs: evalDuration, + suggestion: classified.suggestion, + }); + } + + timing.evaluationEndedAt = Date.now(); + const evalDuration = timing.evaluationEndedAt - timing.evaluationStartedAt; + context.logger.info(`Evaluation completed in ${evalDuration}ms`); + + // ======================================================================== + // STEP 9: Process Results + // ======================================================================== + context.logger.info('Step 9: Processing results...'); + const results = evaluationResult.agentResults || []; + context.logger.info(`Received ${results.length} agent results`); + + // Check for empty results - likely indicates API/model error that wasn't thrown + if (results.length === 0) { + context.logger.error('No agent results returned - evaluation failed silently'); + return createErrorResponse( + 'Evaluation returned no results', + 'The evaluation completed but returned no agent results. This usually indicates an invalid API key or model.', + { + commit_hash, + provider: activeProvider, + model: config.llm.model, + configSource, + evaluationDurationMs: evalDuration, + hint: 'Verify your API key is valid and the model name is correct for your provider.', + suggestion: 'Try testing your API key directly with the provider to confirm it works.', + }, + ); + } + + // Extract metrics + const metrics: Record = {}; + let metricsCount = 0; + for (const result of results) { + if (result.metrics) { + for (const [key, value] of Object.entries(result.metrics)) { + if (typeof value === 'number') { + if (metrics[key] !== undefined) { + metrics[key] = (metrics[key] + value) / 2; + } else { + metrics[key] = value; + } + metricsCount++; + } + } + } + } + context.logger.info(`Extracted ${Object.keys(metrics).length} unique metrics from ${metricsCount} values`); + + // ======================================================================== + // STEP 10: Build Response + // ======================================================================== + timing.totalDurationMs = Date.now() - timing.startTime; + context.logger.info('Step 10: Building response...'); + + const response: Record = { + success: true, + commit_hash: resolvedHash, + depth, + provider: `${activeProvider} (${config.llm.model})`, + configSource, + metrics, + agentCount: results.length, + timing: { + totalDurationMs: timing.totalDurationMs, + evaluationDurationMs: evalDuration, + }, + developerOverview: evaluationResult.developerOverview, + agentSummaries: results.map((r: Record) => ({ + agent: r.agentName || r.agentRole, + summary: r.summary, + concerns: r.concerns || [], + })), + }; + + // ======================================================================== + // STEP 11: Optional Save to Disk + // ======================================================================== + if (save) { + context.logger.info('Step 11: Saving evaluation to disk...'); + try { + // Get commit metadata using git + let commitAuthor: string | undefined; + let commitMessage: string | undefined; + let commitDate: string | undefined; + + const showResult = spawnSync( + 'git', + ['show', '--no-patch', '--format=%an|||%s|||%aI', resolvedHash], + { cwd: projectPath, encoding: 'utf-8' } + ); + + if (showResult.status === 0 && showResult.stdout) { + const [author, message, date] = showResult.stdout.trim().split('|||'); + commitAuthor = author; + commitMessage = message; + commitDate = date; + } + + // Create evaluation directory + const outputDir = await createEvaluationDirectory(resolvedHash, projectPath); + + // Calculate commit statistics from diff + const commitStats = parseCommitStats(diff); + + // Prepare metadata + const metadata = { + timestamp: new Date().toISOString(), + commitHash: resolvedHash, + commitAuthor, + commitMessage, + commitDate, + source: 'commit' as const, + commitStats, + }; + + // Save all reports + await saveEvaluationReports({ + agentResults: results, + outputDir, + metadata, + diff, + developerOverview: evaluationResult.developerOverview, + }); + + response.savedTo = `.evaluated-commits/${resolvedHash}`; + context.logger.info(`Evaluation saved to: ${outputDir}`); + } catch (saveError) { + context.logger.warn(`Failed to save evaluation: ${saveError}`); + response.saveWarning = 'Evaluation completed but failed to save to disk.'; + } + } + + context.logger.info('=== evaluate_commit COMPLETE ==='); + context.logger.info(`Total duration: ${timing.totalDurationMs}ms | Agents: ${results.length} | Metrics: ${Object.keys(metrics).length}`); + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + // Catch-all for any unexpected errors + const totalDuration = Date.now() - timing.startTime; + context.logger.error('=== evaluate_commit FAILED (unexpected error) ==='); + context.logger.error(`Error after ${totalDuration}ms: ${error}`); + if (error instanceof Error && error.stack) { + context.logger.error(`Stack trace: ${error.stack}`); + } + + const classified = classifyError(error); + return createErrorResponse(classified.type, classified.message, { + ...classified.details, + commit_hash, + repo_path: projectPath, + durationMs: totalDuration, + suggestion: classified.suggestion, + }); + } +}; + +// ============================================================================ +// BATCH OPERATIONS +// ============================================================================ + +const MAX_PARALLEL_EXECUTIONS = 10; +const BATCH_COMMIT_PATTERN = /^last:(\d+)$/i; + +/** + * Handler: batch_evaluate + * Evaluate multiple commits in a single call + */ +export const handleBatchEvaluate: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== batch_evaluate START ==='); + + // Extract parameters + const { commits, repo_path, depth = 'normal', provider, api_key, model, save = true } = args as { + commits: string; + repo_path?: string; + depth?: 'fast' | 'normal' | 'deep'; + provider?: string; + api_key?: string; + model?: string; + save?: boolean; + }; + + const projectPath = repo_path || context.projectPath; + + context.logger.info(`Parameters: commits=${commits}, depth=${depth}, save=${save}`); + + try { + // ======================================================================== + // STEP 1: Input Validation + // ======================================================================== + context.logger.info('Step 1: Validating inputs...'); + + if (!commits || commits.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'commits parameter is required', + { hint: 'Use "last:N" for last N commits, or comma-separated hashes like "abc123,def456"' }, + ); + } + + // Validate inline config if partial + const hasProvider = !!provider; + const hasApiKey = !!api_key; + const hasModel = !!model; + const inlineParamCount = [hasProvider, hasApiKey, hasModel].filter(Boolean).length; + + if (inlineParamCount > 0 && inlineParamCount < 3) { + const missing: string[] = []; + if (!hasProvider) missing.push('provider'); + if (!hasApiKey) missing.push('api_key'); + if (!hasModel) missing.push('model'); + return createErrorResponse( + 'Incomplete inline config', + `If using inline parameters, all three are required. Missing: ${missing.join(', ')}`, + { hint: 'Either provide all three (provider, api_key, model) or use a config file.' }, + ); + } + + // Validate git repository + const gitCheck = isGitRepository(projectPath); + if (!gitCheck.valid) { + return createErrorResponse('Invalid repository', gitCheck.error || 'Not a valid git repository', { + repo_path: projectPath, + }); + } + + // ======================================================================== + // STEP 2: Parse Commits + // ======================================================================== + context.logger.info('Step 2: Parsing commits...'); + + let commitHashes: string[] = []; + const batchMatch = commits.trim().match(BATCH_COMMIT_PATTERN); + + if (batchMatch) { + // Handle "last:N" syntax + const n = parseInt(batchMatch[1], 10); + + if (n < 1) { + return createErrorResponse( + 'Invalid batch size', + 'N must be at least 1 in "last:N"', + { value: commits, hint: 'Use "last:1" or higher.' }, + ); + } + + // Get last N commit hashes using git log + context.logger.info(`Fetching last ${n} commits...`); + const gitLogResult = spawnSync( + 'git', + ['log', `-${n}`, '--format=%H'], + { cwd: projectPath, encoding: 'utf-8' }, + ); + + if (gitLogResult.status !== 0) { + return createErrorResponse( + 'Git operation failed', + 'Failed to get commit history', + { originalError: gitLogResult.stderr, hint: 'Ensure the repository has commits.' }, + ); + } + + commitHashes = gitLogResult.stdout + .trim() + .split('\n') + .filter((h) => h.length > 0); + + context.logger.info(`Got ${commitHashes.length} commits from git log`); + } else { + // Handle comma-separated hashes + commitHashes = commits + .split(',') + .map((h) => h.trim()) + .filter((h) => h.length > 0); + + context.logger.info(`Parsed ${commitHashes.length} comma-separated hashes`); + } + + // Validate we have commits + if (commitHashes.length === 0) { + return createErrorResponse( + 'No commits specified', + 'Could not parse any commits from the input', + { value: commits, hint: 'Use "last:N" or comma-separated hashes.' }, + ); + } + + // Validate each hash format + for (const hash of commitHashes) { + if (!COMMIT_HASH_PATTERN.test(hash)) { + return createErrorResponse( + 'Invalid commit hash', + `Invalid hash format: ${hash}`, + { hint: 'Expected git SHA (4-40 hex chars) or HEAD reference.' }, + ); + } + } + + context.logger.info(`Will evaluate ${commitHashes.length} commits: ${commitHashes.map((h) => h.substring(0, 7)).join(', ')}`); + + // ======================================================================== + // STEP 3: Evaluate Each Commit + // ======================================================================== + context.logger.info('Step 3: Starting batch evaluation...'); + + interface CommitResult { + commit_hash: string; + short_hash: string; + status: 'success' | 'failed'; + author?: string; + message?: string; + metrics?: Record; + savedTo?: string; + error?: string; + } + + const results: CommitResult[] = []; + let successful = 0; + let failed = 0; + + // Create evaluation tasks with controlled parallelism (max 10 concurrent) + context.logger.info(`Starting parallel evaluation of ${commitHashes.length} commits (max ${MAX_PARALLEL_EXECUTIONS} concurrent)...`); + + const limit = pLimit(MAX_PARALLEL_EXECUTIONS); + + const evaluationTasks = commitHashes.map((hash, i) => limit(async (): Promise => { + context.logger.info(`[${i + 1}/${commitHashes.length}] Evaluating ${hash.substring(0, 7)}...`); + + try { + // Call the existing evaluate_commit handler + const evalResult = await handleEvaluateCommit( + { + commit_hash: hash, + repo_path: projectPath, + depth, + provider, + api_key, + model, + save, + }, + context, + ); + + // Parse the result + const resultText = evalResult.content[0]?.type === 'text' ? evalResult.content[0].text : '{}'; + const resultData = JSON.parse(resultText || '{}') as Record; + + if (evalResult.isError || resultData.error) { + return { + commit_hash: hash, + short_hash: hash.substring(0, 7), + status: 'failed', + error: (resultData.message as string) || (resultData.error as string) || 'Unknown error', + }; + } else { + // Get commit metadata + const showResult = spawnSync( + 'git', + ['show', '--no-patch', '--format=%an|||%s', hash], + { cwd: projectPath, encoding: 'utf-8' }, + ); + + let author = 'Unknown'; + let message = ''; + if (showResult.status === 0 && showResult.stdout) { + const [a, m] = showResult.stdout.trim().split('|||'); + author = a || 'Unknown'; + message = m || ''; + } + + return { + commit_hash: (resultData.commit_hash as string) || hash, + short_hash: ((resultData.commit_hash as string) || hash).substring(0, 7), + status: 'success', + author, + message, + metrics: resultData.metrics as Record, + savedTo: resultData.savedTo as string | undefined, + }; + } + } catch (evalError) { + return { + commit_hash: hash, + short_hash: hash.substring(0, 7), + status: 'failed', + error: evalError instanceof Error ? evalError.message : String(evalError), + }; + } + })); + + // Execute all evaluations with concurrency limit and wait for all to complete + const settledResults = await Promise.allSettled(evaluationTasks); + + // Process results + for (const settled of settledResults) { + if (settled.status === 'fulfilled') { + const result = settled.value; + results.push(result); + if (result.status === 'success') { + successful++; + } else { + failed++; + } + } else { + // Promise rejected (unexpected error) + failed++; + results.push({ + commit_hash: 'unknown', + short_hash: 'unknown', + status: 'failed', + error: settled.reason instanceof Error ? settled.reason.message : String(settled.reason), + }); + } + } + + // ======================================================================== + // STEP 4: Calculate Summary + // ======================================================================== + context.logger.info('Step 4: Calculating summary...'); + + const successfulResults = results.filter((r) => r.status === 'success' && r.metrics); + const summary: Record = {}; + + if (successfulResults.length > 0) { + // Collect all metric keys + const metricKeys = new Set(); + for (const r of successfulResults) { + if (r.metrics) { + Object.keys(r.metrics).forEach((k) => metricKeys.add(k)); + } + } + + // Calculate averages + for (const key of metricKeys) { + const values = successfulResults + .map((r) => r.metrics?.[key]) + .filter((v): v is number => typeof v === 'number'); + + if (values.length > 0) { + summary[`average${key.charAt(0).toUpperCase() + key.slice(1)}`] = + Math.round((values.reduce((a, b) => a + b, 0) / values.length) * 100) / 100; + } + } + } + + // ======================================================================== + // STEP 5: Build Response + // ======================================================================== + const totalDurationMs = Date.now() - startTime; + context.logger.info(`batch_evaluate completed in ${totalDurationMs}ms`); + context.logger.info(`Results: ${successful} successful, ${failed} failed`); + context.logger.info('=== batch_evaluate COMPLETE ==='); + + const response = { + success: true, + total: commitHashes.length, + successful, + failed, + results, + summary: Object.keys(summary).length > 0 ? summary : null, + timing: { + totalDurationMs, + averagePerCommitMs: Math.round(totalDurationMs / commitHashes.length), + }, + }; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + const totalDuration = Date.now() - startTime; + context.logger.error(`batch_evaluate failed after ${totalDuration}ms: ${error}`); + + const classified = classifyError(error); + return createErrorResponse(classified.type, classified.message, { + ...classified.details, + commits, + durationMs: totalDuration, + suggestion: classified.suggestion, + }); + } +}; + +// ============================================================================ +// COMPARISON TOOLS +// ============================================================================ + +/** + * Metric direction - determines if higher or lower values are better + */ +const METRIC_DIRECTION: Record = { + codeQuality: 'higher', + quality: 'higher', + testCoverage: 'higher', + functionalImpact: 'higher', + complexity: 'lower', // Lower complexity is better + technicalDebt: 'lower', // Lower debt is better + codeComplexity: 'lower', // Lower complexity is better + technicalDebtHours: 'lower', // Lower debt hours is better + commitScore: 'higher', +}; + +/** + * Extract and aggregate metrics from all agents in an evaluation + * Metrics are stored inside each agent, so we need to collect and average them + */ +function extractAggregatedMetrics(data: Record): Record { + const agents = (data.agents as Array>) || []; + const metricsCollector: Record = {}; + + // Collect all metrics from all agents + for (const agent of agents) { + const agentMetrics = (agent.metrics as Record) || {}; + for (const [key, value] of Object.entries(agentMetrics)) { + if (typeof value === 'number') { + if (!metricsCollector[key]) metricsCollector[key] = []; + metricsCollector[key].push(value); + } + } + } + + // Average each metric + const result: Record = {}; + for (const [key, values] of Object.entries(metricsCollector)) { + result[key] = Math.round((values.reduce((a, b) => a + b, 0) / values.length) * 100) / 100; + } + + return result; +} + +/** + * Handler: compare_commits + * Compare metrics between two commits to show improvement or regression + */ +export const handleCompareCommits: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== compare_commits START ==='); + + const { commit_a, commit_b, repo_path } = args as { + commit_a: string; + commit_b: string; + repo_path?: string; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + + context.logger.info(`Comparing commits: ${commit_a} vs ${commit_b}`); + + try { + // ======================================================================== + // STEP 1: Input Validation + // ======================================================================== + context.logger.info('Step 1: Validating inputs...'); + + if (!commit_a || commit_a.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'commit_a is required', + { hint: 'Provide the baseline commit hash.' }, + ); + } + + if (!commit_b || commit_b.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'commit_b is required', + { hint: 'Provide the commit hash to compare against baseline.' }, + ); + } + + // Check for same commit + if (commit_a.toLowerCase() === commit_b.toLowerCase()) { + return createErrorResponse( + 'Invalid comparison', + 'Cannot compare a commit to itself', + { hint: 'Provide two different commit hashes.' }, + ); + } + + // ======================================================================== + // STEP 2: Check evaluation directory exists + // ======================================================================== + context.logger.info('Step 2: Checking evaluation directory...'); + + if (!fs.existsSync(evalRoot)) { + return createErrorResponse( + 'No evaluations found', + 'The .evaluated-commits directory does not exist.', + { + evaluationRoot: evalRoot, + hint: 'Run: codewave evaluate --save to create evaluations first.', + }, + ); + } + + // ======================================================================== + // STEP 3: Find evaluation directories for both commits + // ======================================================================== + context.logger.info(`Step 3: Finding evaluation directories...`); + + // Find commit_a with exact match priority + const resultA = findEvaluationDirectory(evalRoot, commit_a); + if (!resultA.found) { + if (resultA.error === 'not_found') { + return createErrorResponse( + 'Evaluation not found', + `No evaluation found for commit '${commit_a}'.`, + { + commit: commit_a, + hint: `Run: codewave evaluate ${commit_a} --save to evaluate this commit first.`, + }, + ); + } + // resultA.error === 'ambiguous' + return createErrorResponse( + 'Ambiguous commit hash', + `Multiple evaluations match '${commit_a}'.`, + { + commit: commit_a, + matchingCommits: resultA.matches, + hint: 'Provide a longer commit hash to uniquely identify the evaluation.', + }, + ); + } + + // Find commit_b with exact match priority + const resultB = findEvaluationDirectory(evalRoot, commit_b); + if (!resultB.found) { + if (resultB.error === 'not_found') { + return createErrorResponse( + 'Evaluation not found', + `No evaluation found for commit '${commit_b}'.`, + { + commit: commit_b, + hint: `Run: codewave evaluate ${commit_b} --save to evaluate this commit first.`, + }, + ); + } + // resultB.error === 'ambiguous' + return createErrorResponse( + 'Ambiguous commit hash', + `Multiple evaluations match '${commit_b}'.`, + { + commit: commit_b, + matchingCommits: resultB.matches, + hint: 'Provide a longer commit hash to uniquely identify the evaluation.', + }, + ); + } + + const dirA = resultA.dir; + const dirB = resultB.dir; + + // Check if same evaluation (after resolving) + if (dirA === dirB) { + return createErrorResponse( + 'Invalid comparison', + 'Both hashes resolve to the same commit.', + { resolvedHash: dirA, hint: 'Provide two different commits.' }, + ); + } + + // ======================================================================== + // STEP 4: Load results.json for both commits + // ======================================================================== + context.logger.info('Step 4: Loading evaluation results...'); + + const resultsPathA = path.join(evalRoot, dirA, 'results.json'); + const resultsPathB = path.join(evalRoot, dirB, 'results.json'); + + if (!fs.existsSync(resultsPathA)) { + return createErrorResponse( + 'Results file missing', + `Evaluation directory exists but results.json is missing for commit_a.`, + { commit: dirA, hint: `Re-run: codewave evaluate ${commit_a} --save` }, + ); + } + + if (!fs.existsSync(resultsPathB)) { + return createErrorResponse( + 'Results file missing', + `Evaluation directory exists but results.json is missing for commit_b.`, + { commit: dirB, hint: `Re-run: codewave evaluate ${commit_b} --save` }, + ); + } + + let dataA: Record; + let dataB: Record; + + try { + dataA = JSON.parse(fs.readFileSync(resultsPathA, 'utf-8')); + } catch (parseError) { + return createErrorResponse( + 'Results file corrupted', + 'Failed to parse results.json for commit_a.', + { commit: dirA, hint: `Re-run: codewave evaluate ${commit_a} --save` }, + ); + } + + try { + dataB = JSON.parse(fs.readFileSync(resultsPathB, 'utf-8')); + } catch (parseError) { + return createErrorResponse( + 'Results file corrupted', + 'Failed to parse results.json for commit_b.', + { commit: dirB, hint: `Re-run: codewave evaluate ${commit_b} --save` }, + ); + } + + // ======================================================================== + // STEP 5: Extract metadata and metrics + // ======================================================================== + context.logger.info('Step 5: Extracting metrics...'); + + const metadataA = dataA.metadata as Record || {}; + const metadataB = dataB.metadata as Record || {}; + // Extract and aggregate metrics from all agents (metrics are stored per-agent, not at top level) + const metricsA = extractAggregatedMetrics(dataA); + const metricsB = extractAggregatedMetrics(dataB); + + // Build commit info objects + const commitInfoA = { + hash: dirA, + shortHash: dirA.substring(0, 7), + author: (metadataA.commitAuthor as string) || 'Unknown', + message: (metadataA.commitMessage as string) || '', + date: (metadataA.commitDate as string) || '', + }; + + const commitInfoB = { + hash: dirB, + shortHash: dirB.substring(0, 7), + author: (metadataB.commitAuthor as string) || 'Unknown', + message: (metadataB.commitMessage as string) || '', + date: (metadataB.commitDate as string) || '', + }; + + // ======================================================================== + // STEP 6: Calculate comparison + // ======================================================================== + context.logger.info('Step 6: Calculating comparison...'); + + // Get all metric keys from both + const allMetricKeys = new Set([...Object.keys(metricsA), ...Object.keys(metricsB)]); + + const comparison: Record = {}; + + const improved: string[] = []; + const regressed: string[] = []; + const unchanged: string[] = []; + + for (const key of allMetricKeys) { + const before = metricsA[key] ?? null; + const after = metricsB[key] ?? null; + + if (before === null || after === null) { + comparison[key] = { + before, + after, + diff: null, + percentChange: null, + trend: 'unknown', + }; + continue; + } + + const diff = Math.round((after - before) * 100) / 100; + const percentChange = before !== 0 + ? Math.round(((after - before) / before) * 1000) / 10 + : null; + + // Determine trend based on metric direction + const direction = METRIC_DIRECTION[key] || 'higher'; + let trend: 'improved' | 'regressed' | 'unchanged'; + + if (Math.abs(diff) < 0.5) { + trend = 'unchanged'; + unchanged.push(key); + } else if (direction === 'higher') { + trend = diff > 0 ? 'improved' : 'regressed'; + (diff > 0 ? improved : regressed).push(key); + } else { + // Lower is better + trend = diff < 0 ? 'improved' : 'regressed'; + (diff < 0 ? improved : regressed).push(key); + } + + comparison[key] = { + before, + after, + diff, + percentChange, + trend, + }; + } + + // Determine overall trend + let overallTrend: 'improved' | 'regressed' | 'mixed' | 'unchanged'; + if (improved.length > 0 && regressed.length === 0) { + overallTrend = 'improved'; + } else if (regressed.length > 0 && improved.length === 0) { + overallTrend = 'regressed'; + } else if (improved.length > 0 && regressed.length > 0) { + overallTrend = 'mixed'; + } else { + overallTrend = 'unchanged'; + } + + // ======================================================================== + // STEP 7: Build response + // ======================================================================== + const durationMs = Date.now() - startTime; + context.logger.info(`compare_commits completed in ${durationMs}ms`); + context.logger.info('=== compare_commits COMPLETE ==='); + + const response = { + success: true, + commit_a: commitInfoA, + commit_b: commitInfoB, + comparison, + summary: { + improved, + regressed, + unchanged, + overallTrend, + }, + timing: { durationMs }, + }; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + const durationMs = Date.now() - startTime; + context.logger.error(`compare_commits failed after ${durationMs}ms: ${error}`); + + return createErrorResponse( + 'Comparison failed', + error instanceof Error ? error.message : String(error), + { + commit_a, + commit_b, + hint: 'An unexpected error occurred while comparing commits.', + }, + ); + } +}; + +// ============================================================================ +// TEAM ANALYTICS TOOLS +// ============================================================================ + +const DEFAULT_TEAM_STATS_LIMIT = 10; + +/** + * Handler: get_team_stats + * Get aggregated code quality statistics for all authors in a repository + */ +export const handleGetTeamStats: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== get_team_stats START ==='); + + const { repo_path, since, limit } = args as { + repo_path?: string; + since?: string; + limit?: number; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + const effectiveLimit = limit ?? DEFAULT_TEAM_STATS_LIMIT; + + context.logger.info(`Getting team stats from: ${evalRoot}`); + context.logger.info(`Filters: since=${since || 'none'}, limit=${effectiveLimit}`); + + // Validate limit range + if (limit !== undefined) { + if (limit < 1) { + return createErrorResponse( + 'Validation failed', + 'limit must be at least 1', + { value: limit, hint: 'Default is 10 if not specified.' }, + ); + } + if (limit > MAX_EVALUATION_LIMIT) { + return createErrorResponse( + 'Validation failed', + `limit cannot exceed ${MAX_EVALUATION_LIMIT}`, + { value: limit, hint: `Maximum allowed is ${MAX_EVALUATION_LIMIT} authors.` }, + ); + } + } + + try { + // ======================================================================== + // STEP 1: Check evaluation directory exists + // ======================================================================== + context.logger.info('Step 1: Checking evaluation directory...'); + + if (!fs.existsSync(evalRoot)) { + return createErrorResponse( + 'No evaluations found', + 'The .evaluated-commits directory does not exist.', + { + evaluationRoot: evalRoot, + hint: 'Run: codewave evaluate HEAD --save to create evaluations first.', + }, + ); + } + + // ======================================================================== + // STEP 2: Scan all evaluations and group by author + // ======================================================================== + context.logger.info('Step 2: Scanning evaluations...'); + + // Parse since date if provided + let sinceDate: Date | undefined; + if (since) { + sinceDate = new Date(since); + if (isNaN(sinceDate.getTime())) { + return createErrorResponse( + 'Invalid date format', + `Could not parse date: ${since}`, + { hint: 'Use ISO format: 2024-01-01' }, + ); + } + } + + // Group evaluations by author + const authorData: Map; + }>; + }> = new Map(); + + const dirs = fs.readdirSync(evalRoot).filter((d) => { + const fullPath = path.join(evalRoot, d); + return fs.statSync(fullPath).isDirectory() && !d.startsWith('.'); + }); + + let totalEvaluations = 0; + const allDates: Date[] = []; + + for (const dir of dirs) { + const resultsPath = path.join(evalRoot, dir, 'results.json'); + if (!fs.existsSync(resultsPath)) continue; + + try { + const content = fs.readFileSync(resultsPath, 'utf-8'); + const data = JSON.parse(content) as Record; + + const metadata = (data.metadata as Record) || {}; + const author = (metadata.commitAuthor as string) || 'Unknown'; + const commitDate = (metadata.commitDate as string) || (data.timestamp as string) || ''; + const commitMessage = (metadata.commitMessage as string) || ''; + + // Apply date filter + if (sinceDate && commitDate) { + const evalDate = new Date(commitDate); + if (!isNaN(evalDate.getTime()) && evalDate < sinceDate) { + continue; // Skip evaluations before the since date + } + } + + // Track dates + if (commitDate) { + const d = new Date(commitDate); + if (!isNaN(d.getTime())) allDates.push(d); + } + + // Extract aggregated metrics + const metrics = extractAggregatedMetrics(data); + + // Add to author data + if (!authorData.has(author)) { + authorData.set(author, { commits: [] }); + } + authorData.get(author)!.commits.push({ + hash: dir, + message: commitMessage, + date: commitDate, + metrics, + }); + + totalEvaluations++; + } catch { + context.logger.warn(`Skipping malformed results.json in ${dir}`); + } + } + + context.logger.info(`Found ${totalEvaluations} evaluations from ${authorData.size} authors`); + + // ======================================================================== + // STEP 3: Calculate stats for each author + // ======================================================================== + context.logger.info('Step 3: Calculating author stats...'); + + interface AuthorStats { + name: string; + commitCount: number; + averageMetrics: Record; + trend: 'improving' | 'declining' | 'stable' | 'insufficient_data'; + } + + const authorStats: AuthorStats[] = []; + + for (const [author, data] of authorData.entries()) { + const commits = data.commits; + const commitCount = commits.length; + + // Calculate average metrics + const metricsCollector: Record = {}; + for (const commit of commits) { + for (const [key, value] of Object.entries(commit.metrics)) { + if (typeof value === 'number') { + if (!metricsCollector[key]) metricsCollector[key] = []; + metricsCollector[key].push(value); + } + } + } + + const averageMetrics: Record = {}; + for (const [key, values] of Object.entries(metricsCollector)) { + averageMetrics[key] = Math.round((values.reduce((a, b) => a + b, 0) / values.length) * 100) / 100; + } + + // Calculate trend (compare first half to second half) + let trend: 'improving' | 'declining' | 'stable' | 'insufficient_data' = 'insufficient_data'; + if (commitCount >= 4) { + // Sort by date + const sortedCommits = [...commits].sort((a, b) => + new Date(a.date).getTime() - new Date(b.date).getTime() + ); + + const half = Math.floor(sortedCommits.length / 2); + const olderCommits = sortedCommits.slice(0, half); + const recentCommits = sortedCommits.slice(half); + + // Calculate average codeQuality for each half + const olderQuality = olderCommits + .filter((c) => c.metrics.codeQuality !== undefined) + .map((c) => c.metrics.codeQuality); + const recentQuality = recentCommits + .filter((c) => c.metrics.codeQuality !== undefined) + .map((c) => c.metrics.codeQuality); + + if (olderQuality.length > 0 && recentQuality.length > 0) { + const olderAvg = olderQuality.reduce((a, b) => a + b, 0) / olderQuality.length; + const recentAvg = recentQuality.reduce((a, b) => a + b, 0) / recentQuality.length; + + if (recentAvg > olderAvg + 0.5) { + trend = 'improving'; + } else if (recentAvg < olderAvg - 0.5) { + trend = 'declining'; + } else { + trend = 'stable'; + } + } + } + + authorStats.push({ + name: author, + commitCount, + averageMetrics, + trend, + }); + } + + // ======================================================================== + // STEP 4: Sort and limit + // ======================================================================== + context.logger.info('Step 4: Sorting and limiting results...'); + + // Sort by commit count descending + authorStats.sort((a, b) => b.commitCount - a.commitCount); + + // Apply limit + const limitedStats = authorStats.slice(0, effectiveLimit); + + // ======================================================================== + // STEP 5: Calculate team averages + // ======================================================================== + context.logger.info('Step 5: Calculating team averages...'); + + const teamMetricsCollector: Record = {}; + for (const author of authorStats) { + for (const [key, value] of Object.entries(author.averageMetrics)) { + if (typeof value === 'number') { + if (!teamMetricsCollector[key]) teamMetricsCollector[key] = []; + teamMetricsCollector[key].push(value); + } + } + } + + const teamAverages: Record = {}; + for (const [key, values] of Object.entries(teamMetricsCollector)) { + teamAverages[key] = Math.round((values.reduce((a, b) => a + b, 0) / values.length) * 100) / 100; + } + + // ======================================================================== + // STEP 6: Calculate date range + // ======================================================================== + let dateRange: { earliest: string; latest: string } | null = null; + if (allDates.length > 0) { + allDates.sort((a, b) => a.getTime() - b.getTime()); + dateRange = { + earliest: allDates[0].toISOString().split('T')[0], + latest: allDates[allDates.length - 1].toISOString().split('T')[0], + }; + } + + // ======================================================================== + // STEP 7: Build response + // ======================================================================== + const durationMs = Date.now() - startTime; + context.logger.info(`get_team_stats completed in ${durationMs}ms`); + context.logger.info('=== get_team_stats COMPLETE ==='); + + const response = { + success: true, + totalAuthors: authorData.size, + totalEvaluations, + authors: limitedStats, + teamAverages, + dateRange, + filters: { + since: since || null, + limit: effectiveLimit, + }, + timing: { durationMs }, + }; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + const durationMs = Date.now() - startTime; + context.logger.error(`get_team_stats failed after ${durationMs}ms: ${error}`); + + return createErrorResponse( + 'Team stats failed', + error instanceof Error ? error.message : String(error), + { + hint: 'An unexpected error occurred while aggregating team statistics.', + }, + ); + } +}; + +// ============================================================================ +// EXPORT TOOLS +// ============================================================================ + +/** + * Handler: export_report + * Export evaluation report to a file in HTML, Markdown, or JSON format + */ +export const handleExportReport: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== export_report START ==='); + + const { commit_hash, output_path, format = 'html', repo_path } = args as { + commit_hash: string; + output_path: string; + format?: 'html' | 'md' | 'json'; + repo_path?: string; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + + context.logger.info(`Exporting report for commit: ${commit_hash}`); + context.logger.info(`Format: ${format}, output: ${output_path}`); + + try { + // ======================================================================== + // STEP 1: Validate inputs + // ======================================================================== + context.logger.info('Step 1: Validating inputs...'); + + if (!commit_hash || commit_hash.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'commit_hash is required', + { hint: 'Provide a commit hash to export. Use list_evaluations to find available commits.' }, + ); + } + + if (!output_path || output_path.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'output_path is required', + { hint: 'Provide a file path where the report will be saved.' }, + ); + } + + if (format && !VALID_EXPORT_FORMATS.includes(format as typeof VALID_EXPORT_FORMATS[number])) { + return createErrorResponse( + 'Validation failed', + `Invalid format value. Must be one of: ${VALID_EXPORT_FORMATS.join(', ')}`, + { value: format, hint: 'Use "html" (default), "md" (markdown), or "json".' }, + ); + } + + // Validate file extension matches format + const fileExt = path.extname(output_path).toLowerCase().slice(1); // Remove leading dot + const expectedExtensions: Record = { + html: ['html', 'htm'], + md: ['md', 'markdown'], + json: ['json'], + }; + const validExtensions = expectedExtensions[format] || []; + + if (fileExt && !validExtensions.includes(fileExt)) { + return createErrorResponse( + 'File extension mismatch', + `Output file extension (.${fileExt}) doesn't match format (${format}).`, + { + format, + extension: fileExt, + expectedExtensions: validExtensions, + hint: `Use one of: ${validExtensions.map(e => '.' + e).join(', ')} for ${format} format.`, + }, + ); + } + + // Validate output directory exists + const outputDir = path.dirname(output_path); + if (!fs.existsSync(outputDir)) { + return createErrorResponse( + 'Output directory not found', + `The directory "${outputDir}" does not exist.`, + { hint: 'Create the directory first or provide a valid path.' }, + ); + } + + // ======================================================================== + // STEP 2: Find evaluation directory + // ======================================================================== + context.logger.info('Step 2: Finding evaluation...'); + + if (!fs.existsSync(evalRoot)) { + return createErrorResponse( + 'No evaluations found', + 'The .evaluated-commits directory does not exist.', + { + evaluationRoot: evalRoot, + hint: 'Run: codewave evaluate HEAD --save to create evaluations first.', + }, + ); + } + + const result = findEvaluationDirectory(evalRoot, commit_hash); + + if (!result.found) { + if (result.error === 'ambiguous') { + return createErrorResponse( + 'Ambiguous commit hash', + `Multiple commits match "${commit_hash}".`, + { + matches: result.matches?.map((m: string) => m.substring(0, 7)), + hint: 'Provide more characters to uniquely identify the commit.', + }, + ); + } + return createErrorResponse( + 'Evaluation not found', + `No evaluation found for commit "${commit_hash}".`, + { + hint: 'Use list_evaluations to see available commits, or run: codewave evaluate --save', + }, + ); + } + + const targetDir = result.dir!; + const resultsPath = path.join(evalRoot, targetDir, 'results.json'); + + // ======================================================================== + // STEP 3: Load results.json + // ======================================================================== + context.logger.info('Step 3: Loading evaluation data...'); + + if (!fs.existsSync(resultsPath)) { + return createErrorResponse( + 'Results file missing', + 'Evaluation directory exists but results.json is missing.', + { + commit_hash, + directory: targetDir, + hint: 'The evaluation may be incomplete. Re-run: codewave evaluate ' + commit_hash + ' --save', + }, + ); + } + + let data: Record; + try { + const content = fs.readFileSync(resultsPath, 'utf-8'); + data = JSON.parse(content); + } catch (parseError) { + return createErrorResponse( + 'Results file corrupted', + 'Failed to parse results.json.', + { + commit_hash, + directory: targetDir, + originalError: parseError instanceof Error ? parseError.message : String(parseError), + hint: 'Re-run: codewave evaluate ' + commit_hash + ' --save', + }, + ); + } + + // Extract data from results.json + const metadata = (data.metadata as Record) || {}; + const agents = (data.agents as AgentResult[]) || []; + const developerOverview = (data.developerOverview as string) || ''; + + // ======================================================================== + // STEP 4: Generate report based on format + // ======================================================================== + context.logger.info(`Step 4: Generating ${format} report...`); + + let bytesWritten = 0; + const resolvedOutputPath = path.resolve(output_path); + + try { + switch (format) { + case 'html': { + generateEnhancedHtmlReport(agents, resolvedOutputPath, { + commitHash: (metadata.commitHash as string) || targetDir, + timestamp: (metadata.timestamp as string) || (data.timestamp as string) || new Date().toISOString(), + commitAuthor: metadata.commitAuthor as string, + commitMessage: metadata.commitMessage as string, + commitDate: metadata.commitDate as string, + developerOverview, + filesChanged: metadata.filesChanged as number, + insertions: metadata.insertions as number, + deletions: metadata.deletions as number, + }); + bytesWritten = fs.statSync(resolvedOutputPath).size; + break; + } + + case 'md': { + generateConversationTranscript(agents, resolvedOutputPath, { + commitHash: (metadata.commitHash as string) || targetDir, + timestamp: (metadata.timestamp as string) || (data.timestamp as string) || new Date().toISOString(), + }); + bytesWritten = fs.statSync(resolvedOutputPath).size; + break; + } + + case 'json': { + const jsonContent = JSON.stringify(data, null, 2); + fs.writeFileSync(resolvedOutputPath, jsonContent, 'utf-8'); + bytesWritten = Buffer.byteLength(jsonContent, 'utf-8'); + break; + } + } + } catch (writeError) { + return createErrorResponse( + 'Export failed', + 'Failed to write report to output path.', + { + output_path: resolvedOutputPath, + format, + originalError: writeError instanceof Error ? writeError.message : String(writeError), + hint: 'Check file permissions and disk space.', + }, + ); + } + + // ======================================================================== + // STEP 5: Return success + // ======================================================================== + const durationMs = Date.now() - startTime; + context.logger.info(`export_report completed in ${durationMs}ms`); + context.logger.info('=== export_report COMPLETE ==='); + + const response = { + success: true, + commit: { + hash: targetDir, + shortHash: targetDir.substring(0, 7), + author: (metadata.commitAuthor as string) || 'Unknown', + message: (metadata.commitMessage as string) || '', + }, + export: { + format, + outputPath: resolvedOutputPath, + bytesWritten, + bytesHuman: bytesWritten > 1024 + ? `${(bytesWritten / 1024).toFixed(1)} KB` + : `${bytesWritten} bytes`, + }, + timing: { durationMs }, + }; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + const durationMs = Date.now() - startTime; + context.logger.error(`export_report failed after ${durationMs}ms: ${error}`); + + return createErrorResponse( + 'Export failed', + error instanceof Error ? error.message : String(error), + { + commit_hash, + output_path, + format, + hint: 'An unexpected error occurred during export.', + }, + ); + } +}; + +// ============================================================================ +// CONFIGURATION TOOLS +// ============================================================================ + +/** + * Handler: check_config + * Verifies CodeWave configuration exists and is valid + */ +export const handleCheckConfig: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== check_config START ==='); + + const { repo_path } = args as { repo_path?: string }; + const projectPath = repo_path || context.projectPath; + const configFilePath = path.join(projectPath, '.codewave.config.json'); + + context.logger.info(`Checking config at: ${configFilePath}`); + + try { + // Step 1: Check if config file exists + if (!configExists(configFilePath)) { + context.logger.info('Config file not found'); + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + success: true, + config: { + exists: false, + valid: false, + path: configFilePath, + }, + hint: 'No .codewave.config.json found. Run: codewave config --init', + }, + null, + 2, + ), + }, + ], + }; + } + + // Step 2: Try to load and parse config + let config: AppConfig | null; + try { + config = loadConfig(configFilePath); + } catch (parseError) { + context.logger.error(`Config parse error: ${parseError}`); + return createErrorResponse( + 'Invalid configuration', + 'Config file exists but has invalid JSON syntax.', + { + path: configFilePath, + originalError: parseError instanceof Error ? parseError.message : String(parseError), + hint: 'Check the config file for JSON syntax errors (missing commas, brackets, etc.)', + }, + ); + } + + if (!config) { + return createErrorResponse( + 'Configuration load failed', + 'Config file exists but could not be loaded.', + { + path: configFilePath, + hint: 'Ensure the config file is valid JSON and follows the expected schema.', + }, + ); + } + + // Step 3: Verify API key is configured for the active provider + const activeProvider = config.llm.provider; + const activeApiKey = config.apiKeys[activeProvider]; + const apiKeyLower = activeApiKey?.toLowerCase() || ''; + const apiKeyConfigured = !!(activeApiKey && activeApiKey.length > 0 && !apiKeyLower.includes('your_') && !apiKeyLower.includes('xxx') && !apiKeyLower.includes('placeholder')); + + // Step 4: Build response + const durationMs = Date.now() - startTime; + context.logger.info(`Config check completed in ${durationMs}ms`); + context.logger.info('=== check_config COMPLETE ==='); + + const response = { + success: true, + config: { + exists: true, + valid: true, + path: configFilePath, + provider: activeProvider, + model: config.llm.model, + enabledAgents: config.agents.enabled, + apiKeyConfigured, + depthMode: config.agents.depthMode || 'normal', + temperature: config.llm.temperature, + maxTokens: config.llm.maxTokens, + }, + timing: { durationMs }, + }; + + // Add warning if API key not configured + if (!apiKeyConfigured) { + (response as Record).warning = `No valid API key found for provider '${activeProvider}'. Add it to apiKeys.${activeProvider} in your config.`; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + context.logger.error(`check_config failed: ${error}`); + return createErrorResponse( + 'Configuration check failed', + error instanceof Error ? error.message : String(error), + { + path: configFilePath, + hint: 'An unexpected error occurred while checking the configuration.', + }, + ); + } +}; + +// ============================================================================ +// REPORT & DATA RETRIEVAL TOOLS +// ============================================================================ + +/** + * Handler: list_evaluations + * Lists all commit evaluations from .evaluated-commits folder + */ +export const handleListEvaluations: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== list_evaluations START ==='); + + const { repo_path, author, since, limit } = args as { + repo_path?: string; + author?: string; + since?: string; + limit?: number; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + + // Apply limit constraints + const requestedLimit = limit ?? DEFAULT_EVALUATION_LIMIT; + const effectiveLimit = Math.min(Math.max(1, requestedLimit), MAX_EVALUATION_LIMIT); + + context.logger.info(`Listing evaluations from: ${evalRoot}`); + context.logger.info(`Filters: author=${author || 'none'}, since=${since || 'none'}, limit=${effectiveLimit}`); + + try { + // Step 1: Check if evaluation directory exists + if (!fs.existsSync(evalRoot)) { + context.logger.info('Evaluation directory not found'); + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + success: true, + evaluationRoot: evalRoot, + total: 0, + returned: 0, + filters: { author, since, limit: effectiveLimit }, + evaluations: [], + hint: 'No evaluations found. Run: codewave evaluate HEAD', + }, + null, + 2, + ), + }, + ], + }; + } + + // Step 2: Try to read index.json first (fast path) + const indexJsonPath = path.join(evalRoot, 'index.json'); + let evaluations: Array<{ + commitHash: string; + shortHash: string; + author: string; + message: string; + date: string; + directory: string; + evaluatedAt: string; + metrics?: Record; + }> = []; + + if (fs.existsSync(indexJsonPath)) { + context.logger.info('Reading from index.json (fast path)'); + try { + const indexContent = fs.readFileSync(indexJsonPath, 'utf-8'); + const indexData = JSON.parse(indexContent) as Array<{ + directory: string; + commitHash: string; + commitAuthor: string; + commitMessage: string; + commitDate: string; + lastEvaluated: string; + metrics?: Record; + }>; + + evaluations = indexData.map((item) => ({ + commitHash: item.commitHash || item.directory, + shortHash: (item.commitHash || item.directory).substring(0, 7), + author: item.commitAuthor || 'Unknown', + message: item.commitMessage || '', + date: item.commitDate || '', + directory: item.directory, + evaluatedAt: item.lastEvaluated || '', + metrics: item.metrics, + })); + } catch (parseError) { + context.logger.warn(`Failed to parse index.json, falling back to directory scan: ${parseError}`); + } + } + + // Step 3: If no index.json or parsing failed, scan directories + if (evaluations.length === 0) { + context.logger.info('Scanning directories for results.json files'); + const dirs = fs.readdirSync(evalRoot); + + for (const dir of dirs) { + const resultsPath = path.join(evalRoot, dir, 'results.json'); + if (!fs.existsSync(resultsPath)) continue; + + try { + const content = fs.readFileSync(resultsPath, 'utf-8'); + const data = JSON.parse(content); + + evaluations.push({ + commitHash: data.metadata?.commitHash || dir, + shortHash: (data.metadata?.commitHash || dir).substring(0, 7), + author: data.metadata?.commitAuthor || 'Unknown', + message: data.metadata?.commitMessage || '', + date: data.metadata?.commitDate || data.timestamp || '', + directory: dir, + evaluatedAt: data.timestamp || '', + metrics: data.metrics, + }); + } catch { + // Skip malformed files + context.logger.warn(`Skipping malformed results.json in ${dir}`); + } + } + } + + const totalBeforeFilter = evaluations.length; + context.logger.info(`Found ${totalBeforeFilter} total evaluations`); + + // Step 4: Apply filters + let filtered = evaluations; + + // Filter by author (case-insensitive partial match) + if (author) { + const authorLower = author.toLowerCase(); + filtered = filtered.filter((e) => e.author.toLowerCase().includes(authorLower)); + context.logger.info(`After author filter: ${filtered.length} evaluations`); + } + + // Filter by date + if (since) { + const sinceDate = new Date(since); + if (!isNaN(sinceDate.getTime())) { + filtered = filtered.filter((e) => { + const evalDate = new Date(e.date || e.evaluatedAt); + return !isNaN(evalDate.getTime()) && evalDate >= sinceDate; + }); + context.logger.info(`After date filter: ${filtered.length} evaluations`); + } else { + context.logger.warn(`Invalid date format for 'since': ${since}`); + } + } + + // Step 5: Sort by commit date descending + filtered.sort((a, b) => { + const dateA = new Date(a.date || a.evaluatedAt); + const dateB = new Date(b.date || b.evaluatedAt); + return dateB.getTime() - dateA.getTime(); + }); + + // Step 6: Apply limit + const totalAfterFilter = filtered.length; + const limitedResults = filtered.slice(0, effectiveLimit); + + // Step 7: Build response + const durationMs = Date.now() - startTime; + context.logger.info(`list_evaluations completed in ${durationMs}ms`); + context.logger.info('=== list_evaluations COMPLETE ==='); + + const response = { + success: true, + evaluationRoot: evalRoot, + total: totalAfterFilter, + returned: limitedResults.length, + filters: { + author: author || null, + since: since || null, + limit: effectiveLimit, + }, + evaluations: limitedResults, + timing: { durationMs }, + }; + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + context.logger.error(`list_evaluations failed: ${error}`); + return createErrorResponse( + 'Failed to list evaluations', + error instanceof Error ? error.message : String(error), + { + evaluationRoot: evalRoot, + hint: 'An unexpected error occurred while listing evaluations.', + }, + ); + } +}; + +/** + * Handler: get_report + * Retrieves detailed evaluation report for a specific commit + */ +export const handleGetReport: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== get_report START ==='); + + const { commit_hash, repo_path, format = 'summary' } = args as { + commit_hash: string; + repo_path?: string; + format?: 'full' | 'summary' | 'metrics_only'; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + + context.logger.info(`Getting report for commit: ${commit_hash}`); + context.logger.info(`Format: ${format}, evalRoot: ${evalRoot}`); + + // Validate commit_hash + if (!commit_hash || commit_hash.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'commit_hash is required', + { hint: 'Provide a commit hash to retrieve the report for. Use list_evaluations to find available commits.' }, + ); + } + + // Validate format + if (format && !VALID_FORMATS.includes(format as typeof VALID_FORMATS[number])) { + return createErrorResponse( + 'Validation failed', + `Invalid format value. Must be one of: ${VALID_FORMATS.join(', ')}`, + { value: format, hint: 'Use "summary" (default), "full", or "metrics_only".' }, + ); + } + + try { + // Step 1: Check if evaluation directory exists + if (!fs.existsSync(evalRoot)) { + return createErrorResponse( + 'No evaluations found', + 'The .evaluated-commits directory does not exist.', + { + evaluationRoot: evalRoot, + hint: 'Run: codewave evaluate HEAD to create your first evaluation.', + }, + ); + } + + // Step 2: Find matching directory with exact match priority + const result = findEvaluationDirectory(evalRoot, commit_hash); + + if (!result.found) { + if (result.error === 'not_found') { + context.logger.info(`No evaluation found for commit: ${commit_hash}`); + return createErrorResponse( + 'Evaluation not found', + `No evaluation found for commit '${commit_hash}'.`, + { + commit_hash, + hint: 'Run: codewave evaluate ' + commit_hash + ' to evaluate this commit, or use list_evaluations to see available reports.', + }, + ); + } + // result.error === 'ambiguous' + context.logger.warn(`Ambiguous hash: ${commit_hash} matches multiple directories`); + return createErrorResponse( + 'Ambiguous commit hash', + `Multiple evaluations match '${commit_hash}'. Provide more characters.`, + { + commit_hash, + matchingCommits: result.matches, + hint: 'Use a longer commit hash to uniquely identify the evaluation.', + }, + ); + } + + const targetDir = result.dir; + const resultsPath = path.join(evalRoot, targetDir, 'results.json'); + + // Step 3: Read results.json + if (!fs.existsSync(resultsPath)) { + return createErrorResponse( + 'Results file missing', + `Evaluation directory exists but results.json is missing.`, + { + commit_hash, + directory: targetDir, + hint: 'The evaluation may be incomplete. Re-run: codewave evaluate ' + commit_hash, + }, + ); + } + + let data: Record; + try { + const content = fs.readFileSync(resultsPath, 'utf-8'); + data = JSON.parse(content); + } catch (parseError) { + return createErrorResponse( + 'Results file corrupted', + 'Failed to parse results.json.', + { + commit_hash, + directory: targetDir, + originalError: parseError instanceof Error ? parseError.message : String(parseError), + hint: 'Re-run: codewave evaluate ' + commit_hash, + }, + ); + } + + // Step 4: Extract data based on format + const metadata = data.metadata as Record || {}; + const metrics = data.metrics as Record || {}; + const agentResults = data.agentResults as Array> || []; + const developerOverview = data.developerOverview as string || ''; + + // Extract commit hash info (used in all format responses) + const fullHash = (metadata.commitHash as string) || targetDir; + const shortHash = fullHash.substring(0, 7); + + // Extract top concerns from agent results + const topConcerns: string[] = []; + for (const agent of agentResults) { + const concerns = agent.concerns as string[] || []; + topConcerns.push(...concerns.slice(0, 2)); + } + + const durationMs = Date.now() - startTime; + context.logger.info(`get_report completed in ${durationMs}ms`); + context.logger.info('=== get_report COMPLETE ==='); + + // Build response based on format + let response: Record; + + if (format === 'metrics_only') { + response = { + success: true, + commitHash: fullHash, + shortHash, + metrics, + timing: { durationMs }, + }; + } else if (format === 'summary') { + response = { + success: true, + commitHash: fullHash, + shortHash, + author: metadata.commitAuthor || 'Unknown', + message: metadata.commitMessage || '', + date: metadata.commitDate || '', + metrics, + developerOverview: developerOverview || null, + topConcerns: topConcerns.slice(0, 5), + agentCount: agentResults.length, + evaluatedAt: data.timestamp || '', + timing: { durationMs }, + }; + } else { + // format === 'full' + response = { + success: true, + commitHash: fullHash, + shortHash, + author: metadata.commitAuthor || 'Unknown', + message: metadata.commitMessage || '', + date: metadata.commitDate || '', + metrics, + developerOverview: developerOverview || null, + topConcerns, + agentCount: agentResults.length, + agentResults: agentResults.map((agent) => ({ + agentName: agent.agentName || agent.agentRole, + summary: agent.summary, + concerns: agent.concerns || [], + metrics: agent.metrics || {}, + })), + evaluatedAt: data.timestamp || '', + directory: targetDir, + timing: { durationMs }, + }; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + context.logger.error(`get_report failed: ${error}`); + return createErrorResponse( + 'Failed to get report', + error instanceof Error ? error.message : String(error), + { + commit_hash, + hint: 'An unexpected error occurred while retrieving the report.', + }, + ); + } +}; + +// ============================================================================ +// ANALYTICS TOOLS +// ============================================================================ + +/** + * Handler: get_author_stats + * Get aggregated code quality statistics for a developer + */ +export const handleGetAuthorStats: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== get_author_stats START ==='); + + const { author, repo_path, since, limit } = args as { + author: string; + repo_path?: string; + since?: string; + limit?: number; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + + // Apply limit + const effectiveLimit = limit ?? DEFAULT_AUTHOR_STATS_LIMIT; + + context.logger.info(`Getting stats for author: ${author}`); + context.logger.info(`evalRoot: ${evalRoot}, since: ${since || 'none'}, limit: ${effectiveLimit}`); + + // Validate author parameter + if (!author || author.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'author is required', + { hint: 'Provide an author name. Use list_evaluations to see available authors.' }, + ); + } + + try { + // Step 1: Check if evaluation directory exists + if (!fs.existsSync(evalRoot)) { + return createErrorResponse( + 'No evaluations found', + 'The .evaluated-commits directory does not exist.', + { + evaluationRoot: evalRoot, + hint: 'Run: codewave evaluate HEAD to create your first evaluation.', + }, + ); + } + + // Step 2: Aggregate author stats + const sinceDate = since ? new Date(since) : undefined; + if (since && sinceDate && isNaN(sinceDate.getTime())) { + return createErrorResponse( + 'Invalid date format', + `Could not parse date: ${since}`, + { hint: 'Use ISO format: 2024-01-01' }, + ); + } + + context.logger.info('Aggregating author stats...'); + const authorData = await AuthorStatsAggregatorService.aggregateAuthorStats(evalRoot, { + targetAuthor: author, + sinceDate, + countLimit: effectiveLimit, + }); + + // Step 3: Check if author found + if (authorData.size === 0) { + context.logger.info(`No evaluations found for author: ${author}`); + return createErrorResponse( + 'Author not found', + `No evaluations found for author '${author}'.`, + { + author, + hint: 'Check spelling (partial match is supported) or use list_evaluations to see available authors.', + }, + ); + } + + // Step 4: Get the matched author name and evaluations + // Since we use partial match, get the actual author name from the map + const matchedAuthors = Array.from(authorData.keys()); + const matchedAuthor = matchedAuthors[0]; + const evaluations = authorData.get(matchedAuthor) || []; + + if (evaluations.length === 0) { + return createErrorResponse( + 'No evaluations found', + `Author '${matchedAuthor}' found but has no evaluations.`, + { hint: 'The author may have been filtered out by date. Try without the since parameter.' }, + ); + } + + context.logger.info(`Found ${evaluations.length} evaluations for ${matchedAuthor}`); + + // Step 5: Analyze author + const analysis = AuthorStatsAggregatorService.analyzeAuthor(evaluations); + + // Step 6: Calculate date range + const dates = evaluations + .map((e: Record) => new Date((e.metadata as Record)?.commitDate || (e.timestamp as string))) + .filter((d: Date) => !isNaN(d.getTime())) + .sort((a: Date, b: Date) => a.getTime() - b.getTime()); + + const dateRange = dates.length > 0 ? { + earliest: dates[0].toISOString().split('T')[0], + latest: dates[dates.length - 1].toISOString().split('T')[0], + } : null; + + // Step 7: Calculate trend (compare recent vs older commits) + let trend: 'improving' | 'declining' | 'stable' = 'stable'; + if (evaluations.length >= 4) { + const half = Math.floor(evaluations.length / 2); + const recentEvals = evaluations.slice(0, half); + const olderEvals = evaluations.slice(half); + + const recentStats = AuthorStatsAggregatorService.calculateAverageMetrics(recentEvals); + const olderStats = AuthorStatsAggregatorService.calculateAverageMetrics(olderEvals); + + const recentScore = recentStats.commitScore || recentStats.quality; + const olderScore = olderStats.commitScore || olderStats.quality; + + if (recentScore > olderScore + 0.5) { + trend = 'improving'; + } else if (recentScore < olderScore - 0.5) { + trend = 'declining'; + } + } + + // Step 8: Build response + const durationMs = Date.now() - startTime; + context.logger.info(`get_author_stats completed in ${durationMs}ms`); + context.logger.info('=== get_author_stats COMPLETE ==='); + + const response = { + success: true, + author: matchedAuthor, + commitsAnalyzed: evaluations.length, + dateRange, + stats: analysis.stats, + strengths: analysis.strengths, + weaknesses: analysis.weaknesses, + trend, + timing: { durationMs }, + }; + + // Add note if multiple authors matched + if (matchedAuthors.length > 1) { + (response as Record).note = `Multiple authors matched '${author}'. Showing results for '${matchedAuthor}'. Other matches: ${matchedAuthors.slice(1).join(', ')}`; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + context.logger.error(`get_author_stats failed: ${error}`); + return createErrorResponse( + 'Failed to get author stats', + error instanceof Error ? error.message : String(error), + { + author, + hint: 'An unexpected error occurred while aggregating author statistics.', + }, + ); + } +}; + +// ============================================================================ +// OKR GENERATION TOOLS +// ============================================================================ + +/** + * Handler: generate_okr + * Generate personalized OKRs and action plan for a developer using AI analysis + */ +export const handleGenerateOkr: ContextualToolHandler = async ( + args, + context, +): Promise => { + const startTime = Date.now(); + context.logger.info('=== generate_okr START ==='); + + const { author, repo_path, since, save = true, provider, api_key, model } = args as { + author: string; + repo_path?: string; + since?: string; + save?: boolean; + provider?: string; + api_key?: string; + model?: string; + }; + + const projectPath = repo_path || context.projectPath; + const evalRoot = getEvaluationRoot(projectPath); + const configFilePath = path.join(projectPath, '.codewave.config.json'); + + context.logger.info(`Generating OKRs for author: ${author}`); + context.logger.info(`evalRoot: ${evalRoot}, save: ${save}`); + if (provider) { + context.logger.info(`Inline config: provider=${provider}, model=${model}`); + } + + // Validate author parameter + if (!author || author.trim().length === 0) { + return createErrorResponse( + 'Validation failed', + 'author is required', + { hint: 'Provide an author name. Use list_evaluations to see available authors.' }, + ); + } + + try { + // ======================================================================== + // STEP 1: Configuration Loading (reuse pattern from evaluate_commit) + // ======================================================================== + context.logger.info('Step 1: Loading configuration...'); + let config: AppConfig; + let configSource: 'inline' | 'file'; + + const hasInlineConfig = provider && api_key && model; + const hasConfigFile = configExists(configFilePath); + + // Validate inline config completeness + const hasProvider = !!provider; + const hasApiKey = !!api_key; + const hasModel = !!model; + const inlineParamCount = [hasProvider, hasApiKey, hasModel].filter(Boolean).length; + + if (inlineParamCount > 0 && inlineParamCount < 3) { + const missing: string[] = []; + if (!hasProvider) missing.push('provider'); + if (!hasApiKey) missing.push('api_key'); + if (!hasModel) missing.push('model'); + return createErrorResponse( + 'Incomplete inline config', + `If using inline parameters, all three are required: provider, api_key, model. Missing: ${missing.join(', ')}`, + { hint: 'Either provide all three inline parameters or use a .codewave.config.json file.' }, + ); + } + + if (hasInlineConfig) { + configSource = 'inline'; + context.logger.info(`Using INLINE config: provider=${provider}, model=${model}`); + config = buildInlineConfig(provider, api_key, model); + } else if (hasConfigFile) { + configSource = 'file'; + context.logger.info(`Using CONFIG FILE: ${configFilePath}`); + + const loadedConfig = loadConfig(configFilePath); + if (!loadedConfig) { + return createErrorResponse( + 'Configuration load failed', + 'Failed to load .codewave.config.json. Please check the file format.', + { + configPath: configFilePath, + hint: 'Ensure the config file is valid JSON and follows the expected schema.', + }, + ); + } + config = loadedConfig; + } else { + return createErrorResponse( + 'Configuration required', + 'No .codewave.config.json found and no inline config provided.', + { + projectPath, + hint: 'Either create a config file with "codewave config --init" or provide provider, api_key, and model parameters.', + }, + ); + } + + // ======================================================================== + // STEP 2: API Key Verification + // ======================================================================== + context.logger.info('Step 2: Verifying API key...'); + const activeProvider = config.llm.provider; + const activeApiKey = config.apiKeys[activeProvider]; + + if (!activeApiKey) { + return createErrorResponse( + 'API key not configured', + `No API key found for provider: ${activeProvider}`, + { + provider: activeProvider, + configSource, + hint: configSource === 'file' + ? `Add the API key to your .codewave.config.json under apiKeys.${activeProvider}` + : 'Provide a valid api_key parameter.', + }, + ); + } + context.logger.info(`API key verified for provider: ${activeProvider}`); + + // ======================================================================== + // STEP 3: Check evaluation directory exists + // ======================================================================== + if (!fs.existsSync(evalRoot)) { + return createErrorResponse( + 'No evaluations found', + 'The .evaluated-commits directory does not exist.', + { + evaluationRoot: evalRoot, + hint: 'Run: codewave evaluate HEAD to create evaluations before generating OKRs.', + }, + ); + } + + // ======================================================================== + // STEP 4: Aggregate author stats + // ======================================================================== + context.logger.info('Step 4: Aggregating author stats...'); + const sinceDate = since ? new Date(since) : undefined; + + const authorData = await AuthorStatsAggregatorService.aggregateAuthorStats(evalRoot, { + targetAuthor: author, + sinceDate, + }); + + if (authorData.size === 0) { + return createErrorResponse( + 'Author not found', + `No evaluations found for author '${author}'.`, + { + author, + hint: 'Cannot generate OKRs without commit history. Check spelling or evaluate some commits first.', + }, + ); + } + + const matchedAuthor = Array.from(authorData.keys())[0]; + const evaluations = authorData.get(matchedAuthor) || []; + + if (evaluations.length === 0) { + return createErrorResponse( + 'No evaluations found', + `Author '${matchedAuthor}' has no evaluations.`, + { hint: 'Evaluate some commits first before generating OKRs.' }, + ); + } + + context.logger.info(`Found ${evaluations.length} evaluations for ${matchedAuthor}`); + + // ======================================================================== + // STEP 5: Analyze author + // ======================================================================== + context.logger.info('Step 5: Analyzing author...'); + const analysis = AuthorStatsAggregatorService.analyzeAuthor(evaluations); + + // ======================================================================== + // STEP 6: Generate OKRs using OkrAgentService + // ======================================================================== + context.logger.info('Step 6: Generating OKRs (this may take 10-30 seconds)...'); + const okrStartTime = Date.now(); + + const agentService = new OkrAgentService(config); + // Suppress console output during OKR generation to prevent emoji progress + // messages from interfering with MCP stdio JSON-RPC protocol + const okrData = await withSuppressedConsole(() => + agentService.generateOkrsForAuthor( + matchedAuthor, + analysis.stats, + analysis.strengths, + analysis.weaknesses, + evaluations, + evalRoot, + ) + ); + + const okrDurationMs = Date.now() - okrStartTime; + context.logger.info(`OKR generation completed in ${okrDurationMs}ms`); + + // ======================================================================== + // STEP 7: Optionally save to disk + // ======================================================================== + let savedTo: string | null = null; + + if (save) { + context.logger.info('Step 7: Saving OKRs to disk...'); + try { + const orchestrator = new OkrOrchestrator(config); + const okrMap = new Map(); + okrMap.set(matchedAuthor, okrData); + await orchestrator.saveOkrs(evalRoot, okrMap); + + // Construct the save path + const sanitizedAuthor = matchedAuthor.replace(/[^a-z0-9]/gi, '_').toLowerCase(); + const now = new Date(); + const date = now.toISOString().split('T')[0]; + const time = now.toTimeString().split(' ')[0].replace(/:/g, '-'); + savedTo = `.evaluated-commits/.okrs/${sanitizedAuthor}/okr_${date}_${time}.json`; + + context.logger.info(`OKRs saved to: ${savedTo}`); + } catch (saveError) { + context.logger.warn(`Failed to save OKRs: ${saveError}`); + // Don't fail the request, just note it + savedTo = null; + } + } else { + context.logger.info('Step 7: Skipping save (save=false)'); + } + + // ======================================================================== + // STEP 8: Build response + // ======================================================================== + const totalDurationMs = Date.now() - startTime; + context.logger.info('Step 8: Building response...'); + context.logger.info(`generate_okr completed in ${totalDurationMs}ms`); + context.logger.info('=== generate_okr COMPLETE ==='); + + const response: Record = { + success: true, + author: matchedAuthor, + generatedAt: new Date().toISOString(), + commitsAnalyzed: evaluations.length, + strongPoints: okrData.strongPoints || [], + weakPoints: okrData.weakPoints || [], + knowledgeGaps: okrData.knowledgeGaps || [], + okr3Month: okrData.okr3Month || null, + okr6Month: okrData.okr6Month || null, + okr12Month: okrData.okr12Month || null, + actionPlan: okrData.actionPlan || [], + progressReport: okrData.progressReport || null, + timing: { + totalDurationMs, + okrGenerationMs: okrDurationMs, + }, + provider: `${activeProvider} (${config.llm.model})`, + configSource, + }; + + if (savedTo) { + response.savedTo = savedTo; + } else if (save) { + response.saveWarning = 'OKRs generated but save to disk failed.'; + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(response, null, 2), + }, + ], + }; + } catch (error) { + const totalDuration = Date.now() - startTime; + context.logger.error(`generate_okr failed after ${totalDuration}ms: ${error}`); + + // Use existing error classification + const classified = classifyError(error); + return createErrorResponse(classified.type, classified.message, { + ...classified.details, + author, + durationMs: totalDuration, + suggestion: classified.suggestion, + }); + } +}; + +// ============================================================================ +// EXPORTS +// ============================================================================ + +/** + * Map tool names to their handlers + */ +export const TOOL_HANDLERS: Record = { + evaluate_commit: handleEvaluateCommit, + check_config: handleCheckConfig, + list_evaluations: handleListEvaluations, + get_report: handleGetReport, + get_author_stats: handleGetAuthorStats, + generate_okr: handleGenerateOkr, + batch_evaluate: handleBatchEvaluate, + compare_commits: handleCompareCommits, + get_team_stats: handleGetTeamStats, + export_report: handleExportReport, +}; + +/** + * Get handler for a tool by name + */ +export function getToolHandler(toolName: string): ContextualToolHandler | undefined { + return TOOL_HANDLERS[toolName]; +} diff --git a/src/mcp-server/tools/tool-registry.ts b/src/mcp-server/tools/tool-registry.ts new file mode 100644 index 0000000..7b05550 --- /dev/null +++ b/src/mcp-server/tools/tool-registry.ts @@ -0,0 +1,349 @@ +/** + * Tool Registry - Central definition of all MCP tools + */ + +import { ToolDefinition } from '../types'; + +/** + * All available tools in the MCP server + * Add new tools here as we implement them + */ +export const TOOLS: Record = { + evaluate_commit: { + name: 'evaluate_commit', + description: + 'Evaluate a git commit using CodeWave multi-agent AI analysis. Returns code quality, complexity, technical debt, test coverage, and functional impact metrics. Can use .codewave.config.json OR pass provider/api_key/model directly.', + inputSchema: { + type: 'object', + properties: { + commit_hash: { + type: 'string', + description: 'Git commit hash to evaluate (e.g., HEAD, abc123, or full SHA)', + }, + repo_path: { + type: 'string', + description: 'Path to git repository (default: current working directory)', + }, + depth: { + type: 'string', + enum: ['fast', 'normal', 'deep'], + description: 'Analysis depth mode: fast (quick), normal (default), deep (thorough)', + }, + provider: { + type: 'string', + enum: ['openai', 'anthropic', 'groq', 'google', 'ollama', 'xai', 'lm-studio'], + description: 'LLM provider (optional - uses config file if not provided)', + }, + api_key: { + type: 'string', + description: 'API key for the LLM provider (optional - uses config file if not provided)', + }, + model: { + type: 'string', + description: 'Model name (optional - uses provider default if not provided)', + }, + save: { + type: 'boolean', + description: 'Save evaluation results to .evaluated-commits folder (default: false). Enable this if you want to use get_author_stats or generate_okr later.', + }, + }, + required: ['commit_hash'], + }, + }, + + // ============================================================================ + // CONFIGURATION TOOLS + // ============================================================================ + + check_config: { + name: 'check_config', + description: + 'Check if CodeWave configuration exists and is valid. Returns provider, model, and enabled agents. Use this before running evaluations to verify setup.', + inputSchema: { + type: 'object', + properties: { + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + }, + }, + }, + + // ============================================================================ + // REPORT & DATA RETRIEVAL TOOLS + // ============================================================================ + + list_evaluations: { + name: 'list_evaluations', + description: + 'List all commit evaluations from .evaluated-commits folder. Supports filtering by author and date. Use this to browse past evaluations before retrieving specific reports.', + inputSchema: { + type: 'object', + properties: { + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + author: { + type: 'string', + description: 'Filter by author name (case-insensitive partial match)', + }, + since: { + type: 'string', + description: 'Only evaluations after this date (ISO format: 2024-01-01)', + }, + limit: { + type: 'number', + description: 'Maximum number of results (default: 20, max: 100)', + }, + }, + }, + }, + + get_report: { + name: 'get_report', + description: + 'Get detailed evaluation report for a specific commit. Returns metrics, agent summaries, and concerns. Use list_evaluations first to find available commit hashes.', + inputSchema: { + type: 'object', + properties: { + commit_hash: { + type: 'string', + description: 'Commit hash (short or full) to retrieve report for', + }, + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + format: { + type: 'string', + enum: ['full', 'summary', 'metrics_only'], + description: 'Response detail level: full (all data), summary (default), metrics_only (just numbers)', + }, + }, + required: ['commit_hash'], + }, + }, + + // ============================================================================ + // ANALYTICS TOOLS + // ============================================================================ + + get_author_stats: { + name: 'get_author_stats', + description: + 'Get aggregated code quality statistics for a developer. Analyzes their evaluated commits to identify strengths, weaknesses, and trends. No LLM calls required.', + inputSchema: { + type: 'object', + properties: { + author: { + type: 'string', + description: 'Author name to analyze (case-insensitive partial match)', + }, + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + since: { + type: 'string', + description: 'Only include evaluations after this date (ISO format: 2024-01-01)', + }, + limit: { + type: 'number', + description: 'Maximum commits to analyze (default: 50)', + }, + }, + required: ['author'], + }, + }, + + // ============================================================================ + // BATCH OPERATIONS + // ============================================================================ + + batch_evaluate: { + name: 'batch_evaluate', + description: + 'Evaluate multiple commits in a single call. Supports "last:N" syntax (e.g., "last:100") or comma-separated hashes. Returns per-commit results and aggregated summary metrics. Runs up to 10 evaluations in parallel.', + inputSchema: { + type: 'object', + properties: { + commits: { + type: 'string', + description: + 'Commits to evaluate: "last:N" for last N commits, or comma-separated hashes (e.g., "abc123,def456,HEAD")', + }, + repo_path: { + type: 'string', + description: 'Path to git repository (default: current working directory)', + }, + depth: { + type: 'string', + enum: ['fast', 'normal', 'deep'], + description: 'Analysis depth mode: fast (quick), normal (default), deep (thorough)', + }, + save: { + type: 'boolean', + description: 'Save evaluation results to .evaluated-commits folder (default: true)', + }, + provider: { + type: 'string', + enum: ['openai', 'anthropic', 'groq', 'google', 'ollama', 'xai', 'lm-studio'], + description: 'LLM provider (optional - uses config file if not provided)', + }, + api_key: { + type: 'string', + description: 'API key for the LLM provider (optional - uses config file if not provided)', + }, + model: { + type: 'string', + description: 'Model name (optional - uses provider default if not provided)', + }, + }, + required: ['commits'], + }, + }, + + compare_commits: { + name: 'compare_commits', + description: + 'Compare metrics between two commits to show improvement or regression. Reads from saved evaluations (no API calls). Use this to track code quality changes over time.', + inputSchema: { + type: 'object', + properties: { + commit_a: { + type: 'string', + description: 'First commit hash (baseline) - supports partial match', + }, + commit_b: { + type: 'string', + description: 'Second commit hash (to compare against baseline) - supports partial match', + }, + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + }, + required: ['commit_a', 'commit_b'], + }, + }, + + get_team_stats: { + name: 'get_team_stats', + description: + 'Get aggregated code quality statistics for all authors in a repository. Analyzes all evaluated commits and groups by author. No API calls required - reads from saved evaluations.', + inputSchema: { + type: 'object', + properties: { + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + since: { + type: 'string', + description: 'Only include evaluations after this date (ISO format: 2024-01-01)', + }, + limit: { + type: 'number', + description: 'Maximum number of authors to return (default: 10)', + }, + }, + }, + }, + + export_report: { + name: 'export_report', + description: + 'Export evaluation report to a file in HTML, Markdown, or JSON format. Reads from saved evaluations and generates formatted output.', + inputSchema: { + type: 'object', + properties: { + commit_hash: { + type: 'string', + description: 'Commit hash to export (supports partial match)', + }, + output_path: { + type: 'string', + description: 'File path where the report will be saved', + }, + format: { + type: 'string', + enum: ['html', 'md', 'json'], + description: 'Output format: html (default), md (markdown), or json', + }, + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + }, + required: ['commit_hash', 'output_path'], + }, + }, + + // ============================================================================ + // OKR GENERATION TOOLS + // ============================================================================ + + generate_okr: { + name: 'generate_okr', + description: + 'Generate personalized OKRs (Objectives and Key Results) and improvement action plan for a developer using AI analysis. Requires valid configuration with API key. Can optionally save results to disk.', + inputSchema: { + type: 'object', + properties: { + author: { + type: 'string', + description: 'Author name to generate OKRs for', + }, + repo_path: { + type: 'string', + description: 'Path to repository (default: current working directory)', + }, + since: { + type: 'string', + description: 'Only include evaluations after this date', + }, + save: { + type: 'boolean', + description: 'Save OKRs to .evaluated-commits/.okrs/ folder (default: true)', + }, + provider: { + type: 'string', + enum: ['openai', 'anthropic', 'groq', 'google', 'ollama', 'xai', 'lm-studio'], + description: 'LLM provider (optional - uses config file if not provided)', + }, + api_key: { + type: 'string', + description: 'API key for the LLM provider (optional - uses config file if not provided)', + }, + model: { + type: 'string', + description: 'Model name (optional - uses provider default if not provided)', + }, + }, + required: ['author'], + }, + }, +}; + +/** + * Get all tool definitions as array (for ListToolsRequestSchema) + */ +export function getAllTools(): ToolDefinition[] { + return Object.values(TOOLS); +} + +/** + * Get tool definition by name + */ +export function getTool(name: string): ToolDefinition | undefined { + return TOOLS[name]; +} + +/** + * Validate tool name exists + */ +export function isValidTool(name: string): boolean { + return name in TOOLS; +} diff --git a/src/mcp-server/types.ts b/src/mcp-server/types.ts new file mode 100644 index 0000000..1a48387 --- /dev/null +++ b/src/mcp-server/types.ts @@ -0,0 +1,52 @@ +/** + * Type definitions for MCP Server + */ + +import { AppConfig } from '../config/config.interface'; + +/** + * Tool response structure for MCP + */ +export interface ToolResponse { + content: Array<{ + type: 'text' | 'image' | 'resource'; + text?: string; + data?: string; + mimeType?: string; + }>; + isError?: boolean; +} + +/** + * Context passed to all tool handlers + */ +export interface ToolContext { + projectPath: string; + config: AppConfig | null; + logger: { + info: (message: string) => void; + error: (message: string, error?: unknown) => void; + warn: (message: string) => void; + }; +} + +/** + * Tool handler function signature with context + */ +export type ContextualToolHandler = ( + args: Record, + context: ToolContext, +) => Promise; + +/** + * Tool definition with JSON schema + */ +export interface ToolDefinition { + name: string; + description: string; + inputSchema: { + type: 'object'; + properties: Record; + required?: string[]; + }; +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..03f2f6e --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts', 'cli/**/*.test.ts'], + exclude: ['node_modules', 'dist'], + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + include: ['src/mcp-server/**/*.ts'], + exclude: ['**/*.test.ts', '**/fixtures/**'], + }, + // Timeout for async tests + testTimeout: 30000, + // Clear mocks between tests + clearMocks: true, + restoreMocks: true, + }, +});