Skip to content

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.

LocationRole today
.claude/skills/project-check/check_pipe_tail.pyPython 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.pyPython 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.

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.

  1. Port check_pipe_tail.pycheck_pipe_tail.ts (bun), preserving the default antipattern regex, --skills-dir / --pattern flags, the <path>:<line>: <message> output, and exit codes 0 / 1 / 2.
  2. Add tests/check_pipe_tail.test.ts (bun:test) covering a clean SKILL.md (exit 0) and one with a piped-gated-command violation (exit 1).
  3. Drop the check_pipe_tail cases/reference from run_skill_doc_evals.py so deleting the Python checker leaves that harness covering check_skill_docs only.
  4. Delete check_pipe_tail.py.
LocationKindChange
.claude/skills/project-check/check_pipe_tail.tsnewTypeScript port of the linter
.claude/skills/project-check/tests/check_pipe_tail.test.tsnewbun:test coverage for clean + violation cases
.claude/skills/project-check/check_pipe_tail.pydeletereplaced by the .ts port
.claude/skills/project-check/tests/run_skill_doc_evals.pymodifydrop the check_pipe_tail cases/reference
  • AC-1: bun run .claude/skills/project-check/check_pipe_tail.ts exits 0 against the live repo, matching the current Python checker’s clean-tree verdict.
  • AC-2: .claude/skills/project-check/check_pipe_tail.py no longer exists and grep -rn "check_pipe_tail.py" .claude returns no matches.
  • AC-3: .claude/skills/project-check/tests/check_pipe_tail.test.ts passes under bun test and asserts both a clean SKILL.md (exit 0) and a piped-gated-command violation (exit 1).
  • AC-4: check_pipe_tail.ts preserves the --skills-dir and --pattern flags, the <path>:<line>: <message> violation format, and exit codes 0 (clean) / 1 (violation) / 2 (self-error).
  • Porting the other project-check checkers (check_skill_docs, check_skill_prose, check_entities) — 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.
  • Converting run_skill_doc_evals.py wholesale to TypeScript (it still serves check_skill_docs).
  • none
  • First Tier-1 task of the /sdlc:orchestrate/sdlc:task-work TS-migration dogfood; chosen because check_pipe_tail.py has no plugin/ dependency, making it a clean isolated smoke test of the live task-work chain.

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.

  • AC-1: auto — bun run .claude/skills/project-check/check_pipe_tail.ts exits 0 against the live repo (28 skills, no antipatterns); checker stdout/stderr byte-identical to the .py (sub-agent diff).
  • AC-2: auto — .py removed via git rm; command grep -rn "check_pipe_tail.py" .claude exits 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, --pattern override→0).
  • Full lease lifecycle ran clean end-to-end: acquireworking → background heartbeat → (awaiting-review at PR). Implementation took ~52 min, so the heartbeat was load-bearing — it renewed the lease twice past its 1h TTL (expires_at advanced 02:48→03:19), exactly its design purpose.
  • ensure-ready gate, start_task.ts commit-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 + .claude Python gates.
  • Baseline-gated bun test reports 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) — the bun test verb 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.ts bakes 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-coverage into commands (multiple bun run invocations and even grep), 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 test excludes dotfile directories, so .claude/**/*.test.ts suites are never auto-discovered by the configured bun test gate — the migration’s new .claude test suites won’t run in CI without an explicit test path/glob or a dedicated .claude test verb in sdlc.yaml. Important for the broader .claude/→TS migration, not just this task.
  • Deleting check_pipe_tail.py forced edits to three files outside the Files-to-touch table (.claude/skills/project-check/SKILL.md, .claude/skills/project-check/tests/README.md, root lefthook.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.

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.ts now normalizes absolute checkout-root prefixes on both sides, so the validator golden suite passes from any worktree (260/0); sdlc.yaml gained a bun test ./.claude verb so dotfile-dir test suites are gated. The baseline-gated executor now reports OK 6/6 from the worktree (was FAIL=26).
  • Stale spawn procedure (#2): plugin/lib/model/entities/task/spawn-from-post-mortem.md repointed to the .ts scripts (dedup_search.ts, new_task.ts, resolve_plugin_repo.ts, validate_frontmatter.ts, tests/dedup_search.test.ts, summarize_dedup_telemetry.ts) with bun run prefixes. The same post-migration staleness was swept from backlog/template.md, task/template.md, and plugin/skills/CLAUDE.md.
  • Touchpoint authoring (#5): implementation-ready.md now requires delete/rename rows to enumerate referencing files (citations, hooks, gates, harnesses) as their own modify rows.
  • --no-coverage injection (#4): root-caused to the operator’s external panda compressor (~/.claude/hooks/panda-rewrite.shpanda rewrite appends --no-coverage after 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 .py refs but are left untouched because epics are being retired.

← Back to Tasks