fix(worker): surface empty review ranges and blocked children over MCP

preview_merge/preview_merge_set now report isEmpty (ahead==0, or
HandlerBaseCommit==HandlerHeadCommit for a worktree-less list-handler
task) instead of leaving an empty branch indistinguishable from a small
one. preview_merge also stops throwing for worktree-less handler tasks,
falling back to their fixed commit range. review_task's parent approve
returns emptyChildren, naming the Done children whose review range
contributed nothing before the unit merge lands. TaskRefDto/TaskDto now
expose roadblockCount so a CLAUDEDO_BLOCKED child is identifiable over
MCP, since it still reaches Done per the unified parent model.
This commit is contained in:
mika kuns
2026-08-06 11:54:46 +02:00
parent 8247a749a0
commit e59f6c2438
4 changed files with 361 additions and 47 deletions
+27 -9
View File
@@ -1,8 +1,8 @@
# External MCP tool surface
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
> Last verified against commit `86f962e` (2026-08-06).
> Drift check: `git log --oneline 86f962e..HEAD -- src/ClaudeDo.Worker/External`
> Last verified against commit `8247a74` (2026-08-06).
> Drift check: `git log --oneline 8247a74..HEAD -- src/ClaudeDo.Worker/External`
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
Covers `src/ClaudeDo.Worker/External/` — the always-on MCP tools ClaudeDo exposes to general
@@ -85,17 +85,35 @@ happens before the lean/full projection either way.
`WaitingForReview` task. Approve is review+merge exactly like the hub's `ApproveReview`: unit
merge for parents, worktree merge into optional `targetBranch` for childless tasks. Conflicts
are reported in `ReviewTaskResult`.
- A parent's approve also returns `emptyChildren`: the `Done` children about to be unit-merged
whose own review range contributed nothing (computed the same way as `PreviewMerge`'s
`isEmpty`, before the merge starts so it reflects what's about to be approved). Surfaces a
child that reported `CLAUDEDO_BLOCKED` and committed no code — previously that child reached
`Done` and merged silently with `changedFileCount: 0`, indistinguishable from a small-but-real
change. `TaskRefDto.roadblockCount` (on every task-returning tool, stamped by `TaskRunner` from
`result.Blocks.Count`) is the MCP-visible signal for *why* a child is empty.
**`PreviewMerge`** — non-destructive `git merge-tree --write-tree` mergeability check for one
task's worktree branch against `targetBranch` (default: the repo's current branch). Returns
status / conflictFiles / changedFileCount / `behind`. Unlike `TaskMergeService.PreviewAsync`'s
silent *"unavailable"*, this **throws a clear error** when the task has no worktree, the
worktree isn't `Active`, or the list's working dir is missing.
status / conflictFiles / changedFileCount / `behind` / `isEmpty`. Unlike
`TaskMergeService.PreviewAsync`'s silent *"unavailable"*, this **throws a clear error** when the
task has neither an active worktree nor a handler commit range, or the list's working dir is
missing.
- `isEmpty` = the review range contributed nothing — zero files changed against the worktree's
base commit, or (for a worktree-less list-handler host task) `HandlerBaseCommit ==
HandlerHeadCommit`. Distinguishes a genuinely empty branch from one that merely made a small
change (`changedFileCount: 0` alone reads as "tiny", not "nothing to review") — the gap that
let two blocked planning children reach `Done` with unmerged empty branches unnoticed.
- A worktree-less handler task has no separate branch to `merge-tree`-preview (its commits
already sit in `list.WorkingDir`) — `PreviewMergeCoreAsync` falls back to a synthetic `clean`
preview over its own `HandlerBaseCommit..HandlerHeadCommit` diff-stat instead of throwing
"has no worktree".
**`PreviewMergeSet`** — same preview for a batch, plus a file→tasks overlap report built from
each task's own diff-stat. ⚠️ That overlap report is a **same-file-name hint only** — it is
blind to cross-file collisions (e.g. the CS0103 case that motivated it). A task that fails to
preview gets `error` set and is excluded from the overlap instead of aborting the batch.
**`PreviewMergeSet`** — same preview for a batch (each entry also carries `isEmpty`), plus a
file→tasks overlap report built from each task's own diff-stat. ⚠️ That overlap report is a
**same-file-name hint only** — it is blind to cross-file collisions (e.g. the CS0103 case that
motivated it). A task that fails to preview gets `error` set and is excluded from the overlap
instead of aborting the batch.
**`RevertMerge`** — undoes a previously merged task's merge commit on `targetBranch` via
`git revert -m 1`. Always a **new commit**, never a reset/rewrite, because the target working
+11 -2
View File
@@ -1,8 +1,8 @@
# Review, merge & conflict resolution
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
> Last verified against commit `f6cb825` (2026-08-05).
> Drift check: `git log --oneline f6cb825..HEAD -- src/ClaudeDo.Worker/Lifecycle src/ClaudeDo.Worker/State src/ClaudeDo.Worker/Planning src/ClaudeDo.Ui/ViewModels/Conflicts`
> Last verified against commit `8247a74` (2026-08-06).
> Drift check: `git log --oneline 8247a74..HEAD -- src/ClaudeDo.Worker/Lifecycle src/ClaudeDo.Worker/State src/ClaudeDo.Worker/Planning src/ClaudeDo.Ui/ViewModels/Conflicts src/ClaudeDo.Worker/External`
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
Covers the review→merge path: `TaskStateService` review transitions, `TaskMergeService`,
@@ -47,6 +47,15 @@ advance the parent — the parent stays in `WaitingForChildren` until every chil
The UI surfaces blocked children on the parent's Session tab (`ChildOutcomes` + a "children
need attention" band) so the roadblock is visible without forcing a transition.
A blocked planning/improvement child still goes straight to `Done` per the unified parent model
above — it committed nothing, but nothing prevents its (empty) branch from being unit-merged
like any other `Done` child once the parent is approved. The MCP surface has no UI equivalent of
`ChildOutcomes`, so `review_task`'s approve on a parent additionally returns `emptyChildren` (the
`Done` children whose review range is empty) and every task-returning tool exposes
`TaskRefDto.roadblockCount` → [external-mcp.md](external-mcp.md) → `ReviewTask`/`PreviewMerge`.
An empty branch is still mergeable by design (some tasks — e.g. an audit — legitimately produce
no diff); this is a visibility fix, not a merge gate.
## Post-merge verify gate
A list can set `ListConfigEntity.VerifyCommand` (List Settings modal → Verification).