Skip to content
Skillv1.0.0

todo

Use when you have todos in todos/ with status backlog or planned and want to implement them autonomously in parallel

by Xertox1234(0) 0 installs
Free
Sign in to install

Free account. Installing gives you the manifest plus copy-paste snippets.

See reviews

About

Imported from Xertox1234/OCRecipes (.claude/skills/todo/SKILL.md). Install upstream with npx skills add Xertox1234/OCRecipes --skill todo. Copyright stays with the author.

You are running the todo orchestrator. This workflow cleans up prior runs, triages the backlog, plans execution order, dispatches executor agents, and reports results. Never skip phases.

Phase 0 — Cleanup Sweep

Before anything else, clear leftovers from previous /todo runs. This phase always runs and never aborts the workflow — if a step fails (e.g. gh is unauthenticated), report it and continue to Phase 1.

  1. Force-remove leftover executor worktrees. Executor worktrees are created locked, so git worktree prune alone silently skips them and they accumulate forever. Force-remove every one. Use the non---porcelain form and expand a leading ~ manually — some environments proxy git (e.g. this project's rtk hook, see CLAUDE.md/RTK.md) and rewrite --porcelain's output into a condensed, non-standard single-line format with ~-shorthand paths, which breaks a ^worktree anchor silently (zero matches, no error) and which read-into-a-variable does not tilde-expand at use time:

    git worktree list | awk '/\.claude\/worktrees\/agent-/ {sub(/ +[0-9a-f]{4,40} +\[[^]]*\].*$/, ""); print}' | while read -r wt; do
      wt="${wt/#\~/$HOME}"
      git worktree unlock "$wt" 2>/dev/null
      git worktree remove --force "$wt" 2>/dev/null && echo "removed worktree: $wt"
    done
    git worktree prune
  2. Delete stale remote branches. Every /todo run pushes a todo/<slug> branch for its PR; nothing deletes it after the PR merges, so they pile up on origin. Delete every remote branch whose PRs are all MERGED — but never one with an open PR, never one whose PR was closed WITHOUT merging (that is a rejection signal, not cleanup — see below), and never main or the current branch:

    git fetch --prune --quiet
    CURRENT=$(git branch --show-current)
    # Start clean: sweep outputs must never survive from a previous run — a skipped sweep
    # (gh failure / limit cap) would otherwise leave stale lists for later steps to trust.
    rm -f /tmp/todo-open-prs.txt /tmp/todo-delete-branches.txt /tmp/todo-closed-unmerged-branches.txt \
      /tmp/todo-local-branches.txt /tmp/todo-delete-local-branches.txt /tmp/todo-local-closed-unmerged-branches.txt \
      /tmp/todo-local-no-pr-branches.txt /tmp/todo-delete-skipped.txt /tmp/todo-scheduler-state.json
    # ONE fetch of every PR; the open/merged/closed views below derive from it. gh returns
    # newest-first, so a truncated fetch silently drops the OLDEST PRs — exactly the ones
    # the sweep needs. If the returned count EQUALS the limit, treat the sweep as
    # unreliable: keep the open-PR list (best available data for Phase 2) but skip branch
    # deletion this run and note the skip in the Phase 5 summary.
    gh pr list --state all --limit 1000 --json headRefName,state > /tmp/todo-all-prs.json \
      || { rm -f /tmp/todo-all-prs.json; echo "gh pr list failed — step SKIPPED"; }
    if [ -s /tmp/todo-all-prs.json ]; then
      jq -r '.[] | select(.state=="OPEN")   | .headRefName' /tmp/todo-all-prs.json | sort -u > /tmp/todo-open-prs.txt
      if [ "$(jq 'length' /tmp/todo-all-prs.json)" -eq 1000 ]; then
        echo "PR fetch hit the --limit cap — sweep unreliable; skipping branch deletion this run"
      else
        jq -r '.[] | select(.state=="MERGED") | .headRefName' /tmp/todo-all-prs.json | sort -u > /tmp/todo-merged-prs.txt
        jq -r '.[] | select(.state=="CLOSED") | .headRefName' /tmp/todo-all-prs.json | sort -u > /tmp/todo-closed-prs.txt
        # Shared gate: given a sorted branch-list file, print only entries whose PRs are ALL
        # merged (no open PR, no closed-unmerged PR) — used for both remote and local refs
        # below so the two sweeps can't drift out of sync with each other.
        merged_only() {
          comm -12 /tmp/todo-merged-prs.txt "$1" | comm -23 - /tmp/todo-open-prs.txt | comm -23 - /tmp/todo-closed-prs.txt
        }
        git branch -r --format='%(refname:short)' | sed 's#^origin/##' \
          | grep -vxE "HEAD|main|${CURRENT:-main}" | sort -u > /tmp/todo-remote-branches.txt
        # Delete only all-MERGED branches: ≥1 merged PR, no open PR, no closed-unmerged PR.
        merged_only /tmp/todo-remote-branches.txt > /tmp/todo-delete-branches.txt
        # Closed WITHOUT merging and no open PR = a rejected implementation — never sweep
        # it silently; carry this list to the Phase 5 attention section.
        comm -12 /tmp/todo-closed-prs.txt /tmp/todo-remote-branches.txt \
          | comm -23 - /tmp/todo-open-prs.txt > /tmp/todo-closed-unmerged-branches.txt
        if [ -s /tmp/todo-delete-branches.txt ]; then
          # The snapshot only SELECTS candidates. A FRESH per-branch check gates each
          # actual delete — the snapshot can go stale mid-run (the PR #520 incident
          # class: a branch merged-in-snapshot can have a new open PR by delete time).
          : > /tmp/todo-delete-skipped.txt
          while read -r b; do
            if bash scripts/verify-branch-merged.sh "$b"; then
              git push origin --delete "$b"
            else
              echo "$b" >> /tmp/todo-delete-skipped.txt
            fi
          done < /tmp/todo-delete-branches.txt
          git fetch --prune --quiet
        fi
        # Local branch cleanup — mirror the remote sweep for local todo/* refs. `git worktree
        # remove` (step 1) leaves the branch ref, and guard-eligible PRs auto-merge with
        # --delete-branch, so the local todo/<slug> is usually the SOLE survivor and is NOT in
        # /tmp/todo-remote-branches.txt. Join LOCAL branches against MERGED PRs (PRs outlive
        # their deleted branches). Same gate as remote: all-MERGED only, never
        # current/open/closed-unmerged.
        git branch --list 'todo/*' --format='%(refname:short)' \
          | grep -vxF "${CURRENT:-x}" | sort -u > /tmp/todo-local-branches.txt
        merged_only /tmp/todo-local-branches.txt > /tmp/todo-delete-local-branches.txt
        if [ -s /tmp/todo-delete-local-branches.txt ]; then
          while read -r b; do
            bash scripts/verify-branch-merged.sh "$b" \
              || { echo "$b" >> /tmp/todo-delete-skipped.txt; continue; }
            out=$(git branch -D "$b" 2>&1) && echo "deleted local branch: $b" \
              || { echo "$out" | grep -qE "checked out at|used by worktree at" || echo "WARNING: could not delete local branch $b: $out"; }
          done < /tmp/todo-delete-local-branches.txt
        fi
        # Local closed-unmerged (rejection signal) — mirrors the remote check above. Never
        # auto-delete it, but DO surface it in Phase 5, or a local-only rejection signal
        # (its remote branch may already be gone) silently vanishes with no trace at all.
        comm -12 /tmp/todo-closed-prs.txt /tmp/todo-local-branches.txt \
          | comm -23 - /tmp/todo-open-prs.txt > /tmp/todo-local-closed-unmerged-branches.txt
        # No PR at all, in ANY state — a genuine orphan: the executor renamed its worktree
        # branch to todo/<slug> at Step 10, but the process crashed or the push/`gh pr
        # create` call failed before any PR ever existed. Never auto-delete or auto-push
        # it — it might be genuine in-flight work — only detect and surface in Phase 5.
        sort -u /tmp/todo-merged-prs.txt /tmp/todo-open-prs.txt /tmp/todo-closed-prs.txt \
          > /tmp/todo-all-pr-branches.txt
        comm -23 /tmp/todo-local-branches.txt /tmp/todo-all-pr-branches.txt \
          > /tmp/todo-local-no-pr-branches.txt
      fi
    fi

    If the gh call fails (unavailable, unauthenticated, network), the block above deletes the temp file and this step is SKIPPED — no stale /tmp lists survive for later phases to trust, no branch deletion happens, and Phase 2 fetches its own open-PR list (worktree cleanup in step 1 still ran). Continue.

  3. Report what was cleaned: count of worktrees removed, the list of remote branches deleted, and the list of local todo/* branches deleted (or "nothing to clean"). If any WARNING: line was printed (a git branch -D that failed for a reason other than the branch being checked out elsewhere), carry it verbatim. If /tmp/todo-closed-unmerged-branches.txt, /tmp/todo-local-closed-unmerged-branches.txt, /tmp/todo-local-no-pr-branches.txt, or /tmp/todo-delete-skipped.txt (branches whose FRESH merge-state check failed at delete time) is non-empty, or the sweep was skipped (gh failure or the --limit cap), carry that in orchestrator state — Phase 5 surfaces all of it.

  4. Sync the local default branch (main). PRs from prior runs land via auto-merge or the user's review (possibly from another session), so those todos may already be archived on origin/main while the local checkout still shows them at the old path — and the backlog would otherwise re-pick an already-merged todo. Fast-forward local main. Like the rest of Phase 0 this never aborts the run, and it is ff-only so it never disturbs parallel work:

    git fetch origin main --quiet || true
    CUR=$(git branch --show-current)
    if [ "$CUR" = "main" ] || [ "$CUR" = "master" ]; then
      if [ -z "$(git status --porcelain)" ]; then
        git pull --ff-only -q origin "$CUR" 2>/dev/null && echo "synced local $CUR with origin" \
          || echo "local $CUR not fast-forwardable — skipping (pull manually)"
      else
        echo "skipped main sync — working tree dirty"
      fi
    else
      # Not on the default branch: fast-forward the local main ref without touching the
      # current branch/working tree. Refuses (harmlessly) if it would not be a fast-forward.
      git fetch origin main:main 2>/dev/null && echo "fast-forwarded local main ref" \
        || echo "local main not fast-forwardable — pull manually when on main"
    fi

    Then proceed to Phase 1.

Phase 1 — Baseline

Establish a green baseline before touching any code.

  1. Run all three commands:

    npm run test:run
    npm run check:types
    npm run lint
  2. Record the test count (e.g., "1327 tests passed"), the type-check result (e.g., "0 errors"), and the lint result (e.g., "0 warnings, 0 errors").

  3. Capture the base branch before creating any worktrees:

    git branch --show-current

    If the output is empty (detached HEAD state), fall back to:

    git rev-parse --abbrev-ref HEAD

    If that also returns HEAD, stop immediately and report "cannot determine base branch — HEAD is detached. Please check out a named branch before running /todo." Do not proceed to Phase 2.

    Store the branch name as BASE_BRANCH (e.g., feat/nutrition-inline-drawers or main). Pass it to every executor spawn in Phase 4 via the Base branch: line in the prompt.

    Confirm BASE_BRANCH out loud whenever it is not main. Basing a batch on a feature branch is a deliberate, supported choice — but the capture above takes whatever branch you happen to be standing on, and it is silently wrong when /todo is invoked from a linked worktree, where the current branch is that worktree's unrelated feature work. Every executor forks from BASE_BRANCH, so a wrong value puts that unrelated feature diff into every PR in the batch. Check which checkout you are in:

    git rev-parse --git-common-dir

    Use the bare form here. It prints .git at the top level of the main checkout and an absolute path in a linked worktree, which is the signal needed. The --path-format=absolute variant used in the next step is always absolute and can never distinguish the two — the two calls answer different questions and must not be merged.

    If BASE_BRANCH is not main, or the command above printed anything other than .git, stop and report before dispatching. (It also prints an absolute path from a subdirectory of the main checkout, so this can ask one extra time — confirm either way; the cost of a redundant question is nothing against a batch of PRs carrying someone else's diff.)

    BASE_BRANCH = <branch>   (worktree: <linked | main checkout>)
    Every executor will fork from this branch. Confirm or give a different base.
    

    Wait for an explicit answer. Do not proceed to Phase 2 on silence.

    Then capture the main checkout's absolute path using git rev-parse --git-common-dir (worktree-aware — pwd would be wrong if /todo is invoked from inside another worktree):

    MAIN_CHECKOUT="$(dirname "$(git rev-parse --path-format=absolute --git-common-dir)")"

    Important: Shell state does not persist between tool calls — each Bash call runs in a fresh shell. Record the literal output of this command (e.g. /Users/yourname/projects/OCRecipes) and substitute it wherever <MAIN_CHECKOUT> appears in the spawn prompts below and in the executor's instructions. Do not re-run this command in later phases; use the saved value.

    Store as MAIN_CHECKOUT (e.g., /Users/williamtower/projects/OCRecipes). Pass it to every executor spawn in Phase 4 via the Main checkout: line in the prompt. Executors use it to resolve paths outside their worktree when a gitignored artifact must survive worktree teardown (same gitignore/worktree pitfall the /audit skill fixed). Solution files (docs/solutions/...) are git-tracked: executors write them worktree-relative and commit them on the todo branch (see /codify).

  4. Verify executor agent: Confirm the file .claude/agents/todo-executor.md exists by running:

    test -f .claude/agents/todo-executor.md && echo "found" || echo "missing"

    If missing, stop immediately and report "Cannot find .claude/agents/todo-executor.md — the executor agent is required. Please restore it before running /todo."

  5. If ANY command fails or any check above returns missing, stop immediately. Report the failure to the user and exit — do not proceed to Phase 2. The codebase must be green before batch processing begins.

Phase 2 — Triage

Build the work queue from the todos/ backlog.

  1. Read all .md files in todos/exclude README.md, TEMPLATE.md, anything inside todos/archive/, and anything inside todos/deployment/ (deployment/scaling work is parked until launch is financially viable).

  2. Parse each file's YAML frontmatter. Extract: title, status, priority, created, labels, blocked_until, blocked_reason, human_led.

  3. Filter to actionable todos: status is backlog or planned. Skip any todo with status in-progress, blocked, review, or done.

    Stuck todos: If any file has status: in-progress, it was left mid-run by a crashed executor and is being skipped. To re-queue it, manually edit its frontmatter to status: backlog and re-run /todo.

    Awaiting merge (skip). A completed todo's archive move rides its unmerged PR branch, so the local todos/*.md still says backlog until that PR merges (auto-merge or the user's review) — triage must not re-pick it. Reuse this run's open-PR list from Phase 0 step 2 (/tmp/todo-open-prs.txt); only if Phase 0's gh step was skipped, fetch it now (gh pr list --state open --limit 1000 --json headRefName --jq '.[].headRefName' | sort -u > /tmp/todo-open-prs.txt — never trust a /tmp file left by a previous run; Phase 0 rewrites or deletes it every run precisely so this step can trust it). Skip any actionable todo whose slug (filename minus .md) exactly matches an open todo/<branch-slug> branch — executors are required to use the exact filename slug as the branch name, so exact match is the only join. A match means the todo is already implemented and its PR is awaiting merge; re-dispatching would re-implement it and collide with its own open PR. Carry the skipped set (with each todo's PR branch) in orchestrator state and list it in the Phase 5 summary under "Awaiting merge". If the list cannot be fetched at all, continue without this check (the executor's Step 2 remote-branch probe and Step 10 push-collision triage are the downstream backstops).

3a. Gate check (date / human-led) — status-independent, no override in this path. Run once for the whole backlog, in scan mode (no argument):

```bash
scripts/todo-gate-check.sh
```

This reads `blocked_until`/`human_led` directly from every `todos/*.md` file — **independent of what `status` currently says**, so a todo whose `status` was edited away from `blocked` (by hand, by a prior session, or by an agent) is still caught. Exit 0 = no gated todos found, nothing to do. Exit 1 = at least one gated todo; stdout has one TSV line per gated file (`<path>\t<blocked_until-or-dash>\t<reason>`). Exit 2 = the check itself failed — **treat identically to exit 1** (fail-closed: an unverifiable actionable list is not a clean one).

Remove every todo whose path appears in the output from the actionable list — matching on the exact `todos/<filename>.md` path form `scripts/todo-gate-check.sh` emits (relative to the repo root, same form step 1 already reads files as), **regardless of `/goal` directive wording** ("drive every actionable todo," "clear the backlog," etc. is never a per-todo override — see `todos/README.md` → "Date & Human-Led Gates"). Carry the removed set (filename, `blocked_until`, `blocked_reason`/reason text) in orchestrator state for Phase 5's "Gated" heading.

**There is no override in this path — full stop.** `/todo`'s batch run never legitimately targets one specific gated todo by name; if the user wants to run a gated todo anyway, they invoke `/todo-fast <path>` directly, which has its own interactive-confirmation gate (`.claude/skills/todo-fast/SKILL.md` Phase 0). Never edit `blocked_until`, `human_led`, or `status` on a gated todo to make it pass this check — that edit is the exact bypass the 2026-07-16 incident exploited.

4. Quality check. Catching authoring problems here is much cheaper than spawning a researcher + executor only to have them fail on an incoherent spec. For each actionable todo, scan the body and record any flag that fires:

  • empty-AC — the Acceptance Criteria section is missing or contains no - [ ] checkbox lines.
  • thin-IN — the Implementation Notes section body (text between its heading and the next heading) is shorter than 50 characters after trimming whitespace.
  • no-files — the body contains no file reference matching the patterns Phase 3 uses for extraction (path/to/file.ts, path/to/file.ts:123-145, or backtick-quoted paths).

Record each flagged todo with the comma-joined list of triggered flags. Multiple flags are possible.

  1. Sort the actionable list:

    • Priority descending: critical > high > medium > low
    • Within the same priority, oldest created date first (FIFO)
  2. Display the work queue as a markdown table with a Quality column showing OK or the comma-joined flag list:

    # Priority Title Quality Labels Created
    1 high ... OK ... ...
    2 medium ... thin-IN ... ...

    Default behavior: only todos with Quality = OK proceed to Phase 3 and beyond. Flagged todos are dropped from this run's queue and surfaced again in the Phase 5 summary under "Skipped — quality flags" so the user can re-author them and re-run. The dropped set must be carried in orchestrator state for Phase 5.

    If every actionable todo is flagged, report "All actionable todos failed quality checks — re-author and re-run." and exit. If the queue is empty (no actionable todos at all), report "No actionable todos found" and exit.

Phase 3 — Dependency Analysis

Determine which todos can safely run in parallel and which must run sequentially.

  1. Extract file paths from each todo's full body (Implementation Notes, Acceptance Criteria, any other sections). Match these patterns:

    • Bare paths: path/to/file.ts
    • Paths with line ranges: path/to/file.ts:123-145
    • Backtick-quoted paths: `path/to/file.ts`
  2. Build a file-overlap map: two todos are "dependent" if they share any mentioned file path (ignoring line ranges — file-level granularity).

  3. Check inter-todo dependencies. Also parse each todo's Dependencies section. If a todo lists another todo filename as a dependency and that file still exists in todos/ (not yet archived on main), do not schedule the dependent in this run at all — even if the dependency is dispatched and completes earlier in this run's queue, its archive lands on main only when its PR merges (auto-merge or the user's review — either way, not yet), so a same-run dispatch of the dependent is guaranteed to report blocked (wasted worktree + researcher + executor). The skip reason depends on the dependency's actual state — never claim a PR merge will unblock it unless that PR exists:

    • Dependency has an open todo/* PR (check the Phase 0/2 open-PR list): skip with reason gated on <dependency>'s PR (<branch>) landing → Phase 5 "Gated on a pending PR".
    • Dependency has no PR (quality-dropped, previously failed, or never attempted): skip with reason gated on <dependency> — not implemented yet → Phase 5 "Gated on a dependency (not yet implemented)". The unblock is re-authoring or a future run of the dependency, not a merge.
    • Dependency is scheduled in THIS run: defer the wording — at Phase 5 time use the dependency's actual outcome (PR opened → first bullet; failed/blocked → second bullet).
  4. Todos that mention NO specific files must run sequentially. Unknown scope means they could potentially conflict with anything.

  5. DB-serial todos must run alone. A todo whose body mentions shared/schema.ts, migrations/, drizzle, or db:push performs DDL against the shared dev database — mark it [db-serial] in the plan display in addition to its must-run-alone tag (step 6), so Phase 4 knows to add the lock-acquisition dispatch block to it specifically (scope-unknown must-run-alone todos don't need that block). The executor-side advisory lock (scripts/pg-lab/db-serial-lock.sh, dispatched below) is the enforcement backstop for the runs this planning rule cannot see (a second orchestrator, a manual session); it does not replace this rule (feedback_todo_parallel_shared_dev_db).

  6. Tag each remaining todo independent or must-run-alone. DB-serial todos (step 5) and scope-unknown todos (step 4) are must-run-alone — each may only run by itself, with nothing else in flight at the same time. Every other todo (disjoint file sets, each mentioning at least one file) is independent and can run alongside other independent todos.

  7. Order the tagged set into a single priority queue, keeping the priority/date sort from Phase 2. This queue has no execution-boundary structure — it's the fill order Phase 4's rolling scheduler draws from, not a set of batches. The concurrency cap is 4, enforced by the scheduler per completion (Phase 4), not by chunking here.

  8. Display the execution plan as the ordered queue:

    Queue (priority order):
      1. [high, independent]                 Extract suggestion generation service
      2. [high, independent]                 Storage facade re-exports
      3. [medium, must-run-alone]            Remix screen reader announcements — scope unknown
      4. [medium, must-run-alone, db-serial] Add recipe_tags junction table
      5. [low, independent]                  Fix useCollapsible height test type error
      6. [low, independent]                  Extract toDateString utility
    

    independent items fill free slots as they open, in this order; a must-run-alone item only starts once nothing else is running, and blocks every other item — including another must-run-alone item — from starting while it runs (Phase 4). In the queue above: items 1 and 2 start immediately, filling 2 of 4 slots. Item 3 can't start alongside them, so items 5 and 6 fill the remaining 2 slots instead of sitting idle — the exact idle-slot waste this design exists to remove. Item 4 waits behind item 3 even after slots free up, since two must-run-alone items can't run concurrently with each other either.

  9. Advisor review of the schedule (gated). Call the advisor tool before dispatching Phase 4 only when the queue contains 2 or more independent items — those are the only ones the scheduler can ever run at the same time. Skip it when fewer than 2 independent items exist; nothing in that queue can overlap in time. (If the advisor tool is not available in the session, skip this step.)

    The advisor sees this orchestrator's full transcript — the todo bodies, the file-overlap map from steps 1–2, and the queue — and reviews exactly one question: could any two independent items the scheduler might run concurrently actually conflict? Could two of them touch the same file (a shared import, a barrel file, or a type the overlap analysis missed) — the rolling scheduler may co-run any pair of independent items, not just ones that happen to sit near each other in the display, so a missed overlap between any two is live risk. And separately: is anything tagged independent that should be must-run-alone? Two executors editing one file in separate worktrees produce conflicting branches and stacked PRs — expensive and hard to unwind once agents are live.

    Nothing has executed yet, so revising is cheap. If the advisor flags a risky pairing, retag one of the conflicting todos must-run-alone (or drop it from this run), re-display the revised queue, then proceed. Weigh the advice seriously, but it is advisory: if a flag is clearly wrong (the files genuinely do not overlap), note why and continue.

Phase 4 — Execute

Run the queue with a rolling dispatcher: keep up to 4 executors in flight, refilling a slot the instant it frees rather than waiting for every currently-running executor to finish. scripts/todo-scheduler.ts makes the "what's eligible right now" decision — see its header comment for the two invariants it enforces (a must-run-alone item excludes everything else while it runs; an independent item needs a free slot and no file overlap with anything active).

Executor dispatch

Every executor is a todo-executor agent spawned in an isolated worktree via the Agent tool with these parameters:

Agent({
  description: "Execute todo: <todo title>",
  subagent_type: "general-purpose",
  model: "sonnet",
  isolation: "worktree",
  prompt: "You are a todo executor agent. Follow the instructions in .claude/agents/todo-executor.md exactly.\n\nYour todo file: todos/<filename>.md\nBase branch: <BASE_BRANCH>\nMain checkout: <MAIN_CHECKOUT>\n\nFirst action, before any other step: run `scripts/pg-lab/session-coord.sh register --kind todo-executor` (registers this session's kind in the coordination registry; silently no-ops if Postgres is down).\n\nExecute all steps in order and report the result."
})

Substitute the actual branch name you recorded in Phase 1 (e.g., feat/nutrition-inline-drawers) for <BASE_BRANCH> and the actual main checkout path (e.g., /Users/williamtower/projects/OCRecipes) for <MAIN_CHECKOUT>. Never pass the literal text <BASE_BRANCH> or <MAIN_CHECKOUT>.

DB-serial todos only (the subset of must-run-alone items marked so in Phase 3 step 5 — not the scope-unknown ones): add this block to the dispatch prompt —

Before your first db:push/DDL step: resolve the session pid with WATCH_PID=$(bash -c '. scripts/pg-lab/lib/ps-walk.sh && resolve_claude_pid'), then run WATCH_INTERVAL_SECS=10 scripts/pg-lab/db-serial-lock.sh acquire --watch-pid "$WATCH_PID" with a Bash timeout of 600000. Exit 0 → proceed. Exit 2 → retry the acquire ONCE; a second exit 2 means another session is live on the shared dev DB — mark this todo blocked with an ACTION NEEDED reason quoting the holder identity the command printed, and stop. If the printed holder identity contains this session's own id, the lock was abandoned by an earlier executor in this run — recovery is a manual scripts/pg-lab/db-serial-lock.sh release, not waiting for another session. Exit 3 → proceed unlocked and note "db-serial lock unavailable (watch-pid unresolvable)" in the PR body. If the Bash tool call itself times out (600s) with no exit code, treat it exactly like exit 2. If the acquire exited 0, run scripts/pg-lab/db-serial-lock.sh release on completion — success OR failure. Never run release after exit 2 or exit 3: you do not hold the lock, and release force-frees whoever does.

How dispatch actually resumes — read this before the loop below

Dispatched agents run in the background. Launching them does not block your current turn, and there is no in-turn way to wait for one to finish — do not poll, sleep, or reach for a scheduling tool to "wait." Instead: after dispatching, your turn simply ends (respond to the user, or just stop). Later, when ONE dispatched agent completes, the harness re-invokes you as a new turn carrying that agent's result — never when the whole running set finishes, only ever one at a time. The loop below is written around that: each numbered pass through step 2 is a separate turn, not a step inside one long turn.

Because the run now spans many turns instead of one barrier per batch, don't rely on conversational memory for the queue/running/results state — a long run can have its earlier context summarized away. Persist it instead: write /tmp/todo-scheduler-state.json ({"queue": [...], "running": [...], "results": [...]}) every time any of the three changes, and re-read it at the start of each turn below rather than trusting what you remember — this applies as much to each completed todo's recorded outcome (needed for Phase 5's summary table) as it does to the queue and running set. This is the same pattern Phase 0 already uses for cross-step state (/tmp/todo-*.txt), just as JSON.

The rolling loop

  1. Seed dispatch. Write the full queue (from Phase 3) and an empty running set to /tmp/todo-scheduler-state.json, then call the scheduler with that same state:

    npx tsx scripts/todo-scheduler.ts <<'EOF'
    {"cap": 4, "running": [], "queue": [{"id": "<todo slug>", "files": ["<path>", "..."], "tag": "<independent-or-must-run-alone>"}, "..."]}
    EOF

    The script prints the JSON array of queue items eligible to dispatch now. Launch one Agent() call per item — all in the same message — using the dispatch block above, move each dispatched item from queue into running in /tmp/todo-scheduler-state.json, and save it. Then end your turn.

  2. On each wake-up (a task-notification carrying one completed executor's result — see "How dispatch actually resumes" above): re-read /tmp/todo-scheduler-state.json first, don't assume your own memory of it is still accurate. Then: a. Record its result exactly as described under "Recording results" below, appending it to results in the state file. b. Remove that todo's worktree immediately, using the WORKTREE path from its report (every report shape carries one — see todo-executor.md Step 11):

    git worktree unlock "<WORKTREE>" 2>/dev/null
    git worktree remove --force "<WORKTREE>" 2>/dev/null

    Do this for every outcome — success, failed, blocked, and skipped all get their worktree torn down the same way, immediately, rather than waiting for Phase 5. c. Drop it from running in the state file. d. Call the scheduler again with the updated running and remaining queue from the state file (same shape as step 1); dispatch whatever it returns, move each newly-dispatched item from queue into running, save the state file, then end your turn again.

  3. Repeat step 2 — each occurrence is a separate turn — until the state file shows both queue and running empty, then proceed to Phase 5. Do not delete /tmp/todo-scheduler-state.json yet — Phase 5 reads results from it first and deletes it as part of its own cleanup.

Recording results

Each executor reports one of: success, failed, blocked, skipped. Every skipped/blocked report carries a REASON_CODE (enum in the executor's Step 11) — keep it verbatim; Phase 5 routes on it. Each successful executor additionally reports COMMIT, BRANCH, PR_URL (a URL, or null if PR creation failed), MERGE_ELIGIBLE (yes (auto-merge enabled) = guard OK, executor already armed gh pr merge --auto — nothing further needed; yes (auto-merge enable FAILED ...) = guard OK but the gh pr merge --auto call itself errored — needs manual merge or review; held = guard HOLD via the path or todo-frontmatter gate, with the guard's reason line in parentheses; review-required = medium/high/critical/security; unknown = guard couldn't evaluate; n/a = no PR), SHORT_CIRCUIT (a docs/solutions path if a verified solution was reused and the researcher skipped, else none), ADVISOR (green, yellow, red, or skipped), and DEFERRED_WARNINGS. Keep the DEFERRED_WARNINGS lines — Phase 5 surfaces them for triage. Keep the ADVISOR values — Phase 5 tallies them. These accumulate in /tmp/todo-scheduler-state.json's results array as they arrive, rather than per batch — Phase 5 reads that array rather than relying on memory of the whole run.

Phase 5 — Session Summary

First, release all worktree contracts: run bash scripts/declare-worktree.sh --clear. Executors declare their worktrees at Step 0 and remove them at Step 11, but a crashed executor leaves a stale registry entry — and while ANY entry exists, the PreToolUse guards deny YOUR main-checkout git operations (reconciliation, archiving, branch cleanup). --clear is idempotent and safe here: all executors have returned by Phase 5.

Then load the accumulated run results: read results from /tmp/todo-scheduler-state.json (Phase 4) rather than relying on memory of the whole run — this is the authoritative per-todo outcome list for the summary table below. Once read, delete the file: rm -f /tmp/todo-scheduler-state.json.

After the queue is fully drained (or after early termination):

  1. Post-session verification — run the full suite one final time:

    npm run test:run
    npm run check:types
    npm run lint
  2. Compare test count against the Phase 1 baseline. Flag any regressions (fewer tests passing than before). New tests added by todos are expected and welcome.

  3. Print the summary table:

    The Branch / PR column shows the PR URL for every todo (all priorities open a PR). Key off each todo's MERGE_ELIGIBLE: yes (auto-merge enabled) → "auto-merging on green CI"; yes (auto-merge enable FAILED ...) → "auto-merge failed to arm — needs manual merge or review"; held → "held — guard HOLD (path or todo-frontmatter gate; see the executor's reason line)"; review-required → "needs individual review"; unknown → "guard couldn't evaluate — review by hand". Show pending manual creation if PR creation failed.

    # Todo Status Branch / PR Review Rounds Notes
    1 Extract suggestion generation service success github.com/…/pull/42 1
    2 Storage facade re-exports success github.com/…/pull/43 2 auto-merging on green CI
    3 Remix screen reader announcements blocked 0 Depends on remix-carousel-badge
    4 Fix useCollapsible height test failed 1 Type error in mock setup
    5 Fix calorie rounding utility success pending manual creation 1 PR creation failed — push succeeded
  4. Print tallies:

    Completed: N (list PR URLs; mark "auto-merging on green CI" for `MERGE_ELIGIBLE: yes (auto-merge enabled)`; mark "auto-merge failed to arm" for `yes (auto-merge enable FAILED ...)`; for `held`/`review-required`/`unknown` mark "PR open — needs individual review". Note "PR pending manual creation" for any where PR_URL is null)
    Blocked:   M
    Skipped:   S
    Failed:    F
    Remaining: X (todos still in backlog after this session)
    Patterns codified: P
    Short-circuited: SC (todos that reused a verified solution and skipped research; list the docs/solutions paths)
    Advisor: G green, Y yellow, R red, S skipped (not available)
    Final test count: T (baseline was B)
    

    Then list quality-flagged todos that were skipped from this run. Using the dropped set carried over from Phase 2 step 6, print them under the heading "Skipped — quality flags — re-author and re-run to include them:" with one line per todo (todo filename + comma-joined flag list). If none were dropped, omit the heading.

    Then list gated todos that were skipped from this run. Using the gated set carried over from Phase 2 step 3a, print them under the heading "Gated — blocked_until/human_led (skip until the date passes or a human runs it directly via /todo-fast):" with one line per todo (todo filename + blocked_until date or human_led: true + reason). This is a terminal, expected state — not a failure, and never resolved by re-running /todo or by any /goal directive. If none were gated, omit the heading.

    Then report auto-merge status. For every MERGE_ELIGIBLE: yes (auto-merge enabled) PR, nothing further is needed — the executor already ran gh pr merge --auto --squash --delete-branch, so GitHub squash-merges it automatically the instant required CI checks pass. List these under "Auto-merging on green CI (no action needed):" with their PR URLs, for visibility only. For any MERGE_ELIGIBLE: yes (auto-merge enable FAILED ...) PR, the executor's gh pr merge --auto call itself failed — list it under "Auto-merge failed to arm — needs manual gh pr merge --auto --squash --delete-branch <n>, or individual review:". held / unknown / review-required PRs are unaffected by this change — list them exactly as before, under "Needs individual review:", for the user to review and merge by hand.

    Then list todos awaiting merge and gated dependents. Route executor results on REASON_CODE first; matching on reason-text prefixes is the legacy fallback for a report that lacks the field. Four groups:

    • Awaiting merge — the Phase 2 skip set plus any executor skipped result with REASON_CODE: OPEN_PR_COLLISION (legacy fallback: reason begins already implemented; take the PR URL from the reason), each with the PR that must land to unblock it — note whether that PR is auto-merging (nothing to do) or needs individual review (per the reason text) rather than assuming the user must merge it.
    • Gated on a pending PR — Phase 3 dependents whose dependency HAS an open PR, each with that PR.
    • Gated on a dependency (not yet implemented) — Phase 3 dependents whose dependency has no PR (quality-dropped, failed, or never attempted). These do NOT clear on a merge — flag them: the dependency needs re-authoring or a future run first.
    • Stale branch — self-clears next run — executor skipped results with REASON_CODE: STALE_BRANCH_MERGED (legacy fallback: reason begins stale todo/) — a leftover branch whose PRs all MERGED; Phase 0's sweep deletes it and the todo re-runs then — no action needed. (A branch whose PR was closed WITHOUT merging is never in this group — that blocks with REASON_CODE: PR_CLOSED_UNMERGED and lands under "Blocked — needs a one-time manual fix" below.)

    None of these are failures. The first two clear on the next run after the user merges; the stale-branch group clears on the next run automatically; only the not-yet-implemented group needs the user's attention.

    Producer contract: every listing group in this summary is a terminal state for the run — the overnight /goal DONE condition in docs/todo-automation-runbook.md derives from "appears in some listing group", and that paste block's enumeration lists each of these headings by exact name (see its /goal completion-condition section). Never add, rename, or remove a listing group without updating that enumeration in the same change.

    Then list deferred warnings for triage. Collect every non-none DEFERRED_WARNINGS entry from all executors and print them under the heading "Deferred warnings — tell me which (if any) to turn into todos:". Nothing here is filed automatically; the user decides. If there are none, omit the heading.

    Then surface actionable blocks. Dependency-blocks (REASON_CODE: DEPENDENCY_GATED) do NOT resolve on their own — route each into the gated listings above ("Gated on a pending PR" if the dependency's PR is open; "Gated on a dependency (not yet implemented)" if it has none). If a blocked result carries REASON_CODE: ORPHAN_BRANCH, PR_CHECK_FAILED, or PR_CLOSED_UNMERGED (legacy fallback: its REASON contains ACTION NEEDED), print that REASON verbatim under the heading "Blocked — needs a one-time manual fix:" — it will re-block every run until the human clears it (the executor's reason includes the exact steps). Do NOT bury it as an ordinary dependency row. In the same section, also print any closed-unmerged todo/* branches Phase 0 found, both remote (/tmp/todo-closed-unmerged-branches.txt) and local (/tmp/todo-local-closed-unmerged-branches.txt) — each is a rejection signal (its PR was closed without merging); the user decides whether the todo is still wanted. Also print any local todo/* branches with NO PR in any state that Phase 0 found (/tmp/todo-local-no-pr-branches.txt) — each is a possible orphan from an interrupted executor run (the branch was renamed to todo/<slug> at Step 10 but the process crashed, or the push/gh pr create call failed, before any PR ever existed); Phase 0 never deletes or pushes these, so the user should inspect each one and either push it and open a PR manually, or delete the local branch if the work is abandoned. Also print any WARNING: line Phase 0 echoed (a git branch -D that failed for a reason other than the branch being checked out elsewhere) verbatim — the branch needs manual inspection. And note if Phase 0 skipped the branch sweep (gh failure or the --limit cap).

  5. Print verification result:

    Tests: PASS (T tests) | FAIL
    Types: PASS | FAIL (N errors)
    Lint:  PASS | FAIL (N errors)
    
  6. Sweep any executor worktrees still left over — crash backstop only. Phase 4 already removes each todo's worktree immediately when that todo's executor reports (rolling dispatch), so this step should normally find nothing. It exists for the case where an executor crashed before reaching that cleanup, or the run itself was interrupted mid-batch. Force-remove them — a bare git worktree prune cannot, because they are created locked. Use the non---porcelain form and expand a leading ~ manually (see the Phase 0 note on why: a git proxy in this environment can rewrite --porcelain output into a condensed, ~-shorthand format that silently breaks a ^worktree anchor):

    git worktree list | awk '/\.claude\/worktrees\/agent-/ {sub(/ +[0-9a-f]{4,40} +\[[^]]*\].*$/, ""); print}' | while read -r wt; do
      wt="${wt/#\~/$HOME}"
      git worktree unlock "$wt" 2>/dev/null
      git worktree remove --force "$wt" 2>/dev/null && echo "removed worktree: $wt"
    done
    git worktree prune

    This removes worktree directories only — branches and their open PRs are unaffected.

  7. Sync the local default branch with this run's merges. A /todo archive (and its code change) only reaches the local working copy when the merge propagates back — nothing edits local todos/ in place. After the run, fast-forward local main so any PR that merged during this session (a user-requested batch-merge) is reflected locally — ff-only, never disturbs parallel work:

    git fetch origin main --quiet || true
    CUR=$(git branch --show-current)
    if { [ "$CUR" = "main" ] || [ "$CUR" = "master" ]; } && [ -z "$(git status --porcelain)" ]; then
      git pull --ff-only -q origin "$CUR" 2>/dev/null && echo "synced local $CUR with origin" \
        || echo "local $CUR not fast-forwardable — pull manually"
    else
      git fetch origin main:main 2>/dev/null && echo "fast-forwarded local main ref" \
        || echo "local main not fast-forwardable — pull manually when on main + clean"
    fi

    Open PRs land only via auto-merge or the user's review — any still OPEN here merges later and cannot be pulled now. List each in the summary; the next /todo run's Phase 0 sync picks up post-merge state automatically.

Rules

  • Baseline must be green. Never start the run on a broken codebase.
  • Max 4 parallel agents. The rolling scheduler enforces this cap per completion, not by chunking into batches — respect the limit to avoid overwhelming system resources and context.
  • Sequential when scope is unknown. If a todo mentions no files, it's tagged must-run-alone — never assume it is safe to run alongside anything else.
  • Advisor-gate the queue. Before dispatching a queue with 2+ independent items, run the Phase 3 advisor review — catching an unsafe pairing before agents spin up is far cheaper than untangling stacked PRs after.
  • Top-level verification happens in Phase 5 only. Do not run an extra orchestrator-level npm run test:run / check:types / lint pass while executors are in flight. Each executor still performs its own scoped verification inside the worktree before reporting success, and the orchestrator runs one final repo-level verification pass at the end.
  • The executor agent does the work. This orchestrator only triages, dispatches, and summarizes. Never implement todo changes directly.
  • Archive happens in the executor. Completed todos are moved to todos/archive/ by the executor agent, not by this orchestrator.
  • Report everything. Every todo in the queue must appear in the final summary table, even if skipped or blocked.
  • Self-cleaning. Phase 0 force-removes leftover worktrees and deletes both remote AND local todo/* branches whose PRs are all merged (a branch whose PR was closed WITHOUT merging is a rejection signal — surfaced in Phase 5, never auto-swept, for both remote and local); Phase 4 removes each todo's worktree immediately on completion, and Phase 5 sweeps any stragglers as a crash backstop. The user must never have to clean up todo/* branches — local or remote — or agent-* worktrees by hand.
  • Auto-merge only through the guard. Executors enable GitHub's native gh pr merge --auto --squash --delete-branch ONLY when todo-automerge-guard.sh returns exit 0 (low priority, non-security, safe-path-only) — it then merges itself once CI is green, no orchestrator or user step. Every other PR (held, unknown, review-required) stays open and is never auto-merged; the user reviews and merges those individually.
  • Auto-sync local main. Phase 0 fast-forwards local main at the start (catching merges from prior sessions, which also stops the backlog from re-picking an already-archived todo) and Phase 5 fast-forwards again at the end (catching this run's merges). Always ff-only so parallel work is never disturbed — the user must never have to git pull by hand to see a completed todo archived locally.

Use it

Copy one of these into your project. Installing also returns the manifest and these snippets.

yaml
targets:
  - https://api.opensmartroute.ai/api/v1/registry/xertox1234-ocrecipes-todo/manifest   # or paste the manifest below

Manifest

An Open Capability Manifest: the router reads it to know what this does, what it costs and when to pick it.

xertox1234-ocrecipes-todo.ocm.jsonjson
{
  "ocm": "1",
  "id": "xertox1234-ocrecipes-todo",
  "kind": "skill",
  "name": "todo",
  "description": "Use when you have todos in todos/ with status backlog or planned and want to implement them autonomously in parallel",
  "publisher": "Xertox1234",
  "version": "1.0.0",
  "capabilities": {
    "domains": [
      "general"
    ],
    "tags": [
      "skill-md",
      "github"
    ],
    "languages": [
      "en"
    ]
  },
  "quality_prior": 0.6,
  "examples": [
    "Use when you have todos in todos/ with status backlog or planned and want to implement them autonomously in parallel"
  ],
  "primary": false,
  "metadata": {
    "source": {
      "provider": "github",
      "repository": "https://github.com/Xertox1234/OCRecipes",
      "path": ".claude/skills/todo/SKILL.md",
      "ref": "3d4f85aa26cf29219939b759bd63b818cc251b70",
      "url": "https://github.com/Xertox1234/OCRecipes/blob/3d4f85aa26cf29219939b759bd63b818cc251b70/.claude/skills/todo/SKILL.md",
      "key": "Xertox1234/OCRecipes/.claude/skills/todo/SKILL.md"
    }
  },
  "instructions": "You are running the todo orchestrator. This workflow cleans up prior runs, triages the backlog, plans execution order, dispatches executor agents, and reports results. **Never skip phases.**\n\n## Phase 0 — Cleanup Sweep\n\nBefore anything else, clear leftovers from previous `/todo` runs. This phase **always runs** and **never aborts** the workflow — if a step fails (e.g. `gh` is unauthenticated), report it and continue to Phase 1.\n\n1. **Force-remove leftover executor worktrees.** Executor worktrees are created _locked_, so `git worktree prune` alone silently skips them and they accumulate forever",
  "cost": {
    "context_tokens": 11962
  }
}

Fetch it by URL: GET /api/v1/registry/xertox1234-ocrecipes-todo/manifest?version=1.0.0

Reviews

Star ratings from people who tried it. One review per account; edit yours any time.

No reviews yet. Install it, try it, and be the first to rate it.