Skip to content

fix: match requirement identity case-insensitively - #11

Open
N4M3Z wants to merge 1 commit into
mainfrom
change/archive-requirement-identity
Open

fix: match requirement identity case-insensitively#11
N4M3Z wants to merge 1 commit into
mainfrom
change/archive-requirement-identity

Conversation

@N4M3Z

@N4M3Z N4M3Z commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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

  • Adds requirement_identity in model.rs that normalizes names by collapsing whitespace and lowercasing, then uses it in CanonicalSpec.requirement_index and CanonicalSpec.requirement_matches so lookups and equality checks ignore case and whitespace differences.
  • Adjusts apply_renamed in 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.
  • Adds tests in apply/tests.rs covering rejection of case-variant duplicate adds and no-op behavior for identical-up-to-case adds.
  • Behavioral Change: requirement_index and requirement_matches now 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.

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>
@N4M3Z N4M3Z added size:small auto: changed-lines bucket spec:none Owner waiver: no specification change needed, reason in body skip:cursor Owner override: the cursor lane never runs review:runeseer Summons the adjudicating correctness lane labels Aug 27, 2026
@github-actions github-actions Bot added size:med auto: changed-lines bucket and removed size:small auto: changed-lines bucket labels Aug 27, 2026
@runewright runewright Bot added the review:macroscope Summons the macroscope lane label Aug 27, 2026
Comment thread src/spec/model.rs
) -> bool {
self.requirement(element_index).is_some_and(|existing| {
existing.name == requirement.name
requirement_identity(&existing.name) == requirement_identity(&requirement.name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚫 [runeseer] reported by reviewdog 🐶
Highrequirement_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:141 content equality includes the case-bearing header line, so the loosened name check is a no-op on real data.
  • src/spec/apply/tests.rs:43 the regression test overwrites both contents with "shared content", a value the parser cannot produce, so it passes without exercising the path.

Comment thread src/spec/model.rs
}

pub(super) fn requirement_index(&self, name: &str) -> Option<usize> {
let identity = requirement_identity(name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚫 [runeseer] reported by reviewdog 🐶
Highrequirement_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:123 position() stops at the first identity match and no caller checks for a second.
  • src/spec/apply.rs:111 remove_requirement(element_index) deletes that wrong element with no name reconfirmation.

@macroscopeapp

macroscopeapp Bot commented Aug 27, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved at 927ec97

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:

  • Macroscope's correctness review did not run, so approvability was decided on eligibility alone.

You can add or adjust custom eligibility rules. Learn more.

@runeseer

runeseer Bot commented Aug 27, 2026

Copy link
Copy Markdown

Runeseer review — 2 open findings

Risk Finding Location
High First-match lookup mutates wrong element on already-duplicated specs src/spec/model.rs:122
High Case-variant identical add still rejected; content compare includes header src/spec/model.rs:139

Request changes. No new commits landed since the last review, so both previously raised issues in src/spec/model.rs are still open.

  • The case-insensitive name match is undone by the byte-for-byte content compare, because content carries the ### Requirement: header line.
  • Requirement lookup takes the first case-insensitive hit, so remove, modify, and rename can act on the wrong entry in a spec that already holds both variants.
  • Macroscope posted an approvability note only; its correctness review did not run.

2 open · Reviewed 927ec97b · review run · $0.42 · 0m57s · 14 turns

@runeseer

runeseer Bot commented Aug 27, 2026

Copy link
Copy Markdown

@N4M3Z, the following findings block this head:

  • src/spec/model.rs:139: Case-variant identical add still rejected; content compare includes header
  • src/spec/model.rs:122: First-match lookup mutates wrong element on already-duplicated specs

Choose one action:

  • Fix all findings. Then apply review:runeseer to review the new head.
  • Apply ignore:runeseer to accept the complete current verdict.

@github-actions github-actions Bot removed the review:runeseer Summons the adjudicating correctness lane label Aug 27, 2026
@runewright runewright Bot added stage:macroscope Stage settled clean; remove to re-run it review:runeseer Summons the adjudicating correctness lane labels Aug 27, 2026
@github-actions github-actions Bot removed review:macroscope Summons the macroscope lane review:runeseer Summons the adjudicating correctness lane labels Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:med auto: changed-lines bucket skip:cursor Owner override: the cursor lane never runs spec:none Owner waiver: no specification change needed, reason in body stage:macroscope Stage settled clean; remove to re-run it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant