Skip to content

refactor(cut-criteria): unify 3 bash scripts into one Python module#395

Closed
trilamsr wants to merge 1 commit into
mainfrom
worktree-agent-ac5cb4076e8b02b39
Closed

refactor(cut-criteria): unify 3 bash scripts into one Python module#395
trilamsr wants to merge 1 commit into
mainfrom
worktree-agent-ac5cb4076e8b02b39

Conversation

@trilamsr

@trilamsr trilamsr commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Replace scripts/cut-criteria-{status,render,status_test}.sh with a single scripts/cut_criteria.py module exposing status, render, check, and test subcommands. Output is byte-identical for the same YAML + repo state — the only deliberate diff in the rendered markdown is the script-name reference line in the legend.

Closes #386.

Why

PR #374 wrapped each criterion's rubric_check shell snippet in base64 to ship it through a TSV row format the bash while read -d '' loop could parse. That hop existed only because bash's row parser cannot survive literal newlines inside fields. Root cause: shell-as-row-parser against YAML block scalars. The reviewer + the original author both flagged it as over-engineered.

Root cause + fix

Symptom Root cause Fix
base64 -d per row Bash read-loop cannot tolerate \n inside fields Replace shell loop with native Python iteration over the parsed YAML; subprocess.run(snippet, shell=True, executable="/bin/bash") passes multi-line block scalars to bash directly.
Three Python heredocs (one per shell script) One bash entrypoint per concern One module, three CLI subcommands.
Separate *_test.sh (160 LOC) of bash assertions Test harness lived parallel to the production code path cut_criteria.py test reuses status_rows() against in-process fixtures.
11-line cut-criteria-check Makefile target (mktemp + diff -u + rm -f) Diff-via-shell against a tempfile In-process diff via difflib; Makefile target is now 1 line.

What landed

  • scripts/cut_criteria.py — single module. CLI: status | render | check | test.
  • Schema validation at parse time: required keys, allowed tiers, duplicate-id check. Surfaces malformed YAML at make cut-criteria-status instead of producing a quietly-wrong markdown.
  • Regression fixture multiline-shell-no-base64 — locks in the refactor; a future change that reintroduces a TSV/base64 hop without honoring newlines goes red.
  • Deleted: scripts/cut-criteria-status.sh, scripts/cut-criteria-render.sh, scripts/cut-criteria-status_test.sh.
  • Makefile + docs/cut-criteria.yaml{,.md} updated to point at the new entrypoints.

TDD evidence

  1. Captured golden status.tsv + rendered v1-rc1-cut-criteria.md BEFORE editing (against the live repo state).
  2. Wrote the new module.
  3. Diffed new output against the goldens. Status: byte-identical. Render: differs by exactly one line (the script-name reference, intentional).

Test plan

  • python3 scripts/cut_criteria.py test — 7/7 fixtures pass (6 carried forward from the bash suite + 1 new multi-line regression).
  • make cut-criteria-status — 15-row output matches the pre-refactor golden byte-for-byte.
  • make cut-criteria-render — idempotent (two consecutive renders → no diff on disk).
  • make cut-criteria-check — clean on this branch.
  • make doc-check — clean (transitively runs cut-criteria-check).
  • make actionlint — clean.
  • python3 -m py_compile scripts/cut_criteria.py — clean.

Out of scope (deliberate)

  • The issue mentioned git status -s docs/v1-rc1-cut-criteria.md as a candidate drift gate. Kept the in-process diff: git status -s only catches uncommitted local edits, not a deterministically-stale-in-the-repo render (CI's actual failure mode). The in-process diff is strictly simpler than the bash temp-file diff it replaces (1-line Makefile target vs 11 lines).
- `scripts/cut-criteria-{status,render,status_test}.sh` collapsed into a single `scripts/cut_criteria.py` module (`status` / `render` / `check` / `test` subcommands). No behaviour change — output is byte-identical against the same YAML + repo state. Drops the base64-over-TSV plumbing carried for bash macOS-3.2 compat. Schema validation added at parse time.

Replace `scripts/cut-criteria-{status,render,status_test}.sh` with a
single `scripts/cut_criteria.py` exposing `status`, `render`, `check`,
and `test` subcommands. Output is byte-identical for the same YAML +
repo state (the only deliberate diff is the script-name reference line
in the rendered legend).

Drops:
- The base64-over-TSV plumbing in cut-criteria-status.sh. It existed
  only to survive embedded newlines through a bash `read` loop, a
  macOS-3.2 compat tax. subprocess.run(snippet, shell=True) passes
  multi-line YAML block scalars to /bin/bash directly, no encoding
  hop. A regression fixture (`multiline-shell-no-base64`) locks this
  in: a future refactor that reintroduces a TSV/base64 round-trip
  without honoring newlines goes red.
- The per-shell-invocation Python heredocs in render.sh (196 LOC) and
  status.sh (75 LOC). Three Makefile shell-outs to one module now
  replace three bash scripts that each shelled out to python3.
- The separate `*_test.sh` test harness (160 LOC). The fixture suite
  is now a `cut_criteria.py test` subcommand using the same in-process
  status_rows() the production path uses.

Adds (per issue ask):
- YAML schema validation at parse time (required keys, allowed tiers,
  duplicate-id check) — surfaces malformed spec at `make
  cut-criteria-status` instead of producing a quietly-wrong markdown.
- `check` subcommand that renders in-process and diffs against the
  on-disk markdown; no `mktemp`/`diff -u`/`rm -f` choreography. The
  Makefile target collapsed from 11 lines to 1.

Closes #386.

Signed-off-by: Tri Lam <tri@maydow.com>
@trilamsr

trilamsr commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #397 (same commits, properly-named branch).

@trilamsr trilamsr closed this Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] simplify cut-criteria scripts (drop base64, single Python)

1 participant