T-79SM-finish-claude-harness-ts-migration
Status: closed/done · Impact: medium · Complexity: large
The en-masse Python→TypeScript migration converted all of plugin/ and three
of the .claude/ project-check checkers (check_pipe_tail, check_skill_prose,
check_entities). Four .claude/ harness scripts remain Python — two of them
(audit_skill_runtime.py + its eval harness) are still the only .py entries in
sdlc.yaml’s quality_checks:, and a fourth checker’s gate (run_base_evals) is
parked (commented out) because its subject still shells to a .py validator. Port
the remaining four to TypeScript so the whole harness runs on one substrate, the
bun test ./.claude verb covers every .claude suite, and the parked gate comes
back online.
| Location | Role today |
|---|---|
.claude/skills/project-check/check_entity_bases.py | Python checker validating each entity type’s base YAML template against its schema (invokes the now-migrated plugin/validators/validate_base.ts). Its sdlc.yaml gate is parked. |
.claude/skills/project-check/check_skill_docs.py | Python checker linting SKILL.md doc structure across plugin/skills/*/SKILL.md. |
.claude/scripts/audit_skill_runtime.py | Python checker auditing SKILL.md runtime-state drift; a live quality_checks: gate. |
.claude/hooks/warn-skill-doc-drift.py | PostToolUse(Edit/Write) hook warning on skill-doc drift; wired in .claude/settings.json. |
.claude/skills/project-check/tests/run_base_evals.py | Python eval harness for check_entity_bases (its gate is the parked line in sdlc.yaml). |
.claude/skills/project-check/tests/run_skill_doc_evals.py | Python eval harness; after the check_pipe_tail port it now serves only check_skill_docs. |
.claude/scripts/tests/audit_skill_runtime/run_evals.py | Python eval harness for audit_skill_runtime; a live quality_checks: gate. |
sdlc.yaml | quality_checks: still lists two .py gates (audit_skill_runtime.py, its run_evals.py) plus a commented-out parked run_base_evals.py line. |
Proposed
Section titled “Proposed”All four remaining .claude/ harness scripts run under bun as .ts, with
bun:test suites replacing their Python eval harnesses. The Python files are
removed, sdlc.yaml carries zero .py gates, the parked check_entity_bases
gate is restored (its .test.ts covered by bun test ./.claude), the
warn-skill-doc-drift PostToolUse hook points at the .ts, and remaining .py
checker citations in SKILL.md / lefthook.yml are refreshed. The only .py
left under .claude/ is intentional test-fixture sample scripts.
Approach
Section titled “Approach”Each checker is an independent port; commit one checker (+ its test + citation
refresh + delete) at a time. Mirror the three completed ports
(check_pipe_tail.ts, check_skill_prose.ts, check_entities.ts) for bun
idioms and bun:test shape.
- Port
check_entity_bases.py→check_entity_bases.tsinvokingplugin/validators/validate_base.ts; replacerun_base_evals.pywithtests/check_entity_bases.test.ts; delete both.py. - Port
check_skill_docs.py→check_skill_docs.ts; replacerun_skill_doc_evals.py(now sole-serving this checker) withtests/check_skill_docs.test.ts; delete both.py. - Port
audit_skill_runtime.py→.claude/scripts/audit_skill_runtime.ts; replacetests/audit_skill_runtime/run_evals.pywithaudit_skill_runtime.test.ts; delete both.py. Leave the sample-script fixtures undertests/audit_skill_runtime/fixtures/**as.py(they are test data, not migration targets). - Port
warn-skill-doc-drift.py→.tsand repoint the PostToolUse hook in.claude/settings.json. - Rewire
sdlc.yaml: drop the two.pygate lines and the parkedrun_base_evals.pycomment; the three eval suites now ridebun test ./.claude. Add a barebun run .claude/scripts/audit_skill_runtime.tslive-repo check if a non-test repo-state gate is still wanted (mirror howaudit_entities.tsis gated bare). - Refresh remaining
.py→.tschecker citations in.claude/skills/project-check/SKILL.mdandlefthook.yml(the deferred citation sweep from the three dogfood ports).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
.claude/skills/project-check/check_entity_bases.ts | new | TS port invoking validate_base.ts |
.claude/skills/project-check/tests/check_entity_bases.test.ts | new | bun:test replacing run_base_evals.py |
.claude/skills/project-check/check_entity_bases.py | delete | replaced |
.claude/skills/project-check/tests/run_base_evals.py | delete | replaced |
.claude/skills/project-check/check_skill_docs.ts | new | TS port |
.claude/skills/project-check/tests/check_skill_docs.test.ts | new | bun:test replacing run_skill_doc_evals.py |
.claude/skills/project-check/check_skill_docs.py | delete | replaced |
.claude/skills/project-check/tests/run_skill_doc_evals.py | delete | replaced (no longer serves any .py checker) |
.claude/scripts/audit_skill_runtime.ts | new | TS port |
.claude/scripts/tests/audit_skill_runtime/audit_skill_runtime.test.ts | new | bun:test replacing run_evals.py |
.claude/scripts/audit_skill_runtime.py | delete | replaced |
.claude/scripts/tests/audit_skill_runtime/run_evals.py | delete | replaced |
.claude/hooks/warn-skill-doc-drift.ts | new | TS port of the hook |
.claude/hooks/warn-skill-doc-drift.py | delete | replaced |
.claude/settings.json | modify | repoint the PostToolUse hook to the .ts |
sdlc.yaml | modify | drop both .py gates + the parked run_base_evals comment; gates run all-TS |
.claude/skills/project-check/SKILL.md | modify | refresh remaining .py→.ts checker citations |
lefthook.yml | modify | repoint remaining .py checker refs to .ts |
Acceptance criteria
Section titled “Acceptance criteria”-
AC-1:
check_entity_bases.ts,check_skill_docs.ts,.claude/scripts/audit_skill_runtime.ts, and.claude/hooks/warn-skill-doc-drift.tsall exist and run underbun; each checker exits 0 on the live repo. The four corresponding.pyfiles (+ the three Python eval harnesses) no longer exist. -
AC-2:
grep -rn "check_entity_bases.py\|check_skill_docs.py\|audit_skill_runtime.py\|warn-skill-doc-drift.py\|run_base_evals.py\|run_skill_doc_evals.py" .claude lefthook.yml sdlc.yamlreturns no matches.
-
AC-3:
bun test ./.claudepasses and discovers the newcheck_entity_bases.test.ts,check_skill_docs.test.ts, andaudit_skill_runtime.test.tssuites. -
AC-4:
sdlc.yaml’squality_checks:lists zero.pyentries;bun run plugin/scripts/run_quality_checks.ts --config sdlc.yaml --lineruns the all-TS gate set; the previously-parkedcheck_entity_basescoverage is back online (viabun test ./.claude). -
AC-5: the
warn-skill-doc-driftPostToolUse hook in.claude/settings.jsonpoints at the.tsand still fires on Edit/Write.
Out of scope
Section titled “Out of scope”- The sample-script fixtures under
.claude/scripts/tests/audit_skill_runtime/fixtures/**/*.py— they are intentional test data (the audit’s subject), not migration targets. plugin/migration — already complete.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”- Completes the
.claude/→TypeScript migration begun by the three Tier-1/Tier-2 dogfood ports (check_pipe_tail,check_skill_prose,check_entities). Folds in the SKILL.md/lefthook citation-refresh sweep those tasks deferred to a single follow-up, and restores the parkedrun_base_evalsgate.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-02. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: agent-manual (partial) — all four
.tscheckers exist and run underbun; the four.py+ three Python eval harnesses are deleted (verified byls). Exit-0-on-live-repo holds forcheck_entity_bases.ts,audit_skill_runtime.ts, and thewarn-skill-doc-drift.tshook.check_skill_docs.tsexits 1 on the live repo, but this is pre-existing drift the faithful port correctly surfaces — 3 skills (pr-respond,spawn-task-pr,standard-new) genuinely lack theirdocs/skills/<slug>.mdcompanion docs. The TS port produces byte-identical output to the Python original (verified bydiff), so the migration introduced nothing; generating the 3 missing docs is/update-skill-docwork, out of scope for a Python→TS port. Surfaced here rather than papered over. - AC-2: auto —
grep -rn "<six .py filenames>" .claude lefthook.yml sdlc.yamlreturns no matches (verified). - AC-3: auto —
bun test ./.claudepasses (36 tests / 6 files) and discovers the three new suites (check_entity_bases.test.ts,check_skill_docs.test.ts,audit_skill_runtime.test.ts). - AC-4: auto —
sdlc.yaml’squality_checks:lists zero.pyentries (verified by YAML parse); the previously-parkedcheck_entity_basescoverage is back online viabun test ./.claude. - AC-5: agent-manual — the
warn-skill-doc-driftPostToolUse hook in.claude/settings.jsonpoints atbun run …/warn-skill-doc-drift.ts; the hook fires on Edit/Write with byte-identical warning output to the Python original (verified by piping synthetic payloads through both).
What worked
Section titled “What worked”- The three already-merged ports (
check_pipe_tail.ts,check_skill_prose.ts,check_entities.ts) were an excellent template — bun idioms,pyResolve,splitlines/lineOf,Bun.spawnSync, and thebun:testfixture shape all transferred cleanly. - Byte-for-byte
diffagainst the still-present Python originals (before deleting them) gave high-confidence fidelity checks on every port. - The baseline-diff gate correctly classified the bulk of the corpus drift as pre-existing (466 lines), keeping the real signal small.
Friction and automation gaps
Section titled “Friction and automation gaps”*/inside a JSDoc block comment (plugin/skills/*/SKILL.md,entities/*/base.yaml) silently terminates the/* … */comment and breaksbunparse — hit it twice. A lint rule (or a port-checklist note) flagging*/inside.tsblock comments would catch this before the first run.run_quality_checks.ts --diff-against-baselineproduced 17 false-positivenew-driftlines: 15 from non-deterministicbun testfixture output (ephemeral/tmp/claude-502/…paths + transient commit SHAs) and 2 fromaudit_entities.tscorpus-count summary lines that shift run-to-run. The baseline diff is exact-line-text based, so non-deterministic stdout defeats it. A normalization pass (mask tmpdir paths / SHAs / count summaries) before diffing would make the gate trustworthy on suites with non-deterministic output. →2026-06-02-normalize-baseline-diff-nondeterministic-output- The Step 3a baseline is written to the main repo
.sdlc/quality-baselines/, but Step 7’s executor (run from the worktree) defaults--baseline-dirto the worktree’s.sdlc/and reportsbaseline not found. Task-work Step 7 should pass--baseline-dir <main-repo>/.sdlc/quality-baselinesexplicitly, or the executor should resolve the superproject root. audit_skill_runtime’s invocation regex matches only.py/.sh, but theplugin/migration moved all 100 real SKILL.md script citations to.ts— so the auditor now audits zero real flag-bearing scripts (only 2 advisory.sh). The faithful port preserves this, but the auditor is effectively dormant. Extending it to match.ts(introspecting viabun run <s> --help) is a worthwhile follow-up — deliberately deferred here to keep this a pure migration and avoid introducing live-repo gate failures. →2026-06-02-audit-skill-runtime-match-ts-citationscheck_skill_docs/setup_planning.ts writeBasesboth globbase.yamlindiscriminately, conflating Obsidian-Bases docs (task/backlog) with S0005 scaffolder default-frontmatter (decision/milestone/principle/standard) that reuses the filename.check_entity_bases.tsworks around it by content-sniffing for Bases top-level keys;setup_planning.ts’swriteBasesstill has the latent bug (would copy the 4 frontmatter files as broken.basedocs into a consumer’s vault on/sdlc:setup --obsidian). A follow-up should either rename the scaffolder file or teachwriteBasesthe same discrimination.
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”2026-06-02-normalize-baseline-diff-nondeterministic-output(https://github.com/sksizer/dev/pull/214) — spawned (Upstream-plugin / sdlc-meta): normalize non-deterministic verb output before baseline diff.2026-06-02-audit-skill-runtime-match-ts-citations(https://github.com/sksizer/dev/pull/215) — spawned (Upstream-plugin / sdlc-meta): extend audit_skill_runtime to match.tsscript citations.- Remaining friction bullets (the
*/-in-block-comment lint, Step-3a baseline-dir resolution — overlaps with #214’s run_quality_checks scope, and thesetup_planning.ts writeBasesBases-vs-frontmatter discrimination) are captured above for a reviewer to triage; not spawned as separate PRs to keep this run bounded.