@squinch/core 0.1.1 → 0.2.0

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.
package/dist/api.d.ts CHANGED
@@ -36,6 +36,17 @@ export declare function iconsUsedBy(files: ProjectFile[] | string): {
36
36
  * them and a result of only `azure/key-vaults` reads as proof that
37
37
  * `azure/key-vault` doesn't exist) can look them up via `packInfo`. */
38
38
  export declare function searchIcons(query: string, pack?: string): string[];
39
+ /** The same search, saying how it matched. `relaxed: true` means no icon
40
+ * matched every word, so the hits are the closest partial matches instead —
41
+ * callers that print for an agent loop label them (`no exact match —
42
+ * closest:`), because near-misses answer the question and an empty list
43
+ * forces a second pass with different words. Measured before ranking
44
+ * existed: `rag` returned eighteen sto*rag*e rows with `sys/rag` dead last,
45
+ * and "message queue" / "object storage" returned nothing at all. */
46
+ export declare function searchIconsDetailed(query: string, pack?: string): {
47
+ hits: string[];
48
+ relaxed: boolean;
49
+ };
39
50
  export type { ProjectFile };
40
51
  export type * from "./model/types.js";
41
52
  export { HUES } from "./model/types.js";
package/dist/api.js CHANGED
@@ -69,6 +69,16 @@ export function iconsUsedBy(files) {
69
69
  * them and a result of only `azure/key-vaults` reads as proof that
70
70
  * `azure/key-vault` doesn't exist) can look them up via `packInfo`. */
71
71
  export function searchIcons(query, pack) {
72
+ return searchIconsDetailed(query, pack).hits;
73
+ }
74
+ /** The same search, saying how it matched. `relaxed: true` means no icon
75
+ * matched every word, so the hits are the closest partial matches instead —
76
+ * callers that print for an agent loop label them (`no exact match —
77
+ * closest:`), because near-misses answer the question and an empty list
78
+ * forces a second pass with different words. Measured before ranking
79
+ * existed: `rag` returned eighteen sto*rag*e rows with `sys/rag` dead last,
80
+ * and "message queue" / "object storage" returned nothing at all. */
81
+ export function searchIconsDetailed(query, pack) {
72
82
  // Vendors are inconsistent about number — Azure has "Container Registries"
73
83
  // and "Data Factories" where everyone searches "container registry" and
74
84
  // "data factory" — so both sides are singularized before comparing.
@@ -97,20 +107,46 @@ export function searchIcons(query, pack) {
97
107
  }
98
108
  }
99
109
  const words = rawWords.map(stem);
100
- const hits = [];
110
+ const joined = words.join(" ");
111
+ const scored = [];
101
112
  for (const name of allPackNames()) {
102
113
  if (packFilter && name !== packFilter)
103
114
  continue;
104
- const aliases = packInfo(name)?.aliases ?? {};
105
115
  const haystack = (id) => norm(`${id} ${iconTitle(name, id) ?? ""}`);
106
- const matched = new Set(iconIds(name).filter((id) => words.every((w) => haystack(id).includes(w))));
107
- for (const id of [...matched].sort()) {
108
- if (aliases[id] && matched.has(aliases[id]))
109
- continue; // canonical covers it
110
- hits.push(`${name}/${id}`);
116
+ const matchedIds = new Set(iconIds(name).filter((id) => words.some((w) => haystack(id).includes(w)) || !words.length));
117
+ for (const id of matchedIds) {
118
+ const hay = haystack(id);
119
+ const hayWords = new Set(hay.split(" "));
120
+ scored.push({
121
+ hit: `${name}/${id}`,
122
+ exact: joined && norm(id) === joined ? 1 : 0,
123
+ whole: words.length && words.every((w) => hayWords.has(w)) ? 1 : 0,
124
+ matched: words.filter((w) => hay.includes(w)).length,
125
+ });
111
126
  }
112
127
  }
113
- return hits.sort();
128
+ // One row per icon: an alias row is dropped only when its canonical is in
129
+ // the SAME result list. Checking the broader matched set instead deduped
130
+ // `sys/vector-search` (a full match) against a canonical that had only
131
+ // matched one word and was not being returned at all.
132
+ const dedupe = (list) => {
133
+ const present = new Set(list.map((s) => s.hit));
134
+ return list.filter((s) => {
135
+ const name = s.hit.slice(0, s.hit.indexOf("/"));
136
+ const id = s.hit.slice(s.hit.indexOf("/") + 1);
137
+ const canonical = (packInfo(name)?.aliases ?? {})[id];
138
+ return !(canonical && present.has(`${name}/${canonical}`)); // canonical covers it
139
+ });
140
+ };
141
+ const rank = (list) => dedupe(list)
142
+ .sort((a, b) => b.exact - a.exact || b.whole - a.whole || b.matched - a.matched || (a.hit < b.hit ? -1 : 1))
143
+ .map((s) => s.hit);
144
+ const full = scored.filter((s) => s.matched === words.length);
145
+ if (full.length || !words.length)
146
+ return { hits: rank(full), relaxed: false };
147
+ // Nothing matched every word: fall back to partial matches, best first.
148
+ // Capped — the point is a next move, not a directory listing.
149
+ return { hits: rank(scored).slice(0, 12), relaxed: true };
114
150
  }
115
151
  // the one colour vocabulary, for editors that complete it and tests that sweep it
116
152
  export { HUES } from "./model/types.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squinch/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "The Squinch engine: parse, model, layout and deterministic SVG rendering for architecture diagrams as code.",
5
5
  "keywords": [
6
6
  "architecture",
@@ -33,11 +33,11 @@
33
33
  "@lezer/lr": "^1.4.10",
34
34
  "elkjs": "^0.12.0",
35
35
  "fast-xml-parser": "^5.10.1",
36
- "@squinch/pack-aws": "0.1.1",
37
- "@squinch/pack-logos": "0.1.1",
38
- "@squinch/pack-sys": "0.1.1",
39
- "@squinch/pack-k8s": "0.1.1",
40
- "@squinch/pack-azure": "0.1.1"
36
+ "@squinch/pack-aws": "0.2.0",
37
+ "@squinch/pack-azure": "0.2.0",
38
+ "@squinch/pack-k8s": "0.2.0",
39
+ "@squinch/pack-logos": "0.2.0",
40
+ "@squinch/pack-sys": "0.2.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@fontsource/ibm-plex-mono": "^5.3.0",