diff --git a/.changeset/pre.json b/.changeset/pre.json index 42d12890..42023154 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -9,4 +9,4 @@ "eleven-horses-shop", "fix-tag-complexity-detection" ] -} +} \ No newline at end of file diff --git a/.changeset/sour-pans-beam.md b/.changeset/sour-pans-beam.md new file mode 100644 index 00000000..79d7dc25 --- /dev/null +++ b/.changeset/sour-pans-beam.md @@ -0,0 +1,42 @@ +--- +"extension": minor +--- + +πŸŽ‰ **Introducing TaskMaster Extension!** + +We're thrilled to launch the first version of our Code extension, bringing the power of TaskMaster directly into your favorite code editor. While this is our initial release and we've kept things focused, it already packs powerful features to supercharge your development workflow. + +## ✨ Key Features + +### πŸ“‹ Visual Task Management +- **Kanban Board View**: Visualize all your tasks in an intuitive board layout directly in VS Code +- **Drag & Drop**: Easily change task status by dragging cards between columns +- **Real-time Updates**: See changes instantly as you work through your project + +### 🏷️ Multi-Context Support +- **Tag Switching**: Seamlessly switch between different project contexts/tags +- **Isolated Workflows**: Keep different features or experiments organized separately + +### πŸ€– AI-Powered Task Updates +- **Smart Updates**: Use TaskMaster's AI capabilities to update tasks and subtasks +- **Context-Aware**: Leverages your existing TaskMaster configuration and models + +### πŸ“Š Rich Task Information +- **Complexity Scores**: See task complexity ratings at a glance +- **Subtask Visualization**: Expand tasks to view and manage subtasks +- **Dependency Graphs**: Understand task relationships and dependencies visually + +### βš™οΈ Configuration Management +- **Visual Config Editor**: View and understand your `.taskmaster/config.json` settings +- **Easy Access**: No more manual JSON editing for common configuration tasks + +### πŸš€ Quick Actions +- **Status Updates**: Change task status with a single click +- **Task Details**: Access full task information without leaving VS Code +- **Integrated Commands**: All TaskMaster commands available through the command palette + +## 🎯 What's Next? + +This is just the beginning! We wanted to get a solid foundation into your hands quickly. The extension will evolve rapidly with your feedback, adding more advanced features, better visualizations, and deeper integration with your development workflow. + +Thank you for being part of the TaskMaster journey. Your workflow has never looked better! πŸš€ diff --git a/.github/scripts/release.sh b/.github/scripts/release.sh new file mode 100755 index 00000000..ce66c3d1 --- /dev/null +++ b/.github/scripts/release.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +echo "πŸš€ Starting release process..." + +# Double-check we're not in pre-release mode (safety net) +if [ -f .changeset/pre.json ]; then + echo "⚠️ Warning: pre.json still exists. Removing it..." + rm -f .changeset/pre.json +fi + +# Check if the extension version has changed and tag it +# This prevents changeset from trying to publish the private package +node .github/scripts/tag-extension.mjs + +# Run changeset publish for npm packages +npx changeset publish + +echo "βœ… Release process completed!" + +# The extension tag (if created) will trigger the extension-release workflow \ No newline at end of file diff --git a/.github/scripts/tag-extension.mjs b/.github/scripts/tag-extension.mjs new file mode 100644 index 00000000..18f297b6 --- /dev/null +++ b/.github/scripts/tag-extension.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { join, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Read the extension's package.json +const extensionDir = join(__dirname, '..', 'apps', 'extension'); +const pkgPath = join(extensionDir, 'package.json'); + +let pkg; +try { + const pkgContent = readFileSync(pkgPath, 'utf8'); + pkg = JSON.parse(pkgContent); +} catch (error) { + console.error('Failed to read package.json:', error.message); + process.exit(1); +} + +// Read root package.json for repository info +const rootPkgPath = join(__dirname, '..', 'package.json'); +let rootPkg; +try { + const rootPkgContent = readFileSync(rootPkgPath, 'utf8'); + rootPkg = JSON.parse(rootPkgContent); +} catch (error) { + console.error('Failed to read root package.json:', error.message); + process.exit(1); +} + +// Ensure we have required fields +assert(pkg.name, 'package.json must have a name field'); +assert(pkg.version, 'package.json must have a version field'); +assert(rootPkg.repository, 'root package.json must have a repository field'); + +const tag = `${pkg.name}@${pkg.version}`; + +// Get repository URL from root package.json +const repoUrl = rootPkg.repository.url; + +const { status, stdout, error } = spawnSync('git', ['ls-remote', repoUrl, tag]); + +assert.equal(status, 0, error); + +const exists = String(stdout).trim() !== ''; + +if (!exists) { + console.log(`Creating new extension tag: ${tag}`); + + // Create the tag + const tagResult = spawnSync('git', ['tag', tag]); + if (tagResult.status !== 0) { + console.error( + 'Failed to create tag:', + tagResult.error || tagResult.stderr.toString() + ); + process.exit(1); + } + + // Push the tag + const pushResult = spawnSync('git', ['push', 'origin', tag]); + if (pushResult.status !== 0) { + console.error( + 'Failed to push tag:', + pushResult.error || pushResult.stderr.toString() + ); + process.exit(1); + } + + console.log(`βœ… Successfully created and pushed tag: ${tag}`); +} else { + console.log(`Extension tag already exists: ${tag}`); +} diff --git a/.github/workflows/extension-ci.yml b/.github/workflows/extension-ci.yml new file mode 100644 index 00000000..4914852f --- /dev/null +++ b/.github/workflows/extension-ci.yml @@ -0,0 +1,143 @@ +name: Extension CI + +on: + push: + branches: + - main + - next + paths: + - 'apps/extension/**' + - '.github/workflows/extension-ci.yml' + pull_request: + branches: + - main + - next + paths: + - 'apps/extension/**' + - '.github/workflows/extension-ci.yml' + +permissions: + contents: read + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install Extension Dependencies + working-directory: apps/extension + run: npm ci + timeout-minutes: 5 + + typecheck: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + + - name: Restore node_modules + uses: actions/cache@v4 + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install if cache miss + working-directory: apps/extension + run: npm ci + timeout-minutes: 3 + + - name: Type Check Extension + working-directory: apps/extension + run: npm run check-types + env: + FORCE_COLOR: 1 + + build: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + + - name: Restore node_modules + uses: actions/cache@v4 + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install if cache miss + working-directory: apps/extension + run: npm ci + timeout-minutes: 3 + + - name: Build Extension + working-directory: apps/extension + run: npm run build + env: + FORCE_COLOR: 1 + + - name: Package Extension + working-directory: apps/extension + run: npm run package + env: + FORCE_COLOR: 1 + + - name: Verify Package Contents + working-directory: apps/extension + run: | + echo "Checking vsix-build contents..." + ls -la vsix-build/ + echo "Checking dist contents..." + ls -la vsix-build/dist/ + echo "Checking package.json exists..." + test -f vsix-build/package.json + + - name: Create VSIX Package (Test) + working-directory: apps/extension/vsix-build + run: npx vsce package --no-dependencies + env: + FORCE_COLOR: 1 + + - name: Upload Extension Artifact + uses: actions/upload-artifact@v4 + with: + name: extension-package + path: | + apps/extension/vsix-build/*.vsix + apps/extension/dist/ + retention-days: 30 + diff --git a/.github/workflows/extension-release.yml b/.github/workflows/extension-release.yml new file mode 100644 index 00000000..244c490f --- /dev/null +++ b/.github/workflows/extension-release.yml @@ -0,0 +1,137 @@ +name: Extension Release + +on: + push: + tags: + - "extension@*" + +permissions: + contents: write + +concurrency: extension-release-${{ github.ref }} + +jobs: + publish-extension: + runs-on: ubuntu-latest + environment: extension-release + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: | + node_modules + */*/node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + + - name: Install Extension Dependencies + working-directory: apps/extension + run: npm ci + timeout-minutes: 5 + + - name: Type Check Extension + working-directory: apps/extension + run: npm run check-types + env: + FORCE_COLOR: 1 + + - name: Build Extension + working-directory: apps/extension + run: npm run build + env: + FORCE_COLOR: 1 + + - name: Package Extension + working-directory: apps/extension + run: npm run package + env: + FORCE_COLOR: 1 + + - name: Create VSIX Package + working-directory: apps/extension/vsix-build + run: npx vsce package --no-dependencies + env: + FORCE_COLOR: 1 + + - name: Get VSIX filename + id: vsix-info + working-directory: apps/extension/vsix-build + run: | + VSIX_FILE=$(find . -maxdepth 1 -name "*.vsix" -type f | head -n1 | xargs basename) + if [ -z "$VSIX_FILE" ]; then + echo "Error: No VSIX file found" + exit 1 + fi + echo "vsix-filename=$VSIX_FILE" >> "$GITHUB_OUTPUT" + echo "Found VSIX: $VSIX_FILE" + + - name: Publish to VS Code Marketplace + working-directory: apps/extension/vsix-build + run: npx vsce publish --packagePath "${{ steps.vsix-info.outputs.vsix-filename }}" + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + FORCE_COLOR: 1 + + - name: Install Open VSX CLI + run: npm install -g ovsx + + - name: Publish to Open VSX Registry + working-directory: apps/extension/vsix-build + run: ovsx publish "${{ steps.vsix-info.outputs.vsix-filename }}" + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} + FORCE_COLOR: 1 + + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref_name }} + release_name: Extension ${{ github.ref_name }} + body: | + VS Code Extension Release ${{ github.ref_name }} + + **Marketplaces:** + - [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=Hamster.task-master-hamster) + - [Open VSX Registry](https://open-vsx.org/extension/Hamster/task-master-hamster) + draft: false + prerelease: false + + - name: Upload VSIX to Release + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: apps/extension/vsix-build/${{ steps.vsix-info.outputs.vsix-filename }} + asset_name: ${{ steps.vsix-info.outputs.vsix-filename }} + asset_content_type: application/zip + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v4 + with: + name: extension-release-${{ github.ref_name }} + path: | + apps/extension/vsix-build/*.vsix + apps/extension/dist/ + retention-days: 90 + + notify-success: + needs: publish-extension + if: success() + runs-on: ubuntu-latest + steps: + - name: Success Notification + run: | + echo "πŸŽ‰ Extension ${{ github.ref_name }} successfully published!" + echo "πŸ“¦ Available on VS Code Marketplace" + echo "🌍 Available on Open VSX Registry" + echo "🏷️ GitHub release created: ${{ github.ref_name }}" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a19fb49a..3ddb07dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,11 @@ on: concurrency: ${{ github.workflow }}-${{ github.ref }} +permissions: + contents: write + pull-requests: write + id-token: write + jobs: release: runs-on: ubuntu-latest @@ -33,13 +38,31 @@ jobs: run: npm ci timeout-minutes: 2 - - name: Exit pre-release mode (safety check) - run: npx changeset pre exit || true + - name: Exit pre-release mode and clean up + run: | + echo "πŸ”„ Ensuring we're not in pre-release mode for main branch..." + + # Exit pre-release mode if we're in it + npx changeset pre exit || echo "Not in pre-release mode" + + # Remove pre.json file if it exists (belt and suspenders approach) + if [ -f .changeset/pre.json ]; then + echo "🧹 Removing pre.json file..." + rm -f .changeset/pre.json + fi + + # Verify the file is gone + if [ ! -f .changeset/pre.json ]; then + echo "βœ… pre.json successfully removed" + else + echo "❌ Failed to remove pre.json" + exit 1 + fi - name: Create Release Pull Request or Publish to npm uses: changesets/action@v1 with: - publish: npm run release + publish: ./.github/scripts/release.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index f7060852..e13582fb 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,10 @@ dev-debug.log *.njsproj *.sln *.sw? + +# VS Code extension test files +.vscode-test/ +apps/extension/.vscode-test/ + +# apps/extension +apps/extension/vsix-build/ \ No newline at end of file diff --git a/apps/extension/.vscodeignore b/apps/extension/.vscodeignore new file mode 100644 index 00000000..0f5259ef --- /dev/null +++ b/apps/extension/.vscodeignore @@ -0,0 +1,25 @@ +# Ignore everything by default +* + +# Only include specific essential files +!package.json +!README.md +!CHANGELOG.md +!LICENSE +!icon.png +!assets/** + +# Include only the built files we need (not source maps) +!dist/extension.js +!dist/index.js +!dist/index.css + +# Exclude development documentation +docs/extension-CI-setup.md +docs/extension-DEV-guide.md + +# Exclude +assets/.DS_Store +assets/banner.png + + diff --git a/apps/extension/CHANGELOG.md b/apps/extension/CHANGELOG.md new file mode 100644 index 00000000..420e6f23 --- /dev/null +++ b/apps/extension/CHANGELOG.md @@ -0,0 +1 @@ +# Change Log diff --git a/apps/extension/LICENSE b/apps/extension/LICENSE new file mode 100644 index 00000000..08eb5897 --- /dev/null +++ b/apps/extension/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 David Maliglowka + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/apps/extension/README.md b/apps/extension/README.md new file mode 100644 index 00000000..faed730c --- /dev/null +++ b/apps/extension/README.md @@ -0,0 +1,204 @@ +# Official Taskmaster AI Extension + +Transform your AI-driven development workflow with a beautiful, interactive Kanban board directly in VS Code. Seamlessly manage tasks from [Taskmaster AI](https://github.com/eyaltoledano/claude-task-master) projects with real-time synchronization and intelligent task management. + +![Taskmaster AI Extension](https://img.shields.io/badge/VS%20Code-Extension-blue) +![License](https://img.shields.io/badge/License-MIT-green) +![Version](https://img.shields.io/visual-studio-marketplace/v/Hamster.task-master-hamster) +![Installs](https://img.shields.io/visual-studio-marketplace/i/Hamster.task-master-hamster) + +## 🎯 What is Taskmaster AI? + +Taskmaster AI is an intelligent task management system designed for AI-assisted development. It helps you break down complex projects into manageable tasks, track progress, and leverage AI to enhance your development workflow. + +## ✨ Key Features + +### πŸ“Š **Interactive Kanban Board** +- **Drag & Drop Interface** - Effortlessly move tasks between status columns +- **Real-time Sync** - Changes instantly reflect in your Taskmaster project files +- **Multiple Views** - Board view and detailed task sidebar +- **Smart Columns** - Pending, In Progress, Review, Done, Deferred, and Cancelled + +![Kanban Board](assets/screenshots/kanban-board.png) + +### πŸ€– **AI-Powered Features** +- **Task Content Generation** - Regenerate task descriptions using AI +- **Smart Task Updates** - Append findings and progress notes automatically +- **MCP Integration** - Seamless connection to Taskmaster AI via Model Context Protocol +- **Intelligent Caching** - Smart performance optimization with background refresh + +![Task Details](assets/screenshots/task-details.png) + +### πŸš€ **Performance & Usability** +- **Offline Support** - Continue working even when disconnected +- **Auto-refresh** - Automatic polling for task changes with smart frequency +- **VS Code Native** - Perfectly integrated with VS Code themes and UI +- **Modern Interface** - Built with ShadCN UI components and Tailwind CSS + +## πŸ› οΈ Installation + +### Prerequisites + +1. **VS Code** 1.90.0 or higher +2. **Node.js** 18.0 or higher (for Taskmaster MCP server) + +### Install the Extension + +1. **From VS Code Marketplace:** + - Click the **Install** button above + - The extension will be automatically added to your VS Code instance + +## πŸš€ Quick Start + +### 1. **Initialize Taskmaster Project** +If you don't have a Taskmaster project yet: +```bash +cd your-project +npm i -g task-master-ai + task-master init + ``` + +### 2. **Open Kanban Board** +- **Command Palette** (Ctrl+Shift+P): `Taskmaster Kanban: Show Board` +- **Or** the extension automatically activates when you have a `.taskmaster` folder in your workspace + +### 3. **MCP Server Setup** +The extension automatically handles the Taskmaster MCP server connection: +- **No manual installation required** - The extension spawns the MCP server automatically +- **Uses npx by default** - Automatically downloads Taskmaster AI when needed +- **Configurable** - You can customize the MCP server command in settings if needed + +### 4. **Start Managing Tasks** +- **Drag tasks** between columns to change status +- **Click tasks** to view detailed information +- **Use AI features** to enhance task content +- **Add subtasks** with the + button on parent tasks + +## πŸ“‹ Usage Guide + +### Task Management + +| Action | How to Do It | +|--------|--------------| +| **View Kanban Board** | `Ctrl/Cmd + Shift + P` β†’ "Taskmaster: Show Board" | +| **Change Task Status** | Drag task card to different column | +| **View Task Details** | Click on any task card | +| **Edit Task Content** | Click task β†’ Use edit buttons in details panel | +| **Add Subtasks** | Click the + button on parent task cards | +| **Use AI Features** | Open task details β†’ Click AI action buttons | + +### Understanding Task Statuses + +- πŸ“‹ **Pending** - Tasks ready to be started +- πŸš€ **In Progress** - Currently being worked on +- πŸ‘€ **Review** - Awaiting review or feedback +- βœ… **Done** - Completed tasks +- ⏸️ **Deferred** - Postponed for later + +### **AI-Powered Task Management** + +The extension integrates seamlessly with Taskmaster AI via MCP to provide: +- **Smart Task Generation** - AI creates detailed implementation plans +- **Progress Tracking** - Append timestamped notes and findings +- **Content Enhancement** - Regenerate task descriptions for clarity +- **Research Integration** - Get up-to-date information for your tasks + +## βš™οΈ Configuration + +Access settings via **File β†’ Preferences β†’ Settings** and search for "Taskmaster": + +### **MCP Connection Settings** +- **MCP Server Command** - Path to task-master-ai executable (default: `npx`) +- **MCP Server Args** - Arguments for the server command (default: `-y`, `--package=task-master-ai`, `task-master-ai`) +- **Connection Timeout** - Server response timeout (default: 30s) +- **Auto Refresh** - Enable automatic task updates (default: enabled) + +### **UI Preferences** +- **Theme** - Auto, Light, or Dark mode +- **Show Completed Tasks** - Display done tasks in board (default: enabled) +- **Task Display Limit** - Maximum tasks to show (default: 100) + +### **Performance Options** +- **Cache Duration** - How long to cache task data (default: 5s) +- **Concurrent Requests** - Max simultaneous API calls (default: 5) + +## πŸ”§ Troubleshooting + +### **Extension Not Loading** +1. Ensure Node.js 18+ is installed +2. Check workspace contains `.taskmaster` folder +3. Restart VS Code +4. Check Output panel (View β†’ Output β†’ Taskmaster Kanban) + +### **MCP Connection Issues** +1. **Command not found**: Ensure Node.js and npx are in your PATH +2. **Timeout errors**: Increase timeout in settings +3. **Permission errors**: Check Node.js permissions +4. **Network issues**: Verify internet connection for npx downloads + +### **Tasks Not Updating** +1. Check MCP connection status in status bar +2. Verify `.taskmaster/tasks/tasks.json` exists +3. Try manual refresh: `Taskmaster Kanban: Check Connection` +4. Review error logs in Output panel + +### **Performance Issues** +1. Reduce task display limit in settings +2. Increase cache duration +3. Disable auto-refresh if needed +4. Close other VS Code extensions temporarily + +## πŸ†˜ Support & Resources + +### **Getting Help** +- πŸ“– **Documentation**: [Taskmaster AI Docs](https://github.com/eyaltoledano/claude-task-master) +- πŸ› **Report Issues**: [GitHub Issues](https://github.com/eyaltoledano/claude-task-master/issues) +- πŸ’¬ **Discussions**: [GitHub Discussions](https://github.com/eyaltoledano/claude-task-master/discussions) +- πŸ› **Report Issues**: [GitHub Issues](https://github.com/eyaltoledano/claude-task-master/issues) + +## 🎯 Tips for Best Results + +### **Project Organization** +- Use descriptive task titles +- Add detailed implementation notes +- Set appropriate task dependencies +- Leverage AI features for complex tasks + +### **Workflow Optimization** +- Review task details before starting work +- Use subtasks for complex features +- Update task status as you progress +- Add findings and learnings to task notes + +### **Collaboration** +- Keep task descriptions updated +- Use consistent status conventions +- Document decisions in task details +- Share knowledge through task notes + +--- + +## πŸ† Why Taskmaster Kanban? + +βœ… **Visual workflow management** for your Taskmaster projects +βœ… **AI-powered task enhancement** built right in +βœ… **Real-time synchronization** keeps everything in sync +βœ… **Native VS Code integration** feels like part of the editor +βœ… **Free and open source** with active development + +**Transform your development workflow today!** πŸš€ + +--- + +*Originally Made with ❀️ by [David Maliglowka](https://x.com/DavidMaliglowka)* + +## Support + +This is an open-source project maintained in my spare time. While I strive to fix bugs and improve the extension, support is provided on a best-effort basis. Feel free to: +- Report issues on [GitHub](https://github.com/eyaltoledano/claude-task-master/issues) +- Submit pull requests with improvements +- Fork the project if you need specific modifications + +## Disclaimer + +This extension is provided "as is" without any warranties. Use at your own risk. The author is not responsible for any issues, data loss, or damages that may occur from using this extension. Please backup your work regularly and test thoroughly before using in important projects. \ No newline at end of file diff --git a/apps/extension/assets/banner.png b/apps/extension/assets/banner.png new file mode 100644 index 00000000..8d215f7d Binary files /dev/null and b/apps/extension/assets/banner.png differ diff --git a/apps/extension/assets/icon-dark.svg b/apps/extension/assets/icon-dark.svg new file mode 100644 index 00000000..f0e17ef4 --- /dev/null +++ b/apps/extension/assets/icon-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/extension/assets/icon-light.svg b/apps/extension/assets/icon-light.svg new file mode 100644 index 00000000..07b60c34 --- /dev/null +++ b/apps/extension/assets/icon-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/extension/assets/icon.png b/apps/extension/assets/icon.png new file mode 100644 index 00000000..df26f616 Binary files /dev/null and b/apps/extension/assets/icon.png differ diff --git a/apps/extension/assets/screenshots/kanban-board.png b/apps/extension/assets/screenshots/kanban-board.png new file mode 100644 index 00000000..71813e06 Binary files /dev/null and b/apps/extension/assets/screenshots/kanban-board.png differ diff --git a/apps/extension/assets/screenshots/task-details.png b/apps/extension/assets/screenshots/task-details.png new file mode 100644 index 00000000..fe1f9c76 Binary files /dev/null and b/apps/extension/assets/screenshots/task-details.png differ diff --git a/apps/extension/assets/sidebar-icon.svg b/apps/extension/assets/sidebar-icon.svg new file mode 100644 index 00000000..ae5c8395 --- /dev/null +++ b/apps/extension/assets/sidebar-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/apps/extension/components.json b/apps/extension/components.json new file mode 100644 index 00000000..b77ea20d --- /dev/null +++ b/apps/extension/components.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/webview/index.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib" + }, + "iconLibrary": "lucide-react" +} diff --git a/apps/extension/docs/extension-CI-setup.md b/apps/extension/docs/extension-CI-setup.md new file mode 100644 index 00000000..8616bd0b --- /dev/null +++ b/apps/extension/docs/extension-CI-setup.md @@ -0,0 +1,222 @@ +# VS Code Extension CI/CD Setup + +This document explains the CI/CD setup for the Task Master VS Code extension using automated changesets. + +## πŸ”„ Workflows Overview + +### 1. Extension CI (`extension-ci.yml`) + +#### Triggers + +- Push to `main` or `next` branches (only when extension files change) +- Pull requests to `main` or `next` (only when extension files change) + +#### What it does + +- βœ… Lints and type-checks the extension code +- πŸ”¨ Builds the extension (`npm run build`) +- πŸ“¦ Creates a clean package (`npm run package`) +- πŸ§ͺ Runs tests with VS Code test framework +- πŸ“‹ Creates a test VSIX package to verify packaging works +- πŸ’Ύ Uploads build artifacts for inspection + +### 2. Version & Publish (`version.yml`) + +**Triggers:** +- Push to `main` branch + +**What it does:** +- πŸ” Detects changeset files for pending releases +- πŸ“ Creates "Version Packages" PR with updated versions and CHANGELOG +- πŸ€– When Version PR is merged, automatically: + - πŸ”¨ Builds and packages the extension + - 🏷️ Creates git tags with changeset automation + - πŸ“€ Publishes to VS Code Marketplace + - 🌍 Publishes to Open VSX Registry + - πŸ“Š Updates package versions and CHANGELOG + +## πŸš€ Changeset Workflow + +### Creating Changes +When making changes to the extension: + +1. **Make your code changes** +2. **Create a changeset**: + ```bash + # From project root + npx changeset add + ``` +3. **Select the extension package**: Choose `taskr-kanban` when prompted +4. **Select version bump type**: + - `patch`: Bug fixes, minor updates + - `minor`: New features, backwards compatible + - `major`: Breaking changes +5. **Write a summary**: Describe what changed for users +6. **Commit changeset file** along with your code changes +7. **Push to feature branch** and create PR + +### Automated Publishing Process +1. **PR with changeset** gets merged to `main` +2. **Version workflow** detects changesets and creates "Version Packages" PR +3. **Review and merge** the Version PR +4. **Automated publishing** happens immediately: + - Extension is built using 3-file packaging system + - VSIX package is created and tested + - Published to VS Code Marketplace (if `VSCE_PAT` is set) + - Published to Open VSX Registry (if `OVSX_PAT` is set) + - Git tags are created: `taskr-kanban@1.0.1` + - CHANGELOG is updated automatically + +## πŸ”‘ Required Secrets + +To use the automated publishing, you need to set up these GitHub repository secrets: + +### `VSCE_PAT` (VS Code Marketplace Personal Access Token) +1. Go to [Azure DevOps](https://dev.azure.com/) +2. Sign in with your Microsoft account +3. Create a Personal Access Token: + - **Name**: VS Code Extension Publishing + - **Organization**: All accessible organizations + - **Expiration**: Custom (recommend 1 year) + - **Scopes**: Custom defined β†’ **Marketplace** β†’ **Manage** +4. Copy the token and add it to GitHub Secrets as `VSCE_PAT` + +### `OVSX_PAT` (Open VSX Registry Personal Access Token) +1. Go to [Open VSX Registry](https://open-vsx.org/) +2. Sign in with your GitHub account +3. Go to your [User Settings](https://open-vsx.org/user-settings/tokens) +4. Create a new Access Token: + - **Description**: VS Code Extension Publishing + - **Scopes**: Leave default (full access) +5. Copy the token and add it to GitHub Secrets as `OVSX_PAT` + +### `GITHUB_TOKEN` (automatically provided) +This is automatically available in GitHub Actions - no setup required. + +## πŸ“‹ Version Management + +### Changeset-Based Versioning +Versions are automatically managed by changesets: + +- **No manual version updates needed** - changesets handle this automatically +- **Semantic versioning** is enforced based on changeset types +- **Changelog generation** happens automatically +- **Git tagging** is handled by the automation + +### Critical Fields Sync +The automation ensures these fields stay in sync between `package.json` and `package.publish.json`: + +```json +{ + "version": "1.0.2", // βœ… AUTO-SYNCED + "publisher": "Hamster", // ⚠️ MUST MATCH MANUALLY + "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH MANUALLY + "description": "...", // ⚠️ MUST MATCH MANUALLY + "engines": { "vscode": "^1.93.0" }, // ⚠️ MUST MATCH MANUALLY + "categories": [...], // ⚠️ MUST MATCH MANUALLY + "contributes": { ... } // ⚠️ MUST MATCH MANUALLY +} +``` + +**Note**: Only `version` is automatically synced. Other fields must be manually kept in sync. + +## πŸ” Monitoring Builds + +### CI Status + +- **Green βœ…**: Extension builds and tests successfully +- **Red ❌**: Build/test failures - check logs for details +- **Yellow 🟑**: Partial success - some jobs may have warnings + +### Version PR Status + +- **Version PR Created**: Changesets detected, review and merge to publish +- **No Version PR**: No changesets found, no releases pending +- **Version PR Merged**: Automated publishing triggered + +### Release Status + +- **Published πŸŽ‰**: Extension live on VS Code Marketplace and Open VSX +- **Skipped ℹ️**: No changesets found, no release needed +- **Failed ❌**: Check logs - often missing secrets or build issues + +### Artifacts + +Workflows upload artifacts that you can download: + +- **CI**: Test results, built files, and VSIX package +- **Version**: Final VSIX package and published extension + +## πŸ› οΈ Troubleshooting + +### Common Issues + +#### No Version PR Created + +- **Check**: Changeset files exist in `.changeset/` directory +- **Check**: Changeset refers to `taskr-kanban` package name +- **Check**: Changes were pushed to `main` branch +- **Solution**: Create changeset with `npx changeset add` + +#### Version PR Not Publishing + +- **Check**: Version PR was actually merged (not just closed) +- **Check**: Required secrets (`VSCE_PAT`, `OVSX_PAT`) are set +- **Check**: No build failures in workflow logs +- **Solution**: Re-run workflow or check secret configuration + +#### `VSCE_PAT` is not set Error + +- Ensure `VSCE_PAT` secret is added to repository +- Check token hasn't expired +- Verify token has Marketplace β†’ Manage permissions + +#### `OVSX_PAT` is not set Error + +- Ensure `OVSX_PAT` secret is added to repository +- Check token hasn't expired +- Verify you're signed in to Open VSX Registry with GitHub + +#### Build Failures + +- Check extension code compiles locally: `cd apps/extension && npm run build` +- Verify tests pass locally: `npm run test` +- Check for TypeScript errors: `npm run check-types` + +#### Packaging Failures + +- Ensure clean package builds: `npm run package` +- Check vsix-build structure is correct +- Verify `package.publish.json` has correct `repository` field + +#### Changeset Issues + +- **Wrong package name**: Ensure changeset refers to `taskr-kanban` +- **Invalid format**: Check changeset markdown format is correct +- **Merge conflicts**: Resolve any conflicts in changeset files + +## πŸ“ File Structure Impact + +The CI workflows respect the 3-file packaging system: +- **Development**: Uses `package.json` for dependencies and scripts +- **Release**: Uses `package.publish.json` for clean marketplace package +- **Build**: Uses `package.mjs` to create `vsix-build/` for final packaging +- **Changesets**: Automatically manage versions across the system + +## 🌍 Dual Registry Publishing + +Your extension will be automatically published to both: +- **VS Code Marketplace** - For official VS Code users +- **Open VSX Registry** - For Cursor, Windsurf, VSCodium, Gitpod, Eclipse Theia, and other compatible editors + +## 🎯 Benefits of Changeset Automation + +- βœ… **Automated versioning**: No manual version bumps needed +- βœ… **Generated changelogs**: Automatic, accurate release notes +- βœ… **Semantic versioning**: Enforced through changeset types +- βœ… **Git tagging**: Proper tags for extension releases +- βœ… **Conflict prevention**: Clear separation of extension vs. main package versions +- βœ… **Review process**: Version changes are reviewable via PR +- βœ… **Rollback capability**: Easy to revert if issues arise + +This ensures clean, predictable, and fully automated publishing to both registries! πŸš€ diff --git a/apps/extension/docs/extension-development-guide.md b/apps/extension/docs/extension-development-guide.md new file mode 100644 index 00000000..df09b5a2 --- /dev/null +++ b/apps/extension/docs/extension-development-guide.md @@ -0,0 +1,256 @@ +# VS Code Extension Development Guide + +## πŸ“ File Structure Overview + +This VS Code extension uses a **3-file packaging system** to avoid dependency conflicts during publishing: + +``` +apps/extension/ +β”œβ”€β”€ package.json # Development configuration +β”œβ”€β”€ package.publish.json # Clean publishing configuration +β”œβ”€β”€ package.mjs # Build script for packaging +β”œβ”€β”€ .vscodeignore # Files to exclude from extension package +└── vsix-build/ # Generated clean package directory +``` + +## πŸ“‹ File Purposes + +### `package.json` (Development) +- **Purpose**: Development environment with all build tools +- **Contains**: + - All `devDependencies` needed for building + - Development scripts (`build`, `watch`, `lint`, etc.) + - Development package name: `"taskr"` +- **Used for**: Local development, building, testing + +### `package.publish.json` (Publishing) +- **Purpose**: Clean distribution version for VS Code Marketplace +- **Contains**: + - **No devDependencies** (avoids dependency conflicts) + - Publishing metadata (`keywords`, `repository`, `categories`) + - Marketplace package name: `"taskr-kanban"` + - VS Code extension configuration +- **Used for**: Final extension packaging + +### `package.mjs` (Build Script) +- **Purpose**: Creates clean package for distribution +- **Process**: + 1. Builds the extension (`build:js` + `build:css`) + 2. Creates clean `vsix-build/` directory + 3. Copies only essential files (no source code) + 4. Renames `package.publish.json` β†’ `package.json` + 5. Ready for `vsce package` + +## πŸš€ Development Workflow + +### Local Development +```bash +# Install dependencies +npm install + +# Start development with hot reload +npm run watch + +# Run just JavaScript build +npm run build:js + +# Run just CSS build +npm run build:css + +# Full production build +npm run build + +# Type checking +npm run check-types + +# Linting +npm run lint +``` + +### Testing in VS Code +1. Press `F5` in VS Code to launch Extension Development Host +2. Test your extension functionality in the new window +3. Use `Developer: Reload Window` to reload after changes + +## πŸ“¦ Production Packaging + +### Step 1: Build Clean Package +```bash +npm run package +``` +This creates `vsix-build/` with clean distribution files. + +### Step 2: Create VSIX +```bash +cd vsix-build +npx vsce package --no-dependencies +``` +Creates: `taskr-kanban-1.0.1.vsix` + +### Alternative: One Command +```bash +npm run package && cd vsix-build && npx vsce package --no-dependencies +``` + +## πŸ”„ Keeping Files in Sync + +### Critical Fields to Sync Between Files + +When updating extension metadata, ensure these fields match between `package.json` and `package.publish.json`: + +#### Version & Identity +```json +{ + "version": "1.0.1", // ⚠️ MUST MATCH + "publisher": "Hamster", // ⚠️ MUST MATCH + "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH + "description": "A visual Kanban board...", // ⚠️ MUST MATCH +} +``` + +#### VS Code Configuration +```json +{ + "engines": { "vscode": "^1.101.0" }, // ⚠️ MUST MATCH + "categories": [...], // ⚠️ MUST MATCH + "activationEvents": [...], // ⚠️ MUST MATCH + "main": "./dist/extension.js", // ⚠️ MUST MATCH + "contributes": { ... } // ⚠️ MUST MATCH EXACTLY +} +``` + +### Key Differences (Should NOT Match) +```json +// package.json (dev) +{ + "name": "taskr", // βœ… Short dev name + "devDependencies": { ... }, // βœ… Only in dev file + "scripts": { ... } // βœ… Build scripts +} + +// package.publish.json (publishing) +{ + "name": "taskr-kanban", // βœ… Marketplace name + "keywords": [...], // βœ… Only in publish file + "repository": "https://github.com/...", // βœ… Only in publish file + // NO devDependencies // βœ… Clean for publishing + // NO build scripts // βœ… Not needed in package +} +``` + +## πŸ€– Automated Release Process + +### Changesets Workflow +This extension uses [Changesets](https://github.com/changesets/changesets) for automated version management and publishing. + +#### Adding Changes +When making changes to the extension: + +1. **Make your code changes** +2. **Create a changeset**: + ```bash + # From project root + npx changeset add + ``` +3. **Select the extension package**: Choose `taskr-kanban` when prompted +4. **Select version bump type**: + - `patch`: Bug fixes, minor updates + - `minor`: New features, backwards compatible + - `major`: Breaking changes +5. **Write a summary**: Describe what changed for users + +#### Automated Publishing +The automation workflow runs on pushes to `main`: + +1. **Version Workflow** (`.github/workflows/version.yml`): + - Detects when changesets exist + - Creates a "Version Packages" PR with updated versions and CHANGELOG + - When the PR is merged, automatically publishes the extension + +2. **Release Process** (`scripts/release.sh`): + - Builds the extension using the 3-file packaging system + - Creates VSIX package + - Publishes to VS Code Marketplace (if `VSCE_PAT` is set) + - Publishes to Open VSX Registry (if `OVSX_PAT` is set) + - Creates git tags for the extension version + +#### Required Secrets +For automated publishing, these secrets must be set in the repository: + +- `VSCE_PAT`: Personal Access Token for VS Code Marketplace +- `OVSX_PAT`: Personal Access Token for Open VSX Registry +- `GITHUB_TOKEN`: Automatically provided by GitHub Actions + +#### Manual Release +If needed, you can manually trigger a release: + +```bash +# From project root +./scripts/release.sh +``` + +### Extension Tagging +The extension uses a separate tagging strategy from the main package: + +- **Extension tags**: `taskr-kanban@1.0.1` +- **Main package tags**: `task-master-ai@2.1.0` + +This allows independent versioning and prevents conflicts in the monorepo. + +## πŸ” Troubleshooting + +### Dependency Conflicts +**Problem**: `vsce package` fails with missing dependencies +**Solution**: Use the 3-file system - never run `vsce package` from root + +### Build Failures +**Problem**: Extension not working after build +**Check**: +1. All files copied to `vsix-build/dist/` +2. `package.publish.json` has correct `main` field +3. VS Code engine version compatibility + +### Sync Issues +**Problem**: Extension works locally but fails when packaged +**Check**: Ensure critical fields are synced between package files + +### Changeset Issues +**Problem**: Version workflow not triggering +**Check**: +1. Changeset files exist in `.changeset/` +2. Package name in changeset matches `package.publish.json` +3. Changes are pushed to `main` branch + +**Problem**: Publishing fails +**Check**: +1. Required secrets are set in repository settings +2. `package.publish.json` has correct repository URL +3. Build process completes successfully + +## πŸ“ Version Release Checklist + +### Manual Releases +1. **Create changeset**: `npx changeset add` +2. **Update critical fields** in both `package.json` and `package.publish.json` +3. **Test locally** with `F5` in VS Code +4. **Commit and push** to trigger automated workflow + +### Automated Releases (Recommended) +1. **Create changeset**: `npx changeset add` +2. **Push to feature branch** and create PR +3. **Merge PR** - this triggers version PR creation +4. **Review and merge version PR** - this triggers automated publishing + +## 🎯 Why This System? + +- **Avoids dependency conflicts**: VS Code doesn't see dev dependencies +- **Clean distribution**: Only essential files in final package +- **Faster packaging**: No dependency resolution during `vsce package` +- **Maintainable**: Clear separation of dev vs. production configs +- **Reliable**: Consistent, conflict-free packaging process +- **Automated**: Changesets handle versioning and publishing automatically +- **Traceable**: Clear changelog and git tags for every release + +--- + +**Remember**: Always use `npx changeset add` for changes, then push to trigger automated releases! πŸš€ diff --git a/apps/extension/esbuild.js b/apps/extension/esbuild.js new file mode 100644 index 00000000..f7d3a880 --- /dev/null +++ b/apps/extension/esbuild.js @@ -0,0 +1,173 @@ +const esbuild = require('esbuild'); +const path = require('path'); + +const production = process.argv.includes('--production'); +const watch = process.argv.includes('--watch'); + +/** + * @type {import('esbuild').Plugin} + */ +const esbuildProblemMatcherPlugin = { + name: 'esbuild-problem-matcher', + + setup(build) { + build.onStart(() => { + console.log('[watch] build started'); + }); + build.onEnd((result) => { + result.errors.forEach(({ text, location }) => { + console.error(`✘ [ERROR] ${text}`); + console.error( + ` ${location.file}:${location.line}:${location.column}:` + ); + }); + console.log('[watch] build finished'); + }); + } +}; + +/** + * @type {import('esbuild').Plugin} + */ +const aliasPlugin = { + name: 'alias', + setup(build) { + // Handle @/ aliases for shadcn/ui + build.onResolve({ filter: /^@\// }, (args) => { + const resolvedPath = path.resolve(__dirname, 'src', args.path.slice(2)); + + // Try to resolve with common TypeScript extensions + const fs = require('fs'); + const extensions = ['.tsx', '.ts', '.jsx', '.js']; + + // Check if it's a file first + for (const ext of extensions) { + const fullPath = resolvedPath + ext; + if (fs.existsSync(fullPath)) { + return { path: fullPath }; + } + } + + // Check if it's a directory with index file + for (const ext of extensions) { + const indexPath = path.join(resolvedPath, 'index' + ext); + if (fs.existsSync(indexPath)) { + return { path: indexPath }; + } + } + + // Fallback to original behavior + return { path: resolvedPath }; + }); + } +}; + +async function main() { + // Build configuration for the VS Code extension + const extensionCtx = await esbuild.context({ + entryPoints: ['src/extension.ts'], + bundle: true, + format: 'cjs', + minify: production, + sourcemap: !production ? 'inline' : false, + sourcesContent: !production, + platform: 'node', + outdir: 'dist', + external: ['vscode'], + logLevel: 'silent', + // Add production optimizations + ...(production && { + drop: ['debugger'], + pure: ['console.log', 'console.debug', 'console.trace'] + }), + plugins: [esbuildProblemMatcherPlugin, aliasPlugin] + }); + + // Build configuration for the React webview + const webviewCtx = await esbuild.context({ + entryPoints: ['src/webview/index.tsx'], + bundle: true, + format: 'iife', + globalName: 'App', + minify: production, + sourcemap: !production ? 'inline' : false, + sourcesContent: !production, + platform: 'browser', + outdir: 'dist', + logLevel: 'silent', + target: ['es2020'], + jsx: 'automatic', + jsxImportSource: 'react', + external: ['*.css'], + // Bundle React with webview since it's not available in the runtime + // This prevents the multiple React instances issue + // Ensure React is resolved from the workspace root to avoid duplicates + alias: { + react: path.resolve(__dirname, 'node_modules/react'), + 'react-dom': path.resolve(__dirname, 'node_modules/react-dom') + }, + define: { + 'process.env.NODE_ENV': production ? '"production"' : '"development"', + global: 'globalThis' + }, + // Add production optimizations for webview too + ...(production && { + drop: ['debugger'], + pure: ['console.log', 'console.debug', 'console.trace'] + }), + plugins: [esbuildProblemMatcherPlugin, aliasPlugin] + }); + + // Build configuration for the React sidebar + const sidebarCtx = await esbuild.context({ + entryPoints: ['src/webview/sidebar.tsx'], + bundle: true, + format: 'iife', + globalName: 'SidebarApp', + minify: production, + sourcemap: !production ? 'inline' : false, + sourcesContent: !production, + platform: 'browser', + outdir: 'dist', + logLevel: 'silent', + target: ['es2020'], + jsx: 'automatic', + jsxImportSource: 'react', + external: ['*.css'], + alias: { + react: path.resolve(__dirname, 'node_modules/react'), + 'react-dom': path.resolve(__dirname, 'node_modules/react-dom') + }, + define: { + 'process.env.NODE_ENV': production ? '"production"' : '"development"', + global: 'globalThis' + }, + ...(production && { + drop: ['debugger'], + pure: ['console.log', 'console.debug', 'console.trace'] + }), + plugins: [esbuildProblemMatcherPlugin, aliasPlugin] + }); + + if (watch) { + await Promise.all([ + extensionCtx.watch(), + webviewCtx.watch(), + sidebarCtx.watch() + ]); + } else { + await Promise.all([ + extensionCtx.rebuild(), + webviewCtx.rebuild(), + sidebarCtx.rebuild() + ]); + await extensionCtx.dispose(); + await webviewCtx.dispose(); + await sidebarCtx.dispose(); + } +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/apps/extension/package.json b/apps/extension/package.json index ff483de1..b2eec452 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -1,16 +1,281 @@ { "name": "extension", "private": true, - "version": "0.20.0", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "displayName": "TaskMaster", + "description": "A visual Kanban board interface for TaskMaster projects in VS Code", + "version": "0.22.3", + "publisher": "Hamster", + "icon": "assets/icon.png", + "engines": { + "vscode": "^1.93.0" + }, + "categories": ["AI", "Visualization", "Education", "Other"], + "main": "./dist/extension.js", + "activationEvents": ["onStartupFinished", "workspaceContains:.taskmaster/**"], + "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "taskmaster", + "title": "TaskMaster", + "icon": "assets/sidebar-icon.svg" + } + ] + }, + "views": { + "taskmaster": [ + { + "id": "taskmaster.welcome", + "name": "TaskMaster", + "type": "webview" + } + ] + }, + "commands": [ + { + "command": "tm.showKanbanBoard", + "title": "TaskMaster: Show Board", + "icon": "$(checklist)" + }, + { + "command": "tm.checkConnection", + "title": "TaskMaster: Check Connection" + }, + { + "command": "tm.reconnect", + "title": "TaskMaster: Reconnect" + }, + { + "command": "tm.openSettings", + "title": "TaskMaster: Open Settings" + } + ], + "menus": { + "view/title": [ + { + "command": "tm.showKanbanBoard", + "when": "view == taskmaster.welcome", + "group": "navigation" + } + ] + }, + "configuration": { + "title": "TaskMaster Kanban", + "properties": { + "taskmaster.mcp.command": { + "type": "string", + "default": "npx", + "description": "The command or absolute path to execute for the MCP server (e.g., 'npx' or '/usr/local/bin/task-master-ai')." + }, + "taskmaster.mcp.args": { + "type": "array", + "items": { + "type": "string" + }, + "default": ["task-master-ai"], + "description": "An array of arguments to pass to the MCP server command." + }, + "taskmaster.mcp.cwd": { + "type": "string", + "description": "Working directory for the TaskMaster MCP server (defaults to workspace root)" + }, + "taskmaster.mcp.env": { + "type": "object", + "description": "Environment variables for the TaskMaster MCP server" + }, + "taskmaster.mcp.timeout": { + "type": "number", + "default": 30000, + "minimum": 1000, + "maximum": 300000, + "description": "Connection timeout in milliseconds" + }, + "taskmaster.mcp.maxReconnectAttempts": { + "type": "number", + "default": 5, + "minimum": 1, + "maximum": 20, + "description": "Maximum number of reconnection attempts" + }, + "taskmaster.mcp.reconnectBackoffMs": { + "type": "number", + "default": 1000, + "minimum": 100, + "maximum": 10000, + "description": "Initial reconnection backoff delay in milliseconds" + }, + "taskmaster.mcp.maxBackoffMs": { + "type": "number", + "default": 30000, + "minimum": 1000, + "maximum": 300000, + "description": "Maximum reconnection backoff delay in milliseconds" + }, + "taskmaster.mcp.healthCheckIntervalMs": { + "type": "number", + "default": 15000, + "minimum": 5000, + "maximum": 60000, + "description": "Health check interval in milliseconds" + }, + "taskmaster.mcp.requestTimeoutMs": { + "type": "number", + "default": 300000, + "minimum": 30000, + "maximum": 600000, + "description": "MCP request timeout in milliseconds (default: 5 minutes)" + }, + "taskmaster.ui.autoRefresh": { + "type": "boolean", + "default": true, + "description": "Automatically refresh tasks from the server" + }, + "taskmaster.ui.refreshIntervalMs": { + "type": "number", + "default": 10000, + "minimum": 1000, + "maximum": 300000, + "description": "Auto-refresh interval in milliseconds" + }, + "taskmaster.ui.theme": { + "type": "string", + "enum": ["auto", "light", "dark"], + "default": "auto", + "description": "UI theme preference" + }, + "taskmaster.ui.showCompletedTasks": { + "type": "boolean", + "default": true, + "description": "Show completed tasks in the Kanban board" + }, + "taskmaster.ui.taskDisplayLimit": { + "type": "number", + "default": 100, + "minimum": 1, + "maximum": 1000, + "description": "Maximum number of tasks to display" + }, + "taskmaster.ui.showPriority": { + "type": "boolean", + "default": true, + "description": "Show task priority indicators" + }, + "taskmaster.ui.showTaskIds": { + "type": "boolean", + "default": true, + "description": "Show task IDs in the interface" + }, + "taskmaster.performance.maxConcurrentRequests": { + "type": "number", + "default": 5, + "minimum": 1, + "maximum": 20, + "description": "Maximum number of concurrent MCP requests" + }, + "taskmaster.performance.requestTimeoutMs": { + "type": "number", + "default": 30000, + "minimum": 1000, + "maximum": 300000, + "description": "Request timeout in milliseconds" + }, + "taskmaster.performance.cacheTasksMs": { + "type": "number", + "default": 5000, + "minimum": 0, + "maximum": 60000, + "description": "Task cache duration in milliseconds" + }, + "taskmaster.performance.lazyLoadThreshold": { + "type": "number", + "default": 50, + "minimum": 10, + "maximum": 500, + "description": "Number of tasks before enabling lazy loading" + }, + "taskmaster.debug.enableLogging": { + "type": "boolean", + "default": true, + "description": "Enable debug logging" + }, + "taskmaster.debug.logLevel": { + "type": "string", + "enum": ["error", "warn", "info", "debug"], + "default": "info", + "description": "Logging level" + }, + "taskmaster.debug.enableConnectionMetrics": { + "type": "boolean", + "default": true, + "description": "Enable connection performance metrics" + }, + "taskmaster.debug.saveEventLogs": { + "type": "boolean", + "default": false, + "description": "Save event logs to files" + }, + "taskmaster.debug.maxEventLogSize": { + "type": "number", + "default": 1000, + "minimum": 10, + "maximum": 10000, + "description": "Maximum number of events to keep in memory" + } + } + } + }, + "scripts": { + "vscode:prepublish": "npm run build", + "build": "npm run build:js && npm run build:css", + "build:js": "node ./esbuild.js --production", + "build:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --minify", + "package": "npm exec node ./package.mjs", + "package:direct": "node ./package.mjs", + "debug:env": "node ./debug-env.mjs", + "compile": "node ./esbuild.js", + "watch": "npm run watch:js & npm run watch:css", + "watch:js": "node ./esbuild.js --watch", + "watch:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --watch", + "check-types": "tsc --noEmit" }, - "keywords": [], - "author": "", - "license": "ISC", - "description": "", "devDependencies": { - "typescript": "^5.8.3" + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@modelcontextprotocol/sdk": "1.13.3", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-portal": "^1.1.9", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@tailwindcss/postcss": "^4.1.11", + "@types/mocha": "^10.0.10", + "@types/node": "20.x", + "@types/react": "19.1.8", + "@types/react-dom": "19.1.6", + "@types/vscode": "^1.101.0", + "@vscode/test-cli": "^0.0.11", + "@vscode/test-electron": "^2.5.2", + "@vscode/vsce": "^2.32.0", + "autoprefixer": "10.4.21", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "esbuild": "^0.25.3", + "esbuild-postcss": "^0.0.4", + "fs-extra": "^11.3.0", + "lucide-react": "^0.525.0", + "npm-run-all": "^4.1.5", + "postcss": "8.5.6", + "tailwind-merge": "^3.3.1", + "tailwindcss": "4.1.11", + "typescript": "^5.8.3", + "@tanstack/react-query": "^5.83.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "overrides": { + "glob@<8": "^10.4.5", + "inflight": "npm:@tootallnate/once@2" } } diff --git a/apps/extension/package.mjs b/apps/extension/package.mjs new file mode 100644 index 00000000..211f4416 --- /dev/null +++ b/apps/extension/package.mjs @@ -0,0 +1,136 @@ +import { execSync } from 'child_process'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs-extra'; + +// --- Configuration --- +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const packageDir = path.resolve(__dirname, 'vsix-build'); +// --- End Configuration --- + +try { + console.log('πŸš€ Starting packaging process...'); + + // 1. Build Project + console.log('\nBuilding JavaScript...'); + execSync('npm run build:js', { stdio: 'inherit' }); + console.log('\nBuilding CSS...'); + execSync('npm run build:css', { stdio: 'inherit' }); + + // 2. Prepare Clean Directory + console.log(`\nPreparing clean directory at: ${packageDir}`); + fs.emptyDirSync(packageDir); + + // 3. Copy Build Artifacts (excluding source maps) + console.log('Copying build artifacts...'); + const distDir = path.resolve(__dirname, 'dist'); + const targetDistDir = path.resolve(packageDir, 'dist'); + fs.ensureDirSync(targetDistDir); + + // Only copy the files we need (exclude .map files) + const filesToCopy = ['extension.js', 'index.js', 'index.css', 'sidebar.js']; + for (const file of filesToCopy) { + const srcFile = path.resolve(distDir, file); + const destFile = path.resolve(targetDistDir, file); + if (fs.existsSync(srcFile)) { + fs.copySync(srcFile, destFile); + console.log(` - Copied dist/${file}`); + } + } + + // 4. Copy additional files + const additionalFiles = ['README.md', 'CHANGELOG.md', 'AGENTS.md']; + for (const file of additionalFiles) { + if (fs.existsSync(path.resolve(__dirname, file))) { + fs.copySync( + path.resolve(__dirname, file), + path.resolve(packageDir, file) + ); + console.log(` - Copied ${file}`); + } + } + + // 5. Sync versions and prepare the final package.json + console.log('Syncing versions and preparing the final package.json...'); + + // Read current versions + const devPackagePath = path.resolve(__dirname, 'package.json'); + const publishPackagePath = path.resolve(__dirname, 'package.publish.json'); + + const devPackage = JSON.parse(fs.readFileSync(devPackagePath, 'utf8')); + const publishPackage = JSON.parse( + fs.readFileSync(publishPackagePath, 'utf8') + ); + + // Check if versions are in sync + if (devPackage.version !== publishPackage.version) { + console.log( + ` - Version sync needed: ${publishPackage.version} β†’ ${devPackage.version}` + ); + publishPackage.version = devPackage.version; + + // Update the source package.publish.json file + fs.writeFileSync( + publishPackagePath, + JSON.stringify(publishPackage, null, '\t') + '\n' + ); + console.log( + ` - Updated package.publish.json version to ${devPackage.version}` + ); + } else { + console.log(` - Versions already in sync: ${devPackage.version}`); + } + + // Copy the (now synced) package.publish.json as package.json + fs.copySync(publishPackagePath, path.resolve(packageDir, 'package.json')); + console.log(' - Copied package.publish.json as package.json'); + + // 6. Copy .vscodeignore if it exists + if (fs.existsSync(path.resolve(__dirname, '.vscodeignore'))) { + fs.copySync( + path.resolve(__dirname, '.vscodeignore'), + path.resolve(packageDir, '.vscodeignore') + ); + console.log(' - Copied .vscodeignore'); + } + + // 7. Copy LICENSE if it exists + if (fs.existsSync(path.resolve(__dirname, 'LICENSE'))) { + fs.copySync( + path.resolve(__dirname, 'LICENSE'), + path.resolve(packageDir, 'LICENSE') + ); + console.log(' - Copied LICENSE'); + } + + // 7a. Copy assets directory if it exists + const assetsDir = path.resolve(__dirname, 'assets'); + if (fs.existsSync(assetsDir)) { + const targetAssetsDir = path.resolve(packageDir, 'assets'); + fs.copySync(assetsDir, targetAssetsDir); + console.log(' - Copied assets directory'); + } + + // Small delay to ensure file system operations complete + await new Promise((resolve) => setTimeout(resolve, 100)); + + // 8. Final step - manual packaging + console.log('\nβœ… Build preparation complete!'); + console.log('\nTo create the VSIX package, run:'); + console.log( + '\x1b[36m%s\x1b[0m', + `cd vsix-build && npx vsce package --no-dependencies` + ); + + // Use the synced version for output + const finalVersion = devPackage.version; + console.log( + `\nYour extension will be packaged to: vsix-build/task-master-${finalVersion}.vsix` + ); +} catch (error) { + console.error('\n❌ Packaging failed!'); + console.error(error.message); + process.exit(1); +} diff --git a/apps/extension/package.publish.json b/apps/extension/package.publish.json new file mode 100644 index 00000000..d9949c4e --- /dev/null +++ b/apps/extension/package.publish.json @@ -0,0 +1,250 @@ +{ + "name": "task-master-hamster", + "displayName": "Taskmaster AI", + "description": "A visual Kanban board interface for Taskmaster projects in VS Code", + "version": "0.22.3", + "publisher": "Hamster", + "icon": "assets/icon.png", + "engines": { + "vscode": "^1.93.0" + }, + "categories": ["AI", "Visualization", "Education", "Other"], + "keywords": [ + "kanban", + "kanban board", + "productivity", + "todo", + "task tracking", + "project management", + "task-master", + "task management", + "agile", + "scrum", + "ai", + "mcp", + "model context protocol", + "dashboard", + "chatgpt", + "claude", + "openai", + "anthropic", + "task", + "npm", + "intellicode", + "react", + "typescript", + "php", + "python", + "node", + "planner", + "organizer", + "workflow", + "boards", + "cards" + ], + "repository": "https://github.com/eyaltoledano/claude-task-master", + "activationEvents": ["onStartupFinished", "workspaceContains:.taskmaster/**"], + "main": "./dist/extension.js", + "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "taskmaster", + "title": "Taskmaster", + "icon": "assets/sidebar-icon.svg" + } + ] + }, + "views": { + "taskmaster": [ + { + "id": "taskmaster.welcome", + "name": "Taskmaster", + "type": "webview" + } + ] + }, + "commands": [ + { + "command": "tm.showKanbanBoard", + "title": "Taskmaster: Show Board" + }, + { + "command": "tm.checkConnection", + "title": "Taskmaster: Check Connection" + }, + { + "command": "tm.reconnect", + "title": "Taskmaster: Reconnect" + }, + { + "command": "tm.openSettings", + "title": "Taskmaster: Open Settings" + } + ], + "configuration": { + "title": "Taskmaster Kanban", + "properties": { + "taskmaster.mcp.command": { + "type": "string", + "default": "npx", + "description": "The command or absolute path to execute for the MCP server (e.g., 'npx' or '/usr/local/bin/task-master-ai')." + }, + "taskmaster.mcp.args": { + "type": "array", + "items": { + "type": "string" + }, + "default": ["-y", "--package=task-master-ai", "task-master-ai"], + "description": "An array of arguments to pass to the MCP server command." + }, + "taskmaster.mcp.cwd": { + "type": "string", + "description": "Working directory for the Task Master MCP server (defaults to workspace root)" + }, + "taskmaster.mcp.env": { + "type": "object", + "description": "Environment variables for the Task Master MCP server" + }, + "taskmaster.mcp.timeout": { + "type": "number", + "default": 30000, + "minimum": 1000, + "maximum": 300000, + "description": "Connection timeout in milliseconds" + }, + "taskmaster.mcp.maxReconnectAttempts": { + "type": "number", + "default": 5, + "minimum": 1, + "maximum": 20, + "description": "Maximum number of reconnection attempts" + }, + "taskmaster.mcp.reconnectBackoffMs": { + "type": "number", + "default": 1000, + "minimum": 100, + "maximum": 10000, + "description": "Initial reconnection backoff delay in milliseconds" + }, + "taskmaster.mcp.maxBackoffMs": { + "type": "number", + "default": 30000, + "minimum": 1000, + "maximum": 300000, + "description": "Maximum reconnection backoff delay in milliseconds" + }, + "taskmaster.mcp.healthCheckIntervalMs": { + "type": "number", + "default": 15000, + "minimum": 5000, + "maximum": 60000, + "description": "Health check interval in milliseconds" + }, + "taskmaster.mcp.requestTimeoutMs": { + "type": "number", + "default": 300000, + "minimum": 30000, + "maximum": 600000, + "description": "MCP request timeout in milliseconds (default: 5 minutes)" + }, + "taskmaster.ui.autoRefresh": { + "type": "boolean", + "default": true, + "description": "Automatically refresh tasks from the server" + }, + "taskmaster.ui.refreshIntervalMs": { + "type": "number", + "default": 10000, + "minimum": 1000, + "maximum": 300000, + "description": "Auto-refresh interval in milliseconds" + }, + "taskmaster.ui.theme": { + "type": "string", + "enum": ["auto", "light", "dark"], + "default": "auto", + "description": "UI theme preference" + }, + "taskmaster.ui.showCompletedTasks": { + "type": "boolean", + "default": true, + "description": "Show completed tasks in the Kanban board" + }, + "taskmaster.ui.taskDisplayLimit": { + "type": "number", + "default": 100, + "minimum": 1, + "maximum": 1000, + "description": "Maximum number of tasks to display" + }, + "taskmaster.ui.showPriority": { + "type": "boolean", + "default": true, + "description": "Show task priority indicators" + }, + "taskmaster.ui.showTaskIds": { + "type": "boolean", + "default": true, + "description": "Show task IDs in the interface" + }, + "taskmaster.performance.maxConcurrentRequests": { + "type": "number", + "default": 5, + "minimum": 1, + "maximum": 20, + "description": "Maximum number of concurrent MCP requests" + }, + "taskmaster.performance.requestTimeoutMs": { + "type": "number", + "default": 30000, + "minimum": 1000, + "maximum": 300000, + "description": "Request timeout in milliseconds" + }, + "taskmaster.performance.cacheTasksMs": { + "type": "number", + "default": 5000, + "minimum": 0, + "maximum": 60000, + "description": "Task cache duration in milliseconds" + }, + "taskmaster.performance.lazyLoadThreshold": { + "type": "number", + "default": 50, + "minimum": 10, + "maximum": 500, + "description": "Number of tasks before enabling lazy loading" + }, + "taskmaster.debug.enableLogging": { + "type": "boolean", + "default": true, + "description": "Enable debug logging" + }, + "taskmaster.debug.logLevel": { + "type": "string", + "enum": ["error", "warn", "info", "debug"], + "default": "info", + "description": "Logging level" + }, + "taskmaster.debug.enableConnectionMetrics": { + "type": "boolean", + "default": true, + "description": "Enable connection performance metrics" + }, + "taskmaster.debug.saveEventLogs": { + "type": "boolean", + "default": false, + "description": "Save event logs to files" + }, + "taskmaster.debug.maxEventLogSize": { + "type": "number", + "default": 1000, + "minimum": 10, + "maximum": 10000, + "description": "Maximum number of events to keep in memory" + } + } + } + } +} diff --git a/apps/extension/src/components/ConfigView.tsx b/apps/extension/src/components/ConfigView.tsx new file mode 100644 index 00000000..98f11184 --- /dev/null +++ b/apps/extension/src/components/ConfigView.tsx @@ -0,0 +1,291 @@ +import { ArrowLeft, RefreshCw, Settings } from 'lucide-react'; +import type React from 'react'; +import { useEffect, useState, useCallback } from 'react'; +import { Badge } from './ui/badge'; +import { Button } from './ui/button'; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from './ui/card'; +import { ScrollArea } from './ui/scroll-area'; +import { Separator } from './ui/separator'; + +interface ModelConfig { + provider: string; + modelId: string; + maxTokens: number; + temperature: number; +} + +interface ConfigData { + models?: { + main?: ModelConfig; + research?: ModelConfig; + fallback?: ModelConfig; + }; + global?: { + defaultNumTasks?: number; + defaultSubtasks?: number; + defaultPriority?: string; + projectName?: string; + responseLanguage?: string; + }; +} + +interface ConfigViewProps { + sendMessage: (message: any) => Promise; + onNavigateBack: () => void; +} + +export const ConfigView: React.FC = ({ + sendMessage, + onNavigateBack +}) => { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const loadConfig = useCallback(async () => { + setLoading(true); + setError(null); + try { + const response = await sendMessage({ type: 'getConfig' }); + setConfig(response); + } catch (err) { + setError('Failed to load configuration'); + console.error('Error loading config:', err); + } finally { + setLoading(false); + } + }, [sendMessage]); + + useEffect(() => { + loadConfig(); + }, [loadConfig]); + + const modelLabels = { + main: { + label: 'Main Model', + icon: 'πŸ€–', + description: 'Primary model for task generation' + }, + research: { + label: 'Research Model', + icon: 'πŸ”', + description: 'Model for research-backed operations' + }, + fallback: { + label: 'Fallback Model', + icon: 'πŸ”„', + description: 'Backup model if primary fails' + } + }; + + return ( +
+ {/* Header */} +
+
+ +
+ +

Task Master Configuration

+
+
+ +
+ + {/* Content */} + +
+ {loading ? ( +
+ +
+ ) : error ? ( +
{error}
+ ) : config ? ( +
+ {/* Models Section */} + + + AI Models + + Models configured for different Task Master operations + + + + {config.models && + Object.entries(config.models).map(([key, modelConfig]) => { + const label = + modelLabels[key as keyof typeof modelLabels]; + if (!label || !modelConfig) return null; + + return ( +
+
+ {label.icon} +
+

{label.label}

+

+ {label.description} +

+
+
+
+
+ + Provider: + + + {modelConfig.provider} + +
+
+ + Model: + + + {modelConfig.modelId} + +
+
+ + Max Tokens: + + + {modelConfig.maxTokens.toLocaleString()} + +
+
+ + Temperature: + + + {modelConfig.temperature} + +
+
+
+ ); + })} +
+
+ + {/* Task Defaults Section */} + {config.global && ( + + + Task Defaults + + Default values for new tasks and subtasks + + + +
+
+ + Default Number of Tasks + + + {config.global.defaultNumTasks || 10} + +
+ +
+ + Default Number of Subtasks + + + {config.global.defaultSubtasks || 5} + +
+ +
+ + Default Priority + + + {config.global.defaultPriority || 'medium'} + +
+ {config.global.projectName && ( + <> + +
+ + Project Name + + + {config.global.projectName} + +
+ + )} + {config.global.responseLanguage && ( + <> + +
+ + Response Language + + + {config.global.responseLanguage} + +
+ + )} +
+
+
+ )} + + {/* Info Card */} + + +

+ To modify these settings, go to{' '} + + .taskmaster/config.json + {' '} + and modify them, or use the MCP. +

+
+
+
+ ) : ( +
+ No configuration found. Please run `task-master init` in your + project. +
+ )} +
+
+
+ ); +}; diff --git a/apps/extension/src/components/TaskDetails/AIActionsSection.tsx b/apps/extension/src/components/TaskDetails/AIActionsSection.tsx new file mode 100644 index 00000000..098158f8 --- /dev/null +++ b/apps/extension/src/components/TaskDetails/AIActionsSection.tsx @@ -0,0 +1,207 @@ +import type React from 'react'; +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { CollapsibleSection } from '@/components/ui/CollapsibleSection'; +import { Wand2, Loader2, PlusCircle } from 'lucide-react'; +import { + useUpdateTask, + useUpdateSubtask +} from '../../webview/hooks/useTaskQueries'; +import type { TaskMasterTask } from '../../webview/types'; + +interface AIActionsSectionProps { + currentTask: TaskMasterTask; + isSubtask: boolean; + parentTask?: TaskMasterTask | null; + sendMessage: (message: any) => Promise; + refreshComplexityAfterAI: () => void; + onRegeneratingChange?: (isRegenerating: boolean) => void; + onAppendingChange?: (isAppending: boolean) => void; +} + +export const AIActionsSection: React.FC = ({ + currentTask, + isSubtask, + parentTask, + sendMessage, + refreshComplexityAfterAI, + onRegeneratingChange, + onAppendingChange +}) => { + const [prompt, setPrompt] = useState(''); + const [lastAction, setLastAction] = useState<'regenerate' | 'append' | null>( + null + ); + const updateTask = useUpdateTask(); + const updateSubtask = useUpdateSubtask(); + + const handleRegenerate = async () => { + if (!currentTask || !prompt.trim()) { + return; + } + + setLastAction('regenerate'); + onRegeneratingChange?.(true); + + try { + if (isSubtask && parentTask) { + await updateSubtask.mutateAsync({ + taskId: `${parentTask.id}.${currentTask.id}`, + prompt: prompt, + options: { research: false } + }); + } else { + await updateTask.mutateAsync({ + taskId: currentTask.id, + updates: { description: prompt }, + options: { append: false, research: false } + }); + } + + setPrompt(''); + refreshComplexityAfterAI(); + } catch (error) { + console.error('❌ TaskDetailsView: Failed to regenerate task:', error); + } finally { + setLastAction(null); + onRegeneratingChange?.(false); + } + }; + + const handleAppend = async () => { + if (!currentTask || !prompt.trim()) { + return; + } + + setLastAction('append'); + onAppendingChange?.(true); + + try { + if (isSubtask && parentTask) { + await updateSubtask.mutateAsync({ + taskId: `${parentTask.id}.${currentTask.id}`, + prompt: prompt, + options: { research: false } + }); + } else { + await updateTask.mutateAsync({ + taskId: currentTask.id, + updates: { description: prompt }, + options: { append: true, research: false } + }); + } + + setPrompt(''); + refreshComplexityAfterAI(); + } catch (error) { + console.error('❌ TaskDetailsView: Failed to append to task:', error); + } finally { + setLastAction(null); + onAppendingChange?.(false); + } + }; + + // Track loading states based on the last action + const isLoading = updateTask.isPending || updateSubtask.isPending; + const isRegenerating = isLoading && lastAction === 'regenerate'; + const isAppending = isLoading && lastAction === 'append'; + + return ( + +
+
+ +