Skip to content

fix: Exact-ID Match Precedence in RequireSingle#22

Merged
sahil-noon merged 4 commits into
mainfrom
260615-m2qx-exact-id-match-precedence
Jun 15, 2026
Merged

fix: Exact-ID Match Precedence in RequireSingle#22
sahil-noon merged 4 commits into
mainfrom
260615-m2qx-exact-id-match-precedence

Conversation

@sahil-noon

Copy link
Copy Markdown
Collaborator

Meta

ID Type Confidence Plan Review
m2qx fix 4.4/5.0 2/2 tasks, 11/11 acceptance ✓ ✓ 1 cycle

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

Impact: +48/−0 code (excluding fab/, docs/) · +347/−3 total

Summary

The shared idea resolver RequireSingle aborted with "Multiple matches" whenever a query produced more than one hit, with no notion of precedence between match kinds. Passing an exact 4-char ID that also happened to appear as a substring inside another idea's text (e.g. a cross-reference [jznd]) matched both ideas and failed — even though the error told the user to "use the exact ID", which is exactly what they did. The only workaround was hand-editing fab/backlog.md, bypassing the tool entirely. This fix adds exact-ID precedence at the single resolver seam shared by edit/rm/show/done/reopen.

Changes

  • Resolver: RequireSingle exact-ID precedence (src/internal/idea/idea.go) — when a query yields multiple matches, an exact case-insensitive ID match (strings.EqualFold) among the candidates wins over incidental substring text matches. Zero or >1 exact-ID hits fall through to the existing ambiguity error unchanged.
  • Explicitly NOT changedMatch and FindAll keep pure substring semantics for list/search; no CLI/cmd changes, no new flags, no new dependencies.
  • Edge cases preservedexactCount > 1 falls through to the existing "Multiple matches" error (no silent pick); the scan runs over the already-filtered match set, so filter semantics are preserved.
  • Regression test (src/internal/idea/idea_test.go) — adds table-driven TestRequireSingle_ExactIDBeatsSubstring covering the exact-ID-beats-substring case for all five resolver-backed commands.

sahil87 added 2 commits June 15, 2026 10:15
When a query produces multiple matches, an exact case-insensitive ID
match (strings.EqualFold) among the candidates now wins over incidental
substring text matches, instead of aborting with "Multiple matches".
Zero or >1 exact-ID hits fall through to the existing ambiguity error
unchanged. Match/FindAll keep pure substring semantics for list/search.
Fixes all five commands sharing the resolver: edit/rm/show/done/reopen.

Adds regression test TestRequireSingle_ExactIDBeatsSubstring and updates
the cli memory docs.

Copilot AI 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.

Pull request overview

This PR fixes an ambiguity bug in the shared idea resolver (RequireSingle) by adding exact-ID precedence so that a query matching exactly one idea ID (case-insensitively) wins over incidental substring matches in other ideas’ text. This improves behavior across resolver-backed commands (edit/rm/show/done/reopen) without changing Match/FindAll search semantics.

Changes:

  • Add exact-ID tiebreaking in RequireSingle when len(matches) > 1.
  • Add a regression unit test covering the exact-ID-beats-substring scenario.
  • Update CLI memory docs to document the split between substring search (Match/FindAll) and resolver precedence (RequireSingle).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/internal/idea/idea.go Adds exact-ID precedence in RequireSingle using strings.EqualFold before emitting ambiguity errors.
src/internal/idea/idea_test.go Adds regression test for exact-ID precedence behavior.
fab/changes/260615-m2qx-exact-id-match-precedence/plan.md Captures the planned requirements/tasks/acceptance for the change.
fab/changes/260615-m2qx-exact-id-match-precedence/intake.md Documents the bug report, rationale, and agreed approach.
fab/changes/260615-m2qx-exact-id-match-precedence/.status.yaml Tracks pipeline/stage status for the change.
fab/changes/260615-m2qx-exact-id-match-precedence/.history.jsonl Logs stage transitions/commands for the change.
docs/memory/cli/structure.md Documents the query-resolution contract: substring matching vs exact-ID precedence in RequireSingle.
docs/memory/cli/index.md Updates the memory index description for structure.md to include query resolution.
docs/memory/cli/edit.md Notes that idea edit benefits from the RequireSingle exact-ID tiebreaker.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/memory/cli/edit.md Outdated
| `idea edit <query>` | Opens the resolved editor on a temp file containing the idea's **decoded** text (real newlines, real backslashes — `Idea.Text`); on clean exit the buffer is post-processed and persisted via the existing `idea.Edit`. |

**Resolve before launch.** The one-arg form resolves the match via the existing `idea.Show` (LoadFile + `RequireSingle`, `FilterAll`) **before** launching the editor — an ambiguous or unmatched query is refused with the match list and the editor never opens. This reuses `idea.Edit`'s exact match semantics (substring, case-insensitive, ID or text) with zero new resolver code.
**Resolve before launch.** The one-arg form resolves the match via the existing `idea.Show` (LoadFile + `RequireSingle`, `FilterAll`) **before** launching the editor — an ambiguous or unmatched query is refused with the match list and the editor never opens. This reuses `idea.Edit`'s match semantics (substring, case-insensitive, ID or text) with zero new resolver code. Since `260615-m2qx-exact-id-match-precedence`, `RequireSingle` also applies an **exact-ID tiebreaker**: when a query produces multiple matches but exactly one of them is an exact (case-insensitive) ID match, that idea wins over incidental substring text matches instead of aborting — so `idea edit jznd` resolves cleanly even when `jznd` also appears as cross-reference text inside another idea. See `structure.md` § Query resolution for the full contract; `Match`/`FindAll` keep pure substring semantics for `list`/search.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed — rephrased to clarify that no edit-specific resolver code was added (resolution flows through the shared RequireSingle), while explicitly noting the shared resolver behavior did change with the exact-ID tiebreaker. (38788a6)

Comment on lines +233 to +244
{
name: "exact id wins over substring in another idea's text",
query: "jznd",
ideas: []Idea{
{ID: "jznd", Text: "the idea to edit"},
{ID: "qg64", Text: "see related [jznd] for context"},
},
wantID: "jznd",
wantIdx: 0,
},
}
for _, tt := range tests {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed — added a second table entry with an uppercase query (JZND) against a lowercase id (jznd) plus a [JZND] substring in another idea, so the case-insensitive (EqualFold) tiebreaker is now covered. (38788a6)

@sahil-noon sahil-noon marked this pull request as ready for review June 15, 2026 05:59
@sahil-noon sahil-noon merged commit ea2b97a into main Jun 15, 2026
2 checks passed
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.

3 participants