Skip to content

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.

LocationRole today
plugin/skills/task-ensure-ready/SKILL.mdRuns irrespective of worktree dirtiness; stamps and commits whatever it touched.
plugin/skills/task-ensure-ready/ensure_ready_mutate.tsThe 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.

/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.

  1. Add a precondition check in ensure_ready_mutate.ts that runs git diff --cached and git diff on 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.
  2. Update plugin/skills/task-ensure-ready/SKILL.md to document the new precondition.
  3. Update plugin/skills/task-work/SKILL.md Step 5 to mention the precondition and instruct the author to commit body edits first.
  4. Add a fixture test that constructs a worktree with uncommitted body edits and asserts the helper refuses.
LocationKindChange
plugin/skills/task-ensure-ready/ensure_ready_mutate.tsmodifyAdd the uncommitted-body-edits precondition check.
plugin/skills/task-ensure-ready/SKILL.mdmodifyDocument the precondition.
plugin/skills/task-work/SKILL.mdmodifyStep 5: warn that body edits must be committed before the gate.
plugin/skills/task-ensure-ready/tests/ensure_ready.test.tsmodifyAdd the refuses-with-uncommitted-body-edits test.
  • AC-1: Running ensure_ready_mutate.ts against 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.ts against 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-work run 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.
  • Auto-staging body edits as a separate commit. The author makes the commit-boundary call; the gate just refuses to bundle.
  • none

Spawned by /sdlc:task-work post-mortem of T-S0PK-add-lease-protocol-library-and-schemas on 2026-05-23.

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

Captured by /sdlc:task-work on 2026-06-04. PR: pending.

  • 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 the frontmatter close line in stderr.
  • AC-2: auto — same suite; succeeds-on-clean-worktree-frontmatter-only-commit asserts exit 0, the docs(tasks): verify ... implementation-ready commit, 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) and task-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-existing gate-flip-rebase-clean test; no standalone end-to-end task-work harness exists, matching the task’s test plan which scoped only the refusal test.
  • 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 existing applyMutation/commitIn flow with no contract changes to the verifier core.
  • audit_skill_runtime.ts exited 0, confirming the SKILL.md edits introduced no dangling script/flag references.
  • Step 7’s baseline-gated quality check reported FAIL new-drift=8, but all 8 lines were benign - OK <task>.md audit-listing lines for tasks this branch never touched. Cause: quality_baseline.ts capture ran audit_entities.ts against 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 -C a pristine checkout), or have audit_entities.ts emit 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 + cleanup describe-block in ensure_ready.test.ts has 4 pre-existing failures on origin/main (its buildMarkerFixtureRepo fixture predates the id-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 (add id, bump schema_version) would green them — worth a follow-up.
  • macOS /tmp/private/tmp symlink tripped commitIn’s staged-set guard (relative-vs-absolute path compare) the first time a fixture exercised the bare --commit path in a tmpdir; worked around in the test via realpathSync. The guard itself could realpathSync both sides so callers passing a /tmp-rooted path don’t hit a spurious “unexpected staged changes” refusal.

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 buildMarkerFixtureRepo fixture in ensure_ready.test.ts (bullet 2) — add id, bump schema_version so the 4 pre-existing --commit*-path validation failures green. Dedup: no real match (SPAWNED).
  • commitIn staged-set guard should realpathSync both operands (bullet 3) so a /tmp-rooted task path doesn’t spuriously trip “unexpected staged changes” on macOS. Dedup: no real match (SPAWNED).

← Back to Tasks