Skip to content

feat(cli): suggest the nearest module name on not-found ("did you mean?") #10

Description

@grantcarthew

Summary

When a module identifier resolves to nothing, suggest the single nearest known
module name instead of a bare "not found". This supersedes the original idea in
this issue (auto-trying plural/singular variants of the search terms) with a more
general, lower-risk approach: a non-binding "did you mean?" hint computed by edit
distance over the candidate names the surface already gathers.

The original report: start task modules/role/create fails because the real name
is module/role/create (singular). A nearest-name suggestion fixes that case and
every other near-miss — typos, a wrong path segment, a transposition — with one
mechanism, without making resolution itself fuzzy.

Why not auto-plural

+s/-s only handles regular plurals (it misses category/categories), and
auto-resolving a guessed variant could silently install or act on the wrong
module. Edit-distance suggestion keeps resolution strictly literal and
deterministic while covering far more slips, and a wrong suggestion costs nothing
because it never acts on its own.

Behaviour

  • On a confirmed not-found (registry reachable), compute the edit distance from
    the query to the in-scope candidate names and print the single closest if it is
    within a threshold.
  • Suggestion only. Nothing auto-resolves or auto-installs. Exit codes and
    not-found semantics are unchanged; the literal resolution contract is untouched.
  • Top-1, with a length-relative threshold and a deterministic (lexical) tie-break.
  • Do not suggest when the registry was unreachable (the transient/retry path): the
    candidate pool is incomplete and cannot be trusted.
  • Skip suggestions for very short queries where edit distance is not meaningful.

Scope

This assumes the candidate-gathering unification (project
05-unify-candidate-gathering.md) is in place, so the suggester attaches once per
surface and covers every module-selecting surface: the resolution surfaces
(task, --role, --context, --agent, get/describe), uninstall /
config remove, config get, install, and update. search benefits too
wherever it returns zero results.

Implementation notes

  • Edit distance via a copied-in copy of agnivade/levenshtein. Case-fold both
    inputs first (the library is case-sensitive). It is plain Levenshtein, so an
    adjacent transposition costs 2 — size the threshold accordingly.
  • License handling for the copied file is a maintainer action item, deliberately
    deferred (the file is copied without its license header for now).
  • The path separator / is an ordinary character to the metric, so a
    segment-boundary slip like modules/role/create vs module/role/create is
    distance 1 with no need to split on /.

Acceptance

  • start task modules/role/create prints a suggestion for module/role/create
    and still exits as not-found.
  • Garbage input with nothing within the threshold prints no suggestion.
  • Successful resolution and all exit codes are unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions