@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 +8 -0
- package/lib/find.js +38 -14
- package/package.json +1 -1
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
|
|
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))
|
|
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
|
-
|
|
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))
|
|
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))
|
|
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
|
-
|
|
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 };
|