Skip to content

feat: detailed error output, help strings, max-header-length, and full test suite - #28

Merged
sugat009 merged 11 commits into
opensource-nepal:mainfrom
JustBipin:feat/feature-parity
Sep 18, 2026
Merged

sugat009 merged 11 commits into
opensource-nepal:mainfrom
JustBipin:feat/feature-parity

Conversation

@JustBipin

@JustBipin JustBipin commented Sep 5, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes #27, closes #25. Implements full feature parity with Python commitlint v2.0.0.

Behavior changes (user-visible)

  • ^ anchor on Bump ignore pattern. The [Bb]ump regex now anchors at start-of-line. "feat: bump x from 1 to 2" was silently ignored on main; it now lints like upstream. This is a parity fix, not a regression.
  • CRLF and lone CR normalization. read_file, get_commit_message_from_hash, and get_commit_messages_from_hash_range now normalize \r\n and lone \r to \n before linting, matching Python text mode. CRLF commits that passed on main now fail when the header or body violates the format.
  • Trimming. Direct messages and --file contents are trimmed of leading/trailing whitespace before linting. A leading space that failed on main now passes (matching upstream).
  • Ignored messages print success. "Merge pull request #123" now prints Commit validation: successful! in single-message mode, matching upstream.
  • -q silences file/hash errors. A missing --file or unknown --hash exits silently with code 1 when -q is set.
  • --max-header-length -5 reaches the parser. The value parser now accepts negative numbers and reports upstream-consistent error text: "Value must be a positive integer (> 0)".
  • -v verbose output. Prints linting steps, validator names, and source-tracking (direct message, file, hash, or range) to stdout. Matches upstream console.verbose output in the lint path and git helpers.

Architecture

  • LintOptions replaces global config. lint_commit_message now takes &LintOptions instead of reading a process-global Config. This removes ConfigGuard, set_config, config(), and update_config. command.rs builds LintOptions from Cli and passes it down.
  • Error messages pinned in tests. 14 CLI tests assert the exact text of all 13 error messages plus the multi-error output.
  • AGENTS.md removed from this PR. It is repository policy and arrives from main via docs: add contributor and agent guides #29. This diff no longer touches it.

Tests

  • 188 total: 60 unit, 111 CLI, 3 crlf parity, 11 git helpers, 1 message coverage, 2 upstream parity
  • New: error text assertions, ignored-message success, -q file/hash/from-hash guards, CRLF normalization, header-length with CRLF file, ^ anchor regression, max_header_length_string_fails_clap message assertion

Verification

cargo fmt --check             # clean
cargo test                    # 188 pass
cargo clippy -D warnings      # zero warnings

Comment thread src/cli.rs Outdated

@sugat009 sugat009 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

I tested this PR against upstream Python commitlint v2.0.0 and against main. I compared the exit code, stdout, and stderr on 948 inputs. I also ran the 31 rows of upstream tests/fixtures/linter.py. 784 of the 948 results were identical. cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test pass, and CI is green.

The comments use Conventional Comments. Each inline comment gives the command that shows the problem.

Blocking

issue (blocking): detailed mode accepts a commit without a description. cocox "feat:" exits 0. Upstream and main both reject it. See src/validators.rs:110.

issue (blocking): --max-header-length counts UTF-8 bytes, not characters. A header with 18 characters and 40 bytes fails at a limit of 20. See src/validators.rs:29.

issue (blocking): 9 of the 13 error messages have no test. No CLI test asserts a per-field message. The PR also deletes 9 test functions from src/linter.rs. One of them covered the first blocker. See src/validators.rs:173.

Non-blocking

issue (non-blocking): a commit message with \r\n passes with --hash and --from-hash. Upstream and main reject it. See src/linter.rs:26.

issue (non-blocking): -q does not silence file and git errors. See src/command.rs:125.

issue (non-blocking): --max-header-length -5 does not reach the validator. clap rejects the value first. The test for this case asserts only the exit code, so it passes for the wrong reason. See src/cli.rs:77.

issue (non-blocking): the "Invalid type" message lists the types in a different order than upstream. See src/constants.rs:3.

issue (non-blocking): ignored messages give no output in single-message mode. Upstream prints the success line. See src/command.rs:62.

Other

suggestion: update README.md. It shows the old about text and empty help columns. It documents none of the 5 new flags. git diff main..pr-28 -- README.md is empty.

suggestion: split this PR. One commit holds #25, #27, a new flag, a policy file, and a test-suite rewrite. We squash-merge, so this becomes one changelog entry. Remove AGENTS.md from this PR at minimum.

praise: the ^ anchor on the Bump ignore pattern corrects a real parity bug. main ignored feat: bump x from 1 to 2. completely. Upstream and this PR both fail it. See src/constants.rs:16.

praise: the port is accurate outside the items above. 784 of 948 comparisons matched upstream byte for byte. The flag surface, the short flags, the defaults, the mutual exclusion, and the exit codes match argparse.

I could not test one item on this machine, so I ask about it inline at src/validators.rs:150 instead of reporting it.

Comment thread src/validators.rs Outdated
Comment thread src/validators.rs Outdated
Comment thread src/validators.rs Outdated
Comment thread src/validators.rs
Comment thread src/validators.rs Outdated
Comment thread AGENTS.md Outdated
Comment thread AGENTS.md Outdated
Comment thread tests/cli.rs
Comment thread tests/cli.rs Outdated
Comment thread src/console.rs
…l test suite

Implements feature parity with Python commitlint for opensource-nepal#27 and opensource-nepal#25.

- Display specific validation errors (type missing, scope empty, description
  issues, etc.) instead of just "Commit validation: failed!" (opensource-nepal#27)
- Add help = "..." strings to every clap argument (opensource-nepal#25)
- Add --max-header-length <N> CLI flag with positive integer validation
- Add Config (global LazyLock<Mutex>), Console (colored output), and
  Validators (simple + detailed regex patterns) modules
- Add AGENTS.md with architecture docs, parity checklist, and agent
  conventions
- Add 75+ integration tests and 43 unit tests covering all CLI paths,
  output flags, hash ranges, and max-header-length behavior
- Run cargo fmt on all files

Refs: opensource-nepal#27, opensource-nepal#25
- validate_description: use map_or instead of ? early return so missing
  description correctly returns DESCRIPTION_MISSING_ERROR
- validate_header_length: use chars().count() instead of len() to match
  upstream Python code-point counting (not UTF-8 bytes)
- Fix errors.is_empty() nitpick: always false after push, use return (false, errors)
- Add comments explaining ? early returns match upstream 'if group and ...' logic

Refs: review feedback on PR opensource-nepal#28
- COMMIT_TYPES: move 'bump' to last position to match upstream Python order
- Ignored messages: print success line in single-message mode (parity with
  upstream which prints 'Commit validation: successful!' for ignored commits)
- Quiet mode: suppress file and git errors when -q is set (exit silently
  instead of printing anyhow error chain)
- Add allow_negative_numbers = true so clap passes -5 to the value_parser
  instead of treating it as a flag
- Rewrite positive_usize to parse as i64 first, then convert to usize,
  matching upstream error messages for both negative and zero values
- Remove redundant clap attributes: long = 'from-hash'/'to-hash' (derived
  from field names), action = ArgAction::SetTrue (default for bools)
- Drop unused ArgAction import
- README: update about text, help columns with all flags documented
- AGENTS.md: fix test counts (81 CLI + 60 unit), remove is_orphan
  (Rust-only detail), soften parity claims, remove architecture freeze,
  remove fixed divergence opensource-nepal#5 (type order)
- Cargo.toml: bump rust-version to 1.88 (let chains require it)
- Add rejects_missing_description to linter.rs (was deleted in original PR)
- Add 16 unit tests covering all 13 error messages plus multi-error cases
- Add 16 CLI end-to-end tests asserting per-field error output including
  'Found N error(s).' for N > 1
- Add help_flag_shows_descriptions_for_all_flags verifying all help strings
- Fix max_header_length_negative_fails_clap to assert error message text
- Add header_length_counts_chars_not_bytes for non-ASCII parity
Upstream reads git output with subprocess.check_output(text=True) and
files with open(), both of which normalize \r\n and lone \r to \n.
cocox read raw bytes, so CRLF messages passed validation unchanged.

Add normalize_newlines() in utils.rs and call it from:
- get_commit_message_from_hash (git show output)
- get_commit_messages_from_hash_range (git log output)
- read_file (--file input)

CLI arguments are NOT normalized, matching upstream behavior.

Fixes known divergence #2 from AGENTS.md. All 158 tests pass.
@JustBipin
JustBipin requested a review from sugat009 September 11, 2026 10:29

@sugat009 sugat009 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

I did the review again at 1a2dcc1, against upstream commitlint 2.0.0 and main (3b0c33b).

You corrected all three blocking problems and all five non-blocking problems. I found no new
parity defect in the lint path, and no result is worse than main. Every blocking item below is
in a document. None is in the Rust code.

Blocking

issue (blocking): AGENTS.md is now two documents. main got its own from #29 while this
branch was open, and this branch adds its 155-line version below it: 261 lines, two # headings,
two sets of rules that disagree. Refer to AGENTS.md:111. I made this a suggestion last time, and
you could refuse it. The difference now is that the other file exists.

issue (blocking): The divergence list still shows a defect that this pull request corrected.
Refer to AGENTS.md:90.

issue (blocking): rust-version moves to 1.88, but CONTRIBUTING.md still tells contributors
that 1.85 is enough. Refer to Cargo.toml:9.

issue (blocking): The description is no longer correct. It says "43 unit tests" and "118 tests
pass"; the head has 60 and 158. It lists AGENTS.md as a new module, which was true when you
wrote it; that file now comes from 3b0c33b on main. It omits the three changes a user can
see: the ^ anchor, the trim operation, the newline conversion. We squash merge, so this text
becomes the changelog entry.

Non-blocking

Each row is an issue (non-blocking).

Where Problem
src/validators.rs:272 No test holds the text of the error messages. A change to any of the 13 texts keeps all 158 tests green.
src/command.rs:64 The success line for an ignored message is correct, but no test holds it. The test file still describes the old behavior, which gave no output.
src/command.rs:129 The new -q guards on --file, --hash and --from-hash have no test. If you remove them, the exit code does not change.
src/command.rs:25 The trim operation and the newline conversion each correct a real divergence. Neither has a test.
src/command.rs:106 -v gives four of the ten upstream verbose calls in the lint path, and none of the four in the git helpers. In range mode it gives no trace for each commit.
src/config.rs:39 ConfigGuard does not isolate the tests. A second test that uses it makes an unrelated test fail.
src/validators.rs:28 The header-length check counts one character too few for a CRLF header. It passes at the limit where upstream fails.
src/cli.rs:65 cocox refuses --max-header-length " 5 ". Upstream accepts it.

Other

suggestion (src/constants.rs:16): the regression test for the ^ anchor is still not there.
I asked for it last time.

nitpick (tests/cli.rs:783): max_header_length_string_fails_clap examines only the exit
code.

Three items have no line in this diff:

  • clippy.toml:24 points at item 3 of the divergence list. On main item 3 was the splitlines
    entry. Here that entry is item 2, and item 3 is the \x1c entry. This pull request does not
    change clippy.toml, so no test finds the error.
  • My comment at AGENTS.md:109 had two requests, and your answer says "Fixed" for both. The
    second one, cargo fmt --check and cargo clippy -D warnings in .github/workflows/ci.yml, is
    not done. It is correct to keep that work out of a feature pull request. Please say so instead.
  • help_flag_succeeds (tests/cli.rs:558) examines a substring that the old about line also
    contains, so the test stays green if you restore that line.
    message_and_from_hash_together_fail (tests/cli.rs:500) examines contains(""), true for all
    strings. Both come from main. But AGENTS.md names the second one as an anti-pattern, and
    this pull request rewrites the file around it.

I found three pre-existing defects while I tested this branch. They are not your work, and they do
not block this pull request. I will open an issue for each one.

tests/message_coverage.rs cannot catch the gap at src/validators.rs:272. It counts a message
as covered if a test file contains its name, and tests/cli.rs imports every name at the top.
That ratchet is my test from #29, and I will correct it.

praise:

  • Both validator corrections are right, and right for inputs near the ones in the report. :
    gives both errors again, which agrees with main. Each ? early return has the comment that I
    asked for.
  • The CRLF correction is better than the report. normalize_newlines is in all three readers.
    It operates on a lone \r as well. It is before the NUL split in the range helper. It does not
    change the command-line argument, which is what upstream does.
  • -q stops output on all error paths. --max-header-length gives both argparse messages, and
    -5 reaches the parser. The type sequence agrees with upstream. README.md agrees with
    --help, byte for byte. Your counts in the replies are exact too.
  • Your rust-version answer was right. I confirmed it on 1.87 and 1.85.

Comment thread AGENTS.md Outdated
Comment thread AGENTS.md
Comment thread Cargo.toml Outdated
Comment thread src/validators.rs
Comment thread src/command.rs Outdated
Comment thread src/config.rs Outdated
Comment thread src/cli.rs
Comment thread src/validators.rs Outdated
Comment thread src/constants.rs
Comment thread tests/cli.rs
- Add LintOptions struct (skip_detail, hide_input, strip_comments, max_header_length)
- lint_commit_message and run_validators take &LintOptions instead of reading global config
- Remove ConfigGuard, set_config, config(), update_config
- command.rs builds LintOptions from Cli and passes it explicitly
- OutputConfig remains global for console output only

Additional review fixes:
- Remove AGENTS.md from PR (arrives from main via opensource-nepal#29)
- Fix CONTRIBUTING.md rust-version to 1.88
- Fix clippy.toml stale item reference
- Remove COMMIT_HEADER_MAX_LENGTH assert from messages.rs
- Add 14 CLI tests pinning exact error message text
- Add ignored-message success line tests
- Add -q guards tests for --file, --hash, --from-hash
- Add CRLF/CR normalization tests
- Add CRLF header-length test
- Add ^ anchor regression test
- Fix max_header_length_string_fails_clap to assert message text
- Rewrite PR description with accurate test counts and behavior changes
@JustBipin

Copy link
Copy Markdown
Member Author

Re-reviewed feedback addressed. Changes pushed as 849e8e5.

What changed

Blocking:

  • AGENTS.md restored to main's version — removed from this PR entirely
  • CONTRIBUTING.md updated: Rust 1.88
  • PR description rewritten with accurate counts and behavior changes
  • Divergence list is now main's (stale items removed with AGENTS.md)

Non-blocking:

  • ConfigGuard and global config removed. lint_commit_message now takes &LintOptions { skip_detail, hide_input, strip_comments, max_header_length }. command.rs builds it from Cli and passes it down. OutputConfig stays global for console only.
  • 14 new CLI tests pin the exact text of all 13 error messages plus multi-error output
  • Ignored-message success line tested (ignored_message_prints_success_line)
  • -q guards tested for --file, --hash, --from-hash
  • CRLF/CR normalization tested (file_with_crlf_normalized, file_with_lone_cr_normalized)
  • CRLF header-length tested (crlf_header_length_counted_correctly)
  • ^ anchor regression tested (bump_lookalike_is_not_ignored)
  • max_header_length_string_fails_clap now asserts "is not a valid integer"
  • COMMIT_HEADER_MAX_LENGTH assert removed from messages.rs
  • clippy.toml stale item reference fixed

Verified: 184 tests pass, cargo fmt --check clean, cargo clippy -D warnings zero warnings.

Pre-existing bugs (range hash strategy, --to-hash conflicts_with_all, uncolored --hash error) are tracked separately as noted in the review.

…n parity test

- Add verbose lines to linter, validators, and git helpers matching upstream
  commitlint output (HeaderLengthValidator, PatternValidator, git commands)
- Thread &OutputConfig through lint_commit_message_with_errors and
  run_validators so verbose lines can be emitted from the lint path
- Fix failure text to match upstream format (': validation failed')
- upstream_parity.rs now asserts error strings, not just pass/fail
- Remove stale divergence #4 from AGENTS.md (bump already listed last)
- Fix upstream_parity.rs stale doc comment
- Update all unit and integration tests for new function signatures
@JustBipin

Copy link
Copy Markdown
Member Author

Addressed everything from the re-review at 1a2dcc1. Pushed cbe7e10.

What changed since 1a2dcc1:

  • Verbose output now matches upstream across lint path, git helpers, and range mode. , , all emit verbose lines. Git hash/range operations print the command and output.
  • now asserts error strings, not just pass/fail.
  • Removed stale divergence Feat/ignore auto commits #4 from AGENTS.md (bump was already listed last).
  • failure text matches upstream format (': validation failed').

All 187 tests pass, clippy clean, fmt clean.

@JustBipin
JustBipin requested a review from sugat009 September 13, 2026 17:45

@sugat009 sugat009 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

This pull request closes #27 and #25. The LintOptions refactor and the verbose output are in
neither issue. I asked for both, in the first review and in the second, so that is my responsibility. The
verbose commit is also what removed the let chain and made the 1.88 floor wrong.

The refactor changed no behavior in any comparison I ran, and nothing is worse than main.

Four comments block the merge, each a small edit. One more asks you to take AGENTS.md out of the
diff. I will open an issue for everything else I found: the verbose lines that still differ, the
CRLF --file tests, a stricter parity assertion, three weak assertions, and four gaps main has
too. None of that is work for you here.

Comment thread Cargo.toml Outdated
Comment thread CONTRIBUTING.md Outdated
Comment thread tests/upstream_parity.rs Outdated
Comment thread src/command.rs Outdated
Comment thread AGENTS.md Outdated
…utput, correct stale docs

- Restore AGENTS.md from main (remove from diff)
- Lower rust-version from 1.88 to 1.85, remove stale let-chain comment
- Update CONTRIBUTING.md minimum Rust version to 1.85
- Fix stale doc comment in upstream_parity.rs
- Print VALIDATION_FAILED on empty/whitespace/comment-only commit messages
- Add CLI test for comment-only file input
@JustBipin

Copy link
Copy Markdown
Member Author

Round 3 review addressed (at cbe7e10):

  • Restored AGENTS.md from main — removed from diff
  • Lowered rust-version from 1.88 to 1.85, removed stale let-chain comment in Cargo.toml
  • Updated CONTRIBUTING.md to say Rust 1.85
  • Fixed stale doc comment in upstream_parity.rs — now says both success and errors are asserted
  • LintOutcome::Empty now prints VALIDATION_FAILED before exiting (both match arms). Added a CLI test for comment-only files.

@sugat009 sugat009 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Everything from the last review is closed, except the description of this pull request. I ran each item to check it. The code is done.

issue (blocking): the description is not correct. Line 22 says 184 tests and 107 CLI tests, and
line 29 says cargo test # 184 pass. This head has 188 and 111. The description also does not
mention -v anywhere, and the verbose output is a change that a user sees. We squash merge, so
this text becomes the commit message.

The AGENTS.md sentence in the description is true now, and the code part of that item is closed.

I will open five issues for the items we agreed to defer. I will also update the divergence list in
AGENTS.md when this merges, together with the module comment in tests/crlf_parity.rs.

Comment thread src/linter.rs
Comment thread tests/cli.rs

@sugat009 sugat009 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Approved at 9d3a6721.

Two sentences in the description are still wrong, and I will correct them in the squash message.
cocox prints verbose output to stdout, not to stderr, and it does not yet match upstream. #31 has
the five gaps.

The AGENTS.md divergence list and the module comment in tests/crlf_parity.rs are mine to update
at merge.

Thank you for the work. The refactor and the verbose output were my requests, not part of #27 or
#25, and you did both well.

@sugat009
sugat009 merged commit 5789c80 into opensource-nepal:main Sep 18, 2026
1 check passed
@github-actions github-actions Bot mentioned this pull request Sep 18, 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.

Display specific error type during conventional commit validation mismatched [feat]: Detailed Help flag for cocox

2 participants