diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a86ca32 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog + +All notable changes to Taskmaster are documented here. + +## [2.3.0] - 2026-02-25 + +### Changed +- Hook `reason` field now contains only the TASKMASTER_DONE signal token instead + of the full completion checklist. This keeps user-visible terminal output + minimal — one collapsed line rather than a wall of text. +- Full completion checklist lives exclusively in SKILL.md, which is always + loaded as system context. The agent already has all instructions; the reason + field no longer needs to duplicate them. +- Added `last_assistant_message` as the primary done-signal detection path + (faster, no transcript file parsing required). Transcript-based check is + retained as fallback. +- Removed `HAS_RECENT_ERRORS` / `stop_hook_active` escape-hatch logic in favor + of the explicit TASKMASTER_DONE signal protocol. +- `hooks/check-completion.sh` brought in sync with root-level canonical source. + +## [2.2.0] - 2026-02-19 + +### Changed +- Default `TASKMASTER_MAX` set to 100 (previously 0 / infinite). +- Moved full completion checklist from hook `reason` into SKILL.md system + context (first pass; reason still contained a short prompt). +- `install.sh` made POSIX-portable (`sh` shebang, conditional `pipefail`). + +### Fixed +- Resolved infinite loop caused by `set -euo pipefail` in sh-sourced contexts. + +## [2.1.0] + +### Added +- Session-scoped counter with configurable `TASKMASTER_MAX` escape hatch. +- Subagent skip: transcripts shorter than 20 lines are ignored. +- `TASKMASTER_DONE_PREFIX` env var for customising the done token prefix. + +## [2.0.0] + +### Added +- TASKMASTER_DONE signal protocol: stop is allowed only after the agent emits + `TASKMASTER_DONE::` in its response. +- Transcript-based done-signal detection. + +## [1.0.0] + +### Added +- Initial release: stop hook that blocks agent from stopping prematurely. +- Completion checklist injected via hook `reason` field. +- `TASKMASTER_MAX` loop guard. diff --git a/LESSONS.md b/LESSONS.md new file mode 100644 index 0000000..abb2a7d --- /dev/null +++ b/LESSONS.md @@ -0,0 +1,64 @@ +# Lessons Learned + +Append-only log of debugging insights and non-obvious solutions. + +--- + +## 2026-02-25T14:00 - Claude Code hook `reason` is dual-use: user-visible AND AI context + +**Problem**: The taskmaster stop hook embedded a full 5-item completion checklist in the `reason` field of `{ "decision": "block", "reason": "..." }`. Every stop attempt printed the entire checklist to the user's terminal. + +**Root Cause**: Claude Code's stop hook `reason` field serves two purposes simultaneously — it is displayed to the user in the terminal UI ("Stop hook error: ...") AND injected back into the AI conversation as context. Putting verbose instructions in `reason` to inform the AI caused them to also appear as user-visible output. + +**Lesson**: The `reason` field is not a private AI channel. Anything in `reason` is shown to the human. Persistent AI instructions belong in SKILL.md (system context loaded at session start), not in transient hook `reason` values. The `reason` should carry only the minimum transient signal the agent needs right now. + +**Code Issue**: +```bash +# Before (verbose — full checklist in reason, shown to user) +REASON="${LABEL}: ${PREAMBLE} + +Before stopping, do each of these checks: +1. RE-READ THE ORIGINAL USER MESSAGE(S)... +2. CHECK THE TASK LIST... +[etc]" +jq -n --arg reason "$REASON" '{ decision: "block", reason: $reason }' + +# After (minimal — only the done signal; checklist lives in SKILL.md) +DONE_SIGNAL="${DONE_PREFIX}::${SESSION_ID}" +jq -n --arg reason "$DONE_SIGNAL" '{ decision: "block", reason: $reason }' +``` + +**Solution**: Strip the checklist from `reason`. Put it only in SKILL.md, which is always loaded as system context. The `reason` now contains only the done signal token the agent must emit. + +**Prevention**: When designing Claude Code hooks, ask: "Does this text need to be in the reason, or is it already in system context?" If it's in a skill file, it doesn't belong in `reason`. + +--- + +## 2026-02-25T14:30 - `last_assistant_message` is faster than transcript scanning for done-signal detection + +**Problem**: The hook was opening and scanning potentially large transcript JSON files on every stop attempt to detect whether the agent had emitted the done signal. + +**Root Cause**: The hook relied exclusively on transcript-file parsing, which requires disk I/O and JSON scanning on every invocation. + +**Lesson**: The Claude Code hook input JSON exposes `last_assistant_message` directly. Checking that field is O(1) and avoids the file read in the common case (agent just emitted the signal in its latest message). + +**Code Issue**: +```bash +# Before (always scans transcript file) +if tail -400 "$TRANSCRIPT" 2>/dev/null | grep -Fq "$DONE_SIGNAL"; then + HAS_DONE_SIGNAL=true +fi + +# After (fast path via last_assistant_message, transcript as fallback) +LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // ""') +if [ -n "$LAST_MSG" ] && echo "$LAST_MSG" | grep -Fq "$DONE_SIGNAL" 2>/dev/null; then + HAS_DONE_SIGNAL=true +fi +if [ "$HAS_DONE_SIGNAL" = false ] && [ -f "$TRANSCRIPT" ]; then + if tail -400 "$TRANSCRIPT" 2>/dev/null | grep -Fq "$DONE_SIGNAL"; then + HAS_DONE_SIGNAL=true + fi +fi +``` + +**Prevention**: Always check `last_assistant_message` before falling back to transcript file parsing in stop hooks. diff --git a/SKILL.md b/SKILL.md index b0f5e63..a164c4f 100644 --- a/SKILL.md +++ b/SKILL.md @@ -6,7 +6,7 @@ description: | parseable done signal in its final response. Provides deterministic machine detection for true completion. author: blader -version: 2.1.0 +version: 2.3.0 --- # Taskmaster @@ -21,28 +21,92 @@ completion review cycle. 1. **Agent tries to stop** — the stop hook fires every time. 2. **Hook scans transcript** for a parseable token: `TASKMASTER_DONE::` -3. **Token missing** — hook blocks stop and injects a checklist plus the exact - token to emit when truly done. +3. **Token missing** — hook blocks stop with a brief trigger message. 4. **Token present** — hook allows stop and clears session counter state. +## Completion Checklist + +When the stop hook blocks you, execute this checklist before emitting the +done signal. Do NOT narrate or summarize — just DO the work. + +1. **RE-READ THE ORIGINAL USER MESSAGE(S).** List every discrete request or + acceptance criterion. For each one, confirm it is fully addressed — not + just started, FULLY done. If the user explicitly changed their mind, + withdrew a request, or told you to stop or skip something, treat that + item as resolved and do NOT continue working on it. + +2. **CHECK THE TASK LIST.** Review every task. Any task not marked completed? + Do it now — unless the user indicated it is no longer wanted. + +3. **CHECK THE PLAN.** Walk through each step — INCLUDING verification steps. + Any step skipped or partially done? Finish it — unless the user redirected + or deprioritized it. If the plan includes ANY verification steps (builds, + tests, lints, type-checks, manual validation, smoke tests, functional + tests, or any other form of verification), you MUST actually execute them + and see them pass — do not skip them or just claim they pass. + +4. **CHECK FOR ERRORS OR INCOMPLETE WORK.** Did anything fail or remain + unfinished? Fix it. This applies to ALL types of problems — logic errors, + missing functionality, incomplete refactors, broken scripts, configuration + issues, or anything else that prevents the work from being fully done. + +5. **CHECK FOR LOOSE ENDS.** Any TODO comments, placeholder code, missing + tests, untested changes, or follow-ups noted but not acted on? + +6. **CHECK FOR BLOCKERS.** If something is blocking you, do NOT give up. You + are a world-class engineer with access to a full development environment, + a terminal, every tool you need, and all the information on the internet. + If it does not violate the laws of physics, it is within your ability to + solve it. Try a different approach, read more code, search for examples, + re-examine your assumptions. Never declare something a blocker and stop. + Solve it. + +**User priority:** The user's latest instructions always take priority. If the +user said to stop, move on, or skip something, respect that — do not force +completion of work the user no longer wants. + +**DO NOT NARRATE — EXECUTE:** If any incomplete work remains, your ONLY job is +to DO that work right now. Do NOT respond by explaining what the remaining +tasks are, describing their complexity, listing their dependencies, or +analyzing how difficult they will be. Do NOT ask the user for permission or +direction to proceed. Do NOT write summaries of what is left. Just DO the +work. The user asked you to do it — that IS your direction. Every sentence you +spend describing remaining work instead of doing it is wasted. Open files, +write code, run commands, fix bugs. Act. + +**HONESTY CHECK:** Before marking anything as "not possible" or "skipped", ask +yourself: did you actually TRY, or are you rationalizing skipping it because +it seems hard or inconvenient? "I can't do X" is almost never true — what you +mean is "I haven't tried X yet." If you haven't attempted something, you don't +get to claim it's impossible. Attempt it first. + ## Parseable Done Signal -When the work is genuinely complete, the agent must include this exact line -in its final response (on its own line): +When the work is genuinely complete, include this exact line in your final +response (on its own line): ```text TASKMASTER_DONE:: ``` +Do NOT emit that done signal early. If any work remains, continue working. + This gives external automation a deterministic completion marker to parse. ## Configuration -- `TASKMASTER_MAX` (default `0`): Max number of blocked stop attempts before +- `TASKMASTER_MAX` (default `100`): Max number of blocked stop attempts before allowing stop. `0` means infinite (keep firing). - `TASKMASTER_DONE_PREFIX` (default `TASKMASTER_DONE`): Prefix used for the done token. +## Design Notes + +The hook's `reason` field is intentionally minimal — it contains only the done +signal token. The full completion checklist lives here in SKILL.md, which is +always loaded as system context. This keeps the user-visible terminal output +clean while the agent still has all required instructions. + ## Setup The hook must be registered in `~/.claude/settings.json`: diff --git a/check-completion.sh b/check-completion.sh index 3dc01bf..ddc346e 100755 --- a/check-completion.sh +++ b/check-completion.sh @@ -2,18 +2,20 @@ # # Stop hook: keep firing until the agent emits an explicit done signal. # -# The stop is allowed only after the transcript contains: +# The stop is allowed only after the agent emits: # TASKMASTER_DONE:: # # Optional env vars: -# TASKMASTER_MAX Max number of blocks before allowing stop (default: 0 = infinite) +# TASKMASTER_MAX Max number of blocks before allowing stop (default: 100) # TASKMASTER_DONE_PREFIX Prefix for done token (default: TASKMASTER_DONE) # -set -euo pipefail +set -u INPUT=$(cat) SESSION_ID=$(echo "$INPUT" | jq -r '.session_id') TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path') +# Expand leading ~ to $HOME (bash does not expand tilde inside quoted strings) +TRANSCRIPT="${TRANSCRIPT/#\~/$HOME}" if [ -z "$SESSION_ID" ] || [ "$SESSION_ID" = "null" ]; then SESSION_ID="unknown-session" fi @@ -30,7 +32,7 @@ fi COUNTER_DIR="${TMPDIR:-/tmp}/taskmaster" mkdir -p "$COUNTER_DIR" COUNTER_FILE="${COUNTER_DIR}/${SESSION_ID}" -MAX=${TASKMASTER_MAX:-0} +MAX=${TASKMASTER_MAX:-100} COUNT=0 if [ -f "$COUNTER_FILE" ]; then @@ -41,17 +43,17 @@ fi DONE_PREFIX="${TASKMASTER_DONE_PREFIX:-TASKMASTER_DONE}" DONE_SIGNAL="${DONE_PREFIX}::${SESSION_ID}" HAS_DONE_SIGNAL=false -HAS_RECENT_ERRORS=false -if [ -f "$TRANSCRIPT" ]; then - TAIL_400=$(tail -400 "$TRANSCRIPT" 2>/dev/null || true) - if echo "$TAIL_400" | grep -Fq "$DONE_SIGNAL" 2>/dev/null; then - HAS_DONE_SIGNAL=true - fi +# Primary: check last_assistant_message (most reliable — no transcript parsing needed) +LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // ""') +if [ -n "$LAST_MSG" ] && echo "$LAST_MSG" | grep -Fq "$DONE_SIGNAL" 2>/dev/null; then + HAS_DONE_SIGNAL=true +fi - TAIL_40=$(tail -40 "$TRANSCRIPT" 2>/dev/null || true) - if echo "$TAIL_40" | grep -qi '"is_error":\s*true' 2>/dev/null; then - HAS_RECENT_ERRORS=true +# Fallback: check transcript file if last_assistant_message didn't match +if [ "$HAS_DONE_SIGNAL" = false ] && [ -f "$TRANSCRIPT" ]; then + if tail -400 "$TRANSCRIPT" 2>/dev/null | grep -Fq "$DONE_SIGNAL"; then + HAS_DONE_SIGNAL=true fi fi @@ -63,43 +65,12 @@ fi NEXT=$((COUNT + 1)) echo "$NEXT" > "$COUNTER_FILE" -# Optional escape hatch. Default is infinite (0) so hook keeps firing. +# Optional escape hatch. Default is 100. if [ "$MAX" -gt 0 ] && [ "$NEXT" -ge "$MAX" ]; then rm -f "$COUNTER_FILE" exit 0 fi -if [ "$HAS_RECENT_ERRORS" = true ]; then - PREAMBLE="Recent tool errors were detected. Resolve them before declaring done." -else - PREAMBLE="Stop is blocked until completion is explicitly confirmed." -fi - -if [ "$MAX" -gt 0 ]; then - LABEL="TASKMASTER (${NEXT}/${MAX})" -else - LABEL="TASKMASTER (${NEXT})" -fi - -# --- reprompt --- -REASON="${LABEL}: ${PREAMBLE} - -Before stopping, do each of these checks: - -1. RE-READ THE ORIGINAL USER MESSAGE(S). List every discrete request or acceptance criterion. For each one, confirm it is fully addressed — not just started, FULLY done. If the user explicitly changed their mind, withdrew a request, or told you to stop or skip something, treat that item as resolved and do NOT continue working on it. -2. CHECK THE TASK LIST. Review every task. Any task not marked completed? Do it now — unless the user indicated it is no longer wanted. -3. CHECK THE PLAN. Walk through each step — INCLUDING verification steps. Any step skipped or partially done? Finish it — unless the user redirected or deprioritized it. If the plan includes ANY verification steps (builds, tests, lints, type-checks, manual validation, smoke tests, functional tests, or any other form of verification), you MUST actually execute them and see them pass — do not skip them or just claim they pass. -4. CHECK FOR ERRORS OR INCOMPLETE WORK. Did anything fail or remain unfinished? Fix it. This applies to ALL types of problems — logic errors, missing functionality, incomplete refactors, broken scripts, configuration issues, or anything else that prevents the work from being fully done. -5. CHECK FOR LOOSE ENDS. Any TODO comments, placeholder code, missing tests, untested changes, or follow-ups noted but not acted on? -6. CHECK FOR BLOCKERS. If something is blocking you, do NOT give up. You are a world-class engineer with access to a full development environment, a terminal, every tool you need, and all the information on the internet. If it does not violate the laws of physics, it is within your ability to solve it. Try a different approach, read more code, search for examples, re-examine your assumptions. Never declare something a blocker and stop. Solve it. - -IMPORTANT: The user's latest instructions always take priority. If the user said to stop, move on, or skip something, respect that — do not force completion of work the user no longer wants. - -HONESTY CHECK: Before marking anything as \"not possible\" or \"skipped\", ask yourself: did you actually TRY, or are you rationalizing skipping it because it seems hard or inconvenient? \"I can't do X\" is almost never true — what you mean is \"I haven't tried X yet.\" If you haven't attempted something, you don't get to claim it's impossible. Attempt it first. - -When and only when everything is genuinely 100% done (or explicitly deprioritized by the user), include this exact line in your final response on its own line: -${DONE_SIGNAL} - -Do NOT emit that done signal early. If any work remains, continue working now." - -jq -n --arg reason "$REASON" '{ decision: "block", reason: $reason }' +# Minimal reason — full completion checklist lives in SKILL.md (always in system context). +# Only the done signal is included so the agent knows exactly what to emit when complete. +jq -n --arg reason "$DONE_SIGNAL" '{ decision: "block", reason: $reason }' diff --git a/docs/SPEC.md b/docs/SPEC.md index 7bfe90e..aeba22c 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,7 +1,7 @@ # Taskmaster ## Product & Technical Specification -**Version**: 2.1.0 +**Version**: 2.2.0 **Scope**: `taskmaster/hooks/check-completion.sh`, `taskmaster/SKILL.md` ## 1. Goal @@ -39,18 +39,28 @@ On each stop event: 7. Optional safety cap: if `TASKMASTER_MAX > 0` and counter reaches cap, allow stop and clear counter file. -### 3.2 Prompt Injection +### 3.2 Prompt Architecture -When blocking, Taskmaster injects: +The verbose completion checklist lives in `SKILL.md`, which is loaded as system +context (invisible to the user in session history). The hook's `reason` field +is kept minimal — just a label, status, and done signal — so it does not +pollute the conversation transcript. + +When blocking, Taskmaster injects a brief reason: - `TASKMASTER (N)` or `TASKMASTER (N/MAX)` label. -- A completion checklist (requests, plan, errors, loose ends, blockers). -- The exact done line to emit when truly complete. +- Short status (stop blocked / errors detected). +- Reference to follow the taskmaster completion checklist. +- The exact done signal to emit when truly complete. + +The full checklist (re-read user messages, check task list, check plan, check +for errors, check for loose ends, check for blockers, honesty check) is in the +"Completion Checklist" section of `SKILL.md`. ### 3.3 Error Signal Hinting Taskmaster inspects recent transcript lines for `"is_error": true` and adjusts -preamble text to explicitly call out unresolved errors. +the brief preamble text to call out unresolved errors. ## 4. Runtime Interfaces diff --git a/docs/blog/2026-02-25-taskmaster-hook-cleanup.md b/docs/blog/2026-02-25-taskmaster-hook-cleanup.md new file mode 100644 index 0000000..18dea51 --- /dev/null +++ b/docs/blog/2026-02-25-taskmaster-hook-cleanup.md @@ -0,0 +1,105 @@ +# Cleaning up taskmaster's terminal output + +**2026-02-25** + +I forked [taskmaster](https://github.com/micahstubbs/taskmaster) a few recently to stop Claude from quitting early when working in a Claude Code session. The stop [hook](https://github.com/micahstubbs/taskmaster/blob/main/check-completion.sh) fires every time Claude tries to stop and blocks it until he emits an explicit `TASKMASTER_DONE::` token — a parseable signal that confirms Claude is actually finished. + +It works. The terminal output, though, was a way too much. + +#### The problem + +Every time the hook blocked a stop attempt, Claude Code dumped the full completion checklist into the terminal: + +``` + Ran 9 stop hooks (ctrl+o to expand) + ⎿  Stop hook error: TASKMASTER (1/100): Verify that + all work is truly complete before stopping. + + Before stopping, do each of these checks: + + 1. RE-READ THE ORIGINAL USER MESSAGE(S). List every discrete request or acceptance criterion. For each one, confirm it is fully addressed — not just started, FULLY done. If the user explicitly changed their mind, withdrew a request, or told you to stop or skip something, treat that item as resolved and do NOT continue working on it. + + 2. CHECK THE TASK LIST. Review every task. Any task not marked completed? Do it now — unless the user indicated it is no longer wanted. + + 3. CHECK THE PLAN. Walk through each step. Any step skipped or partially done? Finish it — unless the user redirected or deprioritized it. + + 4. CHECK FOR ERRORS. Did any tool call, build, test, or lint fail? Fix it. + + 5. CHECK FOR LOOSE ENDS. Any TODO comments, placeholder code, missing tests, or follow-ups noted but not acted on? + + IMPORTANT: The user's latest instructions always take priority. If the user said to stop, move on, or skip something, respect that — do not force completion of work the user no longer wants. + + If after this review everything is genuinely 100% done (or explicitly deprioritized by the user), briefly confirm completion for each user request. Otherwise, immediately continue working on whatever remains — do not just describe what is left, ACTUALLY DO IT. +``` + +Many lines, every time, accumulating across a long session. The checklist is instructions _for the AI_ — I never needed to read it. + +#### How the `reason` field works + +Claude Code stop hooks return JSON when they want to block a stop: + +```json +{ "decision": "block", "reason": "..." } +``` + +The `reason` field does two things at once: + +1. **User-visible output** — shown in the terminal as a "Stop hook error" +2. **AI context** — injected back into the conversation so that Claude knows what to do next + +Before, taskmaster was putting the full checklist in `reason`, to ensure that Claude got the instructions. However, this meant taskmaster was also printing the full checklist to my terminal. Every single stop attempt. + +#### What I was missing + +Claude already has the checklist from the taskmaster [skill file](https://github.com/micahstubbs/taskmaster/blob/main/SKILL.md). Every Claude Code `SKILL.md` file loads into system context at session start. Claude doesn't need instructions repeated in the hook reason — it just needs to know the specific token to emit. + +So I stripped the reason down to exactly that: + +```bash +DONE_SIGNAL="${DONE_PREFIX}::${SESSION_ID}" + +jq -n --arg reason "$DONE_SIGNAL" '{ decision: "block", reason: $reason }' +``` + +Now the terminal shows one collapsed line: + +``` +● Ran N stop hooks (ctrl+o to expand) + ⎿ Stop hook error: TASKMASTER_DONE::abc123xyz +``` + +Claude sees the signal he needs. I see almost nothing. Both of us get what we need from the same field. + +#### Faster signal detection too + +While I was in there I also changed how the hook detects the done signal. The old version opened the transcript file and scanned potentially hundreds of lines of JSON on every stop attempt. + +The Claude Code hook API passes `last_assistant_message` directly in the hook's input JSON. Checking that first skips the file read in the common case: + +```bash +LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // ""') +if [ -n "$LAST_MSG" ] && echo "$LAST_MSG" | grep -Fq "$DONE_SIGNAL" 2>/dev/null; then + HAS_DONE_SIGNAL=true +fi + +# Only scan the transcript if the message check didn't match +if [ "$HAS_DONE_SIGNAL" = false ] && [ -f "$TRANSCRIPT" ]; then + if tail -400 "$TRANSCRIPT" 2>/dev/null | grep -Fq "$DONE_SIGNAL"; then + HAS_DONE_SIGNAL=true + fi +fi +``` + +When Claude just emitted the done signal in his last message — the normal case — no transcript parsing happens. + +#### The lesson + +Hook reasons and system context have different jobs. System context (skill files, `CLAUDE.md`) carries persistent instructions that shape behavior across a whole session. Hook reasons carry transient, stop-specific information — the minimum Claude needs right now. + +Here that's: "emit `TASKMASTER_DONE::abc123` and you're done." + +The checklist still runs. The skill enforcement is unchanged. It just doesn't output the skill prompt to my terminal anymore. + +These changes shipped as [v2.3.0](https://github.com/micahstubbs/taskmaster/releases/tag/v2.3.0). + +Read more about how stop decision control and the `reason` field works in the [Claude Code Hooks docs](https://code.claude.com/docs/en/hooks#stop-decision-control). diff --git a/docs/session-summaries/2026-02-19-121116-default-tries-100.md b/docs/session-summaries/2026-02-19-121116-default-tries-100.md new file mode 100644 index 0000000..28517a4 --- /dev/null +++ b/docs/session-summaries/2026-02-19-121116-default-tries-100.md @@ -0,0 +1,27 @@ +# Session Summary + +**Date:** 2026-02-19 +**Time:** 12:11 +**Focus:** [Auto-generated - please review and complete] + +## Summary + +Session with 1 commits. Please add context about what was accomplished. + +## Completed Work + +### Commits +- `25bf291` - default tries 100 + +## Key Changes + +### Files Modified +[Review git diff for details] + +## Pending/Blocked + +[TODO: Any tasks started but not finished] + +## Next Session Context + +[TODO: What the next session should know] diff --git a/docs/session-summaries/2026-02-19-142857-make-installsh-posix-portable-.md b/docs/session-summaries/2026-02-19-142857-make-installsh-posix-portable-.md new file mode 100644 index 0000000..dc116d7 --- /dev/null +++ b/docs/session-summaries/2026-02-19-142857-make-installsh-posix-portable-.md @@ -0,0 +1,27 @@ +# Session Summary + +**Date:** 2026-02-19 +**Time:** 14:28 +**Focus:** [Auto-generated - please review and complete] + +## Summary + +Session with 1 commits. Please add context about what was accomplished. + +## Completed Work + +### Commits +- `cbff59c` - Make install.sh POSIX-portable (sh shebang, portable pipefail) + +## Key Changes + +### Files Modified +[Review git diff for details] + +## Pending/Blocked + +[TODO: Any tasks started but not finished] + +## Next Session Context + +[TODO: What the next session should know] diff --git a/docs/session-summaries/2026-02-23-145341-docs-add-session-summary-make-.md b/docs/session-summaries/2026-02-23-145341-docs-add-session-summary-make-.md new file mode 100644 index 0000000..e31fb6c --- /dev/null +++ b/docs/session-summaries/2026-02-23-145341-docs-add-session-summary-make-.md @@ -0,0 +1,30 @@ +# Session Summary + +**Date:** 2026-02-23 +**Time:** 14:53 +**Focus:** [Auto-generated - please review and complete] + +## Summary + +Session with 4 commits. Please add context about what was accomplished. + +## Completed Work + +### Commits +- `fde5036` - docs: add session summary (make-installsh-posix-portable-) +- `46f6a44` - Make install.sh POSIX-portable (sh shebang, portable pipefail) +- `31694ca` - docs: add session summary (default-tries-100) +- `0940f36` - default tries 100 + +## Key Changes + +### Files Modified +[Review git diff for details] + +## Pending/Blocked + +[TODO: Any tasks started but not finished] + +## Next Session Context + +[TODO: What the next session should know] diff --git a/docs/session-summaries/2026-02-25-043407-hide-verbose-checklist-from-us.md b/docs/session-summaries/2026-02-25-043407-hide-verbose-checklist-from-us.md new file mode 100644 index 0000000..79f2973 --- /dev/null +++ b/docs/session-summaries/2026-02-25-043407-hide-verbose-checklist-from-us.md @@ -0,0 +1,34 @@ +# Session Summary + +**Date:** 2026-02-25 +**Time:** 04:34 +**Focus:** [Auto-generated - please review and complete] + +## Summary + +Session with 1 commits. Please add context about what was accomplished. + +## Completed Work + +### Commits +- `9c25e5d` - hide verbose checklist from user output, use TASKMASTER_DONE signal detection + +## Key Changes + +### Files Modified +- `SKILL.md` +- `check-completion.sh` +- `docs/SPEC.md` +- `docs/session-summaries/2026-02-19-121116-default-tries-100.md` +- `docs/session-summaries/2026-02-19-142857-make-installsh-posix-portable-.md` +- `docs/session-summaries/2026-02-23-145341-docs-add-session-summary-make-.md` +- `hooks/check-completion.sh` +- `install.sh` + +## Pending/Blocked + +[TODO: Any tasks started but not finished] + +## Next Session Context + +[TODO: What the next session should know] diff --git a/docs/upstream-reviews/2026-02-25-blader-taskmaster-main.md b/docs/upstream-reviews/2026-02-25-blader-taskmaster-main.md new file mode 100644 index 0000000..504c305 --- /dev/null +++ b/docs/upstream-reviews/2026-02-25-blader-taskmaster-main.md @@ -0,0 +1,192 @@ +# Upstream Review: blader/taskmaster main + +**Date:** 2026-02-25 +**Compare URL:** https://github.com/micahstubbs/taskmaster/compare/main...blader:taskmaster:main +**Upstream:** blader/taskmaster@main +**Our fork:** micahstubbs/taskmaster@main +**Status:** diverged — 13 commits ahead in upstream + +--- + +## Context + +Our fork focuses on Claude Code stop hook behavior. The upstream (blader) has moved to +support OpenAI Codex TUI as a first-class target alongside Claude Code, using external +session-log monitoring and tmux/expect PTY injection instead of native hook registration. + +This architectural divergence drives most of the ignore decisions below. + +--- + +## Commit Decisions + +### cbd9443e — chore: sync local skill updates +**Decision: IGNORE** + +Adds the Codex integration layer: +- `hooks/check-completion-codex.sh` (237 lines, Codex monitor) +- `hooks/inject-continue-codex-tmux.sh` (307 lines, tmux transport) +- `hooks/run-codex-expect-bridge.exp` (91 lines, expect PTY bridge) +- `run-taskmaster-codex.sh` (364 lines, Codex session launcher) + +Also rewrites README, SKILL.md, docs/SPEC.md, install.sh, and uninstall.sh from a Codex-first perspective. + +Codex support is out of scope for this fork. Our focus is the Claude Code stop hook. Adding tmux/expect infrastructure would significantly increase complexity with no benefit to Claude Code users. + +--- + +### 755d165f — chore: sync local skill updates +**Decision: IGNORE** + +Refinements to the Codex layer introduced in cbd9443e: renames +`inject-continue-codex-tmux.sh` → `inject-continue-codex.sh`, simplifies +`run-taskmaster-codex.sh`, and trims docs. + +Depends on Codex infrastructure we're not adopting. + +--- + +### 88ffd335 — feat: support codex+claude auto install and cleanup docs +**Decision: IGNORE** + +Rewrites `install.sh` to auto-detect and install for both Codex (`~/.codex`) and +Claude (`~/.claude`). The new installer is 215 lines vs our 83 lines. While +auto-detection is a nice concept, the upstream now defaults to the Codex path +and the Claude path is a secondary target. Our simpler installer is better +suited to this fork's Claude-only focus. + +--- + +### 452417af — docs: rewrite README for clarity +**Decision: IGNORE** + +README is rewritten to be Codex-first, describing the Codex session-log +monitoring approach. Our README is accurate and Claude-focused. Nothing to port. + +--- + +### 4e5075fd — docs: add taskmaster philosophy and compliance rationale +**Decision: IGNORE** + +Adds 35 lines to README covering Taskmaster's philosophy. The content is already +present in our `SKILL.md` (the 6-item checklist including HONESTY CHECK). Our +approach of keeping the compliance text in SKILL.md (always loaded as system +context) is architecturally correct for Claude Code — no need to duplicate it +in the README. + +--- + +### 547bfa74 — refactor: remove monitor-only mode +**Decision: N/A (IGNORE)** + +Removes `hooks/check-completion-codex.sh` (235 lines) which was added in +cbd9443e and which we never adopted. Also trims SPEC.md. No action needed. + +--- + +### ca471bd8 — refactor: unify codex and claude compliance prompt +**Decision: IGNORE** + +Extracts the compliance prompt into `taskmaster-compliance-prompt.sh`, which +`hooks/check-completion.sh` now sources. This allows the same prompt to be +shared between Claude and Codex hooks. + +Our architecture keeps the compliance checklist in `SKILL.md`, which Claude Code +loads as system context on every turn — no shell file required. The upstream's +shell-based approach is a workaround for the lack of a native context mechanism +in Codex. We don't have this constraint. + +*Note:* The compliance prompt text in the new file is essentially identical to +what we already have in `SKILL.md`. No content to cherry-pick. + +--- + +### c04eeb18 — fix: restore long canonical compliance prompt +**Decision: IGNORE** + +Restores the longer version of `taskmaster-compliance-prompt.sh`. Depends on +the file introduced in ca471bd8, which we're not adopting. + +--- + +### c2475d9c — fix: default QUIET=1 in inject-continue-codex +**Decision: IGNORE** + +One-line fix in `hooks/inject-continue-codex.sh` — a Codex-specific file we +don't have. + +--- + +### 6814a3f5 — fix: symlink taskmaster-compliance-prompt.sh into hooks dir +**Decision: IGNORE** + +Adds one line to `install.sh` to symlink `taskmaster-compliance-prompt.sh` +into the hooks directory. Depends on `taskmaster-compliance-prompt.sh` which +we're not adopting. + +--- + +### 6598e99d — fix: expand tilde in transcript_path for done signal detection +**Decision: APPLY (rewrite to match our structure)** + +**Bug:** Claude Code passes `transcript_path` with a leading `~` (e.g., +`~/.claude/projects/.../session.jsonl`). Bash does not expand `~` inside +double-quoted strings, so `[ -f "$TRANSCRIPT" ]` always fails. The transcript +fallback never fires, and error detection (via `tail -40`) also silently fails. + +**Fix:** Add `TRANSCRIPT="${TRANSCRIPT/#\~/$HOME}"` immediately after reading +`transcript_path` from the input JSON. + +**Upstream patch (hooks/check-completion.sh):** +```diff + TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path') ++# Expand leading ~ to $HOME (tilde not expanded inside quotes by bash) ++TRANSCRIPT="${TRANSCRIPT/#\~/$HOME}" +``` + +**Action:** Apply to both `check-completion.sh` (root) and `hooks/check-completion.sh`. + +*See commit 6598e99d in blader/taskmaster for original.* + +--- + +### 71ff69c4 — fix: check last_assistant_message for done signal before transcript +**Decision: ALREADY IMPLEMENTED** + +This fix checks `last_assistant_message` from the hook input JSON before +falling back to transcript search. The transcript file may not be flushed yet +when the Stop hook fires. + +**Status:** Our v2.3.0 release (commit 1ae2daf, 2026-02-23) independently +implemented this same fix. Both `check-completion.sh` files already check +`last_assistant_message` first. No action needed. + +--- + +### 77c71bbf — fix: honor QUIET for transport banner +**Decision: IGNORE** + +Fixes a QUIET flag check in `run-taskmaster-codex.sh` — a Codex-specific +wrapper we don't have. + +--- + +## Summary + +| Commit | Decision | Reason | +|--------|----------|--------| +| cbd9443e | IGNORE | Codex integration, out of scope | +| 755d165f | IGNORE | Codex refinements, depends on above | +| 88ffd335 | IGNORE | Codex+Claude installer, Codex-first design | +| 452417af | IGNORE | Codex-centric README | +| 4e5075fd | IGNORE | Already in our SKILL.md | +| 547bfa74 | N/A | Removes file we never added | +| ca471bd8 | IGNORE | Shell-based compliance prompt, workaround we don't need | +| c04eeb18 | IGNORE | Depends on ca471bd8 | +| c2475d9c | IGNORE | Codex-only file | +| 6814a3f5 | IGNORE | Depends on ca471bd8 | +| **6598e99d** | **APPLY** | Tilde expansion bug in transcript_path | +| 71ff69c4 | ALREADY DONE | Implemented in our v2.3.0 | +| 77c71bbf | IGNORE | Codex-only file | + +**Net actions: 1 apply, 1 already done, 11 ignored** diff --git a/hooks/check-completion.sh b/hooks/check-completion.sh index 9e1f1f3..ddc346e 100755 --- a/hooks/check-completion.sh +++ b/hooks/check-completion.sh @@ -2,11 +2,11 @@ # # Stop hook: keep firing until the agent emits an explicit done signal. # -# The stop is allowed only after the transcript contains: +# The stop is allowed only after the agent emits: # TASKMASTER_DONE:: # # Optional env vars: -# TASKMASTER_MAX Max number of blocks before allowing stop (default: 0 = infinite) +# TASKMASTER_MAX Max number of blocks before allowing stop (default: 100) # TASKMASTER_DONE_PREFIX Prefix for done token (default: TASKMASTER_DONE) # set -u @@ -14,6 +14,8 @@ set -u INPUT=$(cat) SESSION_ID=$(echo "$INPUT" | jq -r '.session_id') TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path') +# Expand leading ~ to $HOME (bash does not expand tilde inside quoted strings) +TRANSCRIPT="${TRANSCRIPT/#\~/$HOME}" if [ -z "$SESSION_ID" ] || [ "$SESSION_ID" = "null" ]; then SESSION_ID="unknown-session" fi @@ -30,7 +32,7 @@ fi COUNTER_DIR="${TMPDIR:-/tmp}/taskmaster" mkdir -p "$COUNTER_DIR" COUNTER_FILE="${COUNTER_DIR}/${SESSION_ID}" -MAX=${TASKMASTER_MAX:-0} +MAX=${TASKMASTER_MAX:-100} COUNT=0 if [ -f "$COUNTER_FILE" ]; then @@ -41,7 +43,6 @@ fi DONE_PREFIX="${TASKMASTER_DONE_PREFIX:-TASKMASTER_DONE}" DONE_SIGNAL="${DONE_PREFIX}::${SESSION_ID}" HAS_DONE_SIGNAL=false -HAS_RECENT_ERRORS=false # Primary: check last_assistant_message (most reliable — no transcript parsing needed) LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // ""') @@ -49,17 +50,11 @@ if [ -n "$LAST_MSG" ] && echo "$LAST_MSG" | grep -Fq "$DONE_SIGNAL" 2>/dev/null; HAS_DONE_SIGNAL=true fi - # Fallback: check transcript file if last_assistant_message didn't match if [ "$HAS_DONE_SIGNAL" = false ] && [ -f "$TRANSCRIPT" ]; then - # Use grep directly on file (avoids broken-pipe with echo|grep under pipefail) if tail -400 "$TRANSCRIPT" 2>/dev/null | grep -Fq "$DONE_SIGNAL"; then HAS_DONE_SIGNAL=true fi - - if tail -40 "$TRANSCRIPT" 2>/dev/null | grep -qi '"is_error":\s*true'; then - HAS_RECENT_ERRORS=true - fi fi if [ "$HAS_DONE_SIGNAL" = true ]; then @@ -70,45 +65,12 @@ fi NEXT=$((COUNT + 1)) echo "$NEXT" > "$COUNTER_FILE" -# Optional escape hatch. Default is infinite (0) so hook keeps firing. +# Optional escape hatch. Default is 100. if [ "$MAX" -gt 0 ] && [ "$NEXT" -ge "$MAX" ]; then rm -f "$COUNTER_FILE" exit 0 fi -if [ "$HAS_RECENT_ERRORS" = true ]; then - PREAMBLE="Recent tool errors were detected. Resolve them before declaring done." -else - PREAMBLE="Stop is blocked until completion is explicitly confirmed." -fi - -if [ "$MAX" -gt 0 ]; then - LABEL="TASKMASTER (${NEXT}/${MAX})" -else - LABEL="TASKMASTER (${NEXT})" -fi - -# --- reprompt --- -REASON="${LABEL}: ${PREAMBLE} - -Before stopping, do each of these checks: - -1. RE-READ THE ORIGINAL USER MESSAGE(S). List every discrete request or acceptance criterion. For each one, confirm it is fully addressed — not just started, FULLY done. If the user explicitly changed their mind, withdrew a request, or told you to stop or skip something, treat that item as resolved and do NOT continue working on it. -2. CHECK THE TASK LIST. Review every task. Any task not marked completed? Do it now — unless the user indicated it is no longer wanted. -3. CHECK THE PLAN. Walk through each step — INCLUDING verification steps. Any step skipped or partially done? Finish it — unless the user redirected or deprioritized it. If the plan includes ANY verification steps (builds, tests, lints, type-checks, manual validation, smoke tests, functional tests, or any other form of verification), you MUST actually execute them and see them pass — do not skip them or just claim they pass. -4. CHECK FOR ERRORS OR INCOMPLETE WORK. Did anything fail or remain unfinished? Fix it. This applies to ALL types of problems — logic errors, missing functionality, incomplete refactors, broken scripts, configuration issues, or anything else that prevents the work from being fully done. -5. CHECK FOR LOOSE ENDS. Any TODO comments, placeholder code, missing tests, untested changes, or follow-ups noted but not acted on? -6. CHECK FOR BLOCKERS. If something is blocking you, do NOT give up. You are a world-class engineer with access to a full development environment, a terminal, every tool you need, and all the information on the internet. If it does not violate the laws of physics, it is within your ability to solve it. Try a different approach, read more code, search for examples, re-examine your assumptions. Never declare something a blocker and stop. Solve it. - -IMPORTANT: The user's latest instructions always take priority. If the user said to stop, move on, or skip something, respect that — do not force completion of work the user no longer wants. - -DO NOT NARRATE — EXECUTE: If any incomplete work remains, your ONLY job is to DO that work right now. Do NOT respond by explaining what the remaining tasks are, describing their complexity, listing their dependencies, or analyzing how difficult they will be. Do NOT ask the user for permission or direction to proceed. Do NOT write summaries of what is left. Just DO the work. The user asked you to do it — that IS your direction. Every sentence you spend describing remaining work instead of doing it is wasted. Open files, write code, run commands, fix bugs. Act. - -HONESTY CHECK: Before marking anything as \"not possible\" or \"skipped\", ask yourself: did you actually TRY, or are you rationalizing skipping it because it seems hard or inconvenient? \"I can't do X\" is almost never true — what you mean is \"I haven't tried X yet.\" If you haven't attempted something, you don't get to claim it's impossible. Attempt it first. - -When and only when everything is genuinely 100% done (or explicitly deprioritized by the user), include this exact line in your final response on its own line: -${DONE_SIGNAL} - -Do NOT emit that done signal early. If any work remains, continue working now." - -jq -n --arg reason "$REASON" '{ decision: "block", reason: $reason }' +# Minimal reason — full completion checklist lives in SKILL.md (always in system context). +# Only the done signal is included so the agent knows exactly what to emit when complete. +jq -n --arg reason "$DONE_SIGNAL" '{ decision: "block", reason: $reason }' diff --git a/install.sh b/install.sh index 7c46cc4..029890b 100755 --- a/install.sh +++ b/install.sh @@ -1,10 +1,12 @@ -#!/usr/bin/env bash +#!/bin/sh # # Taskmaster installer # # Installs the skill + stop hook and registers it in ~/.claude/settings.json. # -set -euo pipefail +set -eu +# Enable pipefail if the shell supports it (bash, zsh) +(set -o pipefail 2>/dev/null) && set -o pipefail || true SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SKILL_DIR="$HOME/.claude/skills/taskmaster" @@ -44,7 +46,7 @@ EOF echo " Created $SETTINGS with stop hook" elif ! grep -q 'check-completion.sh' "$SETTINGS" 2>/dev/null; then # Settings exists but hook not registered — merge it in - if command -v jq &>/dev/null; then + if command -v jq >/dev/null 2>&1; then HOOK_ENTRY=$(cat <