trueicon 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,11 @@ export interface SearchOptions {
15
15
  }
16
16
  export interface SearchResult {
17
17
  record: IconRecord;
18
- /** Fuse.js score: 0 is a perfect match, 1 is no match. */
18
+ /**
19
+ * Best per-token Fuse.js score: 0 is a perfect match, 1 is no match. For
20
+ * multi-word queries this is the score of the closest-matching token; see
21
+ * {@link searchIcons} for how coverage across tokens breaks ties.
22
+ */
19
23
  score: number;
20
24
  }
21
25
  /** Reads index.json and meta.json from an index directory written by writeIndex. */
@@ -24,7 +28,12 @@ export declare function loadIndex(cacheDir: string): Promise<LoadedIndex>;
24
28
  export declare function indexNeedsRebuild(meta: IndexMeta, synonyms: Synonyms): boolean;
25
29
  /**
26
30
  * Filters records by provider/style/set exactly, then ranks them against the query with Fuse.js.
27
- * Multi-word queries are searched token by token: only records matching every token are kept,
28
- * ranked by their mean per-token score.
31
+ *
32
+ * Multi-word queries are searched token by token. A record is a candidate when it matches
33
+ * at least one token; ranking is by the best per-token score first, then by how many tokens
34
+ * matched, then by the mean matched score. Requiring every token to match (a hard AND) was
35
+ * tried and rejected: on real indexes it excludes the right icons whenever one token is a
36
+ * near-synonym absent from the index ("can" vs keywords trash/delete/bin), while unrelated
37
+ * icons slip through on loose fuzzy matches of every token ("kanban" ~= "can").
29
38
  */
30
39
  export declare function searchIcons(records: readonly IconRecord[], query: string, options?: SearchOptions): SearchResult[];
@@ -32,8 +32,13 @@ function matches(value, filter) {
32
32
  }
33
33
  /**
34
34
  * Filters records by provider/style/set exactly, then ranks them against the query with Fuse.js.
35
- * Multi-word queries are searched token by token: only records matching every token are kept,
36
- * ranked by their mean per-token score.
35
+ *
36
+ * Multi-word queries are searched token by token. A record is a candidate when it matches
37
+ * at least one token; ranking is by the best per-token score first, then by how many tokens
38
+ * matched, then by the mean matched score. Requiring every token to match (a hard AND) was
39
+ * tried and rejected: on real indexes it excludes the right icons whenever one token is a
40
+ * near-synonym absent from the index ("can" vs keywords trash/delete/bin), while unrelated
41
+ * icons slip through on loose fuzzy matches of every token ("kanban" ~= "can").
37
42
  */
38
43
  export function searchIcons(records, query, options = {}) {
39
44
  const tokens = query.trim().split(/\s+/).filter(Boolean);
@@ -42,18 +47,33 @@ export function searchIcons(records, query, options = {}) {
42
47
  const { provider, style, set, limit } = options;
43
48
  const filtered = records.filter((r) => matches(r.provider, provider) && matches(r.style, style) && matches(r.set, set));
44
49
  const fuse = new Fuse(filtered, FUSE_OPTIONS);
45
- const perToken = tokens.map((token) => new Map(fuse.search(token).map((result) => [result.item, result.score ?? 0])));
46
- const [first, ...rest] = perToken;
47
- // Iterate in the first token's ranked order so ties keep Fuse's ordering (sort is stable).
48
- const results = [];
49
- for (const [record, score] of first) {
50
- const others = rest.map((scores) => scores.get(record));
51
- if (others.some((s) => s === undefined))
52
- continue;
53
- const total = others.reduce((sum, s) => sum + s, score);
54
- results.push({ record, score: total / tokens.length });
50
+ const stats = new Map();
51
+ for (const token of tokens) {
52
+ for (const result of fuse.search(token)) {
53
+ const score = result.score ?? 0;
54
+ const st = stats.get(result.item);
55
+ if (st) {
56
+ st.matched += 1;
57
+ st.total += score;
58
+ if (score < st.best)
59
+ st.best = score;
60
+ }
61
+ else {
62
+ stats.set(result.item, { best: score, matched: 1, total: score });
63
+ }
64
+ }
55
65
  }
56
- results.sort((a, b) => a.score - b.score);
57
- return limit === undefined ? results : results.slice(0, Math.max(0, limit));
66
+ // Sort is stable, so full ties keep token order: matches for earlier query
67
+ // tokens were inserted first and stay ahead.
68
+ const ranked = [...stats]
69
+ .map(([record, st]) => ({
70
+ record,
71
+ score: st.best,
72
+ matched: st.matched,
73
+ mean: st.total / st.matched,
74
+ }))
75
+ .sort((a, b) => a.score - b.score || b.matched - a.matched || a.mean - b.mean)
76
+ .map(({ record, score }) => ({ record, score }));
77
+ return limit === undefined ? ranked : ranked.slice(0, Math.max(0, limit));
58
78
  }
59
79
  //# sourceMappingURL=search.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/search/search.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/search/search.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trueicon",
3
3
  "mcpName": "io.github.manikumarkv/trueicon",
4
- "version": "0.3.0",
4
+ "version": "0.3.1",
5
5
  "description": "MCP server that lets AI coding assistants search the icon packages actually installed in your project.",
6
6
  "license": "MIT",
7
7
  "author": "manikumarkv",