Skip to content

T-X3F2-lint-grep-acs-vs-out-of-scope

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

A task-authoring lint should cross-check grep-shaped acceptance criteria against the task’s ## Out of scope deferrals. In T-VOH6-port-check-skill-prose-to-ts an AC asserted a repo-wide grep (grep -rn "check_skill_prose.py|lint_skill_prose.py" .claude returns no matches) while Out-of-scope deferred “refreshing the SKILL.md script citations” — the two contradicted each other, since the grep would catch the very citations the task deferred. The binding AC won and the deferral was silently overridden. A lint that flags a grep-shaped AC whose pattern would match content in a section the task explicitly deferred would catch this class at authoring / ensure-ready time.

From T-VOH6-port-check-skill-prose-to-ts: when a task’s AC contains a repo-wide grep assertion, the Out-of-scope list should not defer edits the grep would catch — the two contradicted each other and the AC (binding contract) won. A task-authoring lint that cross-checks grep-shaped ACs against Out-of-scope deferrals would catch this class.

The readiness gate (/sdlc:task-ensure-ready) already shells out to two co-located body scanners. Neither cross-checks an AC’s grep pattern against the Out-of-scope deferrals, so the contradiction in T-VOH6-port-check-skill-prose-to-ts passed the gate.

LocationRole today
plugin/skills/task-ensure-ready/scan_placeholders.tsCo-located body scanner: walks the required H2 sections, emits one JSON line per placeholder match ({"section", "phrase", "line", "snippet"}), skips fenced/inline code, exit 0/2. The closest structural analog for a new section-pair scanner.
plugin/skills/task-ensure-ready/parse_touchpoints.tsCo-located parser for the ## Today / ## Files to touch tables — shows the H2-splitting + fence-skipping idiom this scanner reuses (H2_RE, FENCE_RE, splitLines).
plugin/skills/task-ensure-ready/SKILL.md#3.Step 3 (disqualifier evaluation) shells out to scan_placeholders.ts and treats each emitted match as a disqualifier. The new scanner hooks in at the same point.
plugin/skills/task-ensure-ready/tests/ensure_ready.test.tsThe bun:test suite the new scanner’s tests sit beside.
plugin/lib/model/entities/task/implementation-ready.md#Acceptance criteriaThe AC contract the new disqualifier extends — ACs must be “objectively verifiable”; a grep AC that contradicts a deferral is not.

Add a co-located scanner scan_grep_acs.ts next to scan_placeholders.ts that parses the ## Acceptance criteria and ## Out of scope sections of a task, extracts grep-shaped ACs (lines whose AC text contains a grep/git grep invocation), and flags any whose pattern would also match a path/term named in an Out-of-scope deferral. /sdlc:task-ensure-ready Step 3 shells out to it and treats each emitted match as a disqualifier, exactly as it already does for scan_placeholders.ts.

  1. Write plugin/skills/task-ensure-ready/scan_grep_acs.ts (bun), modeled on scan_placeholders.ts:
    • Reuse the frontmatter strip + H2_RE + FENCE_RE + splitLines idiom to isolate the ## Acceptance criteria and ## Out of scope section bodies.
    • From the Acceptance-criteria body, collect each AC line and detect a grep-shaped assertion: a substring matching /\b(?:command\s+)?(?:git\s+)?grep\b/. Extract the search pattern — the first single- or double-quoted argument after the grep verb (the argument the -e/positional pattern occupies). Split it on the regex alternation | into candidate terms.
    • From the Out-of-scope body, collect each deferral bullet’s text and the backtick-quoted code spans inside it (these are the concrete path/symbol names a deferral names, e.g. `SKILL.md`).
    • Emit a match when any grep candidate term appears as a substring of (or equals) any Out-of-scope code span or bullet text. One JSON line per match with keys ac_line, pattern_term, deferral_line, deferral_snippet (Python-json.dumps-style encoding, mirroring scan_placeholders.ts’s pyJsonStr).
    • Exit 0 with empty stdout when no contradiction; exit 0 with JSON lines when matches found; exit 2 on argument / I/O failure.
  2. Add plugin/skills/task-ensure-ready/tests/scan_grep_acs.test.ts (bun:test, spawnSync against the script) covering: the check_skill_prose regression shape (a grep ... "lint_skill_prose.py" AC + an Out-of-scope bullet deferring `SKILL.md` citations → one match); a clean task (grep AC whose terms are not deferred → exit 0, empty stdout); a task with no grep AC (exit 0); and a missing-file argument (exit 2).
  3. Wire it into plugin/skills/task-ensure-ready/SKILL.md Step 3’s disqualifier list as a second bun run … scan_grep_acs.ts <path> shell-out, with one sentence describing the emitted shape — paralleling the existing scan_placeholders.ts paragraph.
  4. Refresh the per-skill doc docs/skills/task-ensure-ready.md so its flowchart cites the new Step-3 sub-step (the check_skill_docs gate requires doc coverage of every numbered step).
LocationKindChange
plugin/skills/task-ensure-ready/scan_grep_acs.tsnewThe grep-AC vs Out-of-scope cross-check scanner
plugin/skills/task-ensure-ready/tests/scan_grep_acs.test.tsnewbun:test coverage for the scanner
plugin/skills/task-ensure-ready/SKILL.mdmodifyStep 3 shells out to the new scanner; treat each match as a disqualifier
docs/skills/task-ensure-ready.mdmodifyFlowchart cites the new Step-3 grep-AC disqualifier sub-step
  • AC-1: plugin/skills/task-ensure-ready/scan_grep_acs.ts exists and, run against a task fixture that pairs an AC asserting grep -rn "lint_skill_prose.py" .claude returns no matches with an ## Out of scope bullet deferring the SKILL.md script citations, exits 0 and emits at least one JSON line whose pattern_term is lint_skill_prose.py.
  • AC-2: Run against a task whose grep-AC pattern names nothing in ## Out of scope, the scanner exits 0 with empty stdout; run against a task with no grep-shaped AC at all, it also exits 0 with empty stdout.
  • AC-3: Run with a non-existent path argument, the scanner exits 2 (matching scan_placeholders.ts’s argument-failure contract).
  • AC-4: bun test plugin/skills/task-ensure-ready/tests/scan_grep_acs.test.ts passes and covers the regression shape (AC-1), the clean and no-grep cases (AC-2), and the missing-file case (AC-3).
  • AC-5: plugin/skills/task-ensure-ready/SKILL.md Step 3 contains a bun run invocation of scan_grep_acs.ts and states each emitted match is a disqualifier; bun run .claude/skills/project-check/check_skill_prose.ts exits 0 (no invariant regressions from the prose edit).
  • AC-6: bun run .claude/skills/project-check/check_skill_docs.ts --skip-mermaid exits 0 (the per-skill doc covers the new Step-3 sub-step).
  • Flagging non-grep ACs (ls, test -f, find) — only grep/git grep-shaped assertions are in scope; other shell-command ACs are a separate class.
  • Cross-checking ACs against the ## Approach or ## Files to touch sections — only the ## Acceptance criteria vs ## Out of scope pair.
  • Auto-rewriting the offending AC or deferral — the scanner reports the contradiction; resolving it stays a human/author decision.
  • Changing the implementation-ready contract doc’s wording — the new disqualifier is enforced by the scanner, not by re-specifying implementation-ready.md.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-06-02 UTC from T-VOH6-port-check-skill-prose-to-ts in git@github.com:sksizer/dev.git.


← Back to Tasks