Skip to content

feat(commit): move commit message previews to Go #801

Description

@tbhb-agent

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

  1. AC1: Preview leaves the author's COMMIT_AGENTMSG byte-for-byte unchanged on success and failure.
  2. AC2: An existing sign-off is preserved without duplication or trailer reordering.
  3. 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.
  4. AC4: A candidate sign-off rejected by identity policy prevents gate execution and retains the existing refusal status and guidance.
  5. AC5: Invalid identity policy configuration stops preview with the existing configuration-error status before gate execution.
  6. AC6: Preview runs the actual commit-msg gate interface against a temporary message named COMMIT_EDITMSG and returns the gate's result.
  7. AC7: Temporary preview files are removed after both successful and failed gate execution.
  8. AC8: Non-functional: repository behavior configuration is never used as a sign-off identity source.
  9. 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.
  10. 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

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:43 through tests/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:125 onward 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:98 and tests/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 lint and mise 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.

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 — commit-message preview port has clear, testable requirements and verified baseline citations
    Model: gemini-4-argon-high

    Checked

    • Live issue and referenced issues: Read live tbhb-dev/devtools#801 (0 comments) and referenced issues #588, #330, #321, #322, and #333 against origin/main at a0347a715fab580ece45edfcb3d13509620459a8.
    • Problem and baseline citations:
      • .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 to git config --get user.name and user.email, policy validation via commit-lint --check-signoff (.devtools/scripts/lint-commit-msg.sh:53), and operator guidance on status 1 when unconfigured (.devtools/scripts/lint-commit-msg.sh:59).
      • .devtools/scripts/lint-commit-msg.sh:65 (main) and .devtools/scripts/lint-commit-msg.sh:67 create the temporary preview directory, copy COMMIT_AGENTMSG to COMMIT_EDITMSG, append Signed-off-by when absent via git interpret-trailers, and run prek run --stage commit-msg --commit-msg-filename "$preview".
      • .config/mise/conf.d/30-devtools-skill-contract.toml:10 defines [task_templates."devtools:lint-commit-msg"] invoking bash "$DEVTOOLS_DIR/scripts/lint-commit-msg.sh" (.config/mise/conf.d/30-devtools-skill-contract.toml:12), extended by mise.toml:483.
      • tests/devtools/lint-commit-msg.bats:43 through tests/devtools/lint-commit-msg.bats:143 cover 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), real commit-msg hook enforcement (tests/devtools/lint-commit-msg.bats:73), COMMIT_EDITMSG basename 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).
    • Acceptance criteria (AC1–AC10): Each criterion states a single observable, testable outcome or non-functional requirement (AC8 repository-config trust boundary matching internal/config/config.go:55; AC9 N-2 thin-wrapper compatibility matching docs/compatibility.md:3; AC10 cross-platform parity on Linux, macOS, and Windows). None prescribe internal Go package layout, file structure, or library choices.
    • Scope, non-goals, and dependencies: Scope is cleanly bounded to commit-message preview orchestration and its N-2 wrapper; non-goals exclude real hook changes, message-rule changes, and the commit transaction under #322. Dependencies on #330, #321, #588, and #333 are accurate and non-blocking for this slice.
    • Verify plan: Maps every AC (AC1–AC10) to concrete failing-first tests and parity checks against the frozen shell and existing Bats cases without requiring live protected actions.
    • Risks and operator authority: Risks identify empty-setting precedence differences between .devtools/scripts/lint-commit-msg.sh:28 / .devtools/scripts/lint-commit-msg.sh:45 and internal/config/config.go:25, temporary basename sensitivity (scripts/vale-commit-msg.sh:21), and trailer ordering. .devtools/scripts/lint-commit-msg.sh and tests/devtools/lint-commit-msg.bats do not match golden_paths, constitutional_paths, or owner_paths in .agents/agent-skills.conf:4, so Operator authority: None. is accurate.

    Gaps

    None

    Split

    No split needed. Size S fits a single reviewable PR (18 counted source lines in docs/script-inventory.md:48, 80 physical lines in .devtools/scripts/lint-commit-msg.sh:1, and 148 lines in tests/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.

    • 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–AC10 to explicit test cases, parity checks, and mise run lint / mise run test commands, and cites Definition of done: D1–D10 apply.
    • 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 specifies Definition of done: D1–D10 apply. under the repository's standard PR and merge review gates (AGENTS.md:83, AGENTS.md:142).
    • D3 The required checks pass on that head, and any loosened gate has the owner's approval of that exact head. — Planning coverage: AC10 and Verify plan require 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.).
    • 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 invokes Definition of done: D1–D10 apply. alongside standard commit/PR leak gates (AGENTS.md:128).
    • D5 The author signed each commit, the squash included. — Planning coverage: Verify plan invokes Definition of done: D1–D10 apply. alongside required SSH commit signing (AGENTS.md:53).
    • 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 invokes Definition of done: D1–D10 apply. and AC9/AC10 preserve existing consumer-facing behavior documented in docs/commit-signoff-preview.md:1.
    • D7 The commits and the squash have Assisted-by trailers, and the pull request has the provenance:agent-assisted label. — Planning coverage: Verify plan invokes Definition of done: D1–D10 apply. under repository trailer and PR provenance rules (AGENTS.md:28).
    • 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 PR splitting preview orchestration out of #330), and Verify plan invokes Definition of done: D1–D10 apply.
    • 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: Dependencies and Scope and non-goals link sibling and parent issues (#330, #321, #322, #333, #588), and Verify plan invokes Definition of done: D1–D10 apply.
    • 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 invokes Definition of done: D1–D10 apply.

    Round: 1 of 3

  3. moved this from Backlog to In progress in devtoolson Oct 26, 2026
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
      In progress

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions