diff --git a/AGENTS.md b/AGENTS.md index e7f71a764..b7c93225e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ For feature work in this repository, use this order: 4. Before the user confirms the feature works, do not proactively add guardrails, mutation or regression tests, legacy compatibility protection, defensive extensions, or speculative fallback behavior. 5. User confirmation does not automatically authorize that follow-up work. Add targeted protection or tests only when the user explicitly asks for them, or when the user reports a concrete failure scenario that requires them. -The primary objective is to make the requested function work. Focus on the feature implementation itself and avoid over-design; safety, guardrails, and testing must not dominate the work or turn the feature into a surrounding engineering project. +The primary objective is to make the requested function work. Focus on the feature implementation itself and avoid over-design; safety, guardrails, and testing must not dominate the work or turn the feature into a surrounding engineering project. This rule supersedes the earlier standing instruction that every feature must be developed test-first. Test-first language in older issues does not apply unless the user restates it for that issue after this rule. This ordering does not waive higher-priority safety or security requirements. Keep validation that is necessary at real external boundaries, such as user input or external APIs, but do not expand it into hypothetical protection beyond the requested path. @@ -26,28 +26,15 @@ Use this workflow when the user asks to process Taskboard work. ## 2. Route for efficiency -- Read the complete eligible batch before dispatch. Group work by dependency, shared files, feature state, and runtime conflicts rather than by issue count. - Do not force one issue into one conversation or one worktree. - Group closely related issues in one conversation and worktree when they share the same feature chain and this reduces duplicate work or merge conflicts. - Run independent work in parallel when the paths do not conflict. Queue conflict-prone work and keep it visible. - Research, triage, replies, and other work that does not change code normally do not need a worktree. - For code changes, start from verified current `origin/main`, create a feature branch, and use a worktree. Never implement directly on `main`. -- Every newly dispatched task conversation uses the same model and reasoning level as the coordinating conversation. +- Keep task conversations visible and traceable. Do not pin newly created task conversations. Do not pass this no-pin rule, or restrictions on subagents, into the delegated task prompt unless the user requests it for that task. +- For every newly dispatched task conversation, explicitly use the same model and reasoning level as the coordinating conversation. Do not substitute a skill default, cheaper model, or lower reasoning level. Existing conversations do not need to be recreated when this rule is added later. - Bind each claimed issue to the actual conversation, branch, and worktree used for it, and record the grouping decision in the issue. -### Work-in-progress limits and execution lanes - -- Parallelize independent code paths. Do not parallelize access to a shared mutable runtime. -- Do not use a fixed total number of execution lanes or worktrees. Create one execution group for each independent conflict domain. Ten eligible issues may become several parallel groups or one coherent group, depending on overlap. -- Classify Web or product logic, native or platform work, and research or external contribution work for routing only. These categories are not concurrency limits; multiple independent groups within a category may run in parallel. -- Determine concurrency from shared files, feature state, the Codex App process, Launcher runtime, CDP, browser review state, CI capacity, and release resources. More issues alone do not justify more worktrees, and a category label does not justify serialization. -- Run only one Launcher, Codex injection, native host, updater, signing, or release task against the local App environment at a time. If isolated runtime descriptors, ports, CDP profiles, and user-data directories are available, separate instances may run in parallel. -- Keep external contributor PR maintenance in its own lane. Preserve contributor history and do not let unrelated internal work rewrite its branch. -- Group small changes that share a feature chain into one conversation, worktree, and PR. Amortize branch setup, CI, review, UI confirmation, merge, and cleanup instead of paying that fixed cost per issue. -- Queue work that shares a runtime or conflict domain. A visible queue is better than parallel work that later requires repeated restarts, conflict resolution, or CI reruns. -- A waiting issue does not stop the rest of the batch. Continue every other eligible lane while one item waits for CI, Pro, user input, or an external dependency. -- Requirement-collection and record-only issues are not active implementation. Leave them unassigned in `backlog` or another non-active tracking state; do not keep them in `in_progress` merely to preserve notes. - ## 3. Follow E3 1. **Estimate**: estimate the context, steps, overlap, and risk. @@ -60,15 +47,6 @@ Before editing, record the real path in the issue: Make the smallest root-cause change. Do not add unrelated refactors, abstractions, state machines, compatibility layers, speculative fallbacks, guardrails, or tests. Add targeted protection or tests only when the user explicitly requests them or reports a concrete failure that requires them. -- During **Estimate**, classify the work as small, medium, or high risk and state the intended direct verification before implementation. -- Classify complexity by operation-path breadth, number of affected components and data layers, persistence or process effects, platform reach, external dependencies, shared state, blast radius, and required evidence. Do not classify or stop work by elapsed time. -- Treat a single component or direct data path without persistence, process, or platform effects as small. Treat multiple components or an API and persistence chain as medium. Treat process management, Launcher or injection, updater, migration, destructive operations, cross-platform behavior, or external boundaries as high risk. -- The default verification budget is the user-reported failing path plus one successful main path. Stop when both pass. -- Use **Expand** only when direct verification fails, evidence disproves the estimated root cause, the user reports another concrete case, or the changed boundary creates a real additional risk. Do not expand into a general matrix by default. -- Stop expanding when the reported failure path and one successful main path pass, the root cause remains supported by evidence, and no required boundary check or review has found a real defect. -- Run focused checks that match the changed layer. Do not run a full App build, cross-platform packaging, or a broad test suite for documentation, copy, CSS, or isolated Web changes unless that exact path requires it. -- Do not repeat environment discovery in every task. Reuse a verified toolchain, project ID, packaged `taskctl`, and stable coordinator-owned runtime until evidence shows that one changed. - ## 4. Preserve external contributions - When an issue already has an external contributor PR, review and improve that PR before creating a replacement. @@ -85,32 +63,13 @@ Make the smallest root-cause change. Do not add unrelated refactors, abstraction - Report changed files, commit, exact head SHA, direct verification, PR, CI state, review complexity decision, review result, and remaining limitations in the issue. - Show ongoing status in the Taskboard opened through the injected Codex App. - Execution conversations do not merge, release, mark `done`, or claim user acceptance. -- The same execution conversation owns its group from issue reading and implementation through direct verification, PR, complexity decision, required review, CI diagnosis, and rework. Do not hand off between these stages without a real blocker. - -### Shared runtime isolation - -- The installed or injected Taskboard runtime used for issue reads and writes is coordinator-owned. An execution conversation must not stop it, replace its descriptor, overwrite the installed App, or reuse its CDP profile for worktree validation. -- Independent implementation may continue in parallel while shared App verification is queued. Concentrate real Codex, Launcher, Chrome, updater, signing, and release operations in a coordinator-owned integration lane instead of blocking unrelated code work. -- A worktree Launcher must use an isolated runtime descriptor, port, CDP port, and user-data directory when the product supports them. -- If the product cannot isolate worktree Launcher state, serialize Launcher verification. Do not run two tasks that can replace the same active runtime descriptor. -- Stop only processes that were started by the same task and were resolved by exact PID and executable path. - -### Progress and context economy - -- Write one initial Taskboard comment for claim, grouping, and E3 path, and one final comment for implementation, verification, review, PR, CI, and limitations. -- Add an intermediate comment only for a material blocker, scope correction, failed review, user-visible preview, or changed exact head. Do not write polling or unchanged waiting updates into the issue. -- A resumed conversation may reuse its recorded full issue snapshot. Read only comments and attachments added since that snapshot unless the issue version or requirement changed. -- Return a concise structured handoff. Do not paste full command logs, full API JSON, base64 images, or repeated rule text when identifiers, exact SHA, results, and artifact links are enough. -- The coordinator checks handoff evidence but does not repeat the execution conversation's full review or full validation matrix. ## 6. Review by risk - Each dispatched execution conversation decides the review complexity for its own implementation after direct-path verification. The coordinating conversation does not make this complexity decision or perform the code review. - For lower-complexity work, the dispatched execution Agent performs the code review. It checks implementation correctness, the requested path, scope, and real bugs without sending the PR to ChatGPT web Pro. - For complex or risky work, the corresponding dispatched execution conversation opens ChatGPT web Pro itself and submits the PR URL and exact head SHA for review. It asks Pro to review only implementation correctness and real bugs. -- The user grants standing authorization to submit the public PR URL, exact head SHA, and established review instructions to ChatGPT web Pro; execution conversations send them directly without requesting confirmation each time. - Development and review must avoid over-design and over-defensive recommendations. Do not request or add hypothetical guardrails, unrelated refactors, compatibility layers, style preferences, or scope expansion. -- Before any change is submitted to ChatGPT web Pro, complete the requested function, verify its direct real path, and provide the user with a working demo that uses the relevant real data or runtime. Start Pro review only after the user confirms that the function works. This gate applies to UI and non-UI work. Do not use Pro to discover whether an unfinished function basically works. - Independent dispatched conversations run their required reviews in parallel. Do not serialize independent Agent or Pro reviews through the coordinating conversation. - For Pro review, wait for the complete answer. Do not use an instant-answer result. Check at approximately five-minute intervals when necessary; a complete review can take more than 30 minutes. - Fix actionable blockers in the same PR. The dispatched execution conversation decides whether the changed complexity warrants another Pro review; trivial targeted follow-up edits can use its normal Agent review. @@ -119,46 +78,21 @@ Make the smallest root-cause change. Do not add unrelated refactors, abstraction - Logic-only changes on a UI surface do not need separate user UI confirmation when they do not cause a meaningful visual change. This includes interaction logic, data behavior, toggle behavior, popover close conditions, and copy-and-paste behavior. - Small, low-risk, and visually unambiguous changes can skip user UI confirmation after the coordinator checks the real path and visual evidence. Examples include a local font-size, spacing, alignment, or color adjustment. - Require user UI confirmation before merge when the change adds UI, meaningfully changes layout, information hierarchy, or the presentation of a core interaction, has multiple reasonable visual choices, or the user explicitly asks to confirm the style. -- User confirmation is a functional acceptance gate before Pro review. Ask only after the full function is complete and direct verification passes. Never ask the user to confirm a partially implemented UI. After confirmation, independent required Pro reviews may run in parallel. +- User UI confirmation is a final acceptance gate, not an intermediate development checkpoint. For work that requires it, ask only after the full function is complete, direct verification passes, and any complexity-based Pro review passes. Never ask the user to confirm a partially implemented UI. - After Pro approval, visual-only adjustments made from the user's final UI feedback do not require another Pro review. The coordinator checks that the delta is limited to the requested visual change, reruns the real path, and can then proceed to merge. If the adjustment changes functional logic or introduces new complex risk, reassess whether code review or Pro review is required. - The dispatched execution conversation closes its temporary review browser tabs after review finishes. -Use this review classification: - -- **Agent review**: documentation, README, copy, CSS, a local font/spacing/color change, a small direct UI behavior change, or a narrow logic fix with no process, persistence, migration, concurrency, security, or cross-project effect. -- **Pro review**: Launcher lifecycle, native host or Codex injection, process management, updater or release behavior, persistent-data migration, destructive file handling, complex concurrency, cross-project state, external boundary changes, or a broad external contributor PR. -- File count and UI file location do not determine review level. Use blast radius and failure cost. -- Skipping Pro for a low-risk change is the expected path, not an exception that needs extra justification. -- Before starting Pro, obtain the user's confirmation from the working demo, then use a stable PR head based on the current merge wave. Submit only the public PR URL, exact head SHA, and the instruction to review implementation correctness and real bugs without over-design or over-defense. -- If main advances after Pro, repeat Pro only when the integration changes reviewed functional logic or creates a real overlapping risk. A conflict-free merge commit or trivial targeted fix uses Agent review and direct-path verification. - -## 7. CI and integration waves - -- Use one PR for a coherent group of small issues. Do not create one PR per issue when the changes share a feature chain and can be reviewed and accepted together. -- Establish a merge wave before review starts. Keep its base stable while independent PRs run CI and Pro in parallel, then merge them in a planned conflict order. -- Do not merge an early low-priority PR when doing so will force several active related PRs to update their base, rerun packaging, and invalidate exact-head review. Urgent blockers are the exception. -- After main advances, update only PRs that are actually conflicting, not mergeable, or affected by overlapping behavior. Do not mechanically merge main into every open branch. -- Local validation should be focused. Let PR CI provide the broad repository check. Do not duplicate a successful full CI run with the same full local packaging unless the direct task path requires the local artifact. -- Avoid duplicate branch `push` and `pull_request` CI for the same SHA. Workflow owners should use branch filters and concurrency cancellation so superseded or duplicate runs do not consume both macOS and Windows builders. -- Use path-aware CI when available: Web-only, documentation, CSS, and copy changes use the fast lane; platform packaging runs only for Launcher, bundle, platform, updater, release, or final integration changes. -- CI may run as soon as each PR reaches a stable exact head. After the working demo is confirmed by the user, run independent required Pro reviews in parallel. Do not serialize them through the coordinating conversation. -- Present each task's working demo as soon as that task completes implementation and direct verification. Do not hold completed demos until the whole batch is ready. Other tasks continue in parallel while the user checks it. Related changes may share one preview only when they become ready together or must be integrated to work; this must not delay an already usable demo. - -## 8. Acceptance and issue status +## 7. Acceptance and issue status - Reviewer approval means ready for user inspection, not user acceptance. -- When work meets the user confirmation gate, put the complete directly verified function into the Taskboard-launched Codex App and ask the user to confirm that the function and final visual style work before starting any required Pro review. +- When work meets the UI confirmation gate, put the complete reviewed function into the Taskboard-launched Codex App and ask the user to confirm the final visual style only after implementation and any required Pro review are complete. - Do not merge work that meets the UI confirmation gate until the user confirms its style. After visual-only feedback is applied and directly verified, the change can proceed without repeating Pro review. - UI-surface work that does not meet the confirmation gate can proceed after the coordinator verifies the real path, visual impact, scope, and required review without a separate user UI pause. - After implementation and required review pass, move the issue to `in_review`. - Never move an issue to `done` unless the user explicitly accepts it or asks for completion. - If the user explicitly authorizes finishing and releasing the whole batch without another pause, that instruction authorizes the remaining review, merge, and release steps, but still does not authorize marking issues `done`. -- When a Taskboard issue is linked to a GitHub Issue, keep completion-state changes synchronized across both systems. Record the local state change in the GitHub Issue and the GitHub state change in the local issue. The release gate below controls when the GitHub Issue can be closed. - -- `in_progress` means active implementation, rework, verification, CI, or required review. Do not use it for indefinite notes, requirement collection, or an item that is only waiting to be scheduled. -- Keep explicit external waiting items visible, but do not count them as active code lanes or use them to block batch progress. -## 9. Merge and clean up +## 8. Merge and clean up - Merge only reviewed and authorized PRs into `main`. Do not merge unrelated open PRs. - Confirm the accepted commit is present on remote `main`. @@ -166,7 +100,7 @@ Use this review classification: - Preserve all task conversations for traceability. Do not archive or delete them. - Do not touch unrelated dirty worktrees, branches, files, or active sessions. -## 10. Release +## 9. Release - Release only when the user requests it or explicitly includes release in the task. - Merge all included product PRs first. Use a minimal version PR for the required version fields; do not alter release infrastructure without a separate requirement. @@ -174,11 +108,9 @@ Use this review classification: - Keep the DMG as the first release asset. - Record live build, signing, notarization, upload, and publication progress in the Taskboard. - Verify the tag target, release target, workflow result, asset order, and updater metadata. -- Merging code does not authorize closing a linked GitHub Issue. Keep it open after merge and report that the implementation is merged and awaiting release. -- Close a linked GitHub Issue only after a new version containing the change is published and verified. Reply with the merged PR and released version before closing, then record the closure in the local issue. - Do not overwrite the App in `/Applications`; leave the installed version available for update-check verification. -## 11. Batch completion +## 10. Batch completion A requested batch is complete only when: @@ -192,4 +124,144 @@ A requested batch is complete only when: - task conversations remain available; and - any requested release is published and verified. -Record-only and explicitly waiting issues are excluded from active implementation counts, but they must be reported accurately in the batch summary. +# Setup + +This repository follows standard agent skill setup: + +## Agent Skills + +Skills are loaded from `~/.agents/skills/` (base platform) or IDE-specific locations like `~/.claude/skills/`, `~/.devin/skills/`, `~/.opencode/skills/`, etc. + +### Skill installation + +Copy or symlink `skills/manage-taskboard` to the appropriate IDE skill directory: + +```bash +# Claude Code +ln -s /path/to/dashi-taskboard/skills/manage-taskboard ~/.claude/skills/manage-taskboard + +# OpenCode +ln -s /path/to/dashi-taskboard/skills/manage-taskboard ~/.opencode/skills/manage-taskboard + +# Devin +ln -s /path/to/dashi-taskboard/skills/manage-taskboard ~/.devin/skills/manage-taskboard +``` + +## MCP registration + +The repository includes an MCP server in `mcp/` that provides Taskboard access via Model Context Protocol. Install it using the MCP registry or manually in your IDE configuration. + +### Configuration examples + +**Claude Desktop:** +```json +{ + "mcpServers": { + "codex-taskboard": { + "command": "node", + "args": ["/path/to/dashi-taskboard/mcp/index.mjs"], + "cwd": "/path/to/dashi-taskboard/mcp" + } + } +} +``` + +**OpenCode:** +```json +{ + "mcp": { + "servers": { + "codex-taskboard": { + "command": "npx", + "args": ["mcp", "--server", "index.mjs"], + "cwd": "/path/to/dashi-taskboard/mcp" + } + } + } +} +``` + +**Cursor:** +```json +{ + "mcpServers": { + "codex-taskboard": { + "command": "node", + "args": ["/path/to/dashi-taskboard/mcp/index.mjs"], + "cwd": "/path/to/dashi-taskboard/mcp" + } + } +} +``` + +## Runtime setup + +### Local development + +1. Clone the repository: + ```bash + git clone https://github.com/afonsoft/dashi-taskboard + cd dashi-taskboard + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Build and start the service: + ```bash + npm run build + npm start + ``` + +4. Access the Taskboard UI at + +5. Use the CLI from the project root or install it globally: + ```bash + npm link + taskctl project create --id my-project --name "My Project" --workspace-path /absolute/path/to/repository + ``` + +### Integration with AI agents + +#### Claude Code + +Claude Code automatically uses the Taskboard skill if it's installed in `~/.claude/skills/manage-taskboard`. The skill will claim issues from `todo` status and work in the bound issue's branch/worktree. + +For manual thread attribution, set the environment variable: +```bash +export TASKBOARD_THREAD_ID= +``` + +#### OpenCode + +OpenCode follows the same pattern. Install the skill in `~/.opencode/skills/manage-taskboard` and set `TASKBOARD_THREAD_ID` in your agent configuration. + +#### Cursor + +Cursor reads MCP configurations from `~/.cursor/mcp.json`. The MCP server provides unified access to Taskboard operations across all AI platforms. + +#### Gemini + +Gemini CLI reads MCP configurations from `~/.gemini/mcp.json` or supports direct MCP server connections. + +#### Devin + +Devin reads skills from `~/.devin/skills/` and follows the same issue processing workflow. + +## Cloud collaboration + +Set up cloud mode for shared access: + +```bash +taskctl cloud login --url https://your-cloud-domain.com --actor-name "Team Member" +``` + +Configure the project mapping for local workspace: + +```bash +taskctl project map my-project --workspace-path /absolute/path/to/repository +``` + +See `server/cloud-config.mjs` for authentication details and `wrangler.jsonc` for Cloudflare deployment configuration. diff --git a/README.md b/README.md index eabfa3d58..2c940c5d1 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,21 @@ ln -s /absolute/path/to/codex-taskboard/skills/manage-taskboard \ The desktop app keeps this same directory synchronized with its bundled Skill. The Skill teaches Codex to inspect an issue, move it to `in_progress`, use optimistic versions, verify the work, and then move it to `in_review`; it moves the issue to `done` only after the user explicitly confirms acceptance or asks to mark it complete. +## AI integrations + +- **Claude Code**: Use `taskctl ... --thread-id ` or set `TASKBOARD_THREAD_ID` environment variable. +- **OpenCode, Cursor, Gemini**: Set `TASKBOARD_THREAD_ID` to the session identifier. MCP server available for universal tool access. +- **Devin, WorkBuddy, DeepSeek**: Plugin available in each ecosystem; uses the same underlying `taskctl` CLI. +- **CI/CD**: Use `TASKBOARD_URL` environment variable to point to a shared board. + +## MCP server + +An MCP server is available in `mcp/` that exposes Taskboard operations as Tools. The MCP server automatically uses `TASKBOARD_THREAD_ID` or `CODEX_THREAD_ID` for conversation attribution. + +## Non-Codex CLI usage + +When working outside Codex, always pass `--thread-id ` for operations that modify tasks or comments. For other CLI tools, you can also set the environment variable `TASKBOARD_THREAD_ID`. + ## Embed in Codex ### Manual: use a dedicated CDP port diff --git a/cli/taskctl.mjs b/cli/taskctl.mjs index ec3584d78..fffc9f36c 100755 --- a/cli/taskctl.mjs +++ b/cli/taskctl.mjs @@ -1126,13 +1126,13 @@ function recurrenceFromOptions(options) { function resolveThreadId(options, overrides) { const env = overrides.env ?? process.env; - const value = options["thread-id"] ?? env.CODEX_THREAD_ID; + const value = options["thread-id"] ?? env.TASKBOARD_THREAD_ID ?? env.CODEX_THREAD_ID; if (typeof value !== "string" || value.trim().length === 0) { - throw usageError("Codex conversation attribution requires --thread-id or CODEX_THREAD_ID"); + throw usageError("Taskboard conversation attribution requires --thread-id or TASKBOARD_THREAD_ID/CODEX_THREAD_ID"); } const threadId = value.trim(); if (threadId.length > 256) { - throw usageError("--thread-id and CODEX_THREAD_ID cannot exceed 256 characters"); + throw usageError("--thread-id, TASKBOARD_THREAD_ID, and CODEX_THREAD_ID cannot exceed 256 characters"); } return threadId; } diff --git a/mcp/README.md b/mcp/README.md new file mode 100644 index 000000000..4d097b6f1 --- /dev/null +++ b/mcp/README.md @@ -0,0 +1,193 @@ +# Codex Taskboard MCP Server + +This MCP server exposes Codex Taskboard operations as Tools, enabling universal AI integrations (Claude, OpenCode, Cursor, Gemini, VS Code Copilot, Devin, WorkBuddy, DeepSeek, CI) to work with Taskboard issues and projects. + +## Features + +- **Universal AI Integration**: Works with any AI platform that supports MCP +- **Automatic thread attribution**: Uses `TASKBOARD_THREAD_ID`, `CODEX_THREAD_ID`, or explicit `--thread-id` for conversation tracking +- **Full API coverage**: All taskctl commands as MCP tools (projects, issues, comments, attachments, cloud operations) +- **Session-aware**: Automatically handles conversation binding for proper issue claiming and status tracking + +## Quick Start + +### Installation + +```bash +npm install +cd mcp +``` + +### Start the MCP server + +```bash +node index.mjs +``` + +The server runs on standard input/output and is ready to accept MCP tool calls. + +## Tools + +### Project operations + +- `list_projects`: List all projects +- `get_project`: Get project by ID +- `create_project`: Create a new project + +### Issue operations + +- `list_issues`: List issues in a project with filtering (status, archived) +- `get_issue`: Get issue by ID +- `create_issue`: Create a new issue with full support for Git/worktree development context +- `update_issue`: Update an existing issue +- `move_issue`: Move issue (change status, claim, or review) +- `archive_issue`: Archive or restore an issue + +### Comment and attachment operations + +- `add_comment`: Add a comment to an issue +- `upload_attachment`: Upload an attachment to tasks or comments + +### Utility operations + +- `list_projects`: List all available projects + +## Configuration + +### Thread attribution + +The MCP server automatically uses the following priority for conversation attribution: + +1. Explicit `threadId` parameter passed with the tool call +2. `TASKBOARD_THREAD_ID` environment variable +3. `CODEX_THREAD_ID` environment variable +4. Default to "default-thread-id" if none are set + +**Example with Claude Code:** +```bash +export TASKBOARD_THREAD_ID=claude-conversation-123 +``` + +### Connecting to Taskboard service + +The server communicates with the Taskboard service via the `taskctl` CLI. It automatically: + +- Reads the Taskboard URL from the active runtime file (`.data/launcher-runtime.json`) or `CODEX_TASKBOARD_URL` environment variable +- For cloud mode, reads from the local companion service +- Handles thread attribution for all API calls + +## MCP Client Configuration + +### Claude Desktop + +```json +{ + "mcpServers": { + "codex-taskboard": { + "command": "node", + "args": ["/path/to/dashi-taskboard/mcp/index.mjs"], + "cwd": "/path/to/dashi-taskboard/mcp" + } + } +} +``` + +### OpenCode + +```json +{ + "mcp": { + "servers": { + "codex-taskboard": { + "command": "npx", + "args": ["mcp", "--server", "index.mjs"], + "cwd": "/path/to/dashi-taskboard/mcp" + } + } + } +} +``` + +### Cursor + +```json +{ + "mcpServers": { + "codex-taskboard": { + "command": "node", + "args": ["/path/to/dashi-taskboard/mcp/index.mjs"], + "cwd": "/path/to/dashi-taskboard/mcp" + } + } +} +``` + +## Advanced Usage + +### Working with Git/worktree development context + +When creating or updating issues, the MCP server supports the full development context: + +- `--git-branch`: Associate issue with a specific Git branch +- `--worktree-path`: Associate with a worktree +- `--worktree-branch`: Use a specific worktree branch + +**Example tool call:** +```json +{ + "name": "create_issue", + "arguments": { + "project": "my-project", + "title": "Implement feature", + "description": "Add new user dashboard", + "gitBranch": "feature/user-dashboard", + "threadId": "conversation-123" + } +} +``` + +### Cloud collaboration + +For shared Taskboard instances, the MCP server automatically uses the cloud companion endpoint when configured via `cloud login`. + +## Development + +### Testing tools + +```bash +# Test the MCP server locally +npm test +``` + +### Type checking + +```bash +# TypeScript type checking +npm run typecheck +``` + +## Troubleshooting + +### "Cannot connect to Taskboard service" + +1. Ensure the Taskboard service is running: `npm start` +2. Check `CODEX_TASKBOARD_URL` environment variable +3. Verify the runtime file is valid: `.data/launcher-runtime.json` + +### "Thread ID required" + +The server cannot find a thread identifier. Set `TASKBOARD_THREAD_ID` or `CODEX_THREAD_ID` environment variable: + +```bash +export TASKBOARD_THREAD_ID=your-session-id +``` + +## Security + +- All API calls use the same authentication as the Taskboard service +- Thread attribution ensures proper conversation isolation +- The MCP server runs with the same permissions as the Taskboard CLI + +## License + +Apache-2.0 diff --git a/mcp/index.mjs b/mcp/index.mjs new file mode 100644 index 000000000..5ba7bbf65 --- /dev/null +++ b/mcp/index.mjs @@ -0,0 +1,301 @@ +// Core implementation of MCP server + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { fileURLToPath } from "node:url"; + +import { spawnTaskctl } from "./taskctl-runner.js"; + +// Tool schemas +toolSchemas = { + list_projects: { + description: "List all projects", + parameters: z.object({}).strict(), + }, + get_project: { + description: "Get project by ID", + parameters: z.object({ + id: z.string().describe("Project ID"), + }).strict(), + }, + create_project: { + description: "Create a new project", + parameters: z.object({ + name: z.string().describe("Project name"), + id: z.string().optional().describe("Project ID (auto-generated if not provided)"), + workspacePath: z.string().optional().describe("Local workspace path"), + }).strict(), + }, + list_issues: { + description: "List issues in a project", + parameters: z.object({ + project: z.string().describe("Project ID"), + status: z.enum(["backlog", "todo", "in_progress", "in_review", "blocked", "done", "canceled"]).optional(), + archived: z.enum(["true", "false", "all"]).optional(), + }).strict(), + }, + get_issue: { + description: "Get issue by ID", + parameters: z.object({ + id: z.string().describe("Issue ID"), + }).strict(), + }, + create_issue: { + description: "Create a new issue", + parameters: z.object({ + project: z.string().describe("Project ID"), + title: z.string().describe("Issue title"), + description: z.string().optional().describe("Issue description"), + status: z.enum(["backlog", "todo", "in_progress", "in_review", "blocked", "done", "canceled"]).optional().describe("Issue status (defaults to 'backlog')"), + priority: z.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("Issue priority"), + labels: z.string().optional().describe("Comma-separated labels"), + threadId: z.string().optional().describe("Codex conversation ID (defaults to TASKBOARD_THREAD_ID)"), + gitBranch: z.string().optional().describe("Git branch for development context"), + worktreePath: z.string().optional().describe("Worktree path for development context"), + worktreeBranch: z.string().optional().describe("Worktree branch for development context"), + }).strict(), + }, + update_issue: { + description: "Update an issue", + parameters: z.object({ + id: z.string().describe("Issue ID"), + title: z.string().optional().describe("New title"), + description: z.string().optional().describe("New description"), + status: z.enum(["backlog", "todo", "in_progress", "in_review", "blocked", "done", "canceled"]).optional().describe("New status"), + priority: z.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("New priority"), + labels: z.string().optional().describe("New comma-separated labels"), + threadId: z.string().optional().describe("Codex conversation ID"), + }).strict(), + }, + move_issue: { + description: "Move issue (change status)", + parameters: z.object({ + id: z.string().describe("Issue ID"), + status: z.enum(["todo", "in_progress", "in_review", "blocked", "done", "canceled"]).describe("New status"), + threadId: z.string().optional().describe("Codex conversation ID (defaults to TASKBOARD_THREAD_ID)"), + }).strict(), + }, + archive_issue: { + description: "Archive or restore an issue", + parameters: z.object({ + id: z.string().describe("Issue ID"), + action: z.enum(["archive", "restore"]).describe("Action to perform"), + threadId: z.string().optional().describe("Codex conversation ID (defaults to TASKBOARD_THREAD_ID)"), + }).strict(), + }, + add_comment: { + description: "Add a comment to an issue", + parameters: z.object({ + issueId: z.string().describe("Issue ID"), + body: z.string().describe("Comment content"), + threadId: z.string().optional().describe("Codex conversation ID (defaults to TASKBOARD_THREAD_ID)"), + }).strict(), + }, + upload_attachment: { + description: "Upload an attachment to a task or comment", + parameters: z.object({ + target: z.enum(["task", "comment"]).describe("Target type"), + targetId: z.string().describe("Task or comment ID"), + filePath: z.string().describe("Local file path"), + contentType: z.string().optional().describe("MIME type"), + kind: z.enum(["inline", "attachment"]).optional().describe("Attachment kind"), + }).strict(), + }, +}; + +async function main() { + const server = new Server( + { + name: "codex-taskboard-mcp-server", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } + ); + + // Register all tools + Object.entries(toolSchemas).forEach(([name, schema]) => { + server.setRequestHandler("tools/call", async (request) => { + if (request.params.name === name) { + const { arguments: params } = request.params; + const validatedParams = schema.parameters.parse(params); + return await executeTool(name, validatedParams); + } + + return null; + }); + + server.setRequestHandler("tools/list", async () => { + return { + tools: [ + { + name, + description: schema.description, + inputSchema: schema.parameters.shape, + }, + ], + }; + }); + }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + + console.error("Codex Taskboard MCP server running on stdio"); +} + +async function executeTool(name, params) { + const threadId = params.threadId || process.env.TASKBOARD_THREAD_ID || process.env.CODEX_THREAD_ID || "default-thread-id"; + + try { + switch (name) { + case "list_projects": + return await spawnTaskctl(["project", "list", "--json"], { threadId }); + case "get_project": + return await spawnTaskctl(["project", "get", params.id, "--json"], { threadId }); + case "create_project": + const createArgs = ["project", "create", "--name", params.name, "--json"]; + if (params.id) createArgs.push("--id", params.id); + if (params.workspacePath) createArgs.push("--workspace-path", params.workspacePath); + return await spawnTaskctl(createArgs, { threadId }); + case "list_issues": + const listArgs = ["issue", "list", "--project", params.project, "--json"]; + if (params.status) listArgs.push("--status", params.status); + if (params.archived) listArgs.push("--archived", params.archived); + return await spawnTaskctl(listArgs, { threadId }); + case "get_issue": + return await spawnTaskctl(["issue", "get", params.id, "--json"], { threadId }); + case "create_issue": + const issueArgs = [ + "issue", "create", + "--project", params.project, + "--title", params.title, + "--json" + ]; + if (params.description) { + issueArgs.push("--description", params.description); + } + if (params.status) issueArgs.push("--status", params.status); + if (params.priority) issueArgs.push("--priority", params.priority); + if (params.labels) issueArgs.push("--labels", params.labels); + if (params.threadId) issueArgs.push("--thread-id", params.threadId); + if (params.gitBranch) issueArgs.push("--git-branch", params.gitBranch); + if (params.worktreePath) issueArgs.push("--worktree-path", params.worktreePath); + if (params.worktreeBranch) issueArgs.push("--worktree-branch", params.worktreeBranch); + return await spawnTaskctl(issueArgs, { threadId }); + case "update_issue": + const updateArgs = ["issue", "update", params.id, "--json"]; + if (params.title) updateArgs.push("--title", params.title); + if (params.description) updateArgs.push("--description", params.description); + if (params.status) updateArgs.push("--status", params.status); + if (params.priority) updateArgs.push("--priority", params.priority); + if (params.labels) updateArgs.push("--labels", params.labels); + if (params.threadId) updateArgs.push("--thread-id", params.threadId); + return await spawnTaskctl(updateArgs, { threadId }); + case "move_issue": + const moveArgs = ["issue", "move", params.id, "--status", params.status, "--json"]; + if (params.threadId) moveArgs.push("--thread-id", params.threadId); + return await spawnTaskctl(moveArgs, { threadId }); + case "archive_issue": + return await spawnTaskctl( + ["issue", params.action, params.id, "--json"], + { threadId } + ); + case "add_comment": + return await spawnTaskctl( + ["comment", "add", params.issueId, "--body", params.body, "--json"], + { threadId } + ); + case "upload_attachment": + const attachmentArgs = [ + "attachment", "upload", + params.target === "task" ? "--task" : "--comment", + params.targetId, + "--file", params.filePath, + "--json" + ]; + if (params.contentType) attachmentArgs.push("--content-type", params.contentType); + if (params.kind) attachmentArgs.push("--kind", params.kind); + return await spawnTaskctl(attachmentArgs, { threadId }); + + default: + throw new Error(`Tool ${name} not implemented`); + } + } catch (error) { + return { + content: [ + { + type: "text", + text: `Error: ${error.message}`, + }, + ], + isError: true, + }; + } +} + +function getEnv(threadId) { + const env = { ...process.env }; + env.TASKBOARD_THREAD_ID = threadId; + return env; +} + +async function spawnTaskctl(args, { threadId }) { + const { spawn } = await import("node:child_process"); + + const taskctlPath = require.resolve("./cli/taskctl.mjs"); + + return new Promise((resolve, reject) => { + const child = spawn("node", [taskctlPath, ...args], { + stdio: ["pipe", "pipe", "pipe"], + env: getEnv(threadId), + }); + + let stdout = ""; + let stderr = ""; + + child.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + child.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + child.on("error", (error) => { + reject(new Error(`Failed to spawn taskctl: ${error.message}`)); + }); + + child.on("close", (code) => { + if (code !== 0) { + try { + const errorJson = JSON.parse(stderr); + reject(new Error(errorJson.error.message)); + } catch { + reject(new Error(`taskctl exited with code ${code}: ${stderr}`)); + } + return; + } + + try { + const result = JSON.parse(stdout); + resolve(result); + } catch { + reject(new Error(`Invalid JSON from taskctl: ${stdout}`)); + } + }); + }); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); + }); +} + +export { main }; \ No newline at end of file diff --git a/mcp/package.json b/mcp/package.json new file mode 100644 index 000000000..46bd5fd3a --- /dev/null +++ b/mcp/package.json @@ -0,0 +1,29 @@ +{ + "name": "codex-taskboard-mcp-server", + "version": "1.0.0", + "description": "MCP server exposing Codex Taskboard API (taskctl) as Tools. Calls taskctl with proper thread attribution and automatic fallback to $TASKBOARD_THREAD_ID.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/afonsoft/dashi-taskboard" + }, + "type": "module", + "main": "index.mjs", + "scripts": { + "start": "node index.mjs", + "lint": "biome check", + "format": "biome format" + }, + "keywords": ["mcp", "taskboard", "codex", "issue", "cli"], + "author": "afonsoft", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.8.0", + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "biome": "^0.3.3" + }, + "engines": { + "node": ">=18" + } +} \ No newline at end of file diff --git a/mcp/taskctl-runner.js b/mcp/taskctl-runner.js new file mode 100644 index 000000000..56fef7bd0 --- /dev/null +++ b/mcp/taskctl-runner.js @@ -0,0 +1,57 @@ +// Utility to spawn taskctl process and return parsed JSON output + +const { spawn } = require("node:child_process"); +const path = require("node:path"); + +function fileURLToPath(url) { + return require("url").pathToFileURL(url).href; +} + +const taskctlPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "cli/taskctl.mjs"); + +export async function spawnTaskctl(args, { threadId }) { + const env = { ...process.env }; + const effectiveThreadId = threadId || env.TASKBOARD_THREAD_ID || env.CODEX_THREAD_ID; + if (effectiveThreadId) env.TASKBOARD_THREAD_ID = effectiveThreadId; + + return new Promise((resolve, reject) => { + const child = spawn("node", [taskctlPath, ...args], { + stdio: ["pipe", "pipe", "pipe"], + env, + }); + + let stdout = ""; + let stderr = ""; + + child.stdout.on("data", (data) => { + stdout += data.toString(); + }); + + child.stderr.on("data", (data) => { + stderr += data.toString(); + }); + + child.on("error", (error) => { + reject(new Error(`Failed to spawn taskctl: ${error.message}`)); + }); + + child.on("close", (code) => { + if (code !== 0) { + try { + const errorJson = JSON.parse(stderr); + reject(new Error(errorJson.error.message)); + } catch { + reject(new Error(`taskctl exited with code ${code}: ${stderr}`)); + } + return; + } + + try { + const result = JSON.parse(stdout); + resolve(result); + } catch { + reject(new Error(`Invalid JSON from taskctl: ${stdout}`)); + } + }); + }); +} \ No newline at end of file diff --git a/skills/manage-taskboard/SKILL.md b/skills/manage-taskboard/SKILL.md index 2c771758b..91b9c9589 100644 --- a/skills/manage-taskboard/SKILL.md +++ b/skills/manage-taskboard/SKILL.md @@ -1,6 +1,6 @@ --- name: manage-taskboard -description: Manage Codex Taskboard / e-taskboard work with taskctl. Use for taskboard issue IDs, status sync, comments, or taskctl cloud setup—not for unrelated product docs. +description: Manage Codex Taskboard / e-taskboard work with taskctl. Use for taskboard issue IDs, status sync, comments, or taskctl cloud setup—not for unrelated product docs. Include MCP server support for universal AI integrations (Claude, OpenCode, Cursor, Gemini, VS Code Copilot, Devin, WorkBuddy, DeepSeek, CI). --- # Manage Taskboard @@ -15,14 +15,15 @@ Open only the relevant section of [references/cli.md](references/cli.md) when co - On macOS, when no binary is injected and the desktop app is installed, use `'/Applications/Codex Taskboard.app/Contents/Resources/bin/taskctl' issue get ID --json`. Keep the single quotes because the path contains a space. The packaged wrapper reads the active launcher runtime; do not search the filesystem for another CLI or reconstruct the tokenized URL. - On Linux, when no binary is injected and Codex was started by the desktop app, use `taskctl issue get ID --json`. The desktop app adds its packaged wrapper to the managed Codex `PATH`; do not search the filesystem for another CLI or reconstruct the tokenized URL. - If that exact command reaches a sandbox restriction on the loopback service, retry the same command with the required permission. Do not switch binaries or endpoints. +- When using AI agents (Claude, OpenCode, Cursor, Gemini, etc.), pass the agent's session identifier with `--thread-id` or set `TASKBOARD_THREAD_ID` environment variable. The default fallback is `CODEX_THREAD_ID`. ## Terminology: local companion -In this product, **companion** means the **device-local loopback service** used for cloud mode (Codex/Git/Skill/MCP, path mapping, Basic Auth proxy). Related names: `local companion`, `loopback companion`, `CODEX_TASKBOARD_COMPANION_URL`, `cloud-companion.json`, `LOCAL_COMPANION_REQUIRED`. +In this product, **companion** means the **device-local loopback HTTP service** that `taskctl` talks to in cloud mode. It applies Basic Authentication, stores device-only project path mappings, and keeps Codex/Git/Skill/MCP capabilities on the machine. It is not a chat persona and not a separate public “companion product API”. -When writing Chinese, keep the English word or use **本地 companion** / **本地配套服务** / **环回代理**. Never translate as **伴侣** or invent **伴侣 API**. Ordinary task/comment/attachment HTTP routes (`/api/tasks`, `/api/comments`, `/api/attachments`, …) are the **Taskboard HTTP API** (or local server API)—not “companion API”. +When writing Chinese, keep the English word or use **本地 companion** / **本地配套服务** / **环回代理**. Never translate as **伴侣** or invent **伴侣 API**. Ordinary task/comment/attachment HTTP routes (`/api/tasks`, `/api/comments`, `/api/attachments`, …) are the **Taskboard HTTP API** (or local server API)—not "companion API". -## Core workflow +## Core workflow (extends Codex rules) 1. For an existing issue, first run `issue get` and `comment list`. Also run `attachment list --task`. On the first handoff, omit `--after` and read the full results. Keep the separate `nextCursor` from each list. When the same task resumes, run `issue get` again, then pass each saved cursor to its matching list with `--after` so only new or modified entries are returned. Comment lists include the attachments on returned comments; use `attachment list --comment` with its own cursor when a known comment attachment list can grow. Read the description and latest comments before deciding whether to start. Treat comments as current requirements, including returned work. If they say to wait, not execute, or not start now, stop and report without changing the status. 2. Treat `backlog` as not approved for execution. Unless the user explicitly authorizes that issue, do not claim it, move it to another status, or perform task work; its assignee alone is not authorization. If work may start, claim it before reading code, downloading attachments, analyzing the implementation, or doing any other task work. Move a claimable `todo` to `in_progress` with its current `version`; do not continue until the move succeeds. If it is already `in_progress`, continue only when it is bound to the current conversation. Never move an issue claimed by another conversation. @@ -32,6 +33,35 @@ When writing Chinese, keep the English word or use **本地 companion** / **本 6. Verify the requested operation path. Add a comment with the changes, verification result, outcome, and remaining risks. Read the issue again, then move it to `in_review` with its current `version`. 7. Move an issue to `done` only after the user explicitly accepts it or asks to complete it. Use `blocked` when work cannot continue and `canceled` when it will not continue. +## AI integrations + +- **Claude Code**: Use `taskctl ... --thread-id ` or set `TASKBOARD_THREAD_ID` environment variable. +- **OpenCode, Cursor, Gemini**: Set `TASKBOARD_THREAD_ID` to the session identifier. MCP server available for universal tool access. +- **Devin, WorkBuddy, DeepSeek**: Plugin available in each ecosystem; uses the same underlying `taskctl` CLI. +- **CI/CD**: Use `TASKBOARD_URL` environment variable to point to a shared board. + +## MCP server + +An MCP server is available in `mcp/` that exposes Taskboard operations as Tools. The MCP server automatically uses `TASKBOARD_THREAD_ID` or `CODEX_THREAD_ID` for conversation attribution. + +## Non-Codex CLI usage + +When working outside Codex, always pass `--thread-id ` for operations that modify tasks or comments. For other CLI tools, you can also set the environment variable `TASKBOARD_THREAD_ID`. + +## Bindings + +### Codex conversation binding + +Codex sets `CODEX_THREAD_ID` automatically. Other CLIs (including MCP) prioritize `TASKBOARD_THREAD_ID` over `CODEX_THREAD_ID`. + +### Git/worktree binding + +Use `--git-branch` or `--worktree-path`/`--worktree-branch` options when creating issues to associate development context. + +### Cloud companion + +Cloud mode uses the local companion loopback service with Basic Authentication. See `references/cli.md` for `cloud login`, `project map`, and related commands. + ## Other operations - Run `taskctl project readme get [PROJECT_ID]` to inspect project architecture, constraints, and conventions before planning or executing complex tasks.