@hasna/skills 0.10.74 → 0.10.76

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/bin/mcp.js CHANGED
@@ -23,7 +23,7 @@ var package_default;
23
23
  var init_package = __esm(() => {
24
24
  package_default = {
25
25
  name: "@hasna/skills",
26
- version: "0.10.74",
26
+ version: "0.10.76",
27
27
  description: "Skills library for AI coding agents",
28
28
  type: "module",
29
29
  bin: {
@@ -33567,8 +33567,13 @@ var init_kernel_lock = __esm(() => {
33567
33567
  });
33568
33568
 
33569
33569
  // src/lib/codex-corpus-write.ts
33570
+ import { AsyncLocalStorage } from "async_hooks";
33571
+ var enclosing, leaseHolds;
33570
33572
  var init_codex_corpus_write = __esm(() => {
33571
33573
  init_kernel_lock();
33574
+ init_config();
33575
+ enclosing = new AsyncLocalStorage;
33576
+ leaseHolds = new WeakMap;
33572
33577
  });
33573
33578
 
33574
33579
  // src/lib/codex-hook-rpc.ts
@@ -36452,6 +36457,118 @@ var init_skillinfo = __esm(() => {
36452
36457
  GENERIC_ENV_PATTERN = /\b((?:OPENAI|ANTHROPIC|GEMINI|XAI|ELEVENLABS|DEEPGRAM|REPLICATE|FAL|STABILITY|EXA|FIRECRAWL|TWILIO|SENDGRID|RESEND|SLACK|DISCORD|NOTION|LINEAR|GITHUB|AWS|GOOGLE|CLOUDFLARE|VERCEL|SUPABASE|STRIPE)_[A-Z_]+)\b/g;
36453
36458
  });
36454
36459
 
36460
+ // src/lib/search.ts
36461
+ function editDistance(a, b) {
36462
+ if (a === b)
36463
+ return 0;
36464
+ if (a.length === 0)
36465
+ return b.length;
36466
+ if (b.length === 0)
36467
+ return a.length;
36468
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
36469
+ const curr = new Array(b.length + 1);
36470
+ for (let i = 1;i <= a.length; i++) {
36471
+ curr[0] = i;
36472
+ for (let j = 1;j <= b.length; j++) {
36473
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
36474
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
36475
+ }
36476
+ prev.splice(0, prev.length, ...curr);
36477
+ }
36478
+ return prev[b.length];
36479
+ }
36480
+ function fuzzyMatchScore(word, target) {
36481
+ if (target.includes(word))
36482
+ return 1;
36483
+ const tokens = target.split(/[\s\-_]+/).filter(Boolean);
36484
+ for (const token of tokens) {
36485
+ if (token.startsWith(word))
36486
+ return 0.6;
36487
+ }
36488
+ if (word.length >= 3) {
36489
+ const maxDist = word.length <= 3 ? 1 : 2;
36490
+ for (const token of tokens) {
36491
+ if (Math.abs(token.length - word.length) <= maxDist) {
36492
+ const dist = editDistance(word, token);
36493
+ if (dist <= maxDist)
36494
+ return 0.4;
36495
+ }
36496
+ }
36497
+ }
36498
+ return 0;
36499
+ }
36500
+ function searchSkills(query, registry2 = loadRegistry()) {
36501
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
36502
+ if (words.length === 0)
36503
+ return [];
36504
+ const scored = [];
36505
+ for (const skill of registry2) {
36506
+ const nameLower = skill.name.toLowerCase();
36507
+ const displayNameLower = skill.displayName.toLowerCase();
36508
+ const descriptionLower = skill.description.toLowerCase();
36509
+ const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
36510
+ const tagsCombined = tagsLower.join(" ");
36511
+ let score = 0;
36512
+ let allWordsMatch = true;
36513
+ for (const word of words) {
36514
+ let wordScore = 0;
36515
+ const nameMatch = fuzzyMatchScore(word, nameLower);
36516
+ if (nameMatch > 0)
36517
+ wordScore += 10 * nameMatch;
36518
+ const displayMatch = fuzzyMatchScore(word, displayNameLower);
36519
+ if (displayMatch > 0)
36520
+ wordScore += 7 * displayMatch;
36521
+ const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
36522
+ if (tagMatch > 0)
36523
+ wordScore += 5 * tagMatch;
36524
+ const descMatch = fuzzyMatchScore(word, descriptionLower);
36525
+ if (descMatch > 0)
36526
+ wordScore += 2 * descMatch;
36527
+ if (wordScore === 0) {
36528
+ allWordsMatch = false;
36529
+ break;
36530
+ }
36531
+ score += wordScore;
36532
+ }
36533
+ if (allWordsMatch && score > 0) {
36534
+ scored.push({ skill, score });
36535
+ }
36536
+ }
36537
+ scored.sort((a, b) => b.score - a.score);
36538
+ return scored.map((s) => s.skill);
36539
+ }
36540
+ function findCloseSkillNames(query, names, maxResults = 3) {
36541
+ if (maxResults <= 0)
36542
+ return [];
36543
+ const q = query.trim().toLowerCase();
36544
+ if (q.length === 0)
36545
+ return [];
36546
+ const tokens = q.split("-").filter(Boolean);
36547
+ const ranked = [];
36548
+ if (q.length >= 3) {
36549
+ for (const name of names) {
36550
+ const candidate = name.toLowerCase();
36551
+ const rank = candidate === q ? 0 : candidate.endsWith(`-${q}`) || candidate.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate.split("-").includes(token)) ? 2 : undefined;
36552
+ if (rank !== undefined)
36553
+ ranked.push([name, rank]);
36554
+ }
36555
+ }
36556
+ ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
36557
+ const found = ranked.slice(0, maxResults).map(([name]) => name);
36558
+ if (found.length === maxResults)
36559
+ return found;
36560
+ const taken = new Set(found);
36561
+ const threshold = Math.max(3, Math.floor(q.length / 2));
36562
+ const byDistance = names.filter((name) => !taken.has(name)).map((name) => ({ name, distance: editDistance(q, name.toLowerCase()) })).filter((entry) => entry.distance <= threshold).sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name));
36563
+ return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
36564
+ }
36565
+ function findSimilarSkills(query, maxResults = 3, registry2 = loadRegistry()) {
36566
+ return findCloseSkillNames(query, registry2.map((skill) => skill.name), maxResults);
36567
+ }
36568
+ var init_search = __esm(() => {
36569
+ init_registry();
36570
+ });
36571
+
36455
36572
  // src/lib/selected-document.ts
36456
36573
  function readSelectedDocument(entries, file2) {
36457
36574
  const entry = file2 !== undefined ? entries.find((candidate) => candidate.path === file2) : DOCUMENT_PATHS.map((path) => entries.find((candidate) => candidate.path === path && candidate.bytes.length > 0)).find(Boolean);
@@ -36532,11 +36649,30 @@ function exactProfileSelection(spec, profile) {
36532
36649
  const at = spec.lastIndexOf("@");
36533
36650
  const slug = at > 0 ? spec.slice(0, at) : spec;
36534
36651
  const version2 = at > 0 ? spec.slice(at + 1) : undefined;
36535
- const selection = profile.selections.find((entry) => (entry.slug === slug || entry.aliases?.includes(slug)) && (version2 === undefined || version2 === entry.version));
36536
- if (!selection || version2 === "")
36537
- throw new SkillSelectionError("SKILL_NOT_SELECTED", "The requested exact skill version is not selected by this profile or project lock.");
36652
+ const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
36653
+ if (version2 === "")
36654
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
36655
+ if (named.length === 0)
36656
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
36657
+ const selection = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
36658
+ if (!selection)
36659
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
36538
36660
  return selection;
36539
36661
  }
36662
+ function emptyVersionRefusal(spec) {
36663
+ return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
36664
+ }
36665
+ function wrongVersionRefusal(slug, version2, named) {
36666
+ const versions2 = [...new Set(named.map((entry) => entry.version))].sort();
36667
+ const canonical9 = named[0].slug;
36668
+ return `Skill '${slug}' is selected at version '${versions2.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions2[0]}'.`;
36669
+ }
36670
+ function unknownNameRefusal(slug, profile) {
36671
+ const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
36672
+ const close = findCloseSkillNames(slug, names);
36673
+ const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
36674
+ return `Skill '${slug}' is not selected by this Skills profile or project lock.${hint} ` + `Read the accepted names with 'skills search <term>' or 'skills list --all --json'; accept one with 'skills pin <name>'. ${ACCEPTED_FORM}`;
36675
+ }
36540
36676
  async function loadSelectedSkill(spec, profileId, options = {}) {
36541
36677
  const context = await resolveSelectionContext(profileId, options);
36542
36678
  const selection = exactProfileSelection(spec, context.receipt.profile);
@@ -36581,9 +36717,11 @@ async function selectedSkillRequirements(spec, profileId, options = {}) {
36581
36717
  }
36582
36718
  return { ...getSkillRequirementsFromContent(selection.slug, texts, dependencies), selection };
36583
36719
  }
36720
+ var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
36584
36721
  var init_selection_resolver = __esm(() => {
36585
36722
  init_profile_client();
36586
36723
  init_skillinfo();
36724
+ init_search();
36587
36725
  init_selected_document();
36588
36726
  init_selection_cache();
36589
36727
  });
@@ -38615,95 +38753,6 @@ var init_registry_types = __esm(() => {
38615
38753
  ];
38616
38754
  });
38617
38755
 
38618
- // src/lib/search.ts
38619
- function editDistance(a, b) {
38620
- if (a === b)
38621
- return 0;
38622
- if (a.length === 0)
38623
- return b.length;
38624
- if (b.length === 0)
38625
- return a.length;
38626
- const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
38627
- const curr = new Array(b.length + 1);
38628
- for (let i = 1;i <= a.length; i++) {
38629
- curr[0] = i;
38630
- for (let j = 1;j <= b.length; j++) {
38631
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
38632
- curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
38633
- }
38634
- prev.splice(0, prev.length, ...curr);
38635
- }
38636
- return prev[b.length];
38637
- }
38638
- function fuzzyMatchScore(word, target) {
38639
- if (target.includes(word))
38640
- return 1;
38641
- const tokens = target.split(/[\s\-_]+/).filter(Boolean);
38642
- for (const token of tokens) {
38643
- if (token.startsWith(word))
38644
- return 0.6;
38645
- }
38646
- if (word.length >= 3) {
38647
- const maxDist = word.length <= 3 ? 1 : 2;
38648
- for (const token of tokens) {
38649
- if (Math.abs(token.length - word.length) <= maxDist) {
38650
- const dist = editDistance(word, token);
38651
- if (dist <= maxDist)
38652
- return 0.4;
38653
- }
38654
- }
38655
- }
38656
- return 0;
38657
- }
38658
- function searchSkills(query, registry2 = loadRegistry()) {
38659
- const words = query.toLowerCase().split(/\s+/).filter(Boolean);
38660
- if (words.length === 0)
38661
- return [];
38662
- const scored = [];
38663
- for (const skill of registry2) {
38664
- const nameLower = skill.name.toLowerCase();
38665
- const displayNameLower = skill.displayName.toLowerCase();
38666
- const descriptionLower = skill.description.toLowerCase();
38667
- const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
38668
- const tagsCombined = tagsLower.join(" ");
38669
- let score = 0;
38670
- let allWordsMatch = true;
38671
- for (const word of words) {
38672
- let wordScore = 0;
38673
- const nameMatch = fuzzyMatchScore(word, nameLower);
38674
- if (nameMatch > 0)
38675
- wordScore += 10 * nameMatch;
38676
- const displayMatch = fuzzyMatchScore(word, displayNameLower);
38677
- if (displayMatch > 0)
38678
- wordScore += 7 * displayMatch;
38679
- const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
38680
- if (tagMatch > 0)
38681
- wordScore += 5 * tagMatch;
38682
- const descMatch = fuzzyMatchScore(word, descriptionLower);
38683
- if (descMatch > 0)
38684
- wordScore += 2 * descMatch;
38685
- if (wordScore === 0) {
38686
- allWordsMatch = false;
38687
- break;
38688
- }
38689
- score += wordScore;
38690
- }
38691
- if (allWordsMatch && score > 0) {
38692
- scored.push({ skill, score });
38693
- }
38694
- }
38695
- scored.sort((a, b) => b.score - a.score);
38696
- return scored.map((s) => s.skill);
38697
- }
38698
- function findSimilarSkills(query, maxResults = 3, registry2 = loadRegistry()) {
38699
- const q = query.toLowerCase();
38700
- const scored = registry2.map((s) => ({ name: s.name, dist: editDistance(q, s.name.toLowerCase()) })).filter((s) => s.dist <= Math.max(3, Math.floor(q.length / 2))).sort((a, b) => a.dist - b.dist);
38701
- return scored.slice(0, maxResults).map((s) => s.name);
38702
- }
38703
- var init_search = __esm(() => {
38704
- init_registry();
38705
- });
38706
-
38707
38756
  // src/lib/registry.ts
38708
38757
  import { existsSync as existsSync13, readFileSync as readFileSync18, readdirSync as readdirSync10 } from "fs";
38709
38758
  import { join as join33 } from "path";
package/bin/migrate.js CHANGED
@@ -25,7 +25,7 @@ import { join as join5 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@hasna/skills",
28
- version: "0.10.74",
28
+ version: "0.10.76",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
24140
24140
  // package.json
24141
24141
  var package_default = {
24142
24142
  name: "@hasna/skills",
24143
- version: "0.10.74",
24143
+ version: "0.10.76",
24144
24144
  description: "Skills library for AI coding agents",
24145
24145
  type: "module",
24146
24146
  bin: {
package/bin/worker.js CHANGED
@@ -24143,7 +24143,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24143
24143
  // package.json
24144
24144
  var package_default = {
24145
24145
  name: "@hasna/skills",
24146
- version: "0.10.74",
24146
+ version: "0.10.76",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Stray positionals on a verb that declares none are a phantom verb, not a
3
+ * no-op. Commander's `allowExcessArguments` defaults to true (12.1.0,
4
+ * `lib/command.js:28`), so `skills categories typo` exited 0 with the ordinary
5
+ * output and `skills list typo` printed the whole catalogue: a caller reading
6
+ * the exit code is told the word was understood (BUG e3997558).
7
+ *
8
+ * The default command refuses by hand so it can name the verbs that exist, and
9
+ * `skills mcp` refuses by hand so it can name its three forms. Those two keep
10
+ * their own, better sentences. Every other command that declares no argument of
11
+ * its own is closed here in one pass, so the next command added gets the
12
+ * refusal without its author having to remember the hole exists.
13
+ */
14
+ import type { Command } from "commander";
15
+ /**
16
+ * Opt a command out of the pass because it owns a more precise refusal than
17
+ * commander's. Chainable, and it keeps `allowExcessArguments(true)` so the
18
+ * command still receives the stray word it wants to report.
19
+ */
20
+ export declare function permitStrayArguments(command: Command): Command;
21
+ /**
22
+ * Refuse positionals on every command that declares no argument of its own.
23
+ *
24
+ * A command that declares arguments keeps commander's own arity check, and a
25
+ * command with subcommands never reaches the excess check for a word that names
26
+ * a child (commander dispatches the subcommand first). Commands without an
27
+ * action handler reach it only with zero operands, so the flag is inert there.
28
+ */
29
+ export declare function rejectStrayArguments(command: Command): void;