From 0ac0584a96b603986bde63c57d9eec35b6e1d0d7 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 25 Oct 2026 18:13:20 +0100 Subject: [PATCH] fuzzy: generic FuzzyMatcher, fuzzy @agent/@model mention resolution, research archive Co-Authored-By: Claude Sonnet 5.5 --- research/INDEX.md | 14 +- .../aliyun-analyticdb-pg-trgm.md | 28 ++++ .../analyticsvidhya-fuzzywuzzy.md | 28 ++++ .../autogen-model-family-resolution-commit.md | 28 ++++ .../boyter-pdf-sublime-fuzzy-snapshot.md | 28 ++++ .../cljdoc-wsscode-fuzzy.md | 28 ++++ .../agent-model-mention-resolution.md | 51 ++++++ .../generic-fuzzy-scorer-design.md | 60 +++++++ .../curses-fzf-scoring-result.md | 28 ++++ .../dart-args-edit-distance-206.md | 28 ++++ .../datasette-llm-model-aliases.md | 28 ++++ .../docsrs-code-fuzzy-match.md | 28 ++++ .../elixirforum-levenshtein-removed.md | 28 ++++ .../elixirforum-seqfuzz.md | 28 ++++ .../falkordb-jarowinkler-udf.md | 28 ++++ .../forrestthewoods-blog-original.md | 28 ++++ .../forrestthewoods-sublime-fuzzy-match.md | 50 ++++++ .../fossies-fzf-algo-go.md | 28 ++++ .../fuzzyrust-algorithms-guide.md | 28 ++++ .../git-blob42-fzf-algo-commit.md | 28 ++++ .../git-blob42-fzf-algo-master.md | 28 ++++ ...ub-android-password-store-sublime-fuzzy.md | 28 ++++ .../github-cpython-traceback-suggestions.md | 49 ++++++ .../github-fzf-algo-go.md | 52 ++++++ .../github-fzf-man-page.md | 50 ++++++ .../github-fzy-algorithm-md.md | 51 ++++++ .../github-fzy-match-c.md | 50 ++++++ .../github-jhawthorn-fzy-readme.md | 28 ++++ .../github-rapidfuzz-fuzz-py.md | 50 ++++++ .../github-rapidfuzz-readme.md | 28 ++++ .../github-swarn-fzy-lua-docs.md | 28 ++++ .../glama-edit-mcp-fuzzy-rs.md | 28 ++++ .../hackage-fuzzyfind-docs.md | 28 ++++ .../hexdocs-textmetrics.md | 28 ++++ .../jsr-optique-suggestion.md | 28 ++++ .../kde-kate-improve-fuzzy-matching-mr.md | 28 ++++ .../kitty-tools-fzf-algo-go.md | 28 ++++ .../llvm-clangd-fuzzymatch.md | 51 ++++++ .../mankier-rapidfuzz-man.md | 28 ++++ .../matil-approximate-string-matching.md | 28 ++++ .../medium-levenshtein-go-clis.md | 28 ++++ .../npm-didyoumean.md | 28 ++++ .../fuzzy-matching-selectors/npm-fuzzzzz.md | 28 ++++ .../fuzzy-matching-selectors/npm-fzy-js.md | 28 ++++ .../pfzy-readthedocs-usage.md | 28 ++++ .../pigsty-ext-pg-trgm.md | 28 ++++ .../polytreede-rofi-fuzzy-commit.md | 28 ++++ .../postgresql-docs-pgtrgm-v18.md | 28 ++++ .../postgresql-pg-trgm.md | 49 ++++++ .../pyorps-difflib-source.md | 28 ++++ .../python-docs-difflib.md | 50 ++++++ .../racket-fuzzy-search-package.md | 28 ++++ .../slackbuilds-fzy-package.md | 28 ++++ ...ce-dot-net-ml-tokenizers-model-encoding.md | 28 ++++ .../stx-fuzzymatcher-smalltalk.md | 28 ++++ .../svn-python-difflib-r23.md | 28 ++++ ...wiftpackageregistry-ordo-one-fuzzymatch.md | 28 ++++ .../tessl-rapidfuzz-fuzzy-matching-docs.md | 28 ++++ .../tessl-strsim-phonetic-record-linkage.md | 28 ++++ .../tothenew-llm-naming-conventions.md | 28 ++++ .../uspto-patent-10380248-acronym-domain.md | 28 ++++ .../uspto-patent-9747274-string-comparison.md | 28 ++++ .../vegastrike-difflib-sequencematcher.md | 28 ++++ .../vim-patch-8-2-1893-multiword-fuzzy.md | 28 ++++ .../vim-patch-9-1-1046-camelcase-fuzzy.md | 28 ++++ .../wikipedia-damerau-levenshtein-distance.md | 50 ++++++ .../wikipedia-jaro-winkler-distance.md | 49 ++++++ .../xcmd-didyoumean-install.md | 28 ++++ taicpp/CHANGELOG.md | 1 + taicpp/Makefile | 3 +- taicpp/docs/cap.md | 2 +- taicpp/docs/fuzzy.md | 44 +++++ taicpp/src/agent/agent.cpp | 11 +- taicpp/src/agent/agent.h | 1 + taicpp/src/cap/cap_model_catalog.cpp | 143 +++++++++------- taicpp/src/cap/cap_model_catalog.h | 7 +- taicpp/src/util/fuzzy.cpp | 155 ++++++++++++++++++ taicpp/src/util/fuzzy.h | 52 ++++++ taicpp/tests/cap_model_catalog_test.cpp | 18 +- taicpp/tests/fuzzy_test.cpp | 49 ++++++ 80 files changed, 2615 insertions(+), 81 deletions(-) create mode 100644 research/fuzzy-matching-selectors/aliyun-analyticdb-pg-trgm.md create mode 100644 research/fuzzy-matching-selectors/analyticsvidhya-fuzzywuzzy.md create mode 100644 research/fuzzy-matching-selectors/autogen-model-family-resolution-commit.md create mode 100644 research/fuzzy-matching-selectors/boyter-pdf-sublime-fuzzy-snapshot.md create mode 100644 research/fuzzy-matching-selectors/cljdoc-wsscode-fuzzy.md create mode 100644 research/fuzzy-matching-selectors/conclusions/agent-model-mention-resolution.md create mode 100644 research/fuzzy-matching-selectors/conclusions/generic-fuzzy-scorer-design.md create mode 100644 research/fuzzy-matching-selectors/curses-fzf-scoring-result.md create mode 100644 research/fuzzy-matching-selectors/dart-args-edit-distance-206.md create mode 100644 research/fuzzy-matching-selectors/datasette-llm-model-aliases.md create mode 100644 research/fuzzy-matching-selectors/docsrs-code-fuzzy-match.md create mode 100644 research/fuzzy-matching-selectors/elixirforum-levenshtein-removed.md create mode 100644 research/fuzzy-matching-selectors/elixirforum-seqfuzz.md create mode 100644 research/fuzzy-matching-selectors/falkordb-jarowinkler-udf.md create mode 100644 research/fuzzy-matching-selectors/forrestthewoods-blog-original.md create mode 100644 research/fuzzy-matching-selectors/forrestthewoods-sublime-fuzzy-match.md create mode 100644 research/fuzzy-matching-selectors/fossies-fzf-algo-go.md create mode 100644 research/fuzzy-matching-selectors/fuzzyrust-algorithms-guide.md create mode 100644 research/fuzzy-matching-selectors/git-blob42-fzf-algo-commit.md create mode 100644 research/fuzzy-matching-selectors/git-blob42-fzf-algo-master.md create mode 100644 research/fuzzy-matching-selectors/github-android-password-store-sublime-fuzzy.md create mode 100644 research/fuzzy-matching-selectors/github-cpython-traceback-suggestions.md create mode 100644 research/fuzzy-matching-selectors/github-fzf-algo-go.md create mode 100644 research/fuzzy-matching-selectors/github-fzf-man-page.md create mode 100644 research/fuzzy-matching-selectors/github-fzy-algorithm-md.md create mode 100644 research/fuzzy-matching-selectors/github-fzy-match-c.md create mode 100644 research/fuzzy-matching-selectors/github-jhawthorn-fzy-readme.md create mode 100644 research/fuzzy-matching-selectors/github-rapidfuzz-fuzz-py.md create mode 100644 research/fuzzy-matching-selectors/github-rapidfuzz-readme.md create mode 100644 research/fuzzy-matching-selectors/github-swarn-fzy-lua-docs.md create mode 100644 research/fuzzy-matching-selectors/glama-edit-mcp-fuzzy-rs.md create mode 100644 research/fuzzy-matching-selectors/hackage-fuzzyfind-docs.md create mode 100644 research/fuzzy-matching-selectors/hexdocs-textmetrics.md create mode 100644 research/fuzzy-matching-selectors/jsr-optique-suggestion.md create mode 100644 research/fuzzy-matching-selectors/kde-kate-improve-fuzzy-matching-mr.md create mode 100644 research/fuzzy-matching-selectors/kitty-tools-fzf-algo-go.md create mode 100644 research/fuzzy-matching-selectors/llvm-clangd-fuzzymatch.md create mode 100644 research/fuzzy-matching-selectors/mankier-rapidfuzz-man.md create mode 100644 research/fuzzy-matching-selectors/matil-approximate-string-matching.md create mode 100644 research/fuzzy-matching-selectors/medium-levenshtein-go-clis.md create mode 100644 research/fuzzy-matching-selectors/npm-didyoumean.md create mode 100644 research/fuzzy-matching-selectors/npm-fuzzzzz.md create mode 100644 research/fuzzy-matching-selectors/npm-fzy-js.md create mode 100644 research/fuzzy-matching-selectors/pfzy-readthedocs-usage.md create mode 100644 research/fuzzy-matching-selectors/pigsty-ext-pg-trgm.md create mode 100644 research/fuzzy-matching-selectors/polytreede-rofi-fuzzy-commit.md create mode 100644 research/fuzzy-matching-selectors/postgresql-docs-pgtrgm-v18.md create mode 100644 research/fuzzy-matching-selectors/postgresql-pg-trgm.md create mode 100644 research/fuzzy-matching-selectors/pyorps-difflib-source.md create mode 100644 research/fuzzy-matching-selectors/python-docs-difflib.md create mode 100644 research/fuzzy-matching-selectors/racket-fuzzy-search-package.md create mode 100644 research/fuzzy-matching-selectors/slackbuilds-fzy-package.md create mode 100644 research/fuzzy-matching-selectors/source-dot-net-ml-tokenizers-model-encoding.md create mode 100644 research/fuzzy-matching-selectors/stx-fuzzymatcher-smalltalk.md create mode 100644 research/fuzzy-matching-selectors/svn-python-difflib-r23.md create mode 100644 research/fuzzy-matching-selectors/swiftpackageregistry-ordo-one-fuzzymatch.md create mode 100644 research/fuzzy-matching-selectors/tessl-rapidfuzz-fuzzy-matching-docs.md create mode 100644 research/fuzzy-matching-selectors/tessl-strsim-phonetic-record-linkage.md create mode 100644 research/fuzzy-matching-selectors/tothenew-llm-naming-conventions.md create mode 100644 research/fuzzy-matching-selectors/uspto-patent-10380248-acronym-domain.md create mode 100644 research/fuzzy-matching-selectors/uspto-patent-9747274-string-comparison.md create mode 100644 research/fuzzy-matching-selectors/vegastrike-difflib-sequencematcher.md create mode 100644 research/fuzzy-matching-selectors/vim-patch-8-2-1893-multiword-fuzzy.md create mode 100644 research/fuzzy-matching-selectors/vim-patch-9-1-1046-camelcase-fuzzy.md create mode 100644 research/fuzzy-matching-selectors/wikipedia-damerau-levenshtein-distance.md create mode 100644 research/fuzzy-matching-selectors/wikipedia-jaro-winkler-distance.md create mode 100644 research/fuzzy-matching-selectors/xcmd-didyoumean-install.md create mode 100644 taicpp/docs/fuzzy.md create mode 100644 taicpp/src/util/fuzzy.cpp create mode 100644 taicpp/src/util/fuzzy.h create mode 100644 taicpp/tests/fuzzy_test.cpp diff --git a/research/INDEX.md b/research/INDEX.md index a23ed2c..238832b 100644 --- a/research/INDEX.md +++ b/research/INDEX.md @@ -6,13 +6,13 @@ | Metric | Value | |---|---| -| Subjects | 14 | -| Total source documents | 797 | -| — Tier 1 (Source Dossier, independently analyzed) | 254 | -| — Tier 2 (Source Citation Card, listed only) | 543 | -| Total conclusion documents | 32 | +| Subjects | 15 | +| Total source documents | 862 | +| — Tier 1 (Source Dossier, independently analyzed) | 266 | +| — Tier 2 (Source Citation Card, listed only) | 596 | +| Total conclusion documents | 34 | | Archive opened | 2026-10-09 | -| Last updated | 2026-10-18 | +| Last updated | 2026-10-25 | ## Subjects @@ -32,6 +32,7 @@ | [`sandbox-resource-caps`](sandbox-resource-caps/) | 21 (3 / 18) | 1 | 2026-10-18 | 2026-10-18 | Capping the agent sandbox box CPU/memory/pids after the coverage audit found it uncapped; static trio 2g/2.0/256 at create, best-effort update for existing boxes, pids via recreation; implemented same day | | [`cap-protocol`](cap-protocol/) | 24 (23 / 1) | 2 | 2026-10-18 | 2026-10-18 | Supporting CAP (CLI Agent Protocol) as agent and orchestrator: full read of core + coding profile, quickstart, STATUS, all 5 manifests; selective A2A/ACP wire reads; decided phased rollout without gRPC plus dual A2A spelling | | [`mcp-client-support`](mcp-client-support/) | 106 (38 / 68) | 3 | 2026-10-18 | 2026-10-18 | Designing tai's MCP client: wire spec (stdio + Streamable HTTP on the 2025-11-25 baseline), pasted-config/GitHub-URL resolver, and the trust/approval model; decided minimal stdlib client, normalizer plus resolver agent, untrusted-until-consent with description pinning, no OAuth in v1, nothing built yet | +| [`fuzzy-matching-selectors`](fuzzy-matching-selectors/) | 65 (12 / 53) | 2 | 2026-10-25 | 2026-10-25 | Making `@agent` / `@model` mentions pick the most related candidate: fzf/fzy/Sublime/clangd subsequence scoring, Damerau-Levenshtein, pg_trgm, RapidFuzz WRatio, difflib, CPython suggestions surveyed; decided one generic `FuzzyMatcher` (four signals, deterministic ties, floor 0.3 across agents and models) and implemented it the same day | ## Recent activity log @@ -61,6 +62,7 @@ | 2026-10-18 | `sandbox-resource-caps` | New subject: 21 sources (3 Tier 1 / 18 Tier 2) + 1 conclusion, covering container resource caps (Docker limits reference, podman-run flag parity, podman-update create-only pids gap) plus snippet corroboration of the memory/cpus/pids trio after the coverage audit found the sandbox box fully uncapped. Decision implemented same day: static `--memory=2g --cpus=2.0 --pids-limit=256` at box create for both engines, one best-effort `update` (memory plus cpus) per process for existing boxes, concrete `rm -f` recovery pointer on failure. Counts verified directly against the written files via `research/audit.py` (PASS). | | 2026-10-18 | `cap-protocol` | New subject: 24 sources (23 Tier 1 / 1 Tier 2) + 2 conclusions, covering CAP core v1 and the coding profile read in full, quickstart, STATUS, all 5 example manifests, A2A spec + proto (selective), 10 ACP pages (selective), and the reference A2A driver (grepped). Decisions: implement all bindings except gRPC in 5 phases (manifest/events, agent speak, PTY driver, stream-json + orchestration, ACP/A2A + auto-answer); try draft then current A2A endpoint spellings. Counts verified directly against the written files via `research/audit.py` (PASS). | | 2026-10-18 | `mcp-client-support` | New subject: 106 sources (38 Tier 1 / 68 Tier 2) + 3 conclusions, covering the MCP wire spec (lifecycle, transports, tools, utilities, 2024-11-05 legacy, versioning, changelog), client config ecosystems (Claude Code, VS Code, Cursor, Codex TOML, mcp-remote, Smithery, registries, Glama), and security/operations (authorization draft, trust model, poisoning, rug pulls, production ops) via 3 gather subagents plus first-hand fetches of the load-bearing spec, client-config, and OWASP pages. Decisions: minimal stdlib client on the 2025-11-25 baseline (stdio + Streamable HTTP, no SDK, no OAuth in v1); deterministic normalizer plus resolver subagent for pasted configs and GitHub URLs with live-handshake verification; untrusted-until-consent trust model with description-hash pinning and approval tiers. Nothing built yet. Counts verified directly against the written files via `research/audit.py` (PASS). | +| 2026-10-25 | `fuzzy-matching-selectors` | New subject: 65 sources (12 Tier 1 / 53 Tier 2) + 2 conclusions, from 10 searches and 14 fetches on fuzzy string matching (fzf algo.go and man page, fzy ALGORITHM.md and match.c, Sublime fuzzy match via an AsciiDoc copy because the original blog returned 403, clangd FuzzyMatch, pg_trgm docs, RapidFuzz WRatio source, Jaro-Winkler and Damerau-Levenshtein articles, difflib docs, CPython traceback suggestions). Decisions: one generic `detz::FuzzyMatcher` (subsequence DP with fzf constants + restricted Damerau-Levenshtein + per-token typo + trigram Jaccard, deterministic shorter-then-earlier ties) and a fuzzy catalog resolution order with floor 0.3. Implemented the same session in taicpp (src/util/fuzzy.*, src/cap/cap_model_catalog.cpp, tests/fuzzy_test.cpp). Carried gaps: VS Code fuzzyScorer.ts never retrieved; fzy constants not retrieved (fzf's used); no measurement on a real model-id corpus; the Jaro-Winkler prefix-inflation claim rests on a search listing only. | ## How to use this index diff --git a/research/fuzzy-matching-selectors/aliyun-analyticdb-pg-trgm.md b/research/fuzzy-matching-selectors/aliyun-analyticdb-pg-trgm.md new file mode 100644 index 0000000..ca3c982 --- /dev/null +++ b/research/fuzzy-matching-selectors/aliyun-analyticdb-pg-trgm.md @@ -0,0 +1,28 @@ +# AnalyticDB pg_trgm (Alibaba Cloud) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | AnalyticDB pg_trgm (Alibaba Cloud) | +| Source URL | https://help.aliyun.com/en/analyticdb/analyticdb-for-postgresql/user-guide/pg-trgm | +| Source type | vendor docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by pg_trgm search; vendor copy, quoted a 0.6 word-similarity default. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/analyticsvidhya-fuzzywuzzy.md b/research/fuzzy-matching-selectors/analyticsvidhya-fuzzywuzzy.md new file mode 100644 index 0000000..641b0ed --- /dev/null +++ b/research/fuzzy-matching-selectors/analyticsvidhya-fuzzywuzzy.md @@ -0,0 +1,28 @@ +# FuzzyWuzzy Python library (Analytics Vidhya) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | FuzzyWuzzy Python library (Analytics Vidhya) | +| Source URL | https://www.analyticsvidhya.com/blog/2021/06/fuzzywuzzy-python-library-interesting-tool-for-nlp-and-text-analytics/ | +| Source type | blog post | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by RapidFuzz search; tutorial on the fuzzywuzzy ratios. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/autogen-model-family-resolution-commit.md b/research/fuzzy-matching-selectors/autogen-model-family-resolution-commit.md new file mode 100644 index 0000000..20c198b --- /dev/null +++ b/research/fuzzy-matching-selectors/autogen-model-family-resolution-commit.md @@ -0,0 +1,28 @@ +# AutoGen: model family resolution for non-prefixed names (#6158) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | AutoGen: model family resolution for non-prefixed names (#6158) | +| Source URL | https://code.gitlink.org.cn/wangtao/autogen/commit/27da37efc0e14a8d2c193d732e6899895b14f7bd | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by model-id search; reports prefix-based matching is ambiguous for ids such as mistral-large-latest. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/boyter-pdf-sublime-fuzzy-snapshot.md b/research/fuzzy-matching-selectors/boyter-pdf-sublime-fuzzy-snapshot.md new file mode 100644 index 0000000..246cef0 --- /dev/null +++ b/research/fuzzy-matching-selectors/boyter-pdf-sublime-fuzzy-snapshot.md @@ -0,0 +1,28 @@ +# PDF snapshot of the Sublime fuzzy match post + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | PDF snapshot of the Sublime fuzzy match post | +| Source URL | https://Boyter.org/static/abusing-aws-lambda/snippet/Reverse%20Engineering%20Sublime%20Text%E2%80%99s%20Fuzzy%20Match%20-%20ForrestTheWoods.pdf | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by Sublime search result; a PDF copy of the blog post. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/cljdoc-wsscode-fuzzy.md b/research/fuzzy-matching-selectors/cljdoc-wsscode-fuzzy.md new file mode 100644 index 0000000..5405a82 --- /dev/null +++ b/research/fuzzy-matching-selectors/cljdoc-wsscode-fuzzy.md @@ -0,0 +1,28 @@ +# wsscode fuzzy (Clojure) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | wsscode fuzzy (Clojure) | +| Source URL | https://cljdoc.org/d/com.wsscode/fuzzy/1.0.0 | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by Sublime search result; Clojure wrapper. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/conclusions/agent-model-mention-resolution.md b/research/fuzzy-matching-selectors/conclusions/agent-model-mention-resolution.md new file mode 100644 index 0000000..11af256 --- /dev/null +++ b/research/fuzzy-matching-selectors/conclusions/agent-model-mention-resolution.md @@ -0,0 +1,51 @@ +# Conclusion: How @agent and @agent:model mentions resolve + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Conclusion | +| Tier | N/A | +| Date decided | 2026-10-25 | +| Decided by | retoor, advised by tai (agent, Claude Sonnet 5) | +| Status | Active | +| Confidence | Medium | + +## Question / problem statement + +How should a typed `@word` choose a CAP agent and a model when the agent list and each agent's models are discovered at runtime, and how should a miss be reported. + +## Conclusion + +Order: exact bot, provider, route, backend label, known model id, `@auto`, exact agent or `agent:model`, existing file path, and only then the fuzzy catalog (so a real file or bot is never shadowed). In the catalog: (1) exact agent name or exact model id, case-insensitive; (2) `head:tail` splits at the first colon, head fuzzy-matched to a ready agent (floor 0.3), tail fuzzy-matched to that agent's models with no floor because the agent already disambiguates; an agent with no model list passes the tail through with a note; (3) otherwise the whole text is ranked against every ready agent name and every model, floor 0.3, best wins and ties resolve shorter then earlier. Unusable agents (no binary, bad manifest) are considered only when no ready candidate reaches the floor, and then produce an 'is not usable' error. Whenever the pick differs from the typed text, a one-line note shows what it resolved to and its score. Below the floor, the error names the closest candidate and score and prints the full per-agent report. + +## Supporting sources + +- [github-fzf-man-page](../github-fzf-man-page.md): tie rule. +- [github-cpython-traceback-suggestions](../github-cpython-traceback-suggestions.md): first-wins ties, ceilings. +- [python-docs-difflib](../python-docs-difflib.md): published default cutoff (0.6) to compare the floor against. +- [datasette-llm-model-aliases](../datasette-llm-model-aliases.md) and [autogen-model-family-resolution-commit](../autogen-model-family-resolution-commit.md) (Tier 2): explicit aliases and the ambiguity of pure prefix matching for model ids. + +## Alternatives considered and rejected + +- No floor at all (literally always pick): any unrelated tag such as a typo'd bot name would route to a random model; the floor 0.3 and the closest-candidate message keep the owner's intent for near misses while rejecting letter noise. A one-line constant (`CAP_FUZZY_FLOOR`) turns the floor to 0 if the owner wants it gone. +- Erroring on ties (the first version): replaced because equal matches do not matter to the owner. +- Fuzzy before file lookup: rejected so `@README.md` is never rerouted to an agent. + +## Risks / open questions + +- The floor 0.3 is a judgment between difflib's 0.6 rule of thumb and the owner's 'always pick', not a measured value. +- A very short query (one or two letters) can resolve to whichever candidate has the nearest word boundary; the printed note makes this visible but does not prevent it. +- Model discovery (`[models]` list or `--cap-models` probe) is a tai extension to the CAP manifest, not part of CAP. + +## Action taken + +Implemented in src/cap/cap_model_catalog.cpp and src/agent/agent.cpp; tests in tests/cap_model_catalog_test.cpp; live-checked with a stream-json agent (`@tia:zne` resolved to `tai:zen-free` and ran). Spec in docs/cap.md. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial conclusion recorded. | diff --git a/research/fuzzy-matching-selectors/conclusions/generic-fuzzy-scorer-design.md b/research/fuzzy-matching-selectors/conclusions/generic-fuzzy-scorer-design.md new file mode 100644 index 0000000..0e58982 --- /dev/null +++ b/research/fuzzy-matching-selectors/conclusions/generic-fuzzy-scorer-design.md @@ -0,0 +1,60 @@ +# Conclusion: One generic FuzzyMatcher with four signals + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Conclusion | +| Tier | N/A | +| Date decided | 2026-10-25 | +| Decided by | retoor, advised by tai (agent, Claude Sonnet 5) | +| Status | Active | +| Confidence | Medium | + +## Question / problem statement + +Which string-similarity method should decide what a user meant by a short typed word such as `@opus`, `@sonet` or `@g4m` against ids like `claude-opus-4-5`, when the owner wants the most related candidate always picked and equal ties not to matter. + +## Conclusion + +tai gets one generic, dependency-free scorer, `detz::FuzzyMatcher` (src/util/fuzzy.h, spec docs/fuzzy.md). Score is in [0,1], case-insensitive, and is the max of: (1) a subsequence dynamic program with fzf's constants (match 16, gap -1, boundary 8, white 10, camel/digit 7, consecutive 4, first byte x2) and fzy's D/M recurrences, normalized by the perfect score; (2) restricted Damerau-Levenshtein similarity; (3) the same typo similarity per alphanumeric token, x0.95; (4) pg_trgm trigram Jaccard, x0.9. Only a case-insensitive exact match scores 1. Ranking is score descending, then shorter text, then earlier index, so equal matches resolve deterministically and never error. Static ceilings: query 64 bytes, text 256 bytes, 4096 candidates. + +## Supporting sources + +- [github-fzf-algo-go](../github-fzf-algo-go.md): constants, first-char doubling, optimal-versus-greedy tradeoff. +- [github-fzy-algorithm-md](../github-fzy-algorithm-md.md) and [github-fzy-match-c](../github-fzy-match-c.md): the D/M recurrences and bonus taxonomy. +- [forrestthewoods-sublime-fuzzy-match](../forrestthewoods-sublime-fuzzy-match.md): boundary and camelCase bonuses dominate; best-of-all-matches beats first match. +- [github-fzf-man-page](../github-fzf-man-page.md): shorter-then-earlier tie rule. +- [wikipedia-damerau-levenshtein-distance](../wikipedia-damerau-levenshtein-distance.md): restricted variant for short words, transposition as one edit. +- [postgresql-pg-trgm](../postgresql-pg-trgm.md): trigram definition and the 4/7 oracle. +- [llvm-clangd-fuzzymatch](../llvm-clangd-fuzzymatch.md): normalize by pattern length. +- [github-cpython-traceback-suggestions](../github-cpython-traceback-suggestions.md): candidate and length ceilings, first-wins ties. + +## Alternatives considered and rejected + +- Jaro-Winkler: its prefix boost would flatten `claude-*` ids that share a long prefix (the claim itself comes from a search listing, not a primary source; see the Tier 2 cards). +- RapidFuzz WRatio / token_set_ratio: built for sentences; a subset scores 100, so `claude` would tie with every `claude-*` id. +- difflib SequenceMatcher (already in the tree as `sequencematcher.cpp`): no boundary or camelCase awareness, junk heuristic asymmetric. +- Plain Levenshtein: a short query against a long id always scores low even when it is an exact word of the id. +- CPython-style max-distance rejection: it refuses far matches by design, the opposite of the owner's rule. +- Embeddings or an LLM judge: a new dependency and a network call for a selector that must work offline. +- VS Code `fuzzyScorer.ts` constants: the file was never retrieved, so its numbers were not used. + +## Risks / open questions + +- Weights are fzf's, tuned for file paths, not model ids; no measurement on a real corpus of model ids was done (Forrest Smith states the best weights depend on the dataset). +- ASCII-only case folding; non-ASCII bytes are compared as-is, so accented ids are not normalized (fzf can normalize, tai does not). +- A subsequence can match scattered letters in a long id; the boundary bonuses make this rare but it is not impossible. +- The ceilings (64/256/4096) are untested guesses of 'big enough'. + +## Action taken + +Implemented the same session: src/util/fuzzy.{h,cpp}, tests/fuzzy_test.cpp (`build/fuzzy_test`), docs/fuzzy.md. Used by the CAP model catalog (see the other conclusion). + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial conclusion recorded. | diff --git a/research/fuzzy-matching-selectors/curses-fzf-scoring-result.md b/research/fuzzy-matching-selectors/curses-fzf-scoring-result.md new file mode 100644 index 0000000..05a9902 --- /dev/null +++ b/research/fuzzy-matching-selectors/curses-fzf-scoring-result.md @@ -0,0 +1,28 @@ +# curses-fzf ScoringResult + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | curses-fzf ScoringResult | +| Source URL | https://curses-fzf.readthedocs.io/en/0.3.0/ScoringResult.html | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by a Python fzf-style scorer result type in the fzf search. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/dart-args-edit-distance-206.md b/research/fuzzy-matching-selectors/dart-args-edit-distance-206.md new file mode 100644 index 0000000..bd76ffc --- /dev/null +++ b/research/fuzzy-matching-selectors/dart-args-edit-distance-206.md @@ -0,0 +1,28 @@ +# Dart args: show similar commands with edit distance <= 2 (#206) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Dart args: show similar commands with edit distance <= 2 (#206) | +| Source URL | https://dart.googlesource.com/args/+/refs/tags/2.2.0%5E%21/ | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by did-you-mean search; optimal string alignment, default distance 2. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/datasette-llm-model-aliases.md b/research/fuzzy-matching-selectors/datasette-llm-model-aliases.md new file mode 100644 index 0000000..7c44d5a --- /dev/null +++ b/research/fuzzy-matching-selectors/datasette-llm-model-aliases.md @@ -0,0 +1,28 @@ +# LLM (datasette) model aliases + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | LLM (datasette) model aliases | +| Source URL | https://llm.datasette.io/en/0.24/aliases.html | +| Source type | vendor docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by model-id aliasing search; explicit short aliases such as 4o-mini to gpt-4o-mini. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/docsrs-code-fuzzy-match.md b/research/fuzzy-matching-selectors/docsrs-code-fuzzy-match.md new file mode 100644 index 0000000..22d6cf3 --- /dev/null +++ b/research/fuzzy-matching-selectors/docsrs-code-fuzzy-match.md @@ -0,0 +1,28 @@ +# Crate code_fuzzy_match + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Crate code_fuzzy_match | +| Source URL | https://docs.rs/code-fuzzy-match | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code fuzzyScore search; Rust crate stating subsequence matching with word-start and consecutive preference. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/elixirforum-levenshtein-removed.md b/research/fuzzy-matching-selectors/elixirforum-levenshtein-removed.md new file mode 100644 index 0000000..dffdeb9 --- /dev/null +++ b/research/fuzzy-matching-selectors/elixirforum-levenshtein-removed.md @@ -0,0 +1,28 @@ +# Why was the levenshtein distance removed from Elixir? + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Why was the levenshtein distance removed from Elixir? | +| Source URL | https://elixirforum.com/t/why-was-the-levenshtein-distance-removed-from-elixir/24897 | +| Source type | forum thread | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance search; reported that Elixir tooling uses Jaro distance for suggesting task names. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/elixirforum-seqfuzz.md b/research/fuzzy-matching-selectors/elixirforum-seqfuzz.md new file mode 100644 index 0000000..5478d62 --- /dev/null +++ b/research/fuzzy-matching-selectors/elixirforum-seqfuzz.md @@ -0,0 +1,28 @@ +# Seqfuzz Elixir package thread + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Seqfuzz Elixir package thread | +| Source URL | https://elixirforum.com/t/seqfuzz-sublime-text-like-fuzzy-search-my-first-hex-package/32348 | +| Source type | forum thread | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by Sublime search result; Elixir implementation of the same scoring. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/falkordb-jarowinkler-udf.md b/research/fuzzy-matching-selectors/falkordb-jarowinkler-udf.md new file mode 100644 index 0000000..133913f --- /dev/null +++ b/research/fuzzy-matching-selectors/falkordb-jarowinkler-udf.md @@ -0,0 +1,28 @@ +# FalkorDB jaroWinkler UDF docs + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | FalkorDB jaroWinkler UDF docs | +| Source URL | https://docs.falkordb.com/udfs/flex/text/jaroWinkler.html | +| Source type | vendor docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance search; Jaro-Winkler function docs. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/forrestthewoods-blog-original.md b/research/fuzzy-matching-selectors/forrestthewoods-blog-original.md new file mode 100644 index 0000000..db7cb56 --- /dev/null +++ b/research/fuzzy-matching-selectors/forrestthewoods-blog-original.md @@ -0,0 +1,28 @@ +# Reverse Engineering Sublime Text's Fuzzy Match (original blog) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Reverse Engineering Sublime Text's Fuzzy Match (original blog) | +| Source URL | https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/ | +| Source type | blog post | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by the primary source; fetch returned HTTP 403, so the AsciiDoc copy was read as the Tier 1 dossier instead. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/forrestthewoods-sublime-fuzzy-match.md b/research/fuzzy-matching-selectors/forrestthewoods-sublime-fuzzy-match.md new file mode 100644 index 0000000..7dec953 --- /dev/null +++ b/research/fuzzy-matching-selectors/forrestthewoods-sublime-fuzzy-match.md @@ -0,0 +1,50 @@ +# Reverse Engineering Sublime Text's Fuzzy Match (AsciiDoc copy) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | Reverse Engineering Sublime Text's Fuzzy Match (AsciiDoc copy) | +| Source URL | https://raw.githubusercontent.com/tajmone/fuzzy-search/master/article/forrest2016.asciidoc | +| Source publisher / author | Forrest Smith (copy hosted by tajmone/fuzzy-search) | +| Source type | blog post | +| Source date (published) | 2016-03-28 | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | Medium | +| Status | Active | + +## Summary + +Forrest Smith's analysis of how Sublime Text scores fuzzy matches, with a rule set and an exhaustive best-match search (added in a 2017-02-18 update). The original blog URL (forrestthewoods.com) returned HTTP 403 to the fetcher, so this repository copy was read; the copy states it may not be reused without contacting the author, so only findings are recorded here. + +## Key findings + +- Scoring rules: matched letter 0, unmatched letter -1, consecutive match +5, separator bonus +10 (after space or underscore), camelCase bonus +10, unmatched leading letter -3 each capped at -9 total. +- The 2017 update searches all possible matches and returns the highest-scoring one, slower than stopping at the first. +- Separator and camelCase bonuses are 'worth a LOT'; consecutive matches are worth less; there is no explicit exact-match bonus because shorter strings lose fewer points to unmatched letters. +- Scores have no fixed meaning (estimated range about [-50, 50]); the best weights depend on the dataset (paths, file names, single words). +- Example: pattern LLL against SVisualLoggerLogsList.h has four candidate matches; the best skips the first L and takes the three CamelCase Ls. + +## Pros / strengths + +Shows empirically why boundary bonuses dominate and why best-of-all-matches beats first-match. + +## Cons / limitations / caveats + +Original page unreachable (403); the numeric rules are as quoted by the copy and could differ from the live page. Weights are tuned for Sublime file/symbol search, not model ids. The fetch tool's summary of the copy was relied on. + +## Relevance to tai + +Confirmed that boundary and camelCase bonuses should outrank consecutiveness, which is how tai's fzf-derived constants behave (8 and 7 against 4). + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/fossies-fzf-algo-go.md b/research/fuzzy-matching-selectors/fossies-fzf-algo-go.md new file mode 100644 index 0000000..15a430c --- /dev/null +++ b/research/fuzzy-matching-selectors/fossies-fzf-algo-go.md @@ -0,0 +1,28 @@ +# "Fossies" - fzf src/algo/algo.go + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | "Fossies" - fzf src/algo/algo.go | +| Source URL | https://fossies.org/linux/fzf/src/algo/algo.go | +| Source type | repository mirror | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by a search for the FuzzyMatchV2 algorithm; mirrors the same file read directly as a Tier 1 dossier. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/fuzzyrust-algorithms-guide.md b/research/fuzzy-matching-selectors/fuzzyrust-algorithms-guide.md new file mode 100644 index 0000000..52ee8ed --- /dev/null +++ b/research/fuzzy-matching-selectors/fuzzyrust-algorithms-guide.md @@ -0,0 +1,28 @@ +# fuzzyrust algorithms guide + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | fuzzyrust algorithms guide | +| Source URL | https://fuzzyrust.readthedocs.io/en/latest/guide/algorithms/ | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance comparison search; guide listing Levenshtein, Damerau and Jaro-Winkler examples. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/git-blob42-fzf-algo-commit.md b/research/fuzzy-matching-selectors/git-blob42-fzf-algo-commit.md new file mode 100644 index 0000000..801d17a --- /dev/null +++ b/research/fuzzy-matching-selectors/git-blob42-fzf-algo-commit.md @@ -0,0 +1,28 @@ +# fzf algo.go at commit 2fc7c18 (mirror) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | fzf algo.go at commit 2fc7c18 (mirror) | +| Source URL | https://git.blob42.xyz/Archives/fzf/commit/2fc7c18747250ebf8adf68d2057ec22af6976f29 | +| Source type | repository mirror | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by same fzf search; a commit view of the matcher. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/git-blob42-fzf-algo-master.md b/research/fuzzy-matching-selectors/git-blob42-fzf-algo-master.md new file mode 100644 index 0000000..6b3e519 --- /dev/null +++ b/research/fuzzy-matching-selectors/git-blob42-fzf-algo-master.md @@ -0,0 +1,28 @@ +# fzf algo.go master (mirror) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | fzf algo.go master (mirror) | +| Source URL | https://git.blob42.xyz/Archives/fzf/src/branch/master/src/algo/algo.go | +| Source type | repository mirror | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by same fzf search; mirror of master. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/github-android-password-store-sublime-fuzzy.md b/research/fuzzy-matching-selectors/github-android-password-store-sublime-fuzzy.md new file mode 100644 index 0000000..2fcb271 --- /dev/null +++ b/research/fuzzy-matching-selectors/github-android-password-store-sublime-fuzzy.md @@ -0,0 +1,28 @@ +# sublime-fuzzy (Kotlin port) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | sublime-fuzzy (Kotlin port) | +| Source URL | https://github.com/android-password-store/sublime-fuzzy | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by Sublime search result; Kotlin port of the post. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/github-cpython-traceback-suggestions.md b/research/fuzzy-matching-selectors/github-cpython-traceback-suggestions.md new file mode 100644 index 0000000..944984c --- /dev/null +++ b/research/fuzzy-matching-selectors/github-cpython-traceback-suggestions.md @@ -0,0 +1,49 @@ +# CPython Lib/traceback.py (did-you-mean suggestions) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | CPython Lib/traceback.py (did-you-mean suggestions) | +| Source URL | https://raw.githubusercontent.com/python/cpython/main/Lib/traceback.py | +| Source publisher / author | Python core developers | +| Source type | repository | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | High | +| Status | Active | + +## Summary + +The pure-Python fallback of CPython's NameError/AttributeError suggestion engine, a widely shipped 'closest name' resolver. + +## Key findings + +- Constants: _MOVE_COST 2, _CASE_COST 1 (a substitution that differs only in case), _MAX_CANDIDATE_ITEMS 750, _MAX_STRING_SIZE 40. +- Maximum accepted distance: (len(candidate) + len(wrong_name) + 3) * _MOVE_COST // 6, additionally capped below the best distance so far. +- Exact matches are skipped; a candidate replaces the current suggestion only on a strictly smaller distance, so ties go to the first candidate in iteration order. +- Early exits trim common prefix/suffix, bail when the length difference alone exceeds the budget, and refuse strings over 40 characters or lists over 750 candidates. + +## Pros / strengths + +Shows that a production resolver caps candidate count and string length and resolves ties by first-seen, matching tai's static-ceiling and deterministic-tie rules. + +## Cons / limitations / caveats + +Distance-threshold design rejects far matches by construction, the opposite of 'always pick the closest'. Names only, not ids with version numbers. + +## Relevance to tai + +Confirmed ceilings (tai: 4096 candidates, 64/256 bytes) and first-wins ties. Its reject-by-threshold policy was deliberately not adopted for explicit `@agent:model`. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/github-fzf-algo-go.md b/research/fuzzy-matching-selectors/github-fzf-algo-go.md new file mode 100644 index 0000000..0516a2b --- /dev/null +++ b/research/fuzzy-matching-selectors/github-fzf-algo-go.md @@ -0,0 +1,52 @@ +# fzf src/algo/algo.go + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | fzf src/algo/algo.go | +| Source URL | https://raw.githubusercontent.com/junegunn/fzf/master/src/algo/algo.go | +| Source publisher / author | junegunn (fzf maintainer) | +| Source type | repository | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | High | +| Status | Active | + +## Summary + +The matcher of fzf. FuzzyMatchV2 is a modified Smith-Waterman that finds the highest-scoring occurrence; V1 is a greedy forward/backward scan. All constants were retrievable from the source. + +## Key findings + +- Constants: scoreMatch 16; scoreGapStart -3; scoreGapExtension -1; bonusBoundary 8 (scoreMatch/2); bonusNonWord 8; bonusCamel123 7 (bonusBoundary + scoreGapExtension); bonusConsecutive 4 (-(scoreGapStart + scoreGapExtension)); bonusFirstCharMultiplier 2; bonusBoundaryWhite 10 in the default scheme; bonusBoundaryDelimiter 9 in default and path. +- The source says the parameters were chosen so that the bonus is cancelled when the gap size grows beyond 8 characters. +- Character classes: white, nonword, delimiter, lower, upper, letter, number. Boundary after white/delimiter/nonword gets the matching bonus; lower to upper and non-number to number get bonusCamel123. +- V1 scans forward to the first complete occurrence then backward to tighten; O(n) but 'not guaranteed to find the occurrence with the highest score'. +- V2 examines all occurrences and disallows omissions or mismatches; O(nm) when a match is found, O(n) otherwise; falls back to V1 when N*M exceeds the slab capacity or M > 1000 (16-bit overflow avoidance). +- Accent normalization is optional via a lookup table; case sensitivity is a caller parameter and the pattern is assumed lowercase when insensitive (smart-case itself is decided elsewhere). +- Ties: the backtrace uses preferMatch to break score ties; forward scans use strict > so the earliest occurrence wins (inferred from code, not stated in a comment). + +## Pros / strengths + +Production-tested constants, explicit rationale for the 8-character cancellation, and an explicit optimal-versus-greedy tradeoff. + +## Cons / limitations / caveats + +Mirrors of other fzf versions may differ; the fetch was of master at research time. Tie-breaking across candidates is outside this file. No quality measurements. + +## Relevance to tai + +Source of the numeric constants and the first-character-doubling rule used in `FuzzyMatcher::Subsequence`, and the max-length ceilings idea (tai caps query 64, text 256 bytes). + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/github-fzf-man-page.md b/research/fuzzy-matching-selectors/github-fzf-man-page.md new file mode 100644 index 0000000..9fb0318 --- /dev/null +++ b/research/fuzzy-matching-selectors/github-fzf-man-page.md @@ -0,0 +1,50 @@ +# fzf man page (fzf.1) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | fzf man page (fzf.1) | +| Source URL | https://raw.githubusercontent.com/junegunn/fzf/master/man/man1/fzf.1 | +| Source publisher / author | junegunn (fzf maintainer) | +| Source type | vendor docs | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | High | +| Status | Active | + +## Summary + +The user-facing documentation of fzf, read for its tie-break and case rules and its search syntax. + +## Key findings + +- --tiebreak criteria for tied scores: length (shorter line), chunk (shorter matched chunk), pathname, begin (match closer to start), end, index (earlier in input). +- index is implicitly appended; the default is length (equivalently length,index). +- --algo v2 is the default optimal-scoring algorithm; v1 is faster but not guaranteed optimal. +- Smart case is the default: case-insensitive unless the query contains an uppercase letter. +- Search syntax: 'term exact, ^term and term$ anchored, !term negation, 'term' exact boundary, | as OR. + +## Pros / strengths + +Proves the industry-default tie rule: shorter text first, then input order. + +## Cons / limitations / caveats + +Describes behavior, not scoring math. No measurements. + +## Relevance to tai + +Adopted fzf's default tie rule (shorter candidate, then earlier index) in `FuzzyMatcher::Rank`. Smart case and the exact/anchored syntax were not adopted (not needed for id matching). + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/github-fzy-algorithm-md.md b/research/fuzzy-matching-selectors/github-fzy-algorithm-md.md new file mode 100644 index 0000000..d0a97eb --- /dev/null +++ b/research/fuzzy-matching-selectors/github-fzy-algorithm-md.md @@ -0,0 +1,51 @@ +# fzy ALGORITHM.md + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | fzy ALGORITHM.md | +| Source URL | https://github.com/jhawthorn/fzy/blob/master/ALGORITHM.md | +| Source publisher / author | jhawthorn (fzy maintainer) | +| Source type | repository | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | Medium | +| Status | Active | + +## Summary + +The design note of fzy, a terminal fuzzy selector. It frames fuzzy scoring as a modified edit-distance problem solved with a dynamic program, and compares fzy to other finders. The page names the bonuses and penalties but the fetched text carries no numeric values or complexity analysis. + +## Key findings + +- Scoring is treated as a modified edit distance related to Levenshtein, with dynamic programming similar to Wagner-Fischer and Needleman-Wunsch. +- The matrix is n-by-m (search length by candidate length); an affine gap penalty inspired by the Gotoh algorithm is used. +- A second matrix D holds the best score which ends in a match, computed alongside the score matrix. +- Penalties (negative): gaps at the start of the match, at the end, and inside the match. Bonuses (positive): consecutive match, after a slash, start of a word (after space, underscore or dash), a capital letter (CamelCase), after a dot. +- Comparison: fzf takes the first match with some logic to find a shorter one but is not guaranteed shortest; ctrlp, pick and selecta (<= 0.0.6) score by the length of the first match; pick can find the absolute shortest match at a speed cost; selecta master compares only the shortest match from the same start; fuzzaldrin-plus uses Smith-Waterman. +- Stated limitations: every search is case-insensitive (scoring favors uppercase matches; smart case is suggested), and the page says fuzzy matching only accepts insertions, not deletions or substitutions. + +## Pros / strengths + +A clear statement of the model (consecutive + word-start + camelCase bonuses, gap penalties) and a useful comparison of why first-match and shortest-match schemes misrank. + +## Cons / limitations / caveats + +No constants and no complexity in the fetched text; the recurrences live in src/match.c. The Command-t section is marked incomplete by the author. Not a measured evaluation, a design note. + +## Relevance to tai + +Gave the bonus taxonomy and the D/M two-matrix recurrence structure that tai's `FuzzyMatcher::Subsequence` follows. Constants were taken from fzf instead (see the fzf-algo-go dossier) because they were actually retrieved. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/github-fzy-match-c.md b/research/fuzzy-matching-selectors/github-fzy-match-c.md new file mode 100644 index 0000000..45ccf07 --- /dev/null +++ b/research/fuzzy-matching-selectors/github-fzy-match-c.md @@ -0,0 +1,50 @@ +# fzy src/match.c + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | fzy src/match.c | +| Source URL | https://raw.githubusercontent.com/jhawthorn/fzy/master/src/match.c | +| Source publisher / author | jhawthorn (fzy maintainer) | +| Source type | repository | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | Medium | +| Status | Active | + +## Summary + +The C implementation of fzy's matcher. The scoring constants are not in this file (they live in config.h and bonus.h, which were not fetched), but the dynamic-programming recurrences, the prefilter and the length cap logic are. + +## Key findings + +- `has_match` is a prefilter: for each needle byte it finds the next case-insensitive occurrence in the haystack and advances; it returns 0 when any byte is missing. +- `match()` and `match_positions()` return SCORE_MIN when the haystack is longer than MATCH_MAX_LEN or the needle is longer than the haystack. +- `precompute_bonus` starts with last_ch = '/' and sets match_bonus[i] = COMPUTE_BONUS(last_ch, ch): the bonus depends on the preceding character. +- Row recurrence: gap_score is SCORE_GAP_TRAILING on the last needle row, else SCORE_GAP_INNER. When needle[i] equals haystack[j]: for i == 0, score = j * SCORE_GAP_LEADING + match_bonus[j]; for i > 0 and j > 0, score = max(prev_M + match_bonus[j], prev_D + SCORE_MATCH_CONSECUTIVE). D[j] = score and M[j] = max(score, M[j-1] + gap_score). Otherwise D[j] = SCORE_MIN and M[j] = M[j-1] + gap_score. +- `match()` returns SCORE_MAX when needle and haystack have the same length; otherwise the last cell of M. + +## Pros / strengths + +Exact, short, readable recurrences; shows the cheap prefilter that makes large candidate lists affordable. + +## Cons / limitations / caveats + +Constants (SCORE_GAP_*, SCORE_MATCH_*) and MATCH_MAX_LEN are not defined in the fetched file, so no numbers can be quoted from it. + +## Relevance to tai + +The recurrence is the shape of `FuzzyMatcher::Subsequence` (D = ends in match, M = best prefix). The prefilter idea is covered by the `n > m` early return. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/github-jhawthorn-fzy-readme.md b/research/fuzzy-matching-selectors/github-jhawthorn-fzy-readme.md new file mode 100644 index 0000000..3d94743 --- /dev/null +++ b/research/fuzzy-matching-selectors/github-jhawthorn-fzy-readme.md @@ -0,0 +1,28 @@ +# jhawthorn/fzy repository + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | jhawthorn/fzy repository | +| Source URL | https://github.com/jhawthorn/fzy | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by fzy search; README says consecutive, word-start, shorter-match and shorter-candidate considerations are weighted. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/github-rapidfuzz-fuzz-py.md b/research/fuzzy-matching-selectors/github-rapidfuzz-fuzz-py.md new file mode 100644 index 0000000..ad83046 --- /dev/null +++ b/research/fuzzy-matching-selectors/github-rapidfuzz-fuzz-py.md @@ -0,0 +1,50 @@ +# RapidFuzz fuzz_py.py (WRatio, partial_ratio) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | RapidFuzz fuzz_py.py (WRatio, partial_ratio) | +| Source URL | https://raw.githubusercontent.com/rapidfuzz/RapidFuzz/main/src/rapidfuzz/fuzz_py.py | +| Source publisher / author | maxbachmann and RapidFuzz contributors | +| Source type | repository | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | High | +| Status | Active | + +## Summary + +The pure-Python reference implementation of RapidFuzz scorers, read for how the weighted WRatio combines ratio, token and partial scorers. + +## Key findings + +- WRatio constants: UNBASE_SCALE 0.95; PARTIAL_SCALE 0.9 when the length ratio is <= 8.0, else 0.6. +- Length ratio < 1.5: max(ratio, token_ratio * 0.95) where token_ratio is the max of token_set_ratio and token_sort_ratio. +- Length ratio >= 1.5: ratio, then partial_ratio * PARTIAL_SCALE, then partial_token_ratio * 0.95 * PARTIAL_SCALE; score_cutoff is raised after each step so later scorers can prune. +- partial_ratio slides the shorter string over the longer string (prefix windows, full-length windows, suffix windows), scoring each with normalized Indel similarity and keeping the best. +- Empty input or a None input returns 0. + +## Pros / strengths + +Precise, small and readable; shows the max-of-scaled-scorers pattern tai also uses. + +## Cons / limitations / caveats + +Designed for sentences and records, not identifiers. The docstring and code disagree for needles longer than 64 characters (noted by the fetch). + +## Relevance to tai + +Validated the 'max of several scaled signals' structure. Not ported: token_set semantics (a subset scores 100) would make `claude` tie with every `claude-*` id and hide real differences. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/github-rapidfuzz-readme.md b/research/fuzzy-matching-selectors/github-rapidfuzz-readme.md new file mode 100644 index 0000000..efd81fc --- /dev/null +++ b/research/fuzzy-matching-selectors/github-rapidfuzz-readme.md @@ -0,0 +1,28 @@ +# rapidfuzz/RapidFuzz repository + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | rapidfuzz/RapidFuzz repository | +| Source URL | https://github.com/rapidfuzz/RapidFuzz | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by RapidFuzz search; README examples of ratio and partial_ratio. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/github-swarn-fzy-lua-docs.md b/research/fuzzy-matching-selectors/github-swarn-fzy-lua-docs.md new file mode 100644 index 0000000..1915749 --- /dev/null +++ b/research/fuzzy-matching-selectors/github-swarn-fzy-lua-docs.md @@ -0,0 +1,28 @@ +# fzy-lua docs + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | fzy-lua docs | +| Source URL | https://github.com/swarn/fzy-lua/blob/main/docs/fzy.md | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by fzy search; Lua port documenting that higher scores are better. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/glama-edit-mcp-fuzzy-rs.md b/research/fuzzy-matching-selectors/glama-edit-mcp-fuzzy-rs.md new file mode 100644 index 0000000..ba9158f --- /dev/null +++ b/research/fuzzy-matching-selectors/glama-edit-mcp-fuzzy-rs.md @@ -0,0 +1,28 @@ +# edit-mcp fuzzy.rs (port of VS Code fuzzyScorer.ts) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | edit-mcp fuzzy.rs (port of VS Code fuzzyScorer.ts) | +| Source URL | https://glama.ai/mcp/servers/@mixelpixx/edit-mcp/blob/7cc4d0a4c37474fcdf56fd25f7325825f5bcf9a4/edit%20original/src/fuzzy.rs | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code scorer search; cites src/vs/base/common/fuzzyScorer.ts as origin, which was not itself found. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/hackage-fuzzyfind-docs.md b/research/fuzzy-matching-selectors/hackage-fuzzyfind-docs.md new file mode 100644 index 0000000..fb0bfbc --- /dev/null +++ b/research/fuzzy-matching-selectors/hackage-fuzzyfind-docs.md @@ -0,0 +1,28 @@ +# Text.FuzzyFind (Haskell fuzzyfind, Unison Computing) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Text.FuzzyFind (Haskell fuzzyfind, Unison Computing) | +| Source URL | https://hackage-origin.haskell.org/package/fuzzyfind-2.2.0/docs/Text-FuzzyFind.html | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by listed as a fzf-like matcher port in the fzf search. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/hexdocs-textmetrics.md b/research/fuzzy-matching-selectors/hexdocs-textmetrics.md new file mode 100644 index 0000000..b94ad15 --- /dev/null +++ b/research/fuzzy-matching-selectors/hexdocs-textmetrics.md @@ -0,0 +1,28 @@ +# textmetrics (Elixir) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | textmetrics (Elixir) | +| Source URL | https://textmetrics.hexdocs.pm/0.1.1/ | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance comparison search. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/jsr-optique-suggestion.md b/research/fuzzy-matching-selectors/jsr-optique-suggestion.md new file mode 100644 index 0000000..910d1b4 --- /dev/null +++ b/research/fuzzy-matching-selectors/jsr-optique-suggestion.md @@ -0,0 +1,28 @@ +# Optique suggestion docs + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Optique suggestion docs | +| Source URL | https://jsr.io/@optique/core/doc/suggestion/ | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by did-you-mean search; caps distance ratio for short inputs. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/kde-kate-improve-fuzzy-matching-mr.md b/research/fuzzy-matching-selectors/kde-kate-improve-fuzzy-matching-mr.md new file mode 100644 index 0000000..bfe085e --- /dev/null +++ b/research/fuzzy-matching-selectors/kde-kate-improve-fuzzy-matching-mr.md @@ -0,0 +1,28 @@ +# Kate: Improve fuzzy matching (merge request 910) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Kate: Improve fuzzy matching (merge request 910) | +| Source URL | https://invent.kde.org/utilities/kate/-/merge_requests/910 | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code scorer search; an editor changing its fuzzy matcher. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/kitty-tools-fzf-algo-go.md b/research/fuzzy-matching-selectors/kitty-tools-fzf-algo-go.md new file mode 100644 index 0000000..258d796 --- /dev/null +++ b/research/fuzzy-matching-selectors/kitty-tools-fzf-algo-go.md @@ -0,0 +1,28 @@ +# kitty tools/fzf/algo.go + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | kitty tools/fzf/algo.go | +| Source URL | https://git.wessel.gg/mirrors/kitty/raw/branch/l2/tools/fzf/algo.go | +| Source type | repository mirror | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by shows kitty vendors the fzf algorithm; same family as the fzf source. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/llvm-clangd-fuzzymatch.md b/research/fuzzy-matching-selectors/llvm-clangd-fuzzymatch.md new file mode 100644 index 0000000..a066c7e --- /dev/null +++ b/research/fuzzy-matching-selectors/llvm-clangd-fuzzymatch.md @@ -0,0 +1,51 @@ +# LLVM clangd FuzzyMatch (header comments) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | LLVM clangd FuzzyMatch (header comments) | +| Source URL | https://raw.githubusercontent.com/llvm/llvm-project/main/clang-tools-extra/clangd/FuzzyMatch.cpp | +| Source publisher / author | LLVM project | +| Source type | repository | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | Medium | +| Status | Active | + +## Summary + +Design notes of clangd's code-completion fuzzy matcher. The fetch returned the header-style documentation of the algorithm (FuzzyMatch.h content) for this URL. + +## Key findings + +- The matcher fills a DP grid of pattern positions against word positions: moving right consumes a word char, diagonal consumes both only when they match, down is never legal. +- Characters get roles (Head, Tail, Separator) from neighbor types; the first pattern char may only match the start of a word segment. +- Scoring: PerfectBonus 4 per pattern char, normalized by pattern length to 0..1, doubled when word length equals pattern length; match bonuses: +1 same case or Head on Head, +2 consecutive or first char, -3 matching inside a Tail after a gap, -1 Head on Tail, -4 first pattern char mid-segment; skip penalty 3 for skipping the first word char, 1 for a Head. +- No separate case-mismatch penalty; lowercase-only queries ignore case. Forbidden matches keep false positives low (e.g. [foo] does not match barefoot). +- Stated goals: mostly compatible with VS Code client-side filtering, initials and word-boundary matches such as u_p to unique_ptr, and low false positives. +- Known limit: some words are hard to tokenize algorithmically (vsprintf). + +## Pros / strengths + +Strong evidence that normalizing the score by pattern length and doubling the exact-length case is the practice in editors. + +## Cons / limitations / caveats + +ASCII only (bytes above 127 treated as lowercase). The fetched text was header documentation, not the .cpp body, so MaxPat and MaxWord are unknown. + +## Relevance to tai + +Supports normalizing `Subsequence` by a perfect score and capping at 1; the same ASCII-only limit applies to tai and is documented in docs/fuzzy.md. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/mankier-rapidfuzz-man.md b/research/fuzzy-matching-selectors/mankier-rapidfuzz-man.md new file mode 100644 index 0000000..87a61ed --- /dev/null +++ b/research/fuzzy-matching-selectors/mankier-rapidfuzz-man.md @@ -0,0 +1,28 @@ +# rapidfuzz man page + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | rapidfuzz man page | +| Source URL | https://mankier.com/3/rapidfuzz | +| Source type | vendor docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by RapidFuzz search; C++ API man page. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/matil-approximate-string-matching.md b/research/fuzzy-matching-selectors/matil-approximate-string-matching.md new file mode 100644 index 0000000..7531b49 --- /dev/null +++ b/research/fuzzy-matching-selectors/matil-approximate-string-matching.md @@ -0,0 +1,28 @@ +# Approximate string matching (matil.ai blog) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Approximate string matching (matil.ai blog) | +| Source URL | https://matil.ai/en/blog/approximate-string-matching | +| Source type | blog post | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance comparison search; practical overview. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/medium-levenshtein-go-clis.md b/research/fuzzy-matching-selectors/medium-levenshtein-go-clis.md new file mode 100644 index 0000000..12c5b60 --- /dev/null +++ b/research/fuzzy-matching-selectors/medium-levenshtein-go-clis.md @@ -0,0 +1,28 @@ +# Implementing Levenshtein distance in Go CLIs + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Implementing Levenshtein distance in Go CLIs | +| Source URL | https://prabeshthapa.medium.com/from-frustrating-typos-to-smart-suggestions-implementing-levenshtein-distance-in-go-clis-3708c0a3b4e1 | +| Source type | blog post | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by did-you-mean search; author reports Jaccard ignores order and failed on single-character typos. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/npm-didyoumean.md b/research/fuzzy-matching-selectors/npm-didyoumean.md new file mode 100644 index 0000000..37c6235 --- /dev/null +++ b/research/fuzzy-matching-selectors/npm-didyoumean.md @@ -0,0 +1,28 @@ +# didYouMean.js (npm) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | didYouMean.js (npm) | +| Source URL | https://npmjs.com/package/didyoumean | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by did-you-mean search; Levenshtein, default threshold 0.4 of length. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/npm-fuzzzzz.md b/research/fuzzy-matching-selectors/npm-fuzzzzz.md new file mode 100644 index 0000000..ba85216 --- /dev/null +++ b/research/fuzzy-matching-selectors/npm-fuzzzzz.md @@ -0,0 +1,28 @@ +# fuzzzzz (npm) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | fuzzzzz (npm) | +| Source URL | https://www.npmjs.com/package/fuzzzzz | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code scorer search; an npm fuzzy string matcher. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/npm-fzy-js.md b/research/fuzzy-matching-selectors/npm-fzy-js.md new file mode 100644 index 0000000..94b08b9 --- /dev/null +++ b/research/fuzzy-matching-selectors/npm-fzy-js.md @@ -0,0 +1,28 @@ +# fzy.js (npm) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | fzy.js (npm) | +| Source URL | https://www.npmjs.com/package/fzy.js | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by fzy search; JavaScript port, example scores amuser vs app/models/user.rb 5.595. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/pfzy-readthedocs-usage.md b/research/fuzzy-matching-selectors/pfzy-readthedocs-usage.md new file mode 100644 index 0000000..7adee9f --- /dev/null +++ b/research/fuzzy-matching-selectors/pfzy-readthedocs-usage.md @@ -0,0 +1,28 @@ +# pfzy usage (Python fzy port) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | pfzy usage (Python fzy port) | +| Source URL | https://pfzy.readthedocs.io/en/latest/_sources/pages/usage.md.txt | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by fzy search; a Python fzy-style matcher. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/pigsty-ext-pg-trgm.md b/research/fuzzy-matching-selectors/pigsty-ext-pg-trgm.md new file mode 100644 index 0000000..f6b8b40 --- /dev/null +++ b/research/fuzzy-matching-selectors/pigsty-ext-pg-trgm.md @@ -0,0 +1,28 @@ +# Pigsty pg_trgm extension page + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Pigsty pg_trgm extension page | +| Source URL | https://ext.pigsty.io/e/pg_trgm/ | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by pg_trgm search. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/polytreede-rofi-fuzzy-commit.md b/research/fuzzy-matching-selectors/polytreede-rofi-fuzzy-commit.md new file mode 100644 index 0000000..d09c580 --- /dev/null +++ b/research/fuzzy-matching-selectors/polytreede-rofi-fuzzy-commit.md @@ -0,0 +1,28 @@ +# rofi fuzzy matching commit diff + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | rofi fuzzy matching commit diff | +| Source URL | https://git.causa-arcana.com/PolytreeDE/rofi/commit/4452b08288c25fa833e22c97b57045ec5888b547.diff | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code scorer search; another launcher touching its fuzzy matcher. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/postgresql-docs-pgtrgm-v18.md b/research/fuzzy-matching-selectors/postgresql-docs-pgtrgm-v18.md new file mode 100644 index 0000000..b6486b8 --- /dev/null +++ b/research/fuzzy-matching-selectors/postgresql-docs-pgtrgm-v18.md @@ -0,0 +1,28 @@ +# PostgreSQL 18 pg_trgm docs + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | PostgreSQL 18 pg_trgm docs | +| Source URL | https://www.postgresql.org/docs/18/pgtrgm.html | +| Source type | vendor docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by pg_trgm search; version-pinned copy of the page read as Tier 1. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/postgresql-pg-trgm.md b/research/fuzzy-matching-selectors/postgresql-pg-trgm.md new file mode 100644 index 0000000..e5ab9fe --- /dev/null +++ b/research/fuzzy-matching-selectors/postgresql-pg-trgm.md @@ -0,0 +1,49 @@ +# PostgreSQL pg_trgm documentation + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | PostgreSQL pg_trgm documentation | +| Source URL | https://www.postgresql.org/docs/current/pgtrgm.html | +| Source publisher / author | PostgreSQL Global Development Group | +| Source type | vendor docs | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | High | +| Status | Active | + +## Summary + +The documentation of PostgreSQL's trigram similarity module, the reference definition of trigram matching. + +## Key findings + +- A trigram is three consecutive characters; non-alphanumerics are ignored and each word is padded with two leading spaces and one trailing space: 'cat' gives ' c', ' ca', 'cat', 'at '. +- similarity() ranges from 0 to 1. The page gives no formula, but its example (word vs words: 4 shared of 7 distinct trigrams gives 0.571429) is consistent with Jaccard. +- word_similarity() is the greatest similarity between the first string and any continuous extent of the second (word_similarity('word','two words') = 0.8); strict_word_similarity forces word boundaries. +- Default thresholds: % uses 0.3, <% uses 0.6, <<% uses 0.5. + +## Pros / strengths + +Authoritative, with worked examples that make a test oracle (4/7). + +## Cons / limitations / caveats + +Formula inferred from an example rather than stated. Tuned for database indexing, defaults are not calibrated for identifier ids. + +## Relevance to tai + +Defines `FuzzyMatcher::Trigram` (padding and Jaccard). The test asserts the 4/7 example. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/pyorps-difflib-source.md b/research/fuzzy-matching-selectors/pyorps-difflib-source.md new file mode 100644 index 0000000..7443b84 --- /dev/null +++ b/research/fuzzy-matching-selectors/pyorps-difflib-source.md @@ -0,0 +1,28 @@ +# difflib source code (pyorps docs) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | difflib source code (pyorps docs) | +| Source URL | https://pyorps.readthedocs.io/en/latest/_modules/difflib.html | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by difflib search; source showing _calculate_ratio = 2.0*matches/length. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/python-docs-difflib.md b/research/fuzzy-matching-selectors/python-docs-difflib.md new file mode 100644 index 0000000..e1bd3f9 --- /dev/null +++ b/research/fuzzy-matching-selectors/python-docs-difflib.md @@ -0,0 +1,50 @@ +# difflib - Helpers for computing deltas (Python docs) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | difflib - Helpers for computing deltas (Python docs) | +| Source URL | https://docs.python.org/3/library/difflib.html | +| Source publisher / author | Python Software Foundation | +| Source type | vendor docs | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | High | +| Status | Active | + +## Summary + +Official documentation of SequenceMatcher (Ratcliff/Obershelp variant) and get_close_matches. + +## Key findings + +- ratio() = 2.0*M/T; over 0.6 is a rule of thumb for close matches; quick_ratio and real_quick_ratio are upper bounds. +- autojunk: for a second sequence of 200+ items, items above 1% frequency are junk; asymmetric. +- get_close_matches(word, possibilities, n=3, cutoff=0.6) returns the best matches sorted by score, most similar first. +- The algorithm finds the longest contiguous common block then recurses left and right; the docs say the base algorithm is cubic worst case, quadratic expected, and difflib's is quadratic worst case. +- Examples: get_close_matches('appel', ['ape','apple','peach','puppy']) = ['apple','ape']. + +## Pros / strengths + +Canonical stdlib reference with a published cutoff default (0.6). + +## Cons / limitations / caveats + +Gestalt matching has no boundary or camelCase awareness and is asymmetric in its junk heuristic. The result is a heuristic, not a minimal edit. + +## Relevance to tai + +tai already contains `sequencematcher.cpp`; it was evaluated and not used for ids because it lacks boundary bonuses. The 0.6 cutoff default informed choosing a documented floor (tai: 0.3, lower on purpose, see the conclusion). + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/racket-fuzzy-search-package.md b/research/fuzzy-matching-selectors/racket-fuzzy-search-package.md new file mode 100644 index 0000000..837fe1e --- /dev/null +++ b/research/fuzzy-matching-selectors/racket-fuzzy-search-package.md @@ -0,0 +1,28 @@ +# Racket fuzzy-search package + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Racket fuzzy-search package | +| Source URL | https://pkgs.racket-lang.org/package/fuzzy-search | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by Sublime search result; described as based on the reverse engineering post. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/slackbuilds-fzy-package.md b/research/fuzzy-matching-selectors/slackbuilds-fzy-package.md new file mode 100644 index 0000000..b349e09 --- /dev/null +++ b/research/fuzzy-matching-selectors/slackbuilds-fzy-package.md @@ -0,0 +1,28 @@ +# SlackBuilds fzy package page + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | SlackBuilds fzy package page | +| Source URL | https://SlackBuilds.org/repository/15.0/system/fzy/ | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by fzy search; package description quotes the fzy README. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/source-dot-net-ml-tokenizers-model-encoding.md b/research/fuzzy-matching-selectors/source-dot-net-ml-tokenizers-model-encoding.md new file mode 100644 index 0000000..8826903 --- /dev/null +++ b/research/fuzzy-matching-selectors/source-dot-net-ml-tokenizers-model-encoding.md @@ -0,0 +1,28 @@ +# Microsoft.ML.Tokenizers ModelEncoding references + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Microsoft.ML.Tokenizers ModelEncoding references | +| Source URL | https://source.dot.net/Microsoft.ML.Tokenizers/R/0f3355db9aa70df8.html | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by model-id search; ordered prefix table with trailing hyphen entries. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/stx-fuzzymatcher-smalltalk.md b/research/fuzzy-matching-selectors/stx-fuzzymatcher-smalltalk.md new file mode 100644 index 0000000..94de2ca --- /dev/null +++ b/research/fuzzy-matching-selectors/stx-fuzzymatcher-smalltalk.md @@ -0,0 +1,28 @@ +# Smalltalk/X FuzzyMatcher.st + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Smalltalk/X FuzzyMatcher.st | +| Source URL | https://swing.fit.cvut.cz/hg/stx-libbasic2/comparison/3961346f8256/FuzzyMatcher.st | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by Sublime search result; a port referencing lib_fts. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/svn-python-difflib-r23.md b/research/fuzzy-matching-selectors/svn-python-difflib-r23.md new file mode 100644 index 0000000..8d1f72e --- /dev/null +++ b/research/fuzzy-matching-selectors/svn-python-difflib-r23.md @@ -0,0 +1,28 @@ +# CPython difflib.py (svn tag r23) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | CPython difflib.py (svn tag r23) | +| Source URL | https://svn.python.org/projects/python/tags/r23/Lib/difflib.py | +| Source type | repository | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by difflib search; historical source. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/swiftpackageregistry-ordo-one-fuzzymatch.md b/research/fuzzy-matching-selectors/swiftpackageregistry-ordo-one-fuzzymatch.md new file mode 100644 index 0000000..524375f --- /dev/null +++ b/research/fuzzy-matching-selectors/swiftpackageregistry-ordo-one-fuzzymatch.md @@ -0,0 +1,28 @@ +# ordo-one FuzzyMatch (Swift) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | ordo-one FuzzyMatch (Swift) | +| Source URL | https://swiftpackageregistry.com/ordo-one/FuzzyMatch | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by listed in the fzf search as another fuzzy matching package. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/tessl-rapidfuzz-fuzzy-matching-docs.md b/research/fuzzy-matching-selectors/tessl-rapidfuzz-fuzzy-matching-docs.md new file mode 100644 index 0000000..d75faa1 --- /dev/null +++ b/research/fuzzy-matching-selectors/tessl-rapidfuzz-fuzzy-matching-docs.md @@ -0,0 +1,28 @@ +# RapidFuzz fuzzy matching docs (tessl) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | RapidFuzz fuzzy matching docs (tessl) | +| Source URL | https://tessl.io/registry/tessl/pypi-rapidfuzz/files/docs/fuzzy-matching.md | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by RapidFuzz search; token_set_ratio returns 100 when one string is a subset of the other. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/tessl-strsim-phonetic-record-linkage.md b/research/fuzzy-matching-selectors/tessl-strsim-phonetic-record-linkage.md new file mode 100644 index 0000000..8b0ea38 --- /dev/null +++ b/research/fuzzy-matching-selectors/tessl-strsim-phonetic-record-linkage.md @@ -0,0 +1,28 @@ +# strsim phonetic record linkage docs (tessl) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | strsim phonetic record linkage docs (tessl) | +| Source URL | https://tessl.io/registry/tessl/pypi-strsim/files/docs/phonetic-record-linkage.md | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance comparison search. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/tothenew-llm-naming-conventions.md b/research/fuzzy-matching-selectors/tothenew-llm-naming-conventions.md new file mode 100644 index 0000000..18419c0 --- /dev/null +++ b/research/fuzzy-matching-selectors/tothenew-llm-naming-conventions.md @@ -0,0 +1,28 @@ +# Naming Conventions of LLM Models + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Naming Conventions of LLM Models | +| Source URL | https://www.tothenew.com/blog/naming-conventions-of-llm-models/ | +| Source type | blog post | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by model-id search; overview of how model ids are composed. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/uspto-patent-10380248-acronym-domain.md b/research/fuzzy-matching-selectors/uspto-patent-10380248-acronym-domain.md new file mode 100644 index 0000000..96bf726 --- /dev/null +++ b/research/fuzzy-matching-selectors/uspto-patent-10380248-acronym-domain.md @@ -0,0 +1,28 @@ +# Acronym identification in domain names (US patent 10380248) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Acronym identification in domain names (US patent 10380248) | +| Source URL | https://image-ppubs.uspto.gov/dirsearch-public/print/downloadPdf/10380248 | +| Source type | patent | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance search; the listing said Jaro-Winkler is a variation of Damerau-Levenshtein, which is inaccurate and was disregarded. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/uspto-patent-9747274-string-comparison.md b/research/fuzzy-matching-selectors/uspto-patent-9747274-string-comparison.md new file mode 100644 index 0000000..4b01dba --- /dev/null +++ b/research/fuzzy-matching-selectors/uspto-patent-9747274-string-comparison.md @@ -0,0 +1,28 @@ +# String comparison results using frequency data (US patent 9747274) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | String comparison results using frequency data (US patent 9747274) | +| Source URL | https://image-ppubs.uspto.gov/dirsearch-public/print/downloadPdf/9747274 | +| Source type | patent | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by edit-distance search; notes a single transposition can join unrelated short names. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/vegastrike-difflib-sequencematcher.md b/research/fuzzy-matching-selectors/vegastrike-difflib-sequencematcher.md new file mode 100644 index 0000000..4a502f5 --- /dev/null +++ b/research/fuzzy-matching-selectors/vegastrike-difflib-sequencematcher.md @@ -0,0 +1,28 @@ +# Vega Strike difflib SequenceMatcher class reference + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Vega Strike difflib SequenceMatcher class reference | +| Source URL | https://vegastrikevo.sourceforge.net/documentation/python/html/classdifflib_1_1_sequence_matcher.html | +| Source type | library docs | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by difflib search. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/vim-patch-8-2-1893-multiword-fuzzy.md b/research/fuzzy-matching-selectors/vim-patch-8-2-1893-multiword-fuzzy.md new file mode 100644 index 0000000..73ce815 --- /dev/null +++ b/research/fuzzy-matching-selectors/vim-patch-8-2-1893-multiword-fuzzy.md @@ -0,0 +1,28 @@ +# Vim patch 8.2.1893 fuzzy matching with multiple words + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Vim patch 8.2.1893 fuzzy matching with multiple words | +| Source URL | https://gerrit.omnirom.org/plugins/gitiles/android_external_vim/+/8ded5b647aa4b3338da721b343e0bce0f86655f6%5E%21 | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code scorer search; Vim's fuzzy matcher gaining multiple-word support. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/vim-patch-9-1-1046-camelcase-fuzzy.md b/research/fuzzy-matching-selectors/vim-patch-9-1-1046-camelcase-fuzzy.md new file mode 100644 index 0000000..c9a8ff9 --- /dev/null +++ b/research/fuzzy-matching-selectors/vim-patch-9-1-1046-camelcase-fuzzy.md @@ -0,0 +1,28 @@ +# Vim patch 9.1.1046 fuzzymatching prefers camelcase + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | Vim patch 9.1.1046 fuzzymatching prefers camelcase | +| Source URL | https://gerrit.omnirom.org/plugins/gitiles/android_external_vim/+/9dfc7e5e6169594f6f4607ef1ba9dd347a9194d2%5E%21 | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by VS Code scorer search; Vim adding camelCase preference, consistent with boundary bonuses. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/research/fuzzy-matching-selectors/wikipedia-damerau-levenshtein-distance.md b/research/fuzzy-matching-selectors/wikipedia-damerau-levenshtein-distance.md new file mode 100644 index 0000000..835ee3f --- /dev/null +++ b/research/fuzzy-matching-selectors/wikipedia-damerau-levenshtein-distance.md @@ -0,0 +1,50 @@ +# Damerau-Levenshtein distance (Wikipedia) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | Damerau-Levenshtein distance (Wikipedia) | +| Source URL | https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance | +| Source publisher / author | Wikipedia contributors | +| Source type | other | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | Medium | +| Status | Active | + +## Summary + +Encyclopedia definition of edit distance with adjacent transpositions, and the restricted (optimal string alignment) variant. + +## Key findings + +- Optimal string alignment (restricted) forbids editing a substring more than once; true Damerau-Levenshtein has no such limit. Example: CA to ABC is 2 true, 3 restricted. +- The restricted distance does not satisfy the triangle inequality, so it is not a true metric (matters for metric trees, not for ranking). +- Damerau reported that over 80% of misspellings come from a single insertion, deletion, substitution or transposition. +- Restricted algorithm extends Wagner-Fischer, O(M*N); the true distance is O(M*N) at best via Lowrance-Wagner but needs the alphabet size. +- For natural-language words, errors rarely exceed two, so restricted and true distances seldom differ. + +## Pros / strengths + +Justifies using the cheaper restricted variant for short words. + +## Cons / limitations / caveats + +The 80% figure is attributed to Damerau's study, not independently checked. + +## Relevance to tai + +`FuzzyMatcher::Typo` is the restricted variant (swap counts 1), chosen because model and agent ids are short words. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/wikipedia-jaro-winkler-distance.md b/research/fuzzy-matching-selectors/wikipedia-jaro-winkler-distance.md new file mode 100644 index 0000000..4e5c0a9 --- /dev/null +++ b/research/fuzzy-matching-selectors/wikipedia-jaro-winkler-distance.md @@ -0,0 +1,49 @@ +# Jaro-Winkler distance (Wikipedia) + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Dossier | +| Tier | 1 | +| Source title | Jaro-Winkler distance (Wikipedia) | +| Source URL | https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance | +| Source publisher / author | Wikipedia contributors | +| Source type | other | +| Source date (published) | Unknown — not stated | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | direct page fetch | +| Confidence | Medium | +| Status | Active | + +## Summary + +Encyclopedia definition of the Jaro and Jaro-Winkler string similarity measures, used in record linkage. + +## Key findings + +- Jaro: sim = 1/3 * (m/|s1| + m/|s2| + (m - t)/m), with m matching characters within a window of floor(max(|s1|,|s2|)/2) - 1, and t half the out-of-order matches. +- Winkler: sim_w = sim_j + l*p*(1 - sim_j), l the common prefix capped at 4, p = 0.1 standard and not above 0.25. +- The page notes it is not a metric in the mathematical sense (no triangle inequality; d(x,y) = 0 does not imply x = y). +- Origin: record linkage (1985 Tampa census records, Fellegi-Sunter). The page does not mention a 0.7 boost threshold. + +## Pros / strengths + +Clear formulas. + +## Cons / limitations / caveats + +No guidance for identifiers; a shared prefix boosts the score, which a separate search listing (see the Tier 2 cards of this subject) flags as misleading for ids that share a generic prefix. That claim was not verified in a primary source. + +## Relevance to tai + +Considered and not adopted: every `claude-*` id shares a long prefix, so the prefix boost would flatten exactly the differences users type (opus vs sonnet). + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Initial dossier created. | diff --git a/research/fuzzy-matching-selectors/xcmd-didyoumean-install.md b/research/fuzzy-matching-selectors/xcmd-didyoumean-install.md new file mode 100644 index 0000000..a6fd893 --- /dev/null +++ b/research/fuzzy-matching-selectors/xcmd-didyoumean-install.md @@ -0,0 +1,28 @@ +# x-cmd didyoumean + +## Metadata + +| Field | Value | +|---|---| +| Institute | Molodetz Institute — Research & Retrieval Division | +| Subject | fuzzy-matching-selectors | +| Document type | Source Citation Card | +| Tier | 2 | +| Source title | x-cmd didyoumean | +| Source URL | https://x-cmd.com/install/didyoumean/ | +| Source type | other | +| Date researched | 2026-10-25 | +| Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | +| Research method | Search aggregator result listing (not independently opened or quoted) | +| Confidence | Not independently verified | +| Status | Active | + +## Why it's here + +Surfaced by did-you-mean search; Rust tool using Damerau-Levenshtein. + +## Changelog + +| Date | Change | +|---|---| +| 2026-10-25 | Logged as consulted-but-not-analyzed. | diff --git a/taicpp/CHANGELOG.md b/taicpp/CHANGELOG.md index 8c9365c..b8f70dd 100644 --- a/taicpp/CHANGELOG.md +++ b/taicpp/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] - 2026-10-25 +- Generic `detz::FuzzyMatcher` (src/util/fuzzy.*, docs/fuzzy.md, tests/fuzzy_test.cpp): subsequence DP with fzf constants, Damerau-Levenshtein, per-token typo and trigram signals; `@agent` / `@model` mentions now always pick the most related candidate (floor 0.3, ties shorter then earlier, a note shows what was resolved). Research: research/fuzzy-matching-selectors. - `@agent`, `@agent:model` and bare `@model` mentions resolve against CAP agents via the new `CapModelCatalog` (manifest `[models]` list or `--cap-models` probe); turns run through `CapMention`; failures print a per-agent report of status, binary and models. Test: `build/capmcp/cap_model_catalog_test`. Spec: docs/cap.md. - Build and test integration after the parallel module ports. Not released: `make test` is not green (see below), so VERSION stays 1.44.2 and no tag was made. - Makefile rewritten: objects in `build/obj`, archive `build/libdetz.a` (no main.cpp), one test binary per delivered test source (foundation, store, cli, util/config, agent diff/e2e/sched, shell, cap/mcp unit/probe/cli, misc, tui spec/shot/script/rows/chat/grr, mplex x11), `make test`, `make lint` (real `-Werror -fsyntax-only` over src, main.cpp and tests, no `|| true`), `make parity`, `make parity-check` (docs/wiring/verify.md). diff --git a/taicpp/Makefile b/taicpp/Makefile index 63de422..24c566a 100644 --- a/taicpp/Makefile +++ b/taicpp/Makefile @@ -87,6 +87,7 @@ $(eval $(call TESTBIN,$(BUILD)/capmcp/capmcp_unit,tests/capmcp_unit.cpp)) $(eval $(call TESTBIN,$(BUILD)/capmcp/capmcp_probe,tests/capmcp_probe.cpp)) $(eval $(call TESTBIN,$(BUILD)/capmcp/capmcp_cli,tests/capmcp_cli.cpp)) $(eval $(call TESTBIN,$(BUILD)/capmcp/cap_model_catalog_test,tests/cap_model_catalog_test.cpp)) +$(eval $(call TESTBIN,$(BUILD)/fuzzy_test,tests/fuzzy_test.cpp)) # misc (skills, sched, telegram, rag, install) $(eval $(call TESTBIN,$(BUILD)/misc/misc_test,tests/misc_test.cpp,$(MISC_INC))) $(eval $(call TESTBIN,$(BUILD)/misc/misc_driver,tests/misc_driver.cpp,$(MISC_INC))) @@ -153,7 +154,7 @@ release-dry: PART=$(PART) MSG="$(MSG)" sh -n tools/release.sh && echo "release dry-run OK" docs: - @for f in agent cap mcp tui mplex shell store util llm misc foundation parity; do test -f docs/$$f.md || { echo "missing docs/$$f.md"; exit 1; }; done; echo "docs OK" + @for f in agent cap fuzzy mcp tui mplex shell store util llm misc foundation parity; do test -f docs/$$f.md || { echo "missing docs/$$f.md"; exit 1; }; done; echo "docs OK" clean: rm -rf $(BUILD) diff --git a/taicpp/docs/cap.md b/taicpp/docs/cap.md index 2cf3ba1..7ae6fdc 100644 --- a/taicpp/docs/cap.md +++ b/taicpp/docs/cap.md @@ -90,7 +90,7 @@ hooks: `docs/wiring/capmcp.md`. Model protocol (tai-specific extension, absent from the Python app): `[models] list = [...]` and `default` in the manifest, plus an optional probe, `[models] probe = ["bin", "--cap-models"]` or `capabilities.models = true` for ` --cap-models`. Probe output: JSON array of ids or `{id|name|model}` objects, `{"models": [...], "default": "..."}`, or one id per line. Bounds: `CAP_MODEL_PROBE_TIMEOUT` 5 s, `CAP_MODEL_PROBE_MAX_BYTES` 1 MiB, `CAP_MODEL_MAX` 500 per agent. A failed probe is recorded on the entry, never thrown. Tai's own manifest is unchanged and declares no models. -Mention grammar (after bots, providers, backends, known model ids): `@agent`, `@agent:model` (split at the first `:`), or a bare model id found on exactly one ready agent. Model matching: exact, case-insensitive exact, unique prefix, unique substring; ambiguity is an error. An agent that publishes no list passes the model through with a note. With a prompt the turn runs on that agent (`CapMention`, one cached `CapSession` per agent/model/cwd, model sent via `startup.model_arg`); without a prompt the agent is pinned (`Agent::agent_pin`) until `@auto`. Every failure prints the reason plus the full per-agent report (status, binary, manifest, binding, models, default, probe error); an unknown tag appends the same report. +Mention resolution (after bots, providers, backends, known model ids, `@auto`, exact agent, and file paths; so a real file is never shadowed): the typed text is fuzzy-matched with `FuzzyMatcher` ([fuzzy.md](fuzzy.md)). Order: exact agent name or model id; `head:tail` splits at the first `:`, head fuzzy to a ready agent, tail fuzzy to that agent's models (no floor, the agent already disambiguates); otherwise the whole text is ranked against every ready agent name and every model. Cross-namespace picks need a score of `CAP_FUZZY_FLOOR` (0.3); the best score wins and equal scores resolve shorter id then list order, never an error. Unusable agents (no binary, bad manifest) are considered only when no ready candidate reaches the floor and then report why. An agent without a model list passes the model through with a note. When the pick differs from the typed text a note line shows the resolution and score. With a prompt the turn runs on that agent (`CapMention`, one cached `CapSession` per agent/model/cwd, model sent via `startup.model_arg`); without a prompt the agent is pinned (`Agent::agent_pin`) until `@auto`. A miss prints the closest candidate with its score and the full per-agent report (status, binary, manifest, binding, models, default, probe error); an unknown tag appends the same report. Gaps: no TUI completion for agents yet; `tai --cap-models` is not implemented on tai's own side. diff --git a/taicpp/docs/fuzzy.md b/taicpp/docs/fuzzy.md new file mode 100644 index 0000000..6daaffa --- /dev/null +++ b/taicpp/docs/fuzzy.md @@ -0,0 +1,44 @@ +# FuzzyMatcher (src/util/fuzzy.h) + +The one generic "which candidate did the user mean" scorer. Research: `research/fuzzy-matching-selectors/` (conclusions `generic-fuzzy-scorer-design`, `agent-model-mention-resolution`). Used today by `CapModelCatalog` for `@agent` / `@agent:model` mentions. + +## Contract + +`Score(query, text)` is in [0, 1]. 1 only for a case-insensitive exact match, 0 when nothing relates, never above 0.999 otherwise. It is the max of four signals: + +| Signal | Definition | Scale | +|---|---|---| +| `Subsequence` | query bytes in order inside text; DP with D = best ending in a match, M = best prefix (fzy recurrences); bonuses by the byte before the match; normalized by the perfect score `16n + 20 + 4(n-1)` | 1.0 | +| `Typo` | 1 - restricted Damerau-Levenshtein distance / max(len); insert, delete, substitute, adjacent swap cost 1 | 1.0 | +| `TokenTypo` | best `Typo` against each alphanumeric token of the text | 0.95 | +| `Trigram` | pg_trgm: per word two leading and one trailing space, Jaccard over trigram sets | 0.9 | + +## Constants (fzf algo.go) + +| Name | Value | Meaning | +|---|---|---| +| `kScoreMatch` | 16 | per matched byte | +| `kGap` | -1 | per skipped byte (leading, inner, trailing) | +| `kBonusBoundary` | 8 | match after a non-alphanumeric byte | +| `kBonusWhite` | 10 | match after whitespace or at the start | +| `kBonusCamel` | 7 | lower to Upper, or non-digit to digit | +| `kBonusConsecutive` | 4 | minimum bonus for a run | +| `kFirstMultiplier` | 2 | first query byte bonus factor | +| `kTrigramScale` / `kTokenScale` | 0.9 / 0.95 | signal scales | + +## Ranking and ceilings + +`Rank` returns candidates with score > 0: score descending, then shorter text, then earlier index (fzf default tie rule). Equal matches never error. `Best(query, texts, floor)` returns the top index above `floor` or -1. + +| Ceiling | Value | +|---|---| +| `kFuzzyQueryMax` | 64 bytes | +| `kFuzzyTextMax` | 256 bytes | +| `kFuzzyCandidateMax` | 4096 candidates | + +## Gaps + +- ASCII case folding only; non-ASCII bytes compare as-is. +- Constants are fzf's (paths), not tuned on model ids; no measurement on a real id corpus yet. +- The CAP floor (`CAP_FUZZY_FLOOR` 0.3) is a judgment, not a measurement. +- Tests: `tests/fuzzy_test.cpp` (`build/fuzzy_test`). diff --git a/taicpp/src/agent/agent.cpp b/taicpp/src/agent/agent.cpp index b4d429d..7f9d3c3 100644 --- a/taicpp/src/agent/agent.cpp +++ b/taicpp/src/agent/agent.cpp @@ -666,7 +666,7 @@ ResolvedTag Agent::resolve_tag(const std::string& tag, const std::string& direct auto& catalog = cap_mention().catalog(); if (catalog.isAgent(word)) { auto sel = catalog.resolve(word); - if (sel.ok) { return {"agent", sel.agent, sel.model}; } + if (sel.ok) { mention_note = sel.note; return {"agent", sel.agent, sel.model}; } return {"agent_error", sel.error, ""}; } } catch (...) { @@ -677,7 +677,7 @@ ResolvedTag Agent::resolve_tag(const std::string& tag, const std::string& direct if (!core.empty() && path_exists(path)) { return {"file", abspath(path), ""}; } try { auto sel = cap_mention().catalog().resolve(word); - if (sel.ok) { return {"agent", sel.agent, sel.model}; } + if (sel.ok) { mention_note = sel.note; return {"agent", sel.agent, sel.model}; } } catch (...) { } return {"unknown", word, ""}; @@ -697,7 +697,12 @@ std::pair, std::optional> Agent::apply_m if (loud) { print_out(r.value); } return {text, r.value}; } - if (r.kind == "agent") { sel_agent = std::make_pair(r.value, r.value2); continue; } + if (r.kind == "agent") { + sel_agent = std::make_pair(r.value, r.value2); + if (loud && !mention_note.empty()) { print_out(mention_note); } + mention_note.clear(); + continue; + } if (r.kind == "unknown") { std::string error = mention_error(tag); if (loud) { print_out(error); } diff --git a/taicpp/src/agent/agent.h b/taicpp/src/agent/agent.h index c1ee538..a010c6c 100644 --- a/taicpp/src/agent/agent.h +++ b/taicpp/src/agent/agent.h @@ -111,6 +111,7 @@ class Agent { detz::cap::CapMention& cap_mention(); std::string run_agent(const std::string& agent, const std::string& model, const std::string& rest, const std::string& original, bool loud = false); + std::string mention_note; // set by resolve_tag when a fuzzy pick differs from the typed text std::string agent_pin; // "agent[:model]" pinned by a bare mention, cleared by @auto // ---- the loop ---- diff --git a/taicpp/src/cap/cap_model_catalog.cpp b/taicpp/src/cap/cap_model_catalog.cpp index c47b590..0006f0d 100644 --- a/taicpp/src/cap/cap_model_catalog.cpp +++ b/taicpp/src/cap/cap_model_catalog.cpp @@ -1,9 +1,11 @@ #include "cap_model_catalog.h" #include +#include #include #include +#include "../util/fuzzy.h" #include "cap_error.h" #include "wire_os.h" #include "wire_proc.h" @@ -201,81 +203,98 @@ bool CapModelCatalog::isAgent(const std::string& spec) const { return !findName(colon == std::string::npos ? spec : spec.substr(0, colon)).empty(); } -// Exact, case-insensitive exact, unique prefix, unique substring. Returns the -// id, or "" with `problem` set (ambiguity or no match). -std::string CapModelCatalog::match(const CapAgentEntry& e, const std::string& want, std::string& problem) { - problem.clear(); - for (const std::string& id : e.models) if (id == want) return id; - std::string w = lower(want); - for (const std::string& id : e.models) if (lower(id) == w) return id; - for (int mode = 0; mode < 2; ++mode) { - std::vector hits; - for (const std::string& id : e.models) { - std::string l = lower(id); - if (mode == 0 ? l.compare(0, w.size(), w) == 0 : l.find(w) != std::string::npos) hits.push_back(id); - } - if (hits.size() == 1) return hits[0]; - if (hits.size() > 1) { - problem = "model " + quoted(want) + " is ambiguous on " + e.name + ": "; - for (size_t i = 0; i < hits.size() && i < 10; ++i) problem += (i ? ", " : "") + hits[i]; - return ""; - } - } - problem = "agent " + quoted(e.name) + " has no model " + quoted(want); - return ""; +namespace { + +std::string scoreText(double s) { + char buf[16]; + std::snprintf(buf, sizeof buf, "%.2f", s); + return buf; } +// Best model of one agent by fuzzy score (ties: shorter id, then list order). "" when none relates. +std::string bestModel(const CapAgentEntry& e, const std::string& want, double& score) { + double s = 0; + long i = FuzzyMatcher::Best(want, e.models, 0.0, &s); + score = s; + return i < 0 ? "" : e.models[static_cast(i)]; +} + +} // namespace + CapSelection CapModelCatalog::resolve(const std::string& spec) { CapSelection sel; - std::string s = trim(spec); + const std::string s = trim(spec); auto fail = [&](const std::string& why) { sel.ok = false; sel.error = why + "\n" + report(); return sel; }; + auto done = [&](const std::string& agent, const std::string& model, double score) { + sel.ok = true; + sel.agent = agent; + sel.model = model; + const std::string label = agent + (model.empty() ? "" : ":" + model); + if (lower(label) != lower(s)) sel.note = quoted(s) + " resolved to " + label + " (fuzzy " + scoreText(score) + ")"; + return sel; + }; if (s.empty()) return fail("empty agent mention"); - size_t colon = s.find(':'); - std::string head = colon == std::string::npos ? s : s.substr(0, colon); - std::string name = findName(head); - if (!name.empty()) { - CapAgentEntry e = entry(name); - if (!e.ready()) return fail("agent " + quoted(name) + " is not usable: " + e.error); - sel.agent = name; - std::string want = colon == std::string::npos ? "" : trim(s.substr(colon + 1)); - if (want.empty()) { sel.ok = true; return sel; } - if (e.models.empty()) { - sel.ok = true; - sel.model = want; - sel.note = "agent " + name + " publishes no model list" + (e.modelError.empty() ? "" : " (" + e.modelError + ")") + - ", " + quoted(want) + " passed through unverified"; - return sel; + const std::vector all = entries(); + std::vector readyNames, allNames; + std::vector ready; + for (const CapAgentEntry& e : all) { + allNames.push_back(e.name); + if (e.ready()) { readyNames.push_back(e.name); ready.push_back(&e); } + } + // 1. exact agent name or exact model id (case-insensitive). + for (const CapAgentEntry& e : all) { + if (lower(e.name) != lower(s)) continue; + if (!e.ready()) return fail("agent " + quoted(e.name) + " is not usable: " + e.error); + return done(e.name, "", 1.0); + } + for (const CapAgentEntry* e : ready) + for (const std::string& id : e->models) if (lower(id) == lower(s)) return done(e->name, id, 1.0); + // 2. agent:model, the agent part fuzzy. + const size_t colon = s.find(':'); + if (colon != std::string::npos) { + const std::string head = trim(s.substr(0, colon)), tail = trim(s.substr(colon + 1)); + double hs = 0; + long ai = FuzzyMatcher::Best(head, readyNames, CAP_FUZZY_FLOOR, &hs); + if (ai >= 0) { + const CapAgentEntry& e = *ready[static_cast(ai)]; + if (tail.empty()) return done(e.name, "", hs); + if (e.models.empty()) { + done(e.name, tail, hs); + sel.note = "agent " + e.name + " publishes no model list" + (e.modelError.empty() ? "" : " (" + e.modelError + ")") + + ", " + quoted(tail) + " passed through unverified"; + return sel; + } + double ms = 0; + std::string id = bestModel(e, tail, ms); + if (id.empty()) return fail("no model of agent " + quoted(e.name) + " resembles " + quoted(tail)); + return done(e.name, id, std::min(hs, ms)); } - std::string problem, id = match(e, want, problem); - if (id.empty()) return fail(problem); - sel.ok = true; - sel.model = id; - return sel; + long ui = FuzzyMatcher::Best(head, allNames, CAP_FUZZY_FLOOR, nullptr); + if (ui >= 0 && !all[static_cast(ui)].ready()) + return fail("agent " + quoted(all[static_cast(ui)].name) + " is not usable: " + all[static_cast(ui)].error); } - if (colon != std::string::npos) return fail("unknown agent " + quoted(head)); - // Bare model: valid when exactly one ready agent offers it. - std::vector> hits; - for (const CapAgentEntry& e : entries()) { - if (!e.ready() || e.models.empty()) continue; - std::string problem, id = match(e, s, problem); - if (!id.empty()) hits.push_back({e.name, id}); + // 3. the whole spec against every ready agent name and every model. + std::vector texts; + std::vector> owner; // model "" means the agent itself + for (const CapAgentEntry* e : ready) { + texts.push_back(e->name); + owner.push_back({e, ""}); + for (const std::string& id : e->models) { texts.push_back(id); owner.push_back({e, id}); } } - if (hits.size() == 1) { - sel.ok = true; - sel.agent = hits[0].first; - sel.model = hits[0].second; - return sel; - } - if (hits.size() > 1) { - std::string who; - for (auto& h : hits) who += (who.empty() ? "" : ", ") + h.first + ":" + h.second; - return fail("model " + quoted(s) + " exists on several agents, pick one: " + who); - } - return fail("no agent or model matches " + quoted(s)); + double best = 0; + long bi = FuzzyMatcher::Best(s, texts, CAP_FUZZY_FLOOR, &best); + if (bi >= 0) return done(owner[static_cast(bi)].first->name, owner[static_cast(bi)].second, best); + double us = 0; + long ui = FuzzyMatcher::Best(s, allNames, CAP_FUZZY_FLOOR, &us); + if (ui >= 0) return fail("agent " + quoted(all[static_cast(ui)].name) + " is not usable: " + all[static_cast(ui)].error); + std::string closest; + std::vector r = FuzzyMatcher::Rank(s, texts); + if (!r.empty()) closest = ", closest " + quoted(texts[r[0].index]) + " scored " + scoreText(r[0].score) + " (floor " + scoreText(CAP_FUZZY_FLOOR) + ")"; + return fail("no agent or model resembles " + quoted(s) + closest); } std::string CapModelCatalog::formatReport(const std::vector& all) { diff --git a/taicpp/src/cap/cap_model_catalog.h b/taicpp/src/cap/cap_model_catalog.h index 447b01a..8c67a7a 100644 --- a/taicpp/src/cap/cap_model_catalog.h +++ b/taicpp/src/cap/cap_model_catalog.h @@ -1,6 +1,6 @@ #pragma once // CapModelCatalog: which CAP agents exist on this machine and which models each -// of them offers, so `@agent` and `@agent:model` mentions resolve in one place. +// of them offers, so `@agent` and `@agent:model` mentions resolve in one place, by fuzzy match. // // Model discovery protocol (docs/cap.md "Model catalog"): // 1. `[models] list = [...]` and `default = "..."` in the agent manifest. @@ -24,6 +24,7 @@ inline constexpr size_t CAP_MODEL_PROBE_MAX_BYTES = 1u << 20; inline constexpr size_t CAP_MODEL_MAX = 500; // models kept per agent inline constexpr size_t CAP_AGENT_MAX = 64; // agents kept in the catalog inline constexpr double CAP_CATALOG_TTL = 60.0; // seconds an entry stays cached +inline constexpr double CAP_FUZZY_FLOOR = 0.3; // minimum FuzzyMatcher score to pick across agents and models struct CapAgentEntry { std::string name; @@ -62,7 +63,8 @@ class CapModelCatalog { // True when the first word of `spec` (before ':') names a catalog agent. bool isAgent(const std::string& spec) const; - // `agent`, `agent:model`, or a bare model id found on exactly one agent. + // Fuzzy (util/fuzzy.h): `agent`, `agent:model`, or a bare agent or model; the best score wins, + // equal scores resolve by shorter id then list order. Sets `note` when the pick differs from the text. CapSelection resolve(const std::string& spec); // Detailed per-agent listing used by every failure message. std::string report(); @@ -74,7 +76,6 @@ class CapModelCatalog { static std::string defaultProbe(const std::vector& argv, double timeout); CapAgentEntry load(const std::string& name) const; void probeInto(CapAgentEntry& e, const CapManifest& m) const; - static std::string match(const CapAgentEntry& e, const std::string& want, std::string& problem); std::string findName(const std::string& word) const; CapRegistry registry_; diff --git a/taicpp/src/util/fuzzy.cpp b/taicpp/src/util/fuzzy.cpp new file mode 100644 index 0000000..50cf639 --- /dev/null +++ b/taicpp/src/util/fuzzy.cpp @@ -0,0 +1,155 @@ +#include "fuzzy.h" +#include +#include +#include +namespace detz { +namespace { + +constexpr int kNeg = -1000000000; + +char Lower(char c) { return (c >= 'A' && c <= 'Z') ? static_cast(c - 'A' + 'a') : c; } +bool IsUpper(char c) { return c >= 'A' && c <= 'Z'; } +bool IsLowerCh(char c) { return (c >= 'a' && c <= 'z') || static_cast(c) >= 0x80; } +bool IsDigit(char c) { return c >= '0' && c <= '9'; } +bool IsWhite(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } +bool IsAlnum(char c) { return IsUpper(c) || IsLowerCh(c) || IsDigit(c); } + +std::string Fold(const std::string& s, size_t cap) { + std::string out; + out.reserve(std::min(s.size(), cap)); + for (size_t i = 0; i < s.size() && i < cap; ++i) out.push_back(Lower(s[i])); + return out; +} + +// Bonus of matching text[j], from the class of the byte before it (fzf bonusFor). +int BonusAt(const std::string& t, size_t j) { + char cur = t[j]; + char prev = j == 0 ? ' ' : t[j - 1]; + if (IsWhite(cur)) return FuzzyMatcher::kBonusWhite; + if (!IsAlnum(cur)) return FuzzyMatcher::kBonusBoundary; + if (IsWhite(prev)) return FuzzyMatcher::kBonusWhite; + if (!IsAlnum(prev)) return FuzzyMatcher::kBonusBoundary; + if (IsLowerCh(prev) && IsUpper(cur)) return FuzzyMatcher::kBonusCamel; + if (!IsDigit(prev) && IsDigit(cur)) return FuzzyMatcher::kBonusCamel; + return 0; +} + +} // namespace + +double FuzzyMatcher::Subsequence(const std::string& query, const std::string& text) { + std::string q = Fold(query, kFuzzyQueryMax); + std::string orig = text.substr(0, std::min(text.size(), kFuzzyTextMax)); + std::string t = Fold(orig, kFuzzyTextMax); + const size_t n = q.size(), m = t.size(); + if (n == 0 || n > m) return 0.0; + std::vector bonus(m); + for (size_t j = 0; j < m; ++j) bonus[j] = BonusAt(orig, j); + // D[i][j]: best score with q[i] matched at t[j]; M[i][j]: best for q[0..i] inside t[0..j]. + std::vector D(n * m, kNeg), M(n * m, kNeg); + auto at = [m](size_t i, size_t j) { return i * m + j; }; + for (size_t i = 0; i < n; ++i) { + for (size_t j = i; j < m; ++j) { + int d = kNeg; + if (q[i] == t[j]) { + if (i == 0) { + d = kScoreMatch + bonus[j] * kFirstMultiplier + kGap * static_cast(j); + } else if (j > 0) { + int viaGap = M[at(i - 1, j - 1)]; + if (viaGap > kNeg) d = viaGap + kScoreMatch + bonus[j]; + int viaRun = D[at(i - 1, j - 1)]; + if (viaRun > kNeg) d = std::max(d, viaRun + kScoreMatch + std::max(bonus[j], kBonusConsecutive)); + } + } + D[at(i, j)] = d; + int mv = d; + if (j > 0 && M[at(i, j - 1)] > kNeg) mv = std::max(mv, M[at(i, j - 1)] + kGap); + M[at(i, j)] = mv; + } + } + int raw = M[at(n - 1, m - 1)]; + if (raw <= kNeg) return 0.0; + double perfect = static_cast(n) * kScoreMatch + kBonusWhite * kFirstMultiplier + static_cast(n - 1) * kBonusConsecutive; + return std::clamp(raw / perfect, 0.0, 1.0); +} + +// Optimal string alignment distance (insert, delete, substitute, adjacent swap = 1). +double FuzzyMatcher::Typo(const std::string& query, const std::string& text) { + std::string a = Fold(query, kFuzzyQueryMax), b = Fold(text, kFuzzyTextMax); + const size_t n = a.size(), m = b.size(); + if (n == 0 || m == 0) return 0.0; + std::vector> d(n + 1, std::vector(m + 1, 0)); + for (size_t i = 0; i <= n; ++i) d[i][0] = static_cast(i); + for (size_t j = 0; j <= m; ++j) d[0][j] = static_cast(j); + for (size_t i = 1; i <= n; ++i) { + for (size_t j = 1; j <= m; ++j) { + int cost = a[i - 1] == b[j - 1] ? 0 : 1; + d[i][j] = std::min({d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost}); + if (i > 1 && j > 1 && a[i - 1] == b[j - 2] && a[i - 2] == b[j - 1]) d[i][j] = std::min(d[i][j], d[i - 2][j - 2] + 1); + } + } + return 1.0 - static_cast(d[n][m]) / static_cast(std::max(n, m)); +} + +double FuzzyMatcher::TokenTypo(const std::string& query, const std::string& text) { + std::string f = Fold(text, kFuzzyTextMax), token; + double best = 0.0; + for (size_t i = 0; i <= f.size(); ++i) { + if (i < f.size() && IsAlnum(f[i])) { token.push_back(f[i]); continue; } + if (!token.empty()) best = std::max(best, Typo(query, token)); + token.clear(); + } + return best; +} + +// pg_trgm: per word, two leading spaces and one trailing space; Jaccard over the trigram sets. +double FuzzyMatcher::Trigram(const std::string& query, const std::string& text) { + auto grams = [](const std::string& s, size_t cap) { + std::set out; + std::string f = Fold(s, cap), word; + auto flush = [&]() { + if (word.empty()) return; + std::string p = " " + word + " "; + for (size_t i = 0; i + 3 <= p.size(); ++i) out.insert(p.substr(i, 3)); + word.clear(); + }; + for (char c : f) { if (IsAlnum(c)) word.push_back(c); else flush(); } + flush(); + return out; + }; + std::set a = grams(query, kFuzzyQueryMax), b = grams(text, kFuzzyTextMax); + if (a.empty() || b.empty()) return 0.0; + size_t common = 0; + for (const auto& g : a) common += b.count(g); + size_t all = a.size() + b.size() - common; + return all == 0 ? 0.0 : static_cast(common) / static_cast(all); +} + +double FuzzyMatcher::Score(const std::string& query, const std::string& text) { + if (query.empty() || text.empty()) return 0.0; + if (Fold(query, kFuzzyTextMax) == Fold(text, kFuzzyTextMax)) return 1.0; + double s = std::max({Subsequence(query, text), Typo(query, text), TokenTypo(query, text) * kTokenScale, Trigram(query, text) * kTrigramScale}); + return std::min(s, 0.999); // only an exact match reaches 1 +} + +std::vector FuzzyMatcher::Rank(const std::string& query, const std::vector& texts) { + std::vector out; + for (size_t i = 0; i < texts.size() && i < kFuzzyCandidateMax; ++i) { + double s = Score(query, texts[i]); + if (s > 0.0) out.push_back({i, s}); + } + std::stable_sort(out.begin(), out.end(), [&](const Ranked& x, const Ranked& y) { + if (std::fabs(x.score - y.score) > 1e-9) return x.score > y.score; + if (texts[x.index].size() != texts[y.index].size()) return texts[x.index].size() < texts[y.index].size(); + return x.index < y.index; + }); + return out; +} + +long FuzzyMatcher::Best(const std::string& query, const std::vector& texts, double floor, double* score) { + std::vector r = Rank(query, texts); + if (r.empty() || r[0].score <= floor) return -1; + if (score != nullptr) *score = r[0].score; + return static_cast(r[0].index); +} + +} // namespace detz diff --git a/taicpp/src/util/fuzzy.h b/taicpp/src/util/fuzzy.h new file mode 100644 index 0000000..e3cdabe --- /dev/null +++ b/taicpp/src/util/fuzzy.h @@ -0,0 +1,52 @@ +#pragma once +// FuzzyMatcher: the one generic "which candidate did the user mean" scorer of the +// application (spec: docs/fuzzy.md, research: research/fuzzy-matching-selectors). +// Score(query, text) is in [0, 1], case-insensitive, and the max of four signals: +// 1. subsequence DP with word-boundary / camelCase / consecutive bonuses and +// gap penalties (fzf FuzzyMatchV2 constants, fzy D/M recurrences), +// 2. restricted Damerau-Levenshtein similarity (typos and transpositions), +// 3. the same typo similarity against each alphanumeric token of the text, scaled 0.95 +// (a misspelled word inside a long id such as claude-opus-4-5), +// 4. pg_trgm trigram Jaccard similarity (scrambled or partial words), scaled 0.9. +// Rank() orders candidates by score, then shorter text, then earlier index, so +// equal matches resolve deterministically. Bytes are compared as-is (ASCII case +// folding only); non-ASCII is not normalized. Static ceilings, nothing tunes itself. +#include +#include +#include +namespace detz { + +constexpr size_t kFuzzyQueryMax = 64; // query bytes considered +constexpr size_t kFuzzyTextMax = 256; // candidate bytes considered +constexpr size_t kFuzzyCandidateMax = 4096; // candidates ranked per call + +class FuzzyMatcher { + public: + // fzf constants (algo.go): scoreMatch 16, gap -1 per skipped byte (fzf: -3 start, -1 extension), + // boundary 8, white 10, camel/digit 7, consecutive 4, first byte bonus x2. + static constexpr int kScoreMatch = 16; + static constexpr int kGap = -1; + static constexpr int kBonusBoundary = 8; + static constexpr int kBonusWhite = 10; + static constexpr int kBonusCamel = 7; + static constexpr int kBonusConsecutive = 4; + static constexpr int kFirstMultiplier = 2; + static constexpr double kTrigramScale = 0.9; + static constexpr double kTokenScale = 0.95; + + struct Ranked { size_t index; double score; }; + + // 0 when nothing relates; 1 only for a case-insensitive exact match. + static double Score(const std::string& query, const std::string& text); + // Components, exposed for tests and for the spec table. + static double Subsequence(const std::string& query, const std::string& text); + static double Typo(const std::string& query, const std::string& text); + static double TokenTypo(const std::string& query, const std::string& text); + static double Trigram(const std::string& query, const std::string& text); + // Candidates with score > 0, best first (score desc, length asc, index asc). + static std::vector Rank(const std::string& query, const std::vector& texts); + // Index of the best candidate, or -1 when none scores above `floor`. + static long Best(const std::string& query, const std::vector& texts, double floor = 0.0, double* score = nullptr); +}; + +} // namespace detz diff --git a/taicpp/tests/cap_model_catalog_test.cpp b/taicpp/tests/cap_model_catalog_test.cpp index 98648ac..9d06661 100644 --- a/taicpp/tests/cap_model_catalog_test.cpp +++ b/taicpp/tests/cap_model_catalog_test.cpp @@ -44,17 +44,25 @@ int main() { s = cat.resolve("ALPHA:alpha-s"); CHECK(s.ok && s.model == "alpha-small"); // case-insensitive agent, unique prefix s = cat.resolve("alpha:alpha"); - CHECK(!s.ok && s.error.find("ambiguous") != std::string::npos); + CHECK(s.ok && s.model == "alpha-large"); // equal scores: list order, never an error + s = cat.resolve("alpha:lrg"); + CHECK(s.ok && s.model == "alpha-large" && !s.note.empty()); + s = cat.resolve("alpah:small"); // typo in the agent, partial model + CHECK(s.ok && s.agent == "alpha" && s.model == "alpha-small"); s = cat.resolve("alpha:zzz"); CHECK(!s.ok && s.error.find("alpha [ready]") != std::string::npos && s.error.find("beta-one") != std::string::npos); s = cat.resolve("gone"); CHECK(!s.ok && s.error.find("not on PATH") != std::string::npos && s.error.find("bad [bad_manifest]") != std::string::npos); s = cat.resolve("beta-one"); // bare model, unique agent CHECK(s.ok && s.agent == "beta" && s.model == "beta-one"); - s = cat.resolve("shared-1"); // on two agents - CHECK(!s.ok && s.error.find("several agents") != std::string::npos); - s = cat.resolve("nothing"); - CHECK(!s.ok && s.error.find("cap agents (4)") != std::string::npos); + s = cat.resolve("shared-1"); // on two agents: first agent wins + CHECK(s.ok && s.agent == "alpha" && s.model == "shared-1"); + s = cat.resolve("bet"); + CHECK(s.ok && s.agent == "beta" && s.model.empty()); + s = cat.resolve("betaone"); + CHECK(s.ok && s.agent == "beta" && s.model == "beta-one"); + s = cat.resolve("qqzzxx"); + CHECK(!s.ok && s.error.find("resembles") != std::string::npos && s.error.find("cap agents (4)") != std::string::npos); CHECK(cat.isAgent("alpha:x") && !cat.isAgent("x")); put(dir, "open", "sh", ""); // no list: pass through with a note diff --git a/taicpp/tests/fuzzy_test.cpp b/taicpp/tests/fuzzy_test.cpp new file mode 100644 index 0000000..efcd6c4 --- /dev/null +++ b/taicpp/tests/fuzzy_test.cpp @@ -0,0 +1,49 @@ +// FuzzyMatcher (src/util/fuzzy.h): exactness, ordering, typos, determinism, ceilings. +#include +#include +#include + +#include "fuzzy.h" + +using detz::FuzzyMatcher; + +static int failures = 0; +#define CHECK(c) do { if (!(c)) { std::printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #c); ++failures; } } while (0) + +static std::string top(const std::string& q, const std::vector& c) { + long i = FuzzyMatcher::Best(q, c); + return i < 0 ? "" : c[static_cast(i)]; +} + +int main() { + const std::vector c = {"claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5", "gpt-4o", "gpt-4o-mini", + "gemini-2.5-pro", "llama3:8b", "opencode", "zen-free", "devplace-free"}; + CHECK(FuzzyMatcher::Score("GPT-4O", "gpt-4o") == 1.0); + CHECK(FuzzyMatcher::Score("gpt", "gpt-4o") < 1.0); + CHECK(FuzzyMatcher::Score("", "x") == 0.0 && FuzzyMatcher::Score("x", "") == 0.0); + CHECK(FuzzyMatcher::Score("zzqq", "claude") == 0.0); + CHECK(top("opus", c) == "claude-opus-4-5"); + CHECK(top("son", c) == "claude-sonnet-4-5"); + CHECK(top("sonet", c) == "claude-sonnet-4-5"); // dropped letter + CHECK(top("snonet", c) == "claude-sonnet-4-5"); // transposition + CHECK(top("opuss", c) == "claude-opus-4-5"); // extra letter + CHECK(top("4omini", c) == "gpt-4o-mini"); + CHECK(top("g4m", c) == "gpt-4o-mini"); // acronym of word starts + CHECK(top("OC", c) == "opencode"); + CHECK(top("free", c) == "zen-free"); // shorter id wins the tie + CHECK(FuzzyMatcher::Score("pro", "gemini-2.5-pro") > FuzzyMatcher::Score("pro", "opencode")); // boundary beats scattered + CHECK(FuzzyMatcher::Subsequence("fb", "FooBar") > FuzzyMatcher::Subsequence("fb", "fxxxxbxx")); // camelCase + CHECK(FuzzyMatcher::Typo("ab", "ba") == 0.5); // one swap of two + CHECK(FuzzyMatcher::Trigram("word", "word") == 1.0); + CHECK(FuzzyMatcher::Trigram("word", "words") > 0.57 && FuzzyMatcher::Trigram("word", "words") < 0.58); // pg_trgm 4/7 + // Determinism: equal candidates keep list order. + std::vector twin = {"model-a", "model-a"}; + auto r = FuzzyMatcher::Rank("model-a", twin); + CHECK(r.size() == 2 && r[0].index == 0 && r[1].index == 1); + // Floor and ceilings never crash. + CHECK(FuzzyMatcher::Best("zzqq", c, 0.3) == -1); + CHECK(FuzzyMatcher::Score(std::string(10000, 'a'), std::string(10000, 'a')) == 1.0); + CHECK(FuzzyMatcher::Score(std::string(500, 'a'), std::string(5000, 'b')) == 0.0); + std::printf(failures ? "FAILED %d\n" : "fuzzy_test OK\n", failures); + return failures ? 1 : 0; +}