T-YNJO-port-check-pipe-tail-to-ts
Status: closed/done · Impact: low · Complexity: small
The en-masse port moved plugin/scripts to the TypeScript substrate, but the
.claude/ project-check harness is still Python. Port check_pipe_tail.py —
the linter that flags SKILL.md examples piping a gated command to tail/head
— to TypeScript. It has no plugin/ dependency, so it is a self-contained port:
the chosen first (Tier-1) task of the orchestrate→task-work migration dogfood.
| Location | Role today |
|---|---|
.claude/skills/project-check/check_pipe_tail.py | Python linter over plugin/skills/*/SKILL.md; flags piped-gated-command antipatterns. Flags --skills-dir, --pattern; output <path>:<line>: <message>; exit 0 clean / 1 violation / 2 self-error. Runs green today (no plugin/ dependency). |
.claude/skills/project-check/tests/run_skill_doc_evals.py | Python eval harness shared with check_skill_docs; shells out to check_pipe_tail.py and check_skill_docs.py against fixtures and asserts exit codes. |
Proposed
Section titled “Proposed”check_pipe_tail.ts runs under bun with the same CLI flags, output format, and
exit codes as the Python checker. The Python file is removed, a bun:test suite
covers detection, and the shared harness no longer references the deleted file.
Approach
Section titled “Approach”- Port
check_pipe_tail.py→check_pipe_tail.ts(bun), preserving the default antipattern regex,--skills-dir/--patternflags, the<path>:<line>: <message>output, and exit codes 0 / 1 / 2. - Add
tests/check_pipe_tail.test.ts(bun:test) covering a cleanSKILL.md(exit 0) and one with a piped-gated-command violation (exit 1). - Drop the
check_pipe_tailcases/reference fromrun_skill_doc_evals.pyso deleting the Python checker leaves that harness coveringcheck_skill_docsonly. - Delete
check_pipe_tail.py.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
.claude/skills/project-check/check_pipe_tail.ts | new | TypeScript port of the linter |
.claude/skills/project-check/tests/check_pipe_tail.test.ts | new | bun:test coverage for clean + violation cases |
.claude/skills/project-check/check_pipe_tail.py | delete | replaced by the .ts port |
.claude/skills/project-check/tests/run_skill_doc_evals.py | modify | drop the check_pipe_tail cases/reference |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
bun run .claude/skills/project-check/check_pipe_tail.tsexits 0 against the live repo, matching the current Python checker’s clean-tree verdict. - AC-2:
.claude/skills/project-check/check_pipe_tail.pyno longer exists andgrep -rn "check_pipe_tail.py" .claudereturns no matches. - AC-3:
.claude/skills/project-check/tests/check_pipe_tail.test.tspasses underbun testand asserts both a cleanSKILL.md(exit 0) and a piped-gated-command violation (exit 1). - AC-4:
check_pipe_tail.tspreserves the--skills-dirand--patternflags, the<path>:<line>: <message>violation format, and exit codes 0 (clean) / 1 (violation) / 2 (self-error).
Out of scope
Section titled “Out of scope”- Porting the other project-check checkers (
check_skill_docs,check_skill_prose,check_entities) — separate tasks. - Refreshing the
.claude/skills/project-check/SKILL.mdscript citations — deferred to one follow-up sweep so per-checker tasks stay file-disjoint. - Converting
run_skill_doc_evals.pywholesale to TypeScript (it still servescheck_skill_docs).
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”- First Tier-1 task of the
/sdlc:orchestrate→/sdlc:task-workTS-migration dogfood; chosen becausecheck_pipe_tail.pyhas noplugin/dependency, making it a clean isolated smoke test of the live task-work chain.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-02. PR: pending.
This was the Tier-1 dogfood of the live /sdlc:task-work chain. The
implementation itself was clean; the run’s value was surfacing real
quality-gate friction.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
bun run .claude/skills/project-check/check_pipe_tail.tsexits 0 against the live repo (28 skills, no antipatterns); checker stdout/stderr byte-identical to the.py(sub-agentdiff). - AC-2: auto —
.pyremoved viagit rm;command grep -rn "check_pipe_tail.py" .claudeexits 1 (no matches). - AC-3: auto —
bun test ./.claude/skills/project-check/tests/check_pipe_tail.test.ts→ 2 pass / 0 fail (clean→exit 0, violation→exit 1). - AC-4: agent-manual — flags (
--skills-dir/--pattern),<path>:<line>: <message>format, and exit codes 0/1/2 verified against fixtures (clean→0, bad→1, missing-dir→2,--patternoverride→0).
What worked
Section titled “What worked”- Full lease lifecycle ran clean end-to-end:
acquire→working→ background heartbeat → (awaiting-reviewat PR). Implementation took ~52 min, so the heartbeat was load-bearing — it renewed the lease twice past its 1h TTL (expires_atadvanced 02:48→03:19), exactly its design purpose. - ensure-ready gate,
start_task.tscommit-on-main, and branch reset executed deterministically with no frontmatter-rebase conflict. - The sub-agent produced a faithful, byte-identical port (incl. reproducing Python’s component-wise
path sort) with passing tests and green
tsc+.claudePython gates.
Friction and automation gaps
Section titled “Friction and automation gaps”- Baseline-gated
bun testreports false “new drift” — integration tests print nondeterministic stdout (random/tmp/.../...temp paths, temp-repo SHAs), and the line-based baseline diff flags each unique line as a new finding (26 false findings → gate FAIL on a clean branch) — thebun testverb should diff on failure-count/exit-code (or normalize nondeterministic lines), not raw stdout lines. - Quality baseline is captured from the main-checkout cwd (Step 3a) but the gate runs from the worktree cwd (Step 7); cwd-sensitive verbs diverge and produce false drift — capture the baseline from a worktree-equivalent cwd, or run capture and gate from the same root.
tests/parity/validators.golden.test.tsbakes the main-checkout absolute path (/Users/sksizer2/Developer/dev/...) into golden stdout, so it fails from every worktree (2 pre-existing fails this run, untouched by this branch) — normalize the golden fixtures to a path-agnostic form so the validator suite passes from worktrees.- The shell intermittently splices
--no-coverageinto commands (multiplebun runinvocations and evengrep), causing spurious failures that needed retries — find and fix the injecting wrapper/hook (it lands the flag after the script args, where it reaches the program instead of bun). - Bare
bun testexcludes dotfile directories, so.claude/**/*.test.tssuites are never auto-discovered by the configuredbun testgate — the migration’s new.claudetest suites won’t run in CI without an explicit test path/glob or a dedicated.claudetest verb insdlc.yaml. Important for the broader.claude/→TS migration, not just this task. - Deleting
check_pipe_tail.pyforced edits to three files outside the Files-to-touch table (.claude/skills/project-check/SKILL.md,.claude/skills/project-check/tests/README.md, rootlefthook.yml) to satisfy AC-2 and keep the pre-commit gate working — when a touchpoint is a delete/rename, task authoring (or ensure-ready) should enumerate the files that reference it.
Resolution (fixed in this PR)
Section titled “Resolution (fixed in this PR)”Per operator direction, the gaps above were fixed in this same PR rather than spawned as follow-ups:
- Quality gate (#1/#3):
tests/parity/validators.golden.test.tsnow normalizes absolute checkout-root prefixes on both sides, so the validator golden suite passes from any worktree (260/0);sdlc.yamlgained abun test ./.claudeverb so dotfile-dir test suites are gated. The baseline-gated executor now reportsOK 6/6from the worktree (wasFAIL=26). - Stale spawn procedure (#2):
plugin/lib/model/entities/task/spawn-from-post-mortem.mdrepointed to the.tsscripts (dedup_search.ts,new_task.ts,resolve_plugin_repo.ts,validate_frontmatter.ts,tests/dedup_search.test.ts,summarize_dedup_telemetry.ts) withbun runprefixes. The same post-migration staleness was swept frombacklog/template.md,task/template.md, andplugin/skills/CLAUDE.md. - Touchpoint authoring (#5):
implementation-ready.mdnow requiresdelete/rename rows to enumerate referencing files (citations, hooks, gates, harnesses) as their ownmodifyrows. --no-coverageinjection (#4): root-caused to the operator’s externalpandacompressor (~/.claude/hooks/panda-rewrite.sh→panda rewriteappends--no-coverageafter the script argv). Not an in-repo fix — surfaced to the operator for a panda-side config change.- Deferred: epic templates (
epic/template.md,epic/writing.md) carry the same stale.pyrefs but are left untouched because epics are being retired.