Skip to content

refactor(tools): move script hygiene checks to Go #809

Description

@tbhb-agent

Problem

Script hygiene is still enforced by shell discovery, textual checks and structural-checker result handling. The boundaries are tools/check-script-hygiene.sh:56, tools/check-script-hygiene.sh:92, tools/check-script-hygiene.sh:112 and tools/check-script-hygiene.sh:137, on origin/main at a0347a715fab580ece45edfcb3d13509620459a8. Discovery and diagnostic-processing failures already have regression coverage and must not become a clean gate during the port.

This is the script-hygiene split of #332. Part of #588.

Acceptance criteria

  1. AC1: Default discovery checks the frozen command's owned scripts and local-only skill scripts, including nonignored untracked files and excluding duplicate deployed mirrors.
  2. AC2: Explicit-file checks preserve the frozen command's file selection, missing-file skips and reasoned hygiene-exemption behavior.
  3. AC3: Each checked script receives the same locale-pin and GitHub-retargeting findings as the frozen command.
  4. AC4: Structural findings retain the current rule decisions, path, one-based line, rule ID and message.
  5. AC5: Findings retain the frozen command's ordering, formatting, total and exit status, with silence on a clean run.
  6. AC6: Failed discovery, an unusable structural-checker result or failed diagnostic processing produces failure rather than a clean verdict, including when partial output was returned.
  7. AC7: Non-functional: running the hygiene check changes no repository content or Git state.
  8. AC8: Existing entry points end as thin shell wrappers that probe the Go subcommand without side effects and use frozen shell only when that capability is absent.
  9. AC9: A supported Go failure never invokes shell fallback or repeats a check to obtain success.
  10. AC10: Non-functional: release N-2 invocations retain their findings and behavior without requiring configuration changes or a raised binary floor.
  11. AC11: Equivalent inputs produce the same diagnostics on supported Linux, macOS and Windows environments.

Scope and non-goals

Preserve both default discovery sets, explicit targets, non-file skips and the whole-file hygiene-exempt opt-out (tools/check-script-hygiene.sh:62, tools/check-script-hygiene.sh:75, tools/check-script-hygiene.sh:93). Preserve the existing comment filtering and textual command detection, including what the current unset predicate actually recognizes (tools/check-script-hygiene.sh:103, tools/check-script-hygiene.sh:121).

Preserve the existing structural rules: bare Git output commands (.ast-grep/rules/bare-git-output.yml:14), diff without its required flag (.ast-grep/rules/diff-without-no-ext-diff.yml:10), and newline-losing field-separator construction (.ast-grep/rules/ifs-command-substitution.yml:15). Preserve checker statuses 0/1 as ordinary outcomes and statuses above 1 as tool failures with diagnostics; retain file/line sorting and literal message characters (tools/check-script-hygiene.sh:150, tools/check-script-hygiene.sh:160).

The existing failure coverage is tests/tools/masked-status.bats:98. Add the rule and discovery fixtures that this suite does not currently provide.

Non-goals: changing exemptions, adding or weakening rules, replacing the structural rules' semantics, broadening the scripts being checked, or porting unrelated cases in the shared masked-status suite. The fallback remains until a separate D9 follow-up establishes N-2 retirement eligibility.

Approach

Write the discovery and diagnostic-failure regressions first. Move the gate behavior to Go, comparing findings and failure transcripts with the frozen shell over the same fixtures. Keep the existing lint task (mise.toml:442). No new checker design or library is prescribed.

Dependencies

None blocking. The probe-and-fallback wrappers let this split land without waiting for #542, the #333 → #544 → #382 → #545 → #546 chain or the #359 delivery chain. Removing the fallback later waits for #333, #544 and the N-2 window, and is filed as a follow-up. Script hygiene is repository lint tooling, not merge, commit or pull request enforcement, so it does not wait for #753. #332 and #588 track this migration.

Size

S. One pull request for a single gate, its compatibility wrappers and rule-parity coverage.

Verify plan

  • AC6: first reproduce both discovery failures and the diagnostic-parser failure at tests/tools/masked-status.bats:98. Add checker startup/configuration failure, malformed diagnostics and partial output followed by nonzero status; assert failure, diagnostic preservation and no clean result. Check that ordinary checker status 1 still yields findings rather than a tool-failure report.
  • AC1: fixtures cover packaged scripts, local-only skills, deployed mirrors, tracked and nonignored untracked scripts and ignored files; compare the selected files with frozen shell.
  • AC2: explicit targets include a missing file, an ordinary file, and a reasoned exemption; assert that exempt files reach neither rule pass, while nonexempt files without Git/GitHub calls still reach structural checks.
  • AC3: shell/Go transcript comparisons cover comments, quoted command text, Git-only scripts, GitHub calls, absent/present locale pins and absent/present unset lines, preserving the current predicates rather than assuming stronger rules.
  • AC4: positive and negative fixtures for each of the three structural rules compare rule decisions, literal messages and line numbers, including quoted flag text versus a real argument and harmless field-separator assignment.
  • AC5: mixed textual/structural findings across several files compare full stdout, stderr, order, totals and status; a clean fixture is silent.
  • AC7: filesystem, index and ref snapshots are unchanged after successful and failed checks; use mutation traps for subprocesses.
  • AC8: absent binary and older binary without the capability run the frozen command once with unchanged arguments; verify the capability probe is read-only.
  • AC9: supported-command failure, partial-output failure and an indeterminate capability probe all refuse without shell fallback or repetition.
  • AC10: run unchanged tasks and direct entry points over the N-2 corpus with absent, older and new binaries; compare findings, streams and statuses.
  • AC11: run the Go corpus on Linux, macOS and Windows, retaining wrapper smoke coverage where Bash is supported. Run focused suites, mise run lint and mise run test on the reviewed head.
  • Definition of done: D1 each AC is verified at the merged head, with the commands and results cited in the pull request's Verification. D2 an independent review approves the exact merged head, and every finding has a recorded disposition. D3 required checks pass with no gate loosened. D4 the leak scans the repository specifies are clean, and every post made for this work (comments, descriptions) passes the per-post leak review before it is posted. D5 every commit is signed. D6 the documentation matches, or the description says No doc impact with the reason. D7 every commit carries an Assisted-by trailer and the pull request carries the provenance:agent-assisted label. D8 the change stays within the pull request size limits. D9 follow-up work, including deferred review findings, is filed as issues, each with a Problem section and numbered acceptance criteria and linking this issue; the pull request's Related section lists them, and any code that defers work links its issue. D10 the merge closes this issue through a closing reference, the project board shows the state Done, and a final Status: comment names the merge commit.

Risks

A discovery omission can hide an unchecked script; a swallowed checker error can report false success. Broader text interpretation can also change current rule decisions. File-selection, rule-parity and failure-first evidence cover these risks. Undo: revert the port.

Operator authority

None.

Activity

  1. added
    change:featPR adds or changes a feature (Conventional Commit type feat)
    on Oct 26, 2026
  2. tbhb-agent commented on Oct 26, 2026

    @tbhb-agent
    ContributorAuthor

    Refinement: READY — script-hygiene Go port requirements, parity checks and rollout boundaries are complete
    Model: gemini-4-argon-high

    Checked

    • Live issue tbhb-dev/devtools#809 (refactor(tools): move script hygiene checks to Go, type Feature, label change:feat, 0 comments) and referenced issues #332, #588, #542, #333, #544, #382, #545, #546, #359 and #753 against origin/main at a0347a715fab580ece45edfcb3d13509620459a8.
    • Problem and baseline citations against origin/main:
      • Default argument and discovery branch at tools/check-script-hygiene.sh:56, owned-script git ls-files discovery at tools/check-script-hygiene.sh:62, and .claude/skills/*/scripts/*.sh discovery filtered by packaged() (tools/check-script-hygiene.sh:48) at tools/check-script-hygiene.sh:75.
      • Per-file existence and # hygiene-exempt: handling at tools/check-script-hygiene.sh:92 and tools/check-script-hygiene.sh:93, comment stripping at tools/check-script-hygiene.sh:103, missing-locale-pin rule at tools/check-script-hygiene.sh:112, and missing-gh-unset rule at tools/check-script-hygiene.sh:121.
      • Structural checker pass at tools/check-script-hygiene.sh:137, exit-status handling (ast_status -gt 1) at tools/check-script-hygiene.sh:150, tab-joined diagnostic extraction and file/line sorting at tools/check-script-hygiene.sh:160, and total reporting at tools/check-script-hygiene.sh:169.
      • Structural rule definitions at .ast-grep/rules/bare-git-output.yml:14, .ast-grep/rules/diff-without-no-ext-diff.yml:10 and .ast-grep/rules/ifs-command-substitution.yml:15 (configured via sgconfig.yml:9).
      • Existing discovery and parser failure regression test at tests/tools/masked-status.bats:98.
      • Task entry point at mise.toml:442 (lint-script-hygiene, invoked from lint at mise.toml:311).
    • Acceptance criteria (AC1–AC11): each criterion states one observable, testable outcome free of prescribed package layout, internal file structure or library choices, and includes non-functional requirements for read-only execution (AC7), N-2 compatibility (AC10, matching docs/compatibility.md:3) and cross-platform parity across Linux, macOS and Windows (AC11, matching mise.toml:72).
    • Dependencies and rollout ordering: probe-and-fallback wrappers (AC8, AC9) avoid blocking on #333, #544, #382, #545, #546, #359 or #542, with fallback retirement deferred to a D9 follow-up after #333, #544 and the N-2 window. Because tools/check-script-hygiene.sh:1 is repository lint tooling (mise.toml:442) rather than merge, commit or pull request enforcement (#588, #753), #753 does not block #809.
    • Verify plan: maps every acceptance criterion (AC1–AC11) and D1–D10 to concrete, falsifiable checks covering discovery sets, exemptions, textual predicates, all three structural rules, output ordering and totals, failure propagation (including partial output and status > 1 vs status 1), state-mutation traps, capability-probe fallback and refusal rules, N-2 compatibility and cross-platform execution.
    • Operator authority: None is accurate because tools/check-script-hygiene.sh and tests/tools/masked-status.bats do not match golden_paths, constitutional_paths or owner_paths in .agents/agent-skills.conf:4, .agents/agent-skills.conf:5 and .agents/agent-skills.conf:9, while mise.toml:442, sgconfig.yml:9 and .ast-grep/rules/ (gate_config_paths at .agents/agent-skills.conf:8) remain unchanged.

    Gaps

    None

    Split

    None — size S fits a single pull request covering one script-hygiene gate (tools/check-script-hygiene.sh:1), its thin compatibility wrapper and its Go and Bats parity tests.

    Definition of done

    The definition of done for #809 (Feature). Tick each item and give its evidence. Ticked boxes below record planning coverage in the issue's criteria and verification plan, not completed implementation work.

    • D1 The author verified each AC on the merged head, and the Verification section of the pull request cites each AC ID with its evidence: the command and its result, or a link. — Planning coverage: Verify plan maps AC1–AC11 to specific test and transcript checks and requires D1 verification citations on the merged head.
    • D2 An independent reviewer approved the exact merged head, and the author resolved each finding or filed it as a follow-up. — Planning coverage: Verify plan (Definition of done: D2) requires independent review approval of the exact merged head and recorded dispositions for all findings.
    • D3 The required checks pass on that head, and any loosened gate has the owner's approval of that exact head. — Planning coverage: Scope and non-goals forbids weakening rules, and Verify plan (AC11 and Definition of done: D3) requires mise run lint, mise run test and required checks to pass with no gate loosened.
    • D4 The leak scans come back clean: gitleaks, devtools' check-leaks.sh with the repository's private-strings list, and the leak review of each post. — Planning coverage: Verify plan (Definition of done: D4) requires clean repository leak scans and per-post leak review before posting.
    • D5 The author signed each commit, the squash included. — Planning coverage: Verify plan (Definition of done: D5) requires every commit to be signed.
    • D6 The docs match the change (README, docs/, CHANGELOG or help text), or the Verification section says "No doc impact" and why. — Planning coverage: Verify plan (Definition of done: D6) requires matching documentation or an explicit No doc impact justification.
    • D7 The commits and the squash have Assisted-by trailers, and the pull request has the provenance:agent-assisted label. — Planning coverage: Verify plan (Definition of done: D7) requires Assisted-by trailers on every commit and the provenance:agent-assisted pull request label.
    • D8 The pull request stays within the size limits, or the owner approved its exact head over the hard limit. — Planning coverage: Size scopes the change to S (one pull request for a single gate, wrapper and parity tests), and Verify plan (Definition of done: D8) requires staying within pull request size limits.
    • D9 The author files follow-ups and deferred findings as issues with at least a Problem and numbered AC and lists them under the Related section of the pull request. Any work left in the code links its issue. — Planning coverage: Scope and non-goals, Dependencies and Verify plan (Definition of done: D9) require filing fallback retirement and any deferred findings as issues with Problem and numbered acceptance criteria linked under Related.
    • D10 The merge closes the issue through a closing reference and the board shows Done. The final Status: comment names the merge commit. — Planning coverage: Verify plan (Definition of done: D10) requires a closing reference on merge, board state Done and a final Status: comment naming the merge commit.

    Round: 1 of 3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    change:featPR adds or changes a feature (Conventional Commit type feat)refinement:readyPassed the Definition of Ready; may be picked up

    Fields

    Priority

    None yet

    Work type

    None yet

    Projects

    • Status
      Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions