feat(task-utils): bring MCP task server up to date, factored for ticket-utils reuse (from #157) - #216
feat(task-utils): bring MCP task server up to date, factored for ticket-utils reuse (from #157)#216alex-nsheaps[bot] wants to merge 6 commits into
Conversation
…ored for ticket-utils reuse Reconciles the task-utils redesign from PR #157 onto current main (nx-based): the Bun/TS MCP task server (task_create/update/list/get fallback for contexts without the built-in Task tools), the redesigned hook suite (flat-YAML store, write-gate, 0-or-1 invariant, legacy/pull sync, native-task advisory), the manage-tasks + mcp-task-tools skills, and supporting docs. Wiring adapted to current main (NOT copied from #157's stale base): - mise.toml: added build-task-mcp / test-task-mcp tasks; chained into the nx build/test fan-out so CI compiles + tests the MCP server (it is not a root workspace package — it ships as source and builds on-device via mcp/build.sh). - marketplace.json: updated the existing task-utils entry (description, +mcp keyword, version 0.4.0) — additive, existing entries untouched. - integration test made self-contained (build-if-missing) so it passes under nx test ordering as well as `mise run test-task-mcp`. - ts sources reformatted to the repo's prettier config (printWidth 100). ticket-utils reuse: the MCP base (git-helper auto-commit engine, store flat-file CRUD + git-root resolution, server bootstrap, build/launch/prewarm scripts) is generic and intended to back a future ticket-utils MCP server. The reuse boundary (what is generic vs task-specific) is documented in docs/mcp-base-reuse.md. No ticket-utils code is built here. Source PR #157 left open for Nate to close. Co-Authored-By: Agent Alex Picard <alex-nsheaps[bot]@users.noreply.github.com>
Plugin Version PreviewPreview only — plugin versions and
|
There was a problem hiding this comment.
Review: Correctness 90% | Security 95% | Maintainability 88% | Simplicity 85%
Overview
Reconciles task-utils + MCP task server onto current nx main. 50 files, 2 commits, 66 unit/integration + 7 hook tests. The core acceptance criterion (MCP-created task satisfies the write-gate hook) is verified end-to-end. PR body is thorough and accurate.
What was done well
- Two clean commits: the main feat commit + auto-bump chore. No fixup/WIP commits.
- Atomic JSON→YAML migration:
task-store-lib.shshared lib ensures hooks and MCP server agree on store location.task-sync-from-legacy.shcorrectly handles both JSON and YAML legacy formats. build.sh: Lock-guarded, atomic temp-then-move build with version-pinned binary invalidation. Well-engineered.- Deletion path checked before terminal-status guard in
tasks.ts(correct ordering —deletedcannot hit the "completed is terminal" check). - Dual enforcement: MCP server re-implements the same invariants (0-or-1 in_progress, validation-steps required, RESULT lines required) that the hooks enforce for built-in Task tools. Intentional and correctly documented in
tasks.ts. - Reuse factoring (
git-helper.ts,store.ts,build.sh,launch.sh,prewarm.sh) is solid. The generic/task-specific boundary documented indocs/mcp-base-reuse.mdis a good pattern for the future ticket-utils server. - Test coverage: hook-integration test exercises the full create→promote→gate→complete lifecycle, which is the core acceptance criterion.
Follow-ups:
P2 — Atomicity veto disk-fallback uses jq against a YAML file
hooks/task-invariant.sh ~line 235–237:
TASK_SUBJECT="$(jq -r '.tool_input.subject // empty' <<<"$INPUT" 2>/dev/null || true)"
if [[ -z "$TASK_SUBJECT" ]]; then
# Fallback: read from disk
TASK_SUBJECT="$(jq -r '.subject // empty' "$TASK_FILE" 2>/dev/null || true)"
fiTASK_FILE is now $FLAT_STORE/${TASK_ID}.yaml (YAML), but jq only parses JSON. When an agent calls TaskUpdate(taskId=N, status=in_progress) without supplying a subject (the common case — subject doesn't change on status transitions), the disk-fallback always silently returns empty and the atomicity keyword veto is bypassed.
The primary path (reading tool_input.subject from the JSON hook input) works correctly. The fix is one line — replace the jq disk read with the same grep/sed pattern used elsewhere in the file:
TASK_SUBJECT="$(grep -m1 '^subject: ' "$TASK_FILE" 2>/dev/null | sed 's/^subject: //; s/^"//; s/"$//')"P2 (not P1) because the atomicity check also runs in-process in tasks.ts for MCP tool calls, and jq silently returns empty rather than producing a wrong value — it degrades gracefully.
P2 — task-sync-pull.sh ignores the syncPullEnabled / syncPullIntervalSecs settings-file keys
task-utils.settings.yaml documents syncPullEnabled: true and syncPullIntervalSecs: 60 as user-configurable, but task-sync-pull.sh reads only TASK_UTILS_SYNC_PULL and TASK_UTILS_PULL_INTERVAL_SECS env vars. Neither settings-file key is ever consulted. Users who set these in plugins.settings.yaml will see no effect.
Fix: source plugin-settings-lib.sh and call plugin_get_config "syncPullEnabled" "true" / plugin_get_config "syncPullIntervalSecs" "60" in addition to the existing env-var opt-out — matching the pattern used in require-task-in-progress.sh.
P2 — mise run test-task-mcp uses cd … && command chains
mise.toml test-task-mcp task:
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
"""The adjacent build-task-mcp task correctly uses the dir key. Prefer dir = "plugins/claude-code/task-utils/mcp" for the bun test line (and a separate task or dir = "." with an explicit path for the hook test), consistent with the project's bash-scripting conventions.
🤖 Automated review by Henry Oldenburg (QA). Session: henry/agents#216.
There was a problem hiding this comment.
move all the docs in docs/research in this pr into nsheaps/.org@main and remove from this PR so it's scoped just for the task-utils plugin + mcp server
There was a problem hiding this comment.
move the implementation of each hook to on top of each other, with the bottom being stacked on top of the mcp server pr
There was a problem hiding this comment.
make the mcp server it's own pr. Make it follow repo patterns. Make the service entry point in /services/task-mcp-service and use lib/ and package/ etc to properly abstract re-usable bits. Not building the ticket server in this PR but we know we're gonna be re-using it, and we know that tasks between orgs, and tickets between projects between orgs can all have different schemas stemming from the base.
| @@ -1,15 +1,15 @@ | |||
| --- | |||
| name: task-manage | |||
| name: manage-tasks | |||
There was a problem hiding this comment.
it should be called task-manage, this is an old artifact. but we might wanna keep other stuff here
… fixes) - Rename skill manage-tasks → task-manage (comment 3325267832) - Rename skill mcp-task-tools → tool-task-mcp (comment 3325270819) - Remove docs/research/ from plugin dir — to be moved to nsheaps/.org@main (comment 3325250273) - Update all cross-references in README.md, plugin.json, and SKILL.md files Architectural feedback (MCP server split + hook stacking) posted to Discord for Nate ack before executing.
There was a problem hiding this comment.
Immediate fixes landed in (pushed to branch):\n- Skill renamed → (comment #3325267832)\n- Skill renamed → (comment #3325270819)\n- removed from plugin dir (comment #3325250273) — 9 files deleted; need to move them to separately\n\nAwaiting your direction on two architectural items:\n\n1. MCP server split + service structure (comment #3325263429): You said to make the MCP server its own PR, with entry point at using and structure. My plan: move out of this plugin into a new tree with proper layering, open a new PR for it. This PR would then contain only the hooks + skills (no MCP server). Does that match your intent?\n\n2. Hook stacking (comment #3325254850): You said "move the implementation of each hook to on top of each other, with the bottom being stacked on top of the mcp server pr". I read this as: the hooks should be separate commits stacked on top of the new MCP server PR (i.e., hooks PR depends on MCP service PR). Is that correct? Or did you mean something about how the hooks are ordered within this PR?\n\nHolding on both until I hear back.
| ## 4. Doctrine — breakdown pattern (the worked example) | ||
|
|
||
| Canonical reference: `nsheaps/agents/docs/journal/2026/05/16/entry002-managing-tasks-example.md`. Excerpt: | ||
| Canonical reference: `nsheaps/agents/docs/journal/2026/05/16/managing-tasks-example.md`. Excerpt: |
There was a problem hiding this comment.
reference incorrect. remove file reference
…SKILL.md Line 89 referenced nsheaps/agents/docs/journal/2026/05/16/managing-tasks-example.md which does not exist. Replaced with generic label per Nate review comment. Co-Authored-By: Agent Alex Picard <alex-nsheaps[bot]@users.noreply.github.com>
…-service (#231) The MCP server is now its own PR (agents#231) at services/task-mcp-service/ with a lib/src split (generic store-base + git-helper in lib/, task-specific code in src/). This PR retains only the plugin + hooks. - Remove plugins/claude-code/task-utils/mcp/ entirely - Update .mcp.json launch path → services/task-mcp-service/launch.sh - Update hooks.json prewarm path → services/task-mcp-service/prewarm.sh - Update mise.toml build-task-mcp / test-task-mcp → services/task-mcp-service/ Co-Authored-By: Agent Alex Picard <alex-nsheaps[bot]@users.noreply.github.com>
Summary
Splits the task-utils material out of the bundled, conflicting PR #157 and reconciles it onto current
main(which has since migrated to the nx build system — the primary cause of #157's conflicts).Brings
task-utilsup to date with:mcp/, Bun/TS):task_create/task_update/task_list/task_get— a fallback for contexts where the built-in Task tools are unavailable (e.g. Claude Code on the web), so the write-gate hook stays satisfiable. Flat-YAML store under.claude/tasks/<id>.yaml, best-effort templated git auto-commit/push, shipped as source + built on-device (mcp/build.sh).manage-tasks(renamed fromtask-manage) + newmcp-task-tools.docs/mcp-base-reuse.md.Factored for ticket-utils reuse
The MCP base is structured so the SAME scaffolding can back a future
ticket-utilsMCP server:mcp/src/git-helper.ts—tryGitAutoCommit()already takes the commit message as a parameter; onlybuildCommitMessage()is task-flavoured.mcp/src/store.ts— flat-file CRUD + git-root resolution is format-agnostic; only the env-var name, store sub-path, and record type are task-specific.mcp/build.sh/launch.sh/prewarm.sh+ theserver.tsbootstrap are fully generic.The generic-vs-task-specific boundary and the recommended extraction path are documented in
docs/mcp-base-reuse.md. No ticket-utils code is built here — this only keeps the base reusable and says so.Wiring reconciled to current main (NOT copied from #157's stale base)
mise.toml: addedbuild-task-mcp/test-task-mcptasks and chained them into the nxbuild/testfan-out, somise run build/mise run test(what CI runs) now also compile + test the MCP server. The MCP server is intentionally not a root workspace package (it ships as source and builds on-device)..claude-plugin/marketplace.json: updated the existing task-utils entry only (description,+mcpkeyword, version0.4.0). Add opt-out for require-task-in-progress gate via env var #157's marketplace.json was discarded (it was built on a stale base that removed reddit/review-utils).plugin.jsonbumped to0.4.0to match.printWidth: 100).Validation (all run locally, green)
mise run test-task-mcp→ 66 pass / 0 fail (unit + integration) + all 7 hook-integration tests pass.mise run validate→ all plugin manifests + marketplace validate.prettier --check(plugins workspace) → clean.bash -non all new shell scripts → clean.bun install --frozen-lockfile(mcp) → lockfile intact.Toolchain: bun 1.3.13/1.3.14, mise 2026.5.16 (both available locally).
Notes
Co-Authored-By: Agent Alex Picard <alex-nsheaps[bot]@users.noreply.github.com>