Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/memory/cli/edit.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ description: "`idea edit` two-form contract: inline replacement (two-arg form) v
| `idea edit <query> "text"` | Inline replacement — the quick one-liner and scripting path. Never launches an editor (tripwire-tested) and is byte-for-byte the pre-change behavior: same validation, same output. |
| `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) — no edit-specific resolver code was added; resolution flows entirely through the shared `RequireSingle`. The shared resolver behavior did change, though: since `260615-m2qx-exact-id-match-precedence`, `RequireSingle` 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.

## Editor resolution chain

Expand Down
2 changes: 1 addition & 1 deletion docs/memory/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ description: "CLI source structure (cmd/idea + internal/idea + version wiring),
| [edit](edit.md) | `idea edit` two-form contract: inline replacement (two-arg form) vs. $EDITOR round-trip (`edit <query>`) — editor resolution chain, temp-file mechanics, edge/exit semantics, and the fake-editor test seam | 2026-06-12 |
| [list](list.md) | `idea list`/`ls` rendering contract: TTY-aware rune-safe text truncation, the `--full` flag, the optional `[id...]` positional filter, ANSI color (NO_COLOR-gated), and the pipe contract that keeps piped output canonical | 2026-06-13 |
| [prune](prune.md) | Bulk-remove subcommand (`idea prune`): the TTY × `--force` decision matrix (pipe dry-run vs. interactive `[y/N]` confirm), the leading stderr count header, TTY-aware truncation/color/`--full` listing, stdout/stderr channel split, exit codes, the deliberate non-archival design, and the `removeIdeaAt` seam shared with `Rm` | 2026-06-13 |
| [structure](structure.md) | Source tree layout (cmd/idea + internal/idea), root command factory, backlog path resolution precedence (--system / --main / --file, the XDG system backlog ($XDG_CONFIG_HOME/idea/backlog.md, else ~/.config/idea/backlog.md), and the out-of-git graceful fallback), command aliases vs. the bare-text shorthand, backlog line lifecycle (lenient read / canonical write incl. the escaped-text convention for multiline ideas and the explicit `idea fmt` canonicalizer with bare-checkbox adoption), the TTY/width/color/truncation seam (internal/idea/term.go) + shared printIdeaLines render path, the golang.org/x/term direct dependency, help-dump contract, and version stamping | 2026-06-13 |
| [structure](structure.md) | Source tree layout (cmd/idea + internal/idea), root command factory, backlog path resolution precedence (--system / --main / --file, the XDG system backlog ($XDG_CONFIG_HOME/idea/backlog.md, else ~/.config/idea/backlog.md), and the out-of-git graceful fallback), command aliases vs. the bare-text shorthand, backlog line lifecycle (lenient read / canonical write incl. the escaped-text convention for multiline ideas and the explicit `idea fmt` canonicalizer with bare-checkbox adoption), the query-resolution layer (`Match`/`FindAll` pure substring vs. `RequireSingle`'s exact-ID precedence over incidental substring matches), the TTY/width/color/truncation seam (internal/idea/term.go) + shared printIdeaLines render path, the golang.org/x/term direct dependency, help-dump contract, and version stamping | 2026-06-13 |
| [update](update.md) | Self-update subcommand (`idea update`): Homebrew-backed upgrade flow, non-brew install fallback hint, and the `--skip-brew-update` flag | 2026-06-10 |
12 changes: 11 additions & 1 deletion docs/memory/cli/structure.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: "Source tree layout (cmd/idea + internal/idea), root command factory, backlog path resolution precedence (--system / --main / --file, the XDG system backlog ($XDG_CONFIG_HOME/idea/backlog.md, else ~/.config/idea/backlog.md), and the out-of-git graceful fallback), command aliases vs. the bare-text shorthand, backlog line lifecycle (lenient read / canonical write incl. the escaped-text convention for multiline ideas and the explicit `idea fmt` canonicalizer with bare-checkbox adoption), the TTY/width/color/truncation seam (internal/idea/term.go) + shared printIdeaLines render path, the golang.org/x/term direct dependency, help-dump contract, and version stamping"
description: "Source tree layout (cmd/idea + internal/idea), root command factory, backlog path resolution precedence (--system / --main / --file, the XDG system backlog ($XDG_CONFIG_HOME/idea/backlog.md, else ~/.config/idea/backlog.md), and the out-of-git graceful fallback), command aliases vs. the bare-text shorthand, backlog line lifecycle (lenient read / canonical write incl. the escaped-text convention for multiline ideas and the explicit `idea fmt` canonicalizer with bare-checkbox adoption), the query-resolution layer (`Match`/`FindAll` pure substring vs. `RequireSingle`'s exact-ID precedence over incidental substring matches), the TTY/width/color/truncation seam (internal/idea/term.go) + shared printIdeaLines render path, the golang.org/x/term direct dependency, help-dump contract, and version stamping"
---

# CLI Source Structure
Expand Down Expand Up @@ -193,6 +193,16 @@ Output is always canonical: `- ` bullet, no leading whitespace, date present, si

The behavior contract is documented for external consumers in `../../specs/backlog-format.md` and `../../specs/overview.md`.

### Query resolution (`Match` / `FindAll` / `RequireSingle`)

`internal/idea/idea.go` owns the query-matching layer shared by every command that takes a `<query>` (`show`/`done`/`reopen`/`edit`/`rm`). Two distinct contracts live here, deliberately split:

- **`Match` / `FindAll` — pure substring semantics.** `Match(query, idea)` is true when `query` is a case-insensitive substring of *either* the `ID` or the `Text` (`strings.Contains` on both, lowercased). `FindAll` collects every `Match` hit under a `FilterKind`. These are the **search/list** predicates — `idea list [id...]` and the internal `Prune` collection rely on substring breadth, so they stay pure substring (Constitution VI: search/list semantics are part of the public contract). They are intentionally untouched by exact-ID precedence.

- **`RequireSingle` — exact-ID precedence over substring matches** (added by `260615-m2qx-exact-id-match-precedence`). `RequireSingle` collects matches via `Match`, then in its `len(matches) > 1` branch scans the **already-collected match set** with `strings.EqualFold(m.ID, query)` *before* emitting the ambiguity error. If **exactly one** match is an exact-ID hit, that idea wins (returned with its original index) over incidental substring text matches; **zero or more-than-one** exact-ID hits fall through to the existing `Multiple matches: … Be more specific or use the exact ID.` error unchanged. This fixes the bug where passing a canonical 4-char ID failed because that ID string also appeared as substring text inside another idea (e.g. a cross-reference `[jznd]` written into a different idea's body) — the documented "use the exact ID" escape hatch was exactly what aborted. All five resolver-sharing commands (`edit`/`rm`/`show`/`done`/`reopen`) benefit at the single seam.

Two properties are load-bearing: the precedence scan iterates `matches` (already post-`matchesFilter`), so a filtered-out exact-ID idea — e.g. a done idea under `FilterOpen` — is never force-selected (filter semantics preserved); and the `exactCount > 1` case deliberately falls through to the ambiguity error rather than silently picking one (defensive — Constitution VI guarantees unique IDs within a file, so it should never occur). Regression coverage: `TestRequireSingle_ExactIDBeatsSubstring` in `internal/idea/idea_test.go` (table-driven per Constitution V; GIVEN one exact-ID idea + one substring-only idea, WHEN `RequireSingle`, THEN the exact-ID owner returns with no error). The `Match`-substring contract for `show`/`done`/`reopen`/`edit`/`rm` is also noted in `edit.md` and the user-facing query semantics in `../../specs/overview.md`.

### Explicit canonicalizer & adoption (`idea fmt`)

`idea fmt` (added by `260612-4m3a-add-fmt-canonicalizer-adoption`) is the explicit, gofmt-style trigger for the canonical write above: it rewrites the whole backlog into canonical form with no semantic change required, so the normalize-on-write churn can land as its own commit. `fmt` is the **only explicit whole-file write verb** — mutating CRUD commands keep their incidental normalize-on-write; `list`/`show` stay non-mutating.
Expand Down
12 changes: 12 additions & 0 deletions fab/changes/260615-m2qx-exact-id-match-precedence/.history.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{"action":"enter","driver":"fab-new","event":"stage-transition","stage":"intake","ts":"2026-06-15T04:34:29Z"}
{"args":"Fix: exact-ID match must take precedence over incidental substring matches in RequireSingle (idea resolver). Backlog [m2qx].","cmd":"fab-new","event":"command","ts":"2026-06-15T04:34:29Z"}
{"delta":"+4.4","event":"confidence","score":4.4,"trigger":"calc-score","ts":"2026-06-15T04:35:19Z"}
{"delta":"+0.0","event":"confidence","score":4.4,"trigger":"calc-score","ts":"2026-06-15T04:35:24Z"}
{"cmd":"fab-fff","event":"command","ts":"2026-06-15T04:36:15Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"apply","ts":"2026-06-15T04:36:26Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"review","ts":"2026-06-15T04:38:49Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"hydrate","ts":"2026-06-15T04:42:13Z"}
{"event":"review","result":"passed","ts":"2026-06-15T04:42:13Z"}
{"action":"enter","driver":"fab-fff","event":"stage-transition","stage":"ship","ts":"2026-06-15T04:44:31Z"}
{"action":"enter","driver":"git-pr","event":"stage-transition","stage":"review-pr","ts":"2026-06-15T04:46:06Z"}
{"event":"review","result":"passed","ts":"2026-06-15T04:50:30Z"}
51 changes: 51 additions & 0 deletions fab/changes/260615-m2qx-exact-id-match-precedence/.status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
id: m2qx
name: 260615-m2qx-exact-id-match-precedence
created: 2026-06-15T04:34:29Z
created_by: sahil-noon
change_type: fix
issues: []
progress:
intake: done
apply: done
review: done
hydrate: done
ship: done
review-pr: done
plan:
generated: true
task_count: 2
acceptance_count: 11
acceptance_completed: 11
confidence:
certain: 4
confident: 2
tentative: 0
unresolved: 0
score: 4.4
fuzzy: true
dimensions:
signal: 89.2
reversibility: 82.5
competence: 88.3
disambiguation: 87.5
stage_metrics:
intake: {started_at: "2026-06-15T04:34:29Z", driver: fab-new, iterations: 1, completed_at: "2026-06-15T04:36:26Z"}
apply: {started_at: "2026-06-15T04:36:26Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-15T04:38:49Z"}
review: {started_at: "2026-06-15T04:38:49Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-15T04:42:13Z"}
hydrate: {started_at: "2026-06-15T04:42:13Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-15T04:44:31Z"}
ship: {started_at: "2026-06-15T04:44:31Z", driver: fab-fff, iterations: 1, completed_at: "2026-06-15T04:46:06Z"}
review-pr: {started_at: "2026-06-15T04:46:06Z", driver: git-pr, iterations: 1, completed_at: "2026-06-15T04:50:30Z"}
prs:
- https://github.com/sahil87/idea/pull/22
true_impact:
added: 0
deleted: 0
net: 0
excluding:
added: 0
deleted: 0
net: 0
computed_at: "2026-06-15T04:44:31Z"
computed_at_stage: hydrate
# true_impact: lazily created on first apply-finish (no placeholder here).
last_updated: 2026-06-15T04:50:30Z
Loading
Loading