Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,27 +44,27 @@ description = "Build every workspace package via nx (root and the plugins packag
depends = ["build-task-mcp"]
run = "bunx nx run-many --target=build"

# The task-utils MCP server lives under plugins/claude-code/task-utils/mcp and is
# NOT a workspace package (it ships as source and builds on-device via mcp/build.sh).
# The task-mcp-service lives under services/task-mcp-service and is NOT a
# workspace package (it ships as source and builds on-device via build.sh).
# These dedicated tasks let CI compile + test it without registering it in the
# root bun workspace. The base scaffolding (store/git-helper/server bootstrap +
# build/launch/prewarm scripts) is intended to back a future ticket-utils MCP
# server too — see plugins/claude-code/task-utils/docs/mcp-base-reuse.md.
# root bun workspace. The generic lib/ (store-base, git-helper) is intended to
# back a future ticket-mcp-service — see services/task-mcp-service/ for the
# lib/src split.
[tasks.build-task-mcp]
description = "Compile the task-utils MCP server native binary (mcp/dist/task-mcp) — local-dev/CI check; end users build on-device via mcp/build.sh"
dir = "plugins/claude-code/task-utils/mcp"
description = "Compile the task-mcp-service native binary (dist/task-mcp) — local-dev/CI check; end users build on-device via build.sh"
dir = "services/task-mcp-service"
run = """
bun install
bun build --compile --outfile dist/task-mcp src/server.ts
echo "Built plugins/claude-code/task-utils/mcp/dist/task-mcp"
echo "Built services/task-mcp-service/dist/task-mcp"
"""

[tasks.test-task-mcp]
description = "Run the task-utils MCP server unit + integration tests + the hook write-gate integration test"
description = "Run the task-mcp-service unit + integration tests + the hook write-gate integration test"
depends = ["build-task-mcp"]
run = """
cd plugins/claude-code/task-utils/mcp && bun test
cd "$MISE_PROJECT_ROOT" && bash plugins/claude-code/task-utils/mcp/test/hook-integration.test.sh
cd services/task-mcp-service && bun test
cd "$MISE_PROJECT_ROOT" && bash services/task-mcp-service/test/hook-integration.test.sh
"""

[tasks.test]
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-code/task-utils/.mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"mcpServers": {
"task-mcp": {
"command": "bash",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp/launch.sh"],
"args": ["${CLAUDE_PLUGIN_ROOT}/services/task-mcp-service/launch.sh"],
"env": {
"CLAUDE_PLUGIN_ROOT": "${CLAUDE_PLUGIN_ROOT}",
"CLAUDE_PLUGIN_DATA": "${CLAUDE_PLUGIN_DATA}",
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-code/task-utils/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/mcp/prewarm.sh",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/services/task-mcp-service/prewarm.sh",
"timeout": 10
}
]
Expand Down
195 changes: 0 additions & 195 deletions plugins/claude-code/task-utils/mcp/src/store.ts

This file was deleted.

2 changes: 1 addition & 1 deletion plugins/claude-code/task-utils/skills/task-manage/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ This rule is enforced by a `BEHAVIOR_CHANGING_COACH` line in the TaskCreate path

## 4. Doctrine — breakdown pattern (the worked example)

Canonical reference: `nsheaps/agents/docs/journal/2026/05/16/managing-tasks-example.md`. Excerpt:
Example (worked breakdown):

```
phase 1: [ ] Jack upgrade
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ die() {
# CLAUDE_PLUGIN_ROOT is normally injected; fall back to this script's own dir
# so the script also works when run by hand from a checkout.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
MCP_SRC_DIR="$PLUGIN_ROOT/mcp"
# CLAUDE_PLUGIN_ROOT is injected by Claude Code; when running by hand from the
# monorepo the script lives at services/task-mcp-service/ so we go up two
# levels (services/ -> repo root) to reach the plugin root equivalent.
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
MCP_SRC_DIR="$PLUGIN_ROOT/services/task-mcp-service"

# CLAUDE_PLUGIN_DATA persists across plugin updates — the right home for a
# built artifact. Fall back to a per-plugin dir under the user's claude config
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
/**
* git-helper.ts — best-effort git auto-commit + push for task files.
* lib/git-helper.ts — generic best-effort git auto-commit + push engine.
*
* When the MCP server creates, updates, or deletes a task file, it attempts to
* `git add` + `git commit` + `git push` the change so task state is persisted
* as a repo artifact.
* Generic: contains no task-specific assumptions. Reusable by any MCP server
* that writes files to a git repo (e.g. a future ticket-mcp-service).
*
* The CRUD operation type and tryGitAutoCommit() are fully decoupled from the
* commit-message format — callers supply their own commit message string
* (e.g. "chore(tasks): …" or "chore(tickets): …").
*
* Guarantees:
* - TEMPLATED conventional-commit messages — never an AI model call.
* - BEST-EFFORT — any git failure (not a repo, no remote, nothing staged,
* push rejected, network error, git not on PATH) is caught, logged, and
* the calling tool STILL succeeds. A task write must never fail on git.
* - BEST-EFFORT — any git failure is caught, logged, and the calling tool
* still succeeds. A store write must never fail on git.
* - Git is only attempted when the store is inside a git working tree.
*
* Errors are appended to `<storeRoot>/.git-auto-commit.log` for debugging.
Expand All @@ -19,7 +21,8 @@ import { execFileSync } from "node:child_process";
import { appendFileSync } from "node:fs";
import { join } from "node:path";

export type TaskOperation = "create" | "update" | "delete";
/** Generic CRUD operation type — valid for any flat-file store. */
export type CrudOperation = "create" | "update" | "delete";

export interface GitAutoCommitResult {
/** true if a commit was produced */
Expand All @@ -34,32 +37,6 @@ export interface GitAutoCommitResult {
detail: string;
}

/**
* Build the templated conventional-commit message for a task operation.
*
* create -> chore(tasks): add task <id> <subject>
* update -> chore(tasks): update task <id> (<status>)
* delete -> chore(tasks): remove task <id>
*/
export function buildCommitMessage(
operation: TaskOperation,
id: string,
opts: { subject?: string; status?: string } = {},
): string {
switch (operation) {
case "create": {
const subject = (opts.subject ?? "").trim();
return `chore(tasks): add task ${id}${subject ? ` ${subject}` : ""}`;
}
case "update": {
const status = (opts.status ?? "").trim();
return `chore(tasks): update task ${id}${status ? ` (${status})` : ""}`;
}
case "delete":
return `chore(tasks): remove task ${id}`;
}
}

function git(cwd: string, args: string[]): { ok: boolean; out: string } {
try {
const out = execFileSync("git", args, {
Expand All @@ -79,17 +56,17 @@ function git(cwd: string, args: string[]): { ok: boolean; out: string } {
function logError(
storeRoot: string,
filePath: string,
operation: TaskOperation,
operation: CrudOperation,
stage: string,
detail: string,
): void {
const line =
`[${new Date().toISOString()}] task-utils-mcp git auto-commit\n` +
`[${new Date().toISOString()}] mcp-service git auto-commit\n` +
` file: ${filePath}\n` +
` operation: ${operation}\n` +
` git-status: failed at ${stage} stage\n` +
` error: ${detail}\n` +
` action: logged; task file written; commit + push abandoned\n`;
` action: logged; store file written; commit + push abandoned\n`;
try {
appendFileSync(join(storeRoot, ".git-auto-commit.log"), line, "utf8");
} catch {
Expand All @@ -98,17 +75,17 @@ function logError(
}

/**
* Attempt to auto-commit and push a task file. Never throws.
* Attempt to auto-commit and push a store file. Never throws.
*
* @param storeRoot the task-store root directory (also the git cwd)
* @param filePath absolute path to the task file that changed
* @param operation create | update | delete
* @param commitMessage the templated commit message
* @param storeRoot the store root directory (also the git cwd)
* @param filePath absolute path to the file that changed
* @param operation create | update | delete
* @param commitMessage caller-supplied conventional-commit message
*/
export function tryGitAutoCommit(
storeRoot: string,
filePath: string,
operation: TaskOperation,
operation: CrudOperation,
commitMessage: string,
): GitAutoCommitResult {
// 1. Only proceed inside a git working tree.
Expand Down
Loading
Loading