Skip to content

T-Q4E6-migration-gate-runs-prose-linter

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

Restore-and-prevent follow-up from the check_skill_prose.py → TypeScript port. The en-masse .py.ts migration’s invariants cutover swapped path literals in skill invariants.yaml files but did not re-validate the phrase pins against the parallel SKILL.md prose-sweep. Two stale pins in plugin/skills/task-work/invariants.yaml (a Python threading.Thread heartbeat reference and a pre-reason= LEASE-CONFLICT stderr shape) survived and only surfaced when a downstream dogfood task ran the restored prose checker. The migration quality gate should run the skill-prose linter (or the restored check_skill_prose.ts) so stale invariant pins surface at migration time rather than leaking into downstream tasks.

From T-VOH6-port-check-skill-prose-to-ts: The .py->.ts invariants cutover swapped path literals but did not re-validate the phrase pins against the prose-sweep, leaving two stale task-work/invariants.yaml pins that only surfaced in a downstream dogfood task. The migration quality gate should run the skill-prose linter (or the restored check_skill_prose.ts) so stale invariant pins surface at migration time.

The skill-prose linter runs in two places — the lefthook pre-commit gate and the /project-check skill — but NOT in the project’s quality_checks: list in sdlc.yaml. That list is what the en-masse migration’s cutover gate (and /sdlc:task-work Step 7) actually ran, so the migration never re-validated the phrase pins. Two stale pins in task-work’s invariants.yaml survived and the prose linter still flags them today.

LocationRole today
sdlc.yamlThe migration cutover gate / task-work Step 7 verb list (quality_checks:): bunx tsc --noEmit, bun test, bun test ./.claude, audit_entities.ts, two .claude/ Python harnesses. The skill-prose linter is absent — the gap this task closes.
plugin/scripts/lint_skill_prose.tsThe prose-invariant linter itself. Run bare against plugin/skills/task-work/SKILL.md it currently exits 1 with 2 violations. The migration gate should run it (directly, or via the check_skill_prose.ts wrapper).
.claude/skills/project-check/check_skill_prose.pyThe project-check wrapper on main today: sweeps every plugin/skills/*/SKILL.md and shells the linter. Broken — references the deleted lint_skill_prose.py. Its .ts replacement is restored by T-VOH6-port-check-skill-prose-to-ts (PR #200, not yet merged to main); the gate verb should target the .ts wrapper once that lands.
plugin/skills/task-work/invariants.yamlCarries the two stale pins under required_phrases:: threading.Thread(daemon=True) (a Python heartbeat idiom the SKILL.md prose no longer uses) and LEASE-CONFLICT ref=<ref> owner=<other-host-id> (a pre-reason= stderr shape) — both flagged by the linter today. Its foot comment still cites the deleted lint_skill_prose.py.
plugin/scripts/run_quality_checks.tsThe executor that reads quality_checks: and runs each verb; gates the migration cutover and Step 7. Adding the linter to the list is what makes it run here.

sdlc.yaml’s quality_checks: list includes the skill-prose linter, so any future en-masse cutover (and every task-work Step 7) runs it and fails on stale invariant pins at gate time rather than leaking them to a downstream dogfood task. The two currently-stale task-work pins are realigned with the SKILL.md prose so the newly-added gate verb passes on a clean checkout.

  1. Add the skill-prose linter to sdlc.yaml’s quality_checks: list as a new verb. Prefer the wrapper bun run .claude/skills/project-check/check_skill_prose.ts once T-VOH6-port-check-skill-prose-to-ts (PR #200) merges (it sweeps every plugin/skills/*/SKILL.md, so one verb covers the whole skill corpus). If this task lands before #200, gate against the linter directly with an explicit glob (bun run plugin/scripts/lint_skill_prose.ts plugin/skills/*/SKILL.md). Add a YAML comment explaining it pins skill-prose invariants the migration cutover previously skipped.
  2. Realign the two stale plugin/skills/task-work/invariants.yaml pins so the newly-added gate verb passes on a clean checkout. For each, either update the pinned phrase: to the current SKILL.md wording, or update the SKILL.md prose to re-assert the pin — whichever reflects the intended invariant:
    • threading.Thread(daemon=True) — the heartbeat is now TypeScript; update the pin to the current async heartbeat idiom the Step-6 prose actually uses (or restore the phrase to the prose if the daemon-thread semantics are still the intended contract).
    • LEASE-CONFLICT ref=<ref> owner=<other-host-id> — update the pin to the current reason=-bearing stderr shape emitted by acquire_lease (or restore that exact line to Step 2a’s prose).
  3. Confirm bun run .claude/skills/project-check/check_skill_prose.ts exits 0 across all skills, and that run_quality_checks.ts runs the new verb.
  4. Fix the stale # Linter at plugin/scripts/lint_skill_prose.py comment at the foot of plugin/skills/task-work/invariants.yaml (the migration left it pointing at the deleted .py).
LocationKindChange
sdlc.yamlmodifyAdd bun run .claude/skills/project-check/check_skill_prose.ts to quality_checks:, with an explanatory comment
plugin/skills/task-work/invariants.yamlmodifyRealign the two stale pins (heartbeat idiom, LEASE-CONFLICT shape) with current SKILL.md prose; fix the .py linter-path comment
plugin/skills/task-work/SKILL.mdmodifyIf the chosen realignment is “restore the phrase to the prose,” re-assert the pinned line in Step 2a / Step 6
  • AC-1: sdlc.yaml’s quality_checks: list contains a verb that runs the skill-prose linter. Verifiable: grep -E "check_skill_prose\.ts|lint_skill_prose\.ts" sdlc.yaml returns ≥1 match.
  • AC-2: The new gate verb exits 0 against the live repo (every plugin/skills/*/SKILL.md passes its invariants.yaml) when run via bun run plugin/scripts/run_quality_checks.ts --config sdlc.yaml --line.
  • AC-3: bun run plugin/scripts/lint_skill_prose.ts plugin/skills/task-work/SKILL.md exits 0 (the two stale pins — threading.Thread(daemon=True) and LEASE-CONFLICT ref=<ref> owner=<other-host-id> — no longer violate; they currently produce 2 violations).
  • AC-4: bun run plugin/scripts/run_quality_checks.ts --config sdlc.yaml --line runs the prose-linter verb in its run set and the full gate exits 0 on a clean checkout.
  • AC-5: No lint_skill_prose.py reference remains in plugin/skills/task-work/invariants.yaml. Verifiable: grep -c "lint_skill_prose.py" plugin/skills/task-work/invariants.yaml returns 0.
  • Re-validating invariant pins across ALL skills’ invariants.yaml beyond task-work — AC-2 already gates the whole corpus via the wrapper; any other stale pin surfaced by AC-2 is fixed as part of making AC-2 pass, but no proactive audit of pin intent is in scope.
  • Adding the prose linter to downstream consumer projects’ sdlc.yaml — this task wires only this repo’s gate.
  • Changing the lefthook pre-commit wiring (it already runs the checker; this task adds the parallel quality-gate path).
  • Migrating the remaining .claude/ Python harnesses still listed in quality_checks: — separate claude-ts-migration tasks.
  • Soft: T-VOH6-port-check-skill-prose-to-ts (PR #200) restores check_skill_prose.ts, the preferred gate verb. Not a hard blocker — if this task lands first, gate against plugin/scripts/lint_skill_prose.ts directly (Approach step 1) and switch to the wrapper when

    #200 merges.

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