Skip to content

T-VOH6-port-check-skill-prose-to-ts

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

Port check_skill_prose.py — the project-check wrapper that runs the skill-prose invariant linter over every SKILL.md — to TypeScript. The checker is currently broken: it shells out to plugin/scripts/lint_skill_prose.py, which the en-masse migration renamed to lint_skill_prose.ts. The port restores it by invoking the .ts linter, and advances the .claude/ harness onto the TypeScript substrate.

LocationRole today
.claude/skills/project-check/check_skill_prose.pyPython wrapper: partitions plugin/skills/*/SKILL.md by sibling invariants.yaml, shells out to the prose linter, prints a coverage summary, passes the linter’s exit code through. Broken (exit 2, “linter not found”) — references plugin/scripts/lint_skill_prose.py, now .ts. Flags --skills-dir, --linter.
plugin/scripts/lint_skill_prose.tsThe migrated prose-invariant linter the wrapper must invoke.
.claude/skills/project-check/tests/run_prose_evals.pyPython eval harness for the prose checker.

check_skill_prose.ts runs under bun, invokes lint_skill_prose.ts, and exits 0 on the live repo (checker restored). The Python checker and its Python eval harness are removed; a bun:test suite replaces the harness; the invariants.yaml partition, coverage summary, and exit-code passthrough are preserved.

  1. Port check_skill_prose.pycheck_skill_prose.ts (bun), invoking plugin/scripts/lint_skill_prose.ts (fixing the dangling .py reference), preserving --skills-dir / --linter flags, the invariants.yaml partition, the coverage summary, and exit-code passthrough.
  2. Replace run_prose_evals.py with tests/check_skill_prose.test.ts (bun:test) covering a clean run (exit 0) and the with/without-invariants.yaml coverage partition.
  3. Delete check_skill_prose.py and run_prose_evals.py.
LocationKindChange
.claude/skills/project-check/check_skill_prose.tsnewTypeScript port invoking lint_skill_prose.ts
.claude/skills/project-check/tests/check_skill_prose.test.tsnewbun:test coverage replacing the Python harness
.claude/skills/project-check/check_skill_prose.pydeletereplaced by the .ts port
.claude/skills/project-check/tests/run_prose_evals.pydeletereplaced by the .test.ts suite
  • AC-1: check_skill_prose.ts invokes plugin/scripts/lint_skill_prose.ts (not .py), and bun run .claude/skills/project-check/check_skill_prose.ts exits 0 against the live repo (the checker that exits 2 today is restored).
  • AC-2: check_skill_prose.py and run_prose_evals.py no longer exist, and grep -rn "check_skill_prose.py\|lint_skill_prose.py" .claude returns no matches.
  • AC-3: .claude/skills/project-check/tests/check_skill_prose.test.ts passes under bun test and asserts a clean exit plus the with/without-invariants.yaml coverage partition.
  • AC-4: check_skill_prose.ts preserves the --skills-dir and --linter flags, the invariants.yaml partitioning, the coverage summary line, and exit-code passthrough from the linter.
  • Porting the other project-check checkers — separate tasks.
  • Refreshing the .claude/skills/project-check/SKILL.md script citations — deferred to one follow-up sweep so per-checker tasks stay file-disjoint.
  • Any change to lint_skill_prose.ts itself (already migrated and tested under plugin/).
  • none
  • Tier-2 dogfood task for the /sdlc:orchestrate/sdlc:task-work migration smoke test. Fixes a .claude/ harness regression (dangling lint_skill_prose.py reference) the TS migration introduced. Designed to run concurrently with the check_entities port — the two touch disjoint files.

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

  • AC-1: auto — bun run .claude/skills/project-check/check_skill_prose.ts exits 0 against the live repo (verified in Step 7; also gated by the bun test ./.claude quality verb). Required re-syncing two stale plugin/skills/task-work/invariants.yaml pins — see Friction below.
  • AC-2: auto — check_skill_prose.py / run_prose_evals.py deleted via git rm; command grep -rn "check_skill_prose.py\|lint_skill_prose.py" .claude returns no matches (re-run after patching the SKILL.md / README / fixture citations).
  • AC-3: auto — bun test ./.claude/skills/project-check/tests/check_skill_prose.test.ts → 3 pass / 0 fail (clean run, with/without-invariants.yaml partition, exit-code passthrough).
  • AC-4: agent-manual — confirmed --skills-dir and --linter flags parse, the coverage-summary line prints, the invariants.yaml partition reports counts, and exit codes pass through (0 clean, 1 violation, 2 self-failure on bad dir / missing linter).
  • The sibling check_pipe_tail.ts + check_pipe_tail.test.ts (already ported) gave an exact house-style template — argv parsing, pyResolve/isDir/isFile/displayPath helpers, and ephemeral-fixture test layout — so the port and its tests dropped in cleanly.
  • The baseline-gated quality gate cleanly subtracted the 369 pre-existing audit_entities.ts findings; the run reported OK 6/6 with zero new drift.
  • AC-1 (“checker exits 0 on the live repo”) was blocked by pre-existing drift the TS migration left behind: plugin/skills/task-work/invariants.yaml still pinned threading.Thread(daemon=True) (the heartbeat is now a backgrounded lease_heartbeat_loop.ts process) and LEASE-CONFLICT ref=<ref> owner=<other-host-id> in section 2a (the prose now emits reason=owner=). The .py->.ts invariants cutover (commit bd7ba2b) swapped path literals but did not re-validate the phrase pins against the prose-sweep (commit 6beced3) — a prose-only check_skill_prose run in that PR would have caught it. Automation gap: the en-masse migration’s quality gate should have run the skill-prose linter (or this restored checker) so the two stale pins surfaced at migration time instead of leaking into a downstream dogfood task. Fixing them here was strictly necessary to satisfy AC-1 and is in-scope for “fix the .claude/ harness regression the TS migration introduced”, but it touched a plugin file outside the task’s literal Files-to-touch table. → T-Q4E6-migration-gate-runs-prose-linter
  • The task’s “Out of scope” defers “Refreshing the .claude/skills/project-check/SKILL.md script citations”, but AC-2’s literal grep (check_skill_prose.py\|lint_skill_prose.py across .claude) forced refreshing this checker’s own citations in SKILL.md / tests/README.md / the clean fixture anyway. Gap: 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. → T-X3F2-lint-grep-acs-vs-out-of-scope
  • The Agent tool and the Write/Edit tools were unavailable for worktree paths in this dispatch context, so implementation ran inline (documented Step 6 fallback) and all file writes/edits went through Bash heredocs + small node patch scripts. Gap: the inline-implementation fallback works, but losing Edit for in-worktree files made small surgical edits clumsier than necessary (needed throwaway node scripts to dodge shell-quoting hazards on // and conventional-commit parens).

← Back to Tasks