Skip to content

Fix: five hands-on findings from 1.5.0 release testing#418

Merged
0xLeif merged 1 commit into
mainfrom
fix/hands-on-findings
Jun 11, 2026
Merged

Fix: five hands-on findings from 1.5.0 release testing#418
0xLeif merged 1 commit into
mainfrom
fix/hands-on-findings

Conversation

@0xLeif

@0xLeif 0xLeif commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • fledge work commit no longer double-prefixes the conventional type: -m messages that already start with type: / type(scope): (case-insensitive, including breaking ! variants and CorvidLabs-style Add:/Update:/Remove:) are committed verbatim instead of becoming feat: feat: ...
  • Changelog prefix matching (fledge changelog and the release changelog writer) is now case-insensitive and understands the CorvidLabs commit style: Add: β†’ Features, Update: β†’ Changes, Remove: β†’ Removals, Fix:/Refactor:/etc. map to their lowercase categories instead of landing in "Other"; breaking ! markers (feat!:, fix(core)!:) classify by base type, matching changelog spec invariant 10
  • fledge run --init generic starter template no longer emits an unclosed quote in the commented # lint = "echo 'add your linter'" example β€” uncommenting it previously made fledge.toml unparseable
  • fledge lanes init hint now points at fledge lanes list (previously fledge lane, which is the alias without a subcommand and exits with a usage error)
  • fledge run --help (plus README and run spec) pass-through example now uses fledge run test -- --release, which is valid when appended to cargo test; --nocapture is only accepted after cargo's own -- separator, so the documented example failed against fledge's own rust-cli template. The template keeps plain cargo test since a $@ placement would break the Windows append path
  • Specs kept in sync: work v15, changelog v5, release v6, run v6, lanes v25, main v11; CHANGELOG.md updated under Unreleased

Test Plan

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo test β€” 867 unit + 177 integration tests pass (new tests cover verbatim/case-insensitive commit prefixes, CorvidLabs-style and breaking-marker classification in both classifiers, uncommented starter-template TOML validity, and the lanes init hint)
  • cargo run -- spec check β€” 30 specs, 0 errors, 0 warnings
  • Manually reproduced and re-verified all five findings against the rebuilt binary

πŸ€– Generated with Claude Code

- work commit: -m messages already carrying a conventional-commit prefix
  (type:, type(scope):, breaking ! variants; case-insensitive) are committed
  verbatim instead of double-prefixing (feat: feat: ...)
- changelog/release: prefix classification is now case-insensitive and maps
  the CorvidLabs commit style (Add: -> Features, Update: -> Changes,
  Remove: -> Removals, Fix:/Refactor:/... -> their lowercase categories)
  instead of dumping those commits in "Other"; breaking ! markers classify
  by base type, matching the documented invariant
- run --init: generic starter template no longer emits an unclosed quote in
  the commented `# lint = "echo 'add your linter'"` example
- lanes init: follow-up hint points at `fledge lanes list` instead of
  `fledge lane`, which exits with a usage error
- run --help / README / spec: pass-through example uses `-- --release`,
  which is valid when appended to `cargo test` (`--nocapture` is only
  accepted after cargo's own `--` separator)

Specs bumped (work v15, changelog v5, release v6, run v6, lanes v25,
main v11) and CHANGELOG.md updated under Unreleased.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@0xLeif 0xLeif requested a review from a team as a code owner June 11, 2026 14:02
@0xLeif 0xLeif requested review from 0xGaspar, Kyntrin and tofu-ux June 11, 2026 14:02

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request implements case-insensitive conventional commit prefix matching, supports CorvidLabs-style prefixes, and handles breaking change markers across fledge work commit, fledge changelog, and fledge release. It also fixes an unclosed quote in the run --init template, corrects the CLI hint in lanes init, and updates cargo pass-through examples. Feedback focuses on improving the robustness of conventional commit parsing, specifically by aligning the parsing logic between classify_for_changelog and classify_commit, ensuring has_conventional_prefix does not match malformed headers with multiple parentheses, and verifying that scopes are properly closed in strip_conventional_prefix.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/release/changelog.rs
Comment on lines 112 to 124
for (prefix, label) in &prefixes {
if msg.starts_with(prefix) && msg[prefix.len()..].starts_with([':', '(']) {
return label;
let Some(head) = msg.get(..prefix.len()) else {
continue;
};
if head.eq_ignore_ascii_case(prefix) {
// Optional breaking-change marker: `type!:`.
let rest = &msg[prefix.len()..];
let rest = rest.strip_prefix('!').unwrap_or(rest);
if rest.starts_with([':', '(']) {
return label;
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There is a significant discrepancy between classify_for_changelog in src/release/changelog.rs and classify_commit in src/changelog.rs. The current implementation of classify_for_changelog uses a loose check rest.starts_with([':', '(']) which incorrectly classifies invalid scoped formats (e.g., feat(scope)invalid: message) as valid conventional commits. In contrast, classify_commit robustly parses the scope and ensures that the closing parenthesis is followed by : or !:. We should align classify_for_changelog with the robust parsing logic of classify_commit to prevent inconsistent changelog generation between fledge changelog and fledge release.

    for (prefix, label) in &prefixes {
        let Some(head) = msg.get(..prefix.len()) else {
            continue;
        };
        if head.eq_ignore_ascii_case(prefix) {
            let rest = &msg[prefix.len()...];
            // Optional breaking-change marker: `type!:`.
            let bare = rest.strip_prefix('!').unwrap_or(rest);
            if bare.starts_with(':') {
                return label;
            }
            // Scoped form: `type(scope):` or `type(scope)!:`.
            if let Some(scoped) = rest.strip_prefix('(') {
                if let Some(close) = scoped.find(')') {
                    let tail = &scoped[close + 1..];
                    let tail = tail.strip_prefix('!').unwrap_or(tail);
                    if tail.starts_with(':') {
                        return label;
                    }
                }
            }
        }
    }

Comment thread src/work.rs
Comment on lines +629 to +643
fn has_conventional_prefix(message: &str) -> bool {
let Some(colon) = message.find(':') else {
return false;
};
let head = &message[..colon];
let head = head.strip_suffix('!').unwrap_or(head);
let base = match head.find('(') {
Some(open) if head.ends_with(')') => &head[..open],
Some(_) => return false,
None => head,
};
CONVENTIONAL_COMMIT_TYPES
.iter()
.any(|known| base.eq_ignore_ascii_case(known))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The has_conventional_prefix function has a potential false positive where it can match malformed or complex headers like feat(scope1) and fix(scope2): message or feat(scope)extra): message as valid conventional prefixes. This happens because it only checks if head ends with ) and extracts the substring before the first (. We can make this check more robust by ensuring that there is exactly one ) in head and it is located at the very end of the string.

Suggested change
fn has_conventional_prefix(message: &str) -> bool {
let Some(colon) = message.find(':') else {
return false;
};
let head = &message[..colon];
let head = head.strip_suffix('!').unwrap_or(head);
let base = match head.find('(') {
Some(open) if head.ends_with(')') => &head[..open],
Some(_) => return false,
None => head,
};
CONVENTIONAL_COMMIT_TYPES
.iter()
.any(|known| base.eq_ignore_ascii_case(known))
}
fn has_conventional_prefix(message: &str) -> bool {
let Some(colon) = message.find(':') else {
return false;
};
let head = &message[..colon];
let head = head.strip_suffix('!').unwrap_or(head);
let base = match head.find('(') {
Some(open) if head.ends_with(')') && head.find(')') == Some(head.len() - 1) => &head[..open],
Some(_) => return false,
None => head,
};
CONVENTIONAL_COMMIT_TYPES
.iter()
.any(|known| base.eq_ignore_ascii_case(known))
}

Comment thread src/release/changelog.rs
Comment on lines 130 to 146
if let Some(colon_pos) = msg.find(':') {
let prefix = &msg[..colon_pos];
let after = msg[colon_pos + 1..].trim_start();
let prefix = prefix.strip_suffix('!').unwrap_or(prefix);
let base = if let Some(paren) = prefix.find('(') {
&prefix[..paren]
} else {
prefix
};
let known = [
"feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore",
"add", "update", "remove",
];
if known.contains(&base) {
if known.iter().any(|k| base.eq_ignore_ascii_case(k)) {
return after;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In strip_conventional_prefix, when extracting the base type from a scoped prefix, the code does not verify if the scope is properly closed (i.e., if the prefix ends with )). This can lead to stripping prefixes from malformed or non-conventional commit messages (e.g., feat(scope)invalid: message would be stripped to message). We should ensure that if a ( is found, the prefix must end with ) for it to be considered a valid conventional prefix.

    if let Some(colon_pos) = msg.find(':') {
        let prefix = &msg[..colon_pos];
        let after = msg[colon_pos + 1..].trim_start();
        let prefix = prefix.strip_suffix('!').unwrap_or(prefix);
        let base = if let Some(paren) = prefix.find('(') {
            if prefix.ends_with(')') {
                &prefix[..paren]
            } else {
                return msg;
            }
        } else {
            prefix
        };
        let known = [
            "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore",
            "add", "update", "remove",
        ];
        if known.iter().any(|k| base.eq_ignore_ascii_case(k)) {
            return after;
        }
    }

@github-actions github-actions Bot 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.

βœ… Corvin says...

      _
    <(^\  .oO(Caw! ^v^)
     |/(\
      \(\\
      " "\\

"Caw! Your code sparkles like a dropped french fry."

CI Summary

Check Status
Dependency Audit βœ… Passed
Integration (3 OS) βœ… Passed
Lint (fmt + clippy) βœ… Passed
Spec Validation βœ… Passed
Tests (3 OS) βœ… Passed

Powered by corvid-pet

@0xLeif 0xLeif merged commit 1c43cb6 into main Jun 11, 2026
13 checks passed
@0xLeif 0xLeif deleted the fix/hands-on-findings branch June 11, 2026 15:00
0xLeif added a commit that referenced this pull request Jun 11, 2026
fledge release generated the v1.5.1 section from commit subjects but left
the pre-written [Unreleased] Fixes block stranded below it. Merge those
detail bullets under the #418 entry and drop the stale section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant