Skip to content

T-7RST-ensure-ready-flags-mid-migration-corpus-assumptions

Status: closed/done · Impact: medium · Complexity: small

The readiness gate accepts tasks whose Approach implicitly assumes a uniform corpus shape even when the actual corpus is mid-migration. An implementer then has to design the strictness/tolerance split on the spot. Surfacing the assumption at the readiness gate turns it into a design conversation before implementation.

From the originating task’s post-mortem:

“T0011’s design needed a discover()/from_dir() strictness split absorbing the pre-D0004 corpus shape. The readiness gate should flag tasks whose Approach implicitly assumes a uniform corpus when the corpus is mid-migration, so the design conversation happens before implementation.”

LocationRole today
plugin/lib/model/entities/task/implementation-ready.mdThe readiness contract. Its ## Disqualifiers list enumerates every condition that makes a doc NOT implementation-ready (subjective ACs, design-deferred-to-implementer, surviving placeholders). It has no entry for “Approach assumes a uniform corpus mid-migration.”
plugin/skills/task-ensure-ready/SKILL.mdThe skill spec. Step 3 (“Evaluate the contract”) runs the mechanical disqualifier scanners (parse_touchpoints.ts, scan_placeholders.ts) and then an LLM evaluation of the contract. There is no corpus-assumption check in either half.
plugin/skills/task-ensure-ready/scan_placeholders.tsThe mechanical placeholder scanner: walks the required body sections (skipping fenced/inline code), emits one JSON line per hit ({section, phrase, line, snippet}), exit 0 with empty stdout when clean. The structural sibling a new corpus-assumption scanner is modelled on — same section-walk, same JSON-line output contract.
plugin/skills/task-ensure-ready/scan_placeholders.ts#REQUIRED_SECTIONSThe canonical map of required-section H2 headings the scanner restricts itself to. A corpus-assumption scanner reuses the same section-scoping (it only inspects Approach/Proposed).
plugin/skills/task-ensure-ready/ensure_ready_mutate.tsThe pass/fail mutation tail (stamps readiness_verified_at, or records definition_gap and downshifts status). Unchanged by this task — a corpus-assumption hit is just another disqualifier the LLM folds into the gap description it already composes in SKILL.md Step 5.
plugin/skills/task-ensure-ready/tests/ensure_ready.test.tsThe skill’s test suite, including per-scanner behavioural cases (scan_placeholders, parse_touchpoints). New scanner cases land here.

Add “Approach assumes a uniform corpus shape while the corpus is mid-migration” to the readiness contract’s disqualifier list, and back it with a heuristic scanner (scan_corpus_assumptions.ts) modelled on scan_placeholders.ts. Because whether an assumption is wrong is a semantic judgment (the corpus may legitimately be uniform), the scanner is advisory: it surfaces candidate signals — uniform-corpus phrasing in ## Approach/## Proposed with no accompanying tolerance/strictness language — and the existing LLM evaluation in SKILL.md Step 3 makes the final call. This mirrors the gate’s existing split: mechanical scanners narrow where to look; the LLM step decides. A pure regex cannot be the gate here (it would false-positive on genuinely-uniform corpora), so the contract entry is phrased as an LLM-judged disqualifier with the scanner as its candidate-finder, exactly as the subjective-AC disqualifier is LLM-judged with the body as its input.

  1. Add the disqualifier to the contract. In plugin/lib/model/entities/task/implementation-ready.md’s ## Disqualifiers list, add a bullet: a doc is not implementation-ready when its Approach assumes a single uniform corpus shape while the corpus it operates on is mid-migration (different instances carry different shapes), without specifying the strictness/tolerance split the implementer would otherwise have to invent on the spot. State explicitly that this is an LLM-judged disqualifier (like the subjective-AC entry), not a purely mechanical one, and that the corpus-assumption scanner only surfaces candidates.
  2. Build scan_corpus_assumptions.ts next to scan_placeholders.ts, reusing its frontmatter-strip, fenced/inline-code masking, and required-section walk (restricted to ## Approach and ## Proposed). Emit one JSON line per candidate ({section, signal, line, snippet}), exit 0 with empty stdout when none, exit 2 on arg/IO failure — the same output contract as scan_placeholders.ts.
  3. Define the candidate heuristic. A line is a candidate when it carries uniform-corpus phrasing — e.g. “every entity”, “all instances”, “all definition.md”, “the corpus”, “uniform shape”, “each *.md has the same”, “assume … same shape” — AND the surrounding section contains no tolerance/strictness signal (“legacy”, “mid-migration”, “tolerate”, “strict vs”, “both shapes”, “pre-D0004”, “discover()/from_dir()”, “escape hatch”, “version skew”). The patterns are documented in the module header so the heuristic is auditable and tunable. The scanner is a candidate-finder, not a verdict — false positives are expected and resolved by the LLM step.
  4. Wire the scanner into SKILL.md Step 3 as an advisory input to the LLM evaluation (NOT a hard mechanical disqualifier). Add a sub-bullet under the disqualifier checks: run bun run ${CLAUDE_PLUGIN_ROOT}/skills/task-ensure-ready/scan_corpus_assumptions.ts <path>; each emitted candidate is a prompt for the LLM to inspect that line and decide whether the Approach genuinely assumes a uniform corpus the corpus does not have. If the LLM confirms, it folds the finding into the gap description it already composes in Step 5; if the corpus is genuinely uniform (or the Approach already handles the split), it is not a gap. Keep the pipe-tail-safe shape (no | tail) per plugin/skills/CLAUDE.md.
  5. Add behavioural test cases to plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts: an Approach with bare uniform-corpus phrasing → one candidate; an Approach that names the strictness/tolerance split → zero candidates; phrasing inside a fenced code block → zero candidates (masking works); a non-required section carrying the phrasing → zero candidates (section-scoping works).
  6. Keep the mutation tail untouched. ensure_ready_mutate.ts already records whatever gap description the LLM composes; a confirmed corpus-assumption finding rides the existing definition_gap + status downshift path with no code change there.
LocationKindChange
plugin/skills/task-ensure-ready/scan_corpus_assumptions.tsnewThe heuristic candidate-finder modelled on scan_placeholders.ts: same section-walk and JSON-line output, restricted to Approach/Proposed, emitting uniform-corpus candidates with no nearby tolerance signal.
plugin/lib/model/entities/task/implementation-ready.mdmodifyAdd the corpus-assumption disqualifier to the ## Disqualifiers list, phrased as an LLM-judged check with the scanner as candidate-finder.
plugin/skills/task-ensure-ready/SKILL.mdmodifyAdd the scanner invocation as an advisory input to Step 3’s LLM evaluation; document that candidates are LLM-confirmed, not auto-failed.
plugin/skills/task-ensure-ready/tests/ensure_ready.test.tsmodifyAdd behavioural cases for the new scanner (candidate, tolerance-present, fenced-code, non-required-section).
  • AC-1: plugin/lib/model/entities/task/implementation-ready.md’s ## Disqualifiers list contains an entry for an Approach that assumes a uniform corpus shape mid-migration, and that entry states it is LLM-judged (not a purely mechanical disqualifier).
  • AC-2: scan_corpus_assumptions.ts exists and, given a task whose ## Approach carries uniform-corpus phrasing with no nearby tolerance/strictness signal, emits at least one JSON candidate line on stdout and exits 0.
  • AC-3: Given a task whose ## Approach names the strictness/tolerance split (e.g. mentions “legacy”, “both shapes”, or a discover/from_dir split), scan_corpus_assumptions.ts emits zero candidates and exits 0.
  • AC-4: The scanner skips fenced code blocks and inline-code spans and restricts itself to the ## Approach / ## Proposed sections (proven: uniform-corpus phrasing inside a fence, or under a non-required H2, yields zero candidates) — same scoping contract as scan_placeholders.ts.
  • AC-5: plugin/skills/task-ensure-ready/SKILL.md Step 3 invokes the scanner as an advisory input to the LLM evaluation, states each candidate is LLM-confirmed before becoming a gap, and uses a pipe-tail-safe invocation (no unguarded | tail/| head).
  • AC-6: bun test plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts passes, including the new scanner behavioural cases (candidate / tolerance-present / fenced-code / non-required-section).
  • AC-7: No change is made to ensure_ready_mutate.ts’s mutation logic — a confirmed corpus-assumption finding rides the existing definition_gap + status-downshift path.
  • A pure-regex hard-fail disqualifier — corpus uniformity is context-dependent; a regex alone would false-positive on genuinely-uniform corpora, so the gate stays LLM-judged with the scanner as candidate-finder only.
  • Detecting which specific corpus is mid-migration (D-0004 column drift, schema-version skew, etc.) — the scanner flags the assumption pattern in the spec text; it does not inspect the live corpus.
  • Auto-rewriting the Approach to add the strictness/tolerance split — that is /sdlc:task-define’s interactive job; this task only surfaces the gap.
  • Changing ensure_ready_mutate.ts, parse_touchpoints.ts, or scan_placeholders.ts — the new scanner is a sibling, not a modification of the existing ones.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-05-31 UTC from T-0011 in git@github.com:sksizer/dev.git.

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

  • AC-1: auto — contract entry present and flagged LLM-judged; grep "LLM-judged disqualifier" plugin/lib/model/entities/task/implementation-ready.md.
  • AC-2: auto — bun test ensure_ready.test.ts -t "scan corpus assumptions" case bare-uniform-approach-flagged (candidate emitted, exit 0).
  • AC-3: auto — same suite, case tolerance-named-not-flagged (zero candidates).
  • AC-4: auto — same suite, cases fenced-uniform-not-flagged and non-required-section-not-flagged (masking + section-scoping).
  • AC-5: agent-manual — read plugin/skills/task-ensure-ready/SKILL.md Step 3: scanner invoked as advisory input, candidates LLM-confirmed before becoming a gap, pipe-tail-safe invocation (no | tail/| head).
  • AC-6: auto — bun test plugin/skills/task-ensure-ready/tests/ensure_ready.test.ts: 42 pass, 4 fail; the 4 failures are the pre-existing marker emission + cleanup lease-transition tests (reproduced identically on a clean origin/main checkout) and are unrelated to this task’s scope.
  • AC-7: auto — git diff origin/main..HEAD -- ensure_ready_mutate.ts is empty (mutation logic untouched).
  • The new scanner cloned scan_placeholders.ts’s structure cleanly — frontmatter strip, fenced/inline-code masking, and section-scoped walk all transferred, so the four behavioural cases (candidate / tolerance-present / fenced / non-required-section) passed first try.
  • Baseline-gated quality checks correctly subtracted the 4 pre-existing lease-transition test failures, leaving a clean OK 5/5.
  • The quality baseline captured from the main repo’s working tree was polluted by deliberate uncommitted task-file WIP, so audit_entities.ts reported phantom new-drift on - OK enumeration lines; capturing the baseline from a clean detached origin/main worktree fixed it — quality_baseline.ts capture should warn (or capture from a clean tree) when the cwd working tree is dirty under audited paths. → T-TWZD-normalize-baseline-diff-nondeterministic-output
  • Resume after a killed run left the task branch based on a stale local main; the check_ancestry.ts helper reported clean while a foreign docs(tasks): verify <other> commit still sat in origin/main..HEAD — the helper only matches chore(tasks): start <other> subjects, so it misses foreign verify-stamp commits; widening it to also match docs(tasks): verify <other> implementation-ready would catch this class. → T-61OI-check-ancestry-flags-stale-base
  • origin/main advanced three times during the run (parallel orchestration), forcing repeated re-fetch + re-baseline; a baseline keyed to a moving origin/main SHA is inherently racy — the gate could pin the baseline SHA at Step 3a and reuse it rather than re-resolving the tip. → T-TWZD-normalize-baseline-diff-nondeterministic-output

← Back to Tasks