T-407I-subagent-invariant-preflight-lint
Status: closed/superseded · Impact: medium · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
plugin/scripts/lint_skill_prose.py already exists and is wired into
the project-check gate, but it only fires post-hoc — after a
task-work sub-agent has finished writing. When the sub-agent’s edits
happen to introduce a forbidden phrase that the skill’s own
invariants.yaml declares illegal (e.g. pr-check forbids prose
mentioning the raw GitHub-CLI comment command), the violation
surfaces during the quality-check gate at the end of the task, not
during the edit loop. Running the linter inline after each Edit
that touches an instrumented SKILL.md would catch the violation
seconds after it lands, while the sub-agent still has full context to
rephrase. Cite T-5VN7-pr-check-cursor-bootstrap-misses-existing-comments
for the live near-miss this surfaced.
| Location | Role today |
|---|---|
plugin/scripts/lint_skill_prose.py | Lints a passed SKILL.md against its co-located invariants.yaml; called by project_check.py’s skill-doc suite. |
plugin/skills/pr-check/invariants.yaml | Declares forbidden_phrases that include the raw GitHub-CLI comment command pattern. |
plugin/skills/task-work/SKILL.md | Step 6 dispatches an implementation sub-agent with a brief; the brief does not require running any linter after Edits. |
plugin/scripts/run_quality_checks.py | Surfaces lint failures only at Step 7 (end-of-task quality-check gate). |
So today: a sub-agent can land a forbidden phrase in a SKILL.md during Step 6, finish all its edits, and only discover the violation at Step 7 when the gate fails. The sub-agent then has to retrace the edits to find which one introduced the phrase.
Proposed
Section titled “Proposed”The task-work Step 6 sub-agent brief acquires a “lint after Edit”
clause: whenever the sub-agent edits any file matching
plugin/skills/*/SKILL.md (or any other file that has a co-located
invariants.yaml), the next operation in its loop is to run
lint_skill_prose.py on that file and address any reported
violations before moving on. If the linter is clean, the sub-agent
proceeds normally. If not, the sub-agent rephrases and re-runs until
clean.
The check is inline (no new infrastructure) — it leverages the
linter that already exists and the sub-agent’s existing Bash
affordance. The deliverable is a documentation change in
plugin/skills/task-work/SKILL.md (and possibly its sub-agent
brief template) that makes this loop explicit.
Approach
Section titled “Approach”- Identify the exact location in
plugin/skills/task-work/SKILL.md(or the sub-agent brief it ships) where the implementation sub-agent receives its tool/loop instructions. Add a “lint after SKILL.md edit” bullet that nameslint_skill_prose.pyand the trigger condition. - Optionally add a thin helper at
plugin/skills/task-work/lint_touched_skill_docs.shthat, given the set of files just edited, picks out the SKILL.md ones and invokes the linter against each — so the sub-agent’s loop only has to invoke one command rather than discovering touched files itself. - Add a test under
plugin/skills/task-work/test_*.py(or a fixture in the eval harness) that exercises the loop: sub-agent edits a SKILL.md introducing a forbidden phrase, the loop runs the linter, the linter reports the violation, the sub-agent rephrases.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/task-work/SKILL.md | modify | Add an inline-lint loop bullet to Step 6’s sub-agent brief. |
plugin/skills/task-work/lint_touched_skill_docs.sh | new | Optional convenience wrapper around lint_skill_prose.py for the sub-agent’s loop. |
plugin/skills/task-work/test_*.py | modify | Add a regression test that pins the inline-lint behavior. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
plugin/skills/task-work/SKILL.md’s Step 6 sub-agent brief instructs the sub-agent to runlint_skill_prose.pyimmediately after anyEditto a file matchingplugin/skills/*/SKILL.md, before continuing the loop. - AC-2: A regression test demonstrates that introducing a known
forbidden_phrasesentry into a SKILL.md fixture during a sub-agent loop is caught inline by the linter (not at the end-of-task gate). - AC-3: If a helper wrapper is added, it accepts a list of
recently-edited files, filters to SKILL.md instances with
co-located
invariants.yaml, and exits non-zero if any linter run reports violations.
Out of scope
Section titled “Out of scope”- Inventing a generic “lint after edit” framework for arbitrary
linters. The deliverable is specifically wiring the existing
lint_skill_prose.pyinto the task-work sub-agent loop. - Lifting the
invariants.yamlschema into a shared cross-skill format. Today each skill carries its own; the inline-lint loop works with whatever shape exists.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-5VN7-pr-check-cursor-bootstrap-misses-existing-comments on 2026-05-23.
Dedup search (spawn-from-post-mortem)
Section titled “Dedup search (spawn-from-post-mortem)”Bullet: The sub-agent’s first-pass attempt to wire gh pr comment… mentions into plugin/skills/pr-check/SKILL.md would have tripped pr-check’s invariants.yaml forbidden-phrase rule. The sub-agent caught it manually and rephrased around the raw GitHub-CLI comment command, but a pre-flight lint check during authoring (the linter that already exists, just run inline in the sub-agent’s loop after each Edit) would catch this class of accidental invariant violation faster than the post-hoc gate. Keywords searched: forbidden-phrase, first-pass, invariants, github-cli, pre-flight, accidental, sub-agent, rephrased Excluded: 2026-05-21-pr-check-cursor-bootstrap-misses-existing-comments Top candidates (score / status / headline):
- 33 / 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
- 22 / closed/done / 2026-05-19-skill-prose-invariant-linter — Doc-linter that asserts required invariants in skill prose
- 18 / closed/done / 2026-05-19-add-orchestrate-skill — Add /sdlc:orchestrate skill — one-tick reconcile + dispatch loop
- 16 / closed/done / 2026-05-20-orchestrate-enforces-one-line-verdict — Orchestrate enforces one-line verdicts from sub-agents
- 16 / planning/draft / 2026-05-21-orchestrated-sub-agent-design-call-gap — Surface design questions before orchestrated /sdlc:task-work commits to an approach Decision: SPAWNED