fix: match requirement identity case-insensitively - #11
Conversation
Archive merges compared requirement names byte-for-byte, so a Title Case delta appended a duplicate of its sentence-case canonical twin instead of matching it. Lookups now normalize case and whitespace, an identical case-variant add is idempotent, and a conflicting one fails closed. Equality checks that decide whether an edit applies keep exact comparison, so a case-only rename still lands. Two regression tests pin both paths. Co-Authored-By: Martin Zeman <N4M3Z@users.noreply.github.com>
| ) -> bool { | ||
| self.requirement(element_index).is_some_and(|existing| { | ||
| existing.name == requirement.name | ||
| requirement_identity(&existing.name) == requirement_identity(&requirement.name) |
There was a problem hiding this comment.
🚫 [runeseer] reported by reviewdog 🐶
High — requirement_matches compares names case-insensitively but still compares content byte-for-byte, and parsed content begins with ### Requirement: <name>, so a genuinely identical case-variant ADD never matches and apply_added returns "cannot add existing requirement" instead of skipping.
src/spec/model.rs:141content equality includes the case-bearing header line, so the loosened name check is a no-op on real data.src/spec/apply/tests.rs:43the regression test overwrites both contents with"shared content", a value the parser cannot produce, so it passes without exercising the path.
| } | ||
|
|
||
| pub(super) fn requirement_index(&self, name: &str) -> Option<usize> { | ||
| let identity = requirement_identity(name); |
There was a problem hiding this comment.
🚫 [runeseer] reported by reviewdog 🐶
High — requirement_index returns the first case-insensitive match, so on a spec that already holds both case variants — the duplicate state this commit exists to stop — a REMOVED, MODIFIED, or RENAMED operation naming the Title Case requirement silently mutates the sentence-case one instead.
src/spec/model.rs:123position()stops at the first identity match and no caller checks for a second.src/spec/apply.rs:111remove_requirement(element_index)deletes that wrong element with no name reconfirmation.
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused archive-merge bug fix that normalizes requirement-name matching and adds regression coverage for duplicate and conflicting case variants. Its runtime impact is confined to the existing requirement application path, with no schema, deployment, or sensitive-domain changes. Notes:
You can add or adjust custom eligibility rules. Learn more. |
Runeseer review — 2 open findings
Request changes. No new commits landed since the last review, so both previously raised issues in
2 open · Reviewed |
|
@N4M3Z, the following findings block this head:
Choose one action:
|
Archive merges compared requirement names byte-for-byte, so a Title Case delta appended a duplicate of its sentence-case canonical twin instead of matching it. Lookups now normalize case and whitespace, an identical case-variant add is idempotent, and a conflicting one fails closed. Equality checks that decide whether an edit applies keep exact comparison, so a case-only rename still lands. Two regression tests pin both paths.
spec:none: the change repairs merge behavior and adds no new contract.
Note
Make requirement name matching case- and whitespace-insensitive
requirement_identityin model.rs that normalizes names by collapsing whitespace and lowercasing, then uses it inCanonicalSpec.requirement_indexandCanonicalSpec.requirement_matchesso lookups and equality checks ignore case and whitespace differences.apply_renamedin apply.rs to allow a rename when the target name resolves to the same requirement index, only erroring when it resolves to a different requirement.requirement_indexandrequirement_matchesnow treat names that differ only by case or whitespace as equal; callers relying on exact-string matching for requirement names will see different results.Macroscope summarized 927ec97.