@h0tp/shucky 0.4.6 → 0.4.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.7
4
+
5
+ - **Smarter `find` ranking.** Results are now **relevance-first** — shucky preserves the order each
6
+ registry returned (skills.sh fuzzy, GitHub search) instead of overriding it with raw popularity, so
7
+ a precise niche match no longer loses to a popular loose one. **Trusted sources get a bounded boost**,
8
+ and **popularity is normalised per-source** (log scale) so GitHub star counts can't dominate skills.sh
9
+ install counts. New pure, unit-tested `rankResults()` (5 checks → 189 total).
10
+
3
11
  ## 0.4.6
4
12
 
5
13
  - **Docs — README rebalanced installer-first:** leads with the *one command · any source · into
package/lib/find.js CHANGED
@@ -83,17 +83,48 @@ async function searchGitHub(query, opts) {
83
83
  } catch (e) { return { error: 'github: ' + e.message }; }
84
84
  }
85
85
 
86
+ // How shucky merges + ranks results across sources:
87
+ // • relevance first — preserve the order each registry returned (skills.sh fuzzy, GitHub search),
88
+ // so a perfect niche match isn't buried under a popular loose one;
89
+ // • trusted sources get a bounded boost (~TRUST_BOOST positions), never overriding relevance;
90
+ // • popularity (installs / GitHub stars) is normalised PER SOURCE on a log scale — so a 200k-star
91
+ // repo can't dominate a 5k-install skill; it's a tiebreak, not the primary signal.
92
+ const TRUST_BOOST = 1.5;
93
+
94
+ function rankResults(groups, trusted) {
95
+ const out = [];
96
+ for (const group of groups) {
97
+ let maxPop = 0;
98
+ for (const r of group) maxPop = Math.max(maxPop, r.installs || 0);
99
+ const logMax = Math.log10(maxPop + 1) || 1;
100
+ group.forEach(function (r, idx) {
101
+ if (!r.trust) {
102
+ const owner = r.source ? String(r.source).toLowerCase().split('/')[0] : '';
103
+ if (owner && (trusted.has(owner) || trusted.has(String(r.source).toLowerCase()))) r.trust = 'trusted';
104
+ }
105
+ const isTrusted = r.trust === 'trusted' ? 1 : 0;
106
+ r._rank = idx - (isTrusted ? TRUST_BOOST : 0); // relevance position, trust-boosted
107
+ r._pop = maxPop > 0 ? Math.log10((r.installs || 0) + 1) / logMax : 0; // 0..1 within this source only
108
+ r._trust = isTrusted;
109
+ out.push(r);
110
+ });
111
+ }
112
+ out.sort(function (a, b) { return a._rank - b._rank || b._trust - a._trust || b._pop - a._pop || String(a.name).localeCompare(String(b.name)); });
113
+ out.forEach(function (r) { delete r._rank; delete r._pop; delete r._trust; });
114
+ return out;
115
+ }
116
+
86
117
  async function findSkills(query, opts) {
87
118
  opts = opts || {};
88
119
  const cwd = opts.cwd;
89
- const results = [];
120
+ const groups = []; // one results array per source, kept IN that source's relevance order
90
121
  const searched = [];
91
122
  const errors = [];
92
123
 
93
124
  if (!opts.localOnly) {
94
125
  searched.push('skills.sh');
95
126
  const r = await searchSkillsSh(query, opts);
96
- if (Array.isArray(r)) results.push.apply(results, r);
127
+ if (Array.isArray(r)) groups.push(r);
97
128
  else if (r && r.error) errors.push(r.error);
98
129
  }
99
130
 
@@ -102,7 +133,7 @@ async function findSkills(query, opts) {
102
133
  searched.push('github');
103
134
  const r = await searchGitHub(query, opts);
104
135
  if (Array.isArray(r)) {
105
- results.push.apply(results, r);
136
+ groups.push(r);
106
137
  if (!ghToken) errors.push('github: showing repo matches — set GITHUB_TOKEN for precise SKILL.md code search');
107
138
  } else if (r && r.error) errors.push(r.error);
108
139
  }
@@ -111,25 +142,18 @@ async function findSkills(query, opts) {
111
142
  if (src.type === 'list') {
112
143
  searched.push(src.name);
113
144
  const r = await searchListSource(src, query, opts);
114
- if (Array.isArray(r)) results.push.apply(results, r); else if (r && r.error) errors.push(r.error);
145
+ if (Array.isArray(r)) groups.push(r); else if (r && r.error) errors.push(r.error);
115
146
  } else if (src.type === 'registry') {
116
147
  searched.push(src.name);
117
148
  const r = await searchWellKnown(src, query, opts);
118
- if (Array.isArray(r)) results.push.apply(results, r); else if (r && r.error) errors.push(r.error);
149
+ if (Array.isArray(r)) groups.push(r); else if (r && r.error) errors.push(r.error);
119
150
  }
120
151
  }
121
152
 
122
- // Annotate trust from the built-in trustedSources + any registered `trusted` owners.
123
153
  const trusted = new Set();
124
154
  (loadConfig(null, {}).trustedSources || []).concat(registry.trustedOwners(cwd)).forEach(function (t) { trusted.add(String(t).toLowerCase()); });
125
- for (const r of results) {
126
- if (r.trust) continue;
127
- const owner = r.source ? String(r.source).toLowerCase().split('/')[0] : '';
128
- if (owner && (trusted.has(owner) || trusted.has(String(r.source).toLowerCase()))) r.trust = 'trusted';
129
- }
130
155
 
131
- results.sort(function (a, b) { return (b.installs || 0) - (a.installs || 0) || String(a.name).localeCompare(String(b.name)); });
132
- return { results: results, searched: searched, errors: errors };
156
+ return { results: rankResults(groups, trusted), searched: searched, errors: errors };
133
157
  }
134
158
 
135
159
  async function cmdFind(args) {
@@ -159,4 +183,4 @@ async function cmdFind(args) {
159
183
  return 0;
160
184
  }
161
185
 
162
- module.exports = { findSkills, cmdFind, searchSkillsSh };
186
+ module.exports = { findSkills, cmdFind, searchSkillsSh, rankResults };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h0tp/shucky",
3
- "version": "0.4.6",
3
+ "version": "0.4.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },