T-XBJY-ensure-ready-refuses-with-unstaged-body-edits
Status: closed/done · Impact: medium · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
/sdlc:task-ensure-ready stamps readiness_verified_at into the task’s
frontmatter and commits the stamp. If the worktree has uncommitted
body edits to the same task file at the time the gate runs, the stamp
commit ends up bundled with the body edits — and /sdlc:task-work’s
Step 5b rebase then sees a single commit touching both frontmatter
(conflicts with the start-commit’s frontmatter edit) and body (clean
merge), forcing a manual conflict resolution that should never happen
on a green path. Cite T-S0PK-add-lease-protocol-library-and-schemas
for the live example. The fix: ensure-ready either refuses to run with
uncommitted body edits OR task-work’s prose tells the author to commit
body edits separately before invoking the gate.
| Location | Role today |
|---|---|
plugin/skills/task-ensure-ready/SKILL.md | Runs irrespective of worktree dirtiness; stamps and commits whatever it touched. |
plugin/skills/task-ensure-ready/ensure_ready_mutate.ts | The script that performs the stamp + commit; treats the worktree as a black box. |
plugin/skills/task-work/SKILL.md (Step 5) | Invokes ensure-ready; does not warn the author to commit body edits first. |
Proposed
Section titled “Proposed”/sdlc:task-ensure-ready (or its ensure_ready_mutate.ts helper)
inspects the worktree before stamping. If the target task file has
uncommitted body edits (any non-frontmatter changes), the gate refuses
to run with a clear error: “commit body edits in a separate commit
before invoking ensure-ready; the gate must be the only thing in the
stamp commit.” The error message names the file and the rough hunk
ranges so the author can fix in one step.
Approach
Section titled “Approach”- Add a precondition check in
ensure_ready_mutate.tsthat runsgit diff --cachedandgit diffon the target task file and parses the hunk ranges; if any hunk falls outside the frontmatter block (lines between---markers), refuse with a clear message. - Update
plugin/skills/task-ensure-ready/SKILL.mdto document the new precondition. - Update
plugin/skills/task-work/SKILL.mdStep 5 to mention the precondition and instruct the author to commit body edits first. - Add a fixture test that constructs a worktree with uncommitted body edits and asserts the helper refuses.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/task-ensure-ready/ensure_ready_mutate.ts | modify | Add the uncommitted-body-edits precondition check. |
plugin/skills/task-ensure-ready/SKILL.md | modify | Document the precondition. |
plugin/skills/task-work/SKILL.md | modify | Step 5: warn that body edits must be committed before the gate. |
plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts | modify | Add the refuses-with-uncommitted-body-edits test. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Running
ensure_ready_mutate.tsagainst a task file with uncommitted body edits exits non-zero with a clear error message naming the file and the offending hunk ranges. - AC-2: Running
ensure_ready_mutate.tsagainst a clean worktree (only the in-memory frontmatter stamp pending) succeeds and commits the stamp as a frontmatter-only commit. - AC-3: A fresh
/sdlc:task-workrun that picks up a task whose body needs fixes uses the new contract: the author commits body edits first, then ensure-ready commits only the stamp, then Step 5b rebases without conflict.
Out of scope
Section titled “Out of scope”- Auto-staging body edits as a separate commit. The author makes the commit-boundary call; the gate just refuses to bundle.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-S0PK-add-lease-protocol-library-and-schemas on 2026-05-23.
Dedup search (spawn-from-post-mortem)
Section titled “Dedup search (spawn-from-post-mortem)”Bullet: /sdlc:task-work Step 5b rebase conflict on first run when verify-stamp commit included body fixes alongside the readiness stamp. ensure-ready could refuse to run if worktree has unstaged body edits at gate-time, OR task-work Step 5 prose could warn body edits should be committed separately before invoking the gate. Keywords searched: verify-stamp, ensure-ready, separately, task-work, alongside, readiness, gate-time, committed Excluded: 2026-05-23-add-lease-protocol-library-and-schemas Top candidates (score / status / headline):
- 55 / closed/done / 2026-05-20-task-work-sub-agent-verdict-contract-clarity — Tighten task-work sub-agent verdict contract so ensure-ready’s READY marker isn’t mistaken for task-work’s final verdict
- 46 / closed/done / 2026-05-19-task-ensure-ready-accepts-in-progress — task-ensure-ready accepts in-progress when readiness_verified_at is set
- 43 / planning/draft / 2026-05-22-task-new-commits-task-file — task-new commits the task file (or task-work stages it) so the canonical flow doesn’t require an out-of-band commit
- 42 / closed/done / 2026-05-19-resolve-ensure-ready-in-progress-contract — Resolve task-ensure-ready contract conflict with task-work in-progress flip
- 41 / planning/draft / 2026-05-22-task-work-uses-worktree-skill-md — task-work loads SKILL.md from the worktree, not ${CLAUDE_PLUGIN_ROOT} Decision: SPAWNED
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-04. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
bun test plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts; three new tests (refuses-on-unstaged-body-edits-commit-path,refuses-on-unstaged-body-edits-commit-on-main-path,refuses-on-staged-body-edits) assert exit 4 with the file name, hunk line ranges, and thefrontmatterclose line in stderr. - AC-2: auto — same suite;
succeeds-on-clean-worktree-frontmatter-only-commitasserts exit 0, thedocs(tasks): verify ... implementation-readycommit, and that the stamp commit adds exactly one line (readiness_verified_at). - AC-3: agent-manual — the new contract is documented in
task-ensure-ready/SKILL.md(new Step 3b) andtask-work/SKILL.md(Step 5 warning). The “commit body edits first → stamp is frontmatter-only → Step 5b rebases clean” chain is mechanized by the frontmatter-only-commit test plus the pre-existinggate-flip-rebase-cleantest; no standalone end-to-end task-work harness exists, matching the task’s test plan which scoped only the refusal test.
What worked
Section titled “What worked”- The deterministic readiness gate (touchpoint parser + placeholder scanner) and the baseline-capture step ran without friction.
- The precondition slotted cleanly into both commit paths (
--commit,--commit-on main) ahead of the existingapplyMutation/commitInflow with no contract changes to the verifier core. audit_skill_runtime.tsexited 0, confirming the SKILL.md edits introduced no dangling script/flag references.
Friction and automation gaps
Section titled “Friction and automation gaps”- Step 7’s baseline-gated quality check reported
FAIL new-drift=8, but all 8 lines were benign- OK <task>.mdaudit-listing lines for tasks this branch never touched. Cause:quality_baseline.ts captureranaudit_entities.tsagainst the main checkout (which had the user’s parallel uncommitted WIP edits to many task files), while the gate ran it against the worktree’s clean-at-branch-point corpus — so the per-task OK listing differs for unrelated tasks and is mis-attributed as new drift. Fix: either capture the baseline against a clean tree (stash/git -Ca pristine checkout), or haveaudit_entities.tsemit a stable repo-wide summary line that the line-diff can key on instead of one line per task. Tracked separately as a baseline-isolation concern. - The
marker emission + cleanupdescribe-block inensure_ready.test.tshas 4 pre-existing failures onorigin/main(itsbuildMarkerFixtureRepofixture predates theid-required / schema_version bump, so the validator now rejects it on the--commit*paths). Out of scope for this task; left untouched. A one-line fixture refresh (addid, bumpschema_version) would green them — worth a follow-up. - macOS
/tmp→/private/tmpsymlink trippedcommitIn’s staged-set guard (relative-vs-absolute path compare) the first time a fixture exercised the bare--commitpath in a tmpdir; worked around in the test viarealpathSync. The guard itself couldrealpathSyncboth sides so callers passing a/tmp-rooted path don’t hit a spurious “unexpected staged changes” refusal.
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”All three bullets classify Upstream-plugin (this repo IS the SDLC
plugin’s dev repo, so the degenerate-case spawn lands here with the
sdlc-meta tag). The /sdlc:spawn-task-pr dispatch surface (Agent /
sub-skill PR-opening) was not available in this non-interactive
task-work sub-agent run, so the independent follow-up PRs are deferred
rather than fabricated; the gaps are recorded here as actionable
intent for the orchestrator or a human to spawn via
/sdlc:spawn-task-pr. Dedup search ran for each (telemetry appended to
.claude/dedup-telemetry.jsonl):
- Baseline-capture-vs-gate corpus-state mismatch (bullet 1) — dedup top
match
T-TWZD-normalize-baseline-diff-nondeterministic-output(score 30), but distinct: T-TWZD masks ephemeral tokens within a line (tmpdir paths, SHAs); this gap is the whole-line- OK <task>set differing because capture and gate saw different corpus dirtiness. Related, not duplicate — recorded as a sibling follow-up. - Refresh the stale
buildMarkerFixtureRepofixture inensure_ready.test.ts(bullet 2) — addid, bumpschema_versionso the 4 pre-existing--commit*-path validation failures green. Dedup: no real match (SPAWNED). commitInstaged-set guard shouldrealpathSyncboth operands (bullet 3) so a/tmp-rooted task path doesn’t spuriously trip “unexpected staged changes” on macOS. Dedup: no real match (SPAWNED).