T-QOL7-gap-report-runs-before-lease-acquire
Status: closed/superseded · Impact: medium · Complexity: small
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-19 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
/sdlc:task-work runs its touchpoint-drift check too late: sdlc task gap-report fires only after Step 2a has taken the lifecycle lease and Step 4
has built the worktree, so drift is caught after irreversible side effects
already landed. Moving the call into Step 2’s relevance check — before the
lease acquire — makes drift a cheap, side-effect-free early exit.
Reported from T-J5DI-remove-clear-duplicate-augmented-packages in
https://github.com/sksizer/dev:
/sdlc:task-work runs
sdlc task gap-reportafter Step 2a acquires the lifecycle lease and Step 4 builds the worktree. During T-J5DI the task cited packages/ts/obsidian-bases-augment/ in both ## Today and ## Files to touch, but commit 09d8f775 had already deleted it. gap-report flagged it directory-missing correctly — but only after the lease and worktree existed, so a scope-narrowing commit had to land on origin/main mid-flow. Move the gap-report call into Step 2’s relevance check, before Step 2a’s lease acquire, so touchpoint drift is caught before any side effect. Touchpoints: apps/sdlc/skills/task-work/SKILL.md (Step 2 / Step 2a ordering) and apps/sdlc/lib/model/entities/task/ops/gap-report.ts.
| Location | Role today |
|---|---|
apps/sdlc/skills/task-work/SKILL.md | Step 2’s “Relevance check (the important one)” is a hand-rolled LLM sweep — regex-extract paths, ls/Read each, command grep cited symbols. It never calls sdlc task gap-report. Step 2a then acquires the lifecycle lease and Step 4 creates the worktree. |
apps/sdlc/lib/model/entities/task/ops/gap-report.ts | The deterministic, read-only, no-LLM readiness-gap composite. Its touchpoints.unresolved[] and touchpoints.parse_errors[] are exactly the touchpoint-drift facts Step 2’s relevance check is trying to derive by hand, and it mutates nothing, so it is safe to run before any side effect. |
apps/sdlc/skills/task-ensure-ready/SKILL.md | The only gap-report caller reachable from the task-work flow — invoked at task-work Step 5a, i.e. after the lease (2a) and the worktree (4) already exist. |
apps/sdlc/lib/services/gate/ops/_skill_prose_contract.ts#SKILL_PROSE_REGISTRY | Pins task-work’s prose invariants per section. The "2. Pre-flight: verify task is workable" entry pins only the verify-stamp commit subject (skill-prose/task-work/s9); nothing pins a pre-lease drift check, so the ordering can silently regress. |
docs/skills/task-work.md | Per-skill doc. Its Mermaid flowchart labels the relevance node Relevance check: ls cited paths; command grep cited symbols and arrows into 2a. Acquire the lease. |
Proposed
Section titled “Proposed”Step 2’s relevance check opens with one deterministic sdlc task gap-report <path> --output json call. A non-empty
touchpoints.unresolved[] or touchpoints.parse_errors[] feeds the
step’s existing “materially out of date” AskUserQuestion branch, which
already returns before Step 2a. Touchpoint drift then costs one
read-only op call and zero side effects — no lease ref at
refs/sdlc/tasks/<basename>, no worktree, no task branch, and no
mid-flow scope-narrowing commit on origin/main.
Step 5a’s /sdlc:task-ensure-ready keeps running the same composite as
the authoritative, stamping gate; the Step 2 call is an early advisory
read that writes no frontmatter.
Approach
Section titled “Approach”- In
apps/sdlc/skills/task-work/SKILL.md, rewrite the numbered list under “Relevance check (the important one)” so its first item is the deterministic composite, run un-piped so the exit code propagates:${CLAUDE_PLUGIN_ROOT}cli/sdlc task gap-report <task-path> --output json. Readtouchpoints.unresolved[](cited Location missing from the working tree) andtouchpoints.parse_errors[](malformed row, symbol-on-glob, missing column) as the authoritative drift facts. - Demote the existing
ls/Read/command grepitems to the interpretive follow-up the op deliberately does not cover — the “currently does X” prose claims and therelevance_notespot-check — rather than re-deriving path existence the op already decided. - Route a non-empty
touchpoints.unresolved[]into the step’s existing out-of-date AskUserQuestion (update the doc / close the task with acompletion_note/ stop), and state in that prose that the branch returns before Step 2a, so no lease or worktree exists yet. - Add one sentence to the new Step 2 text marking the call advisory:
it does not stamp
readiness_verified_at:and does not replace Step 5a’s/sdlc:task-ensure-readygate, which re-runs the same composite authoritatively. - Add one sentence to Step 2a’s opening paragraph naming Step 2’s
gap-reportas the last read-only check before the first side effect, so the ordering invariant is stated on both sides. - In
apps/sdlc/lib/services/gate/ops/_skill_prose_contract.ts, add arequiresentry to the"2. Pre-flight: verify task is workable"section ofSKILL_PROSE_REGISTRY["task-work"]: patterntask gap-report, idskill-prose/task-work/s16(the next free id — s15 is the highest in use), and a note explaining that the drift check must stay in Step 2 ahead of Step 2a’s lease acquire. - Refresh
docs/skills/task-work.mdso the Mermaid relevance node names thesdlc task gap-reportcall upstream of the2a. Acquire the leasenode./sdlc:update-skill-doc task-workis the canonical way to regenerate it. - Verify:
sdlc gate skill-prose --paths apps/sdlc/skills/task-work/SKILL.mdexits 0, andbun test apps/sdlc/lib/services/gate/ops/_skill_prose_contract.test.tspasses.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/skills/task-work/SKILL.md | modify | Move the deterministic touchpoint-drift check into Step 2’s relevance check as its first item; demote the hand-rolled path/symbol sweep to the interpretive follow-up; route drift into the existing out-of-date branch; note the call is advisory; name the ordering invariant in Step 2a. |
apps/sdlc/lib/services/gate/ops/_skill_prose_contract.ts | modify | Add the task gap-report requires pin (skill-prose/task-work/s16) under the task-work entry’s "2. Pre-flight: verify task is workable" section. |
docs/skills/task-work.md | modify | Refresh the Mermaid relevance node to name the gap-report call ahead of the lease-acquire node. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
apps/sdlc/skills/task-work/SKILL.mdcontains the literal stringtask gap-reportinside the “Relevance check (the important one)” subsection of Step 2, at a lower line number than the## 2a. Acquire the leaseheading. - AC-2: That invocation is written un-piped (no
| tail, no| head), per the “Don’t pipe commands you gate on” rule inapps/sdlc/skills/CLAUDE.md. - AC-3: Step 2’s out-of-date prose names
touchpoints.unresolved[]as a trigger for the existing AskUserQuestion branch and states that the branch returns before any lease acquire. - AC-4: Step 2’s new text states the call does not stamp
readiness_verified_at:, and Step 5a still routes readiness through/sdlc:task-ensure-ready. - AC-5:
SKILL_PROSE_REGISTRY["task-work"]’s"2. Pre-flight: verify task is workable"section carries arequiresentry with patterntask gap-reportand idskill-prose/task-work/s16. - AC-6:
sdlc gate skill-prose --paths apps/sdlc/skills/task-work/SKILL.mdexits 0 and reports no violation. - AC-7:
bun test apps/sdlc/lib/services/gate/ops/_skill_prose_contract.test.tspasses. - AC-8:
docs/skills/task-work.md’s Mermaid flowchart names thegap-reportcall in the relevance node, and that node still arrows into the2a. Acquire the leasenode.
Out of scope
Section titled “Out of scope”- Changing
apps/sdlc/lib/model/entities/task/ops/gap-report.ts— the op already emits the needed facts read-only; only its call site moves. - Removing or weakening Step 5a’s
/sdlc:task-ensure-readygate. The Step 2 call is additive and advisory; the stamping gate stays. - Folding the drift check into
sdlc task probe-state(the Step 2 pre-flight probe). That would be a second op-composition change; the early call stays a separate shell-out. - Auto-repairing drifted touchpoint rows. The out-of-date branch stays a human decision via AskUserQuestion.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:spawn-task-pr on 2026-07-20 UTC from T-J5DI-remove-clear-duplicate-augmented-packages in https://github.com/sksizer/dev.