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.
| Location | Role today |
|---|---|
plugin/skills/task-ensure-ready/scan_placeholders.ts | Co-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.ts | Co-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.ts | The bun:test suite the new scanner’s tests sit beside. |
plugin/lib/model/entities/task/implementation-ready.md#Acceptance criteria | The AC contract the new disqualifier extends — ACs must be “objectively verifiable”; a grep AC that contradicts a deferral is not. |
Proposed
Section titled “Proposed”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.
Approach
Section titled “Approach”- Write
plugin/skills/task-ensure-ready/scan_grep_acs.ts(bun), modeled onscan_placeholders.ts:- Reuse the frontmatter strip +
H2_RE+FENCE_RE+splitLinesidiom to isolate the## Acceptance criteriaand## Out of scopesection 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, mirroringscan_placeholders.ts’spyJsonStr). - Exit 0 with empty stdout when no contradiction; exit 0 with JSON lines when matches found; exit 2 on argument / I/O failure.
- Reuse the frontmatter strip +
- 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 (agrep ... "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). - Wire it into
plugin/skills/task-ensure-ready/SKILL.mdStep 3’s disqualifier list as a secondbun run … scan_grep_acs.ts <path>shell-out, with one sentence describing the emitted shape — paralleling the existingscan_placeholders.tsparagraph. - Refresh the per-skill doc
docs/skills/task-ensure-ready.mdso its flowchart cites the new Step-3 sub-step (thecheck_skill_docsgate requires doc coverage of every numbered step).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/task-ensure-ready/scan_grep_acs.ts | new | The grep-AC vs Out-of-scope cross-check scanner |
plugin/skills/task-ensure-ready/tests/scan_grep_acs.test.ts | new | bun:test coverage for the scanner |
plugin/skills/task-ensure-ready/SKILL.md | modify | Step 3 shells out to the new scanner; treat each match as a disqualifier |
docs/skills/task-ensure-ready.md | modify | Flowchart cites the new Step-3 grep-AC disqualifier sub-step |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
plugin/skills/task-ensure-ready/scan_grep_acs.tsexists and, run against a task fixture that pairs an AC assertinggrep -rn "lint_skill_prose.py" .claudereturns no matches with an## Out of scopebullet deferring theSKILL.mdscript citations, exits 0 and emits at least one JSON line whosepattern_termislint_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.tspasses 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.mdStep 3 contains abun runinvocation ofscan_grep_acs.tsand states each emitted match is a disqualifier;bun run .claude/skills/project-check/check_skill_prose.tsexits 0 (no invariant regressions from the prose edit). - AC-6:
bun run .claude/skills/project-check/check_skill_docs.ts --skip-mermaidexits 0 (the per-skill doc covers the new Step-3 sub-step).
Out of scope
Section titled “Out of scope”- 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
## Approachor## Files to touchsections — only the## Acceptance criteriavs## Out of scopepair. - 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.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”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.