Repository navigation
Description
Problem
Part of #588. Split commit-message preview orchestration out of #330 as one reviewable PR. The underlying message rules already run through commit-lint and commit-msg gates; the remaining shell selects a sign-off identity, prepares a temporary preview, and runs those gates without editing the draft.
Evidence on origin/main at a0347a715fab580ece45edfcb3d13509620459a8: identity selection is .devtools/scripts/lint-commit-msg.sh:5, validation is .devtools/scripts/lint-commit-msg.sh:42, and preview orchestration is .devtools/scripts/lint-commit-msg.sh:65. The published task interface is .config/mise/conf.d/30-devtools-skill-contract.toml:10. Behavioral coverage is tests/devtools/lint-commit-msg.bats:43 through tests/devtools/lint-commit-msg.bats:143.
Acceptance criteria
- AC1: Preview leaves the author's
COMMIT_AGENTMSGbyte-for-byte unchanged on success and failure. - AC2: An existing sign-off is preserved without duplication or trailer reordering.
- AC3: A missing sign-off uses the existing precedence: explicit environment setting, per-repository machine setting, global machine setting, then configured Git identity, including the current empty-setting behavior.
- AC4: A candidate sign-off rejected by identity policy prevents gate execution and retains the existing refusal status and guidance.
- AC5: Invalid identity policy configuration stops preview with the existing configuration-error status before gate execution.
- AC6: Preview runs the actual commit-msg gate interface against a temporary message named
COMMIT_EDITMSGand returns the gate's result. - AC7: Temporary preview files are removed after both successful and failed gate execution.
- AC8: Non-functional: repository behavior configuration is never used as a sign-off identity source.
- AC9: Every existing consumer-facing preview entry point ends as a thin shell wrapper that probes the Go subcommand and falls back to the frozen shell when that capability is unavailable throughout N-2 compatibility.
- AC10: Non-functional: supported fixtures preserve the frozen shell's statuses, diagnostic streams, and preview message bytes on Linux, macOS, and Windows.
Scope and non-goals
Cover the existing task/helper interface, Git identity fallback rather than committer-environment identity, machine-setting precedence across worktrees, trailer parsing, missing/unreadable draft failure, temporary preview lifecycle, commit-scope gate results, and existing trailer order. The sign-off source and fallback branches are .devtools/scripts/lint-commit-msg.sh:26 and .devtools/scripts/lint-commit-msg.sh:42; temporary execution is .devtools/scripts/lint-commit-msg.sh:67.
Non-goals: changing message rules, identity policy, the real commit-msg hook, trailer requirements, or confirmation; rewriting Vale/cspell/prek; creating a commit; or changing the separate commit transaction under #322. Retain the real-hook coverage in tests/devtools/lint-commit-msg.bats:73 as integration evidence rather than treating preview as a substitute for that hook. No new machine-setting syntax is introduced.
Approach
Establish failing preview tests before moving orchestration to Go. Preserve the existing commit-lint and hook interfaces and compare the captured preview against frozen shell fixtures. Keep the required N-2 wrapper and frozen fallback; probe the consumer-selected subcommand without resolving identity or running gates, and never fall back after an operational Go failure.
Dependencies
- Port the pins, vendoring, preapproval and APM sync task logic #330 tracks this adapter port.
- Port the shared skill library (lib.sh, agent.sh, signing.sh, gates.py) to Go #321 owns shared machine-setting contracts; preserve the deployed contract without requiring the whole library port to land first.
- Replace complex shell with tested Go, starting with gates and merging #588 supplies the migration requirements.
- Ship the Go binaries as release assets installed through mise's github backend #333 tracks binary distribution; N-2 consumers retain the shell path when the preview capability is absent.
Size
S. One PR for preview behavior, its identity/refusal coverage, and compatible dispatch.
Verify plan
Write and observe the named replacement tests fail before changing dispatch or removing shell behavior. Use frozen-shell comparisons as the parity oracle; no live protected action is needed.
- AC1, AC2: Failing tests first for missing and existing sign-offs, mixed-case recognized trailers, trailer ordering, and gate failure; compare original draft bytes and captured preview bytes. Port
tests/devtools/lint-commit-msg.bats:43throughtests/devtools/lint-commit-msg.bats:65. - AC3, AC8: Failing tests first with distinct identities at every existing source, explicitly empty environment/file settings, absent sources, and a worktree name different from its main checkout. Supply synthetic machine configuration only; prove repository behavior settings cannot supply the sign-off identity.
- AC4, AC5: Port refusals from
tests/devtools/lint-commit-msg.bats:125onward and add failing tests first for unset Git identity and malformed configuration. Assert no gate execution on rejection and the existing distinction between refusal and configuration errors. - AC6, AC7: Failing tests first for exact gate arguments, preview basename, cleanup after success and nonzero gate status, and absent/unreadable drafts. Retain the 72/73-character boundary and commit-scope Vale cases at
tests/devtools/lint-commit-msg.bats:98andtests/devtools/lint-commit-msg.bats:113; the real-hook contract remains separately covered. - AC9: Failing wrapper tests first for capable, absent, old, and unsupported binaries, selected-version precedence, unchanged arguments/streams, and no shell rerun after a Go failure.
- AC10: Run frozen/new parity for status, stdout, stderr, and captured preview. Run Go tests on Linux, macOS, and Windows, applicable shell wiring tests,
mise run lintandmise run test, and relevant payload checks; remove only Bats assertions whose behavior has migrated.
Definition of done: D1–D10 apply.
Risks
Normalizing explicit empty settings through a different configuration reader can change identity selection. A preview must neither rewrite the author's draft nor imply that the real hook ran on a committed message. Temporary basename and trailer order are observable parts of the current contract.
Operator authority
None.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Work type
Projects
- StatusShow more project fieldsIn progress
Activity
tbhb-agent commentedon Oct 26, 2026
Refinement: READY — commit-message preview port has clear, testable requirements and verified baseline citations
Model: gemini-4-argon-high
Checked
tbhb-dev/devtools#801(0 comments) and referenced issues#588,#330,#321,#322, and#333againstorigin/mainata0347a715fab580ece45edfcb3d13509620459a8..devtools/scripts/lint-commit-msg.sh:5(conf_value) and.devtools/scripts/lint-commit-msg.sh:26(configured_signoff) implement machine sign-off configuration lookup (DEVTOOLS_SIGNOFF_IDENTITY,<repo>.conf,devtools.conf)..devtools/scripts/lint-commit-msg.sh:42(signoff_identity) implements fallback togit config --get user.nameanduser.email, policy validation viacommit-lint --check-signoff(.devtools/scripts/lint-commit-msg.sh:53), and operator guidance on status1when unconfigured (.devtools/scripts/lint-commit-msg.sh:59)..devtools/scripts/lint-commit-msg.sh:65(main) and.devtools/scripts/lint-commit-msg.sh:67create the temporary preview directory, copyCOMMIT_AGENTMSGtoCOMMIT_EDITMSG, appendSigned-off-bywhen absent viagit interpret-trailers, and runprek run --stage commit-msg --commit-msg-filename "$preview"..config/mise/conf.d/30-devtools-skill-contract.toml:10defines[task_templates."devtools:lint-commit-msg"]invokingbash "$DEVTOOLS_DIR/scripts/lint-commit-msg.sh"(.config/mise/conf.d/30-devtools-skill-contract.toml:12), extended bymise.toml:483.tests/devtools/lint-commit-msg.bats:43throughtests/devtools/lint-commit-msg.bats:143cover fallback sign-off and trailer order (tests/devtools/lint-commit-msg.bats:43), configured override (tests/devtools/lint-commit-msg.bats:50), existing sign-off preservation (tests/devtools/lint-commit-msg.bats:58), draft preservation and cleanup on gate failure (tests/devtools/lint-commit-msg.bats:65), realcommit-msghook enforcement (tests/devtools/lint-commit-msg.bats:73),COMMIT_EDITMSGbasename and directory cleanup (tests/devtools/lint-commit-msg.bats:91), 72/73-char body width (tests/devtools/lint-commit-msg.bats:98), commit-scope Vale (tests/devtools/lint-commit-msg.bats:113), denied Git and configured sign-offs (tests/devtools/lint-commit-msg.bats:125,tests/devtools/lint-commit-msg.bats:134), and invalid identity configuration (tests/devtools/lint-commit-msg.bats:143).internal/config/config.go:55; AC9 N-2 thin-wrapper compatibility matchingdocs/compatibility.md:3; AC10 cross-platform parity on Linux, macOS, and Windows). None prescribe internal Go package layout, file structure, or library choices.#322. Dependencies on#330,#321,#588, and#333are accurate and non-blocking for this slice.AC1–AC10) to concrete failing-first tests and parity checks against the frozen shell and existing Bats cases without requiring live protected actions..devtools/scripts/lint-commit-msg.sh:28/.devtools/scripts/lint-commit-msg.sh:45andinternal/config/config.go:25, temporary basename sensitivity (scripts/vale-commit-msg.sh:21), and trailer ordering..devtools/scripts/lint-commit-msg.shandtests/devtools/lint-commit-msg.batsdo not matchgolden_paths,constitutional_paths, orowner_pathsin.agents/agent-skills.conf:4, soOperator authority: None.is accurate.Gaps
None
Split
No split needed. Size
Sfits a single reviewable PR (18counted source lines indocs/script-inventory.md:48,80physical lines in.devtools/scripts/lint-commit-msg.sh:1, and148lines intests/devtools/lint-commit-msg.bats:1).Definition of done
The definition of done for #801 (Feature). Tick each item and give its evidence. Each ticked box below indicates planning coverage in the issue's requirements and verification plan, not completed implementation work.
Verify planmapsAC1–AC10to explicit test cases, parity checks, andmise run lint/mise run testcommands, and citesDefinition of done: D1–D10 apply.Verify planspecifiesDefinition of done: D1–D10 apply.under the repository's standard PR and merge review gates (AGENTS.md:83,AGENTS.md:142).AC10andVerify planrequire Go tests on Linux, macOS, and Windows, shell wiring tests,mise run lint,mise run test, and payload checks (Definition of done: D1–D10 apply.).check-leaks.shwith the repository's private-strings list, and the leak review of each post. — Planning coverage:Verify planinvokesDefinition of done: D1–D10 apply.alongside standard commit/PR leak gates (AGENTS.md:128).Verify planinvokesDefinition of done: D1–D10 apply.alongside required SSH commit signing (AGENTS.md:53).docs/, CHANGELOG or help text), or the Verification section says "No doc impact" and why. — Planning coverage:Verify planinvokesDefinition of done: D1–D10 apply.andAC9/AC10preserve existing consumer-facing behavior documented indocs/commit-signoff-preview.md:1.provenance:agent-assistedlabel. — Planning coverage:Verify planinvokesDefinition of done: D1–D10 apply.under repository trailer and PR provenance rules (AGENTS.md:28).Sizescopes the change toS(one PR splitting preview orchestration out of#330), andVerify planinvokesDefinition of done: D1–D10 apply.DependenciesandScope and non-goalslink sibling and parent issues (#330,#321,#322,#333,#588), andVerify planinvokesDefinition of done: D1–D10 apply.Status:comment names the merge commit. — Planning coverage:Verify planinvokesDefinition of done: D1–D10 apply.Round: 1 of 3