@hasna/skills 0.10.73 → 0.10.75

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/index.js CHANGED
@@ -8566,8 +8566,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
8566
8566
  displayName: manifest.displayName ?? displayName(manifest.name),
8567
8567
  category: manifest.category ?? "Development Tools",
8568
8568
  tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
8569
- inputs: [],
8570
- commands: []
8569
+ inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
8570
+ commands: Array.isArray(manifest.commands) ? manifest.commands : []
8571
8571
  };
8572
8572
  if (!existsSync6(join6(skillPath, "SKILL.md"))) {
8573
8573
  writeFileSync2(join6(skillPath, "SKILL.md"), renderSkillMd(next));
@@ -15144,7 +15144,7 @@ import { basename as basename13, dirname as dirname24, isAbsolute as isAbsolute2
15144
15144
  import { homedir as homedir5 } from "os";
15145
15145
  import { isAbsolute as isAbsolute25, join as join36, resolve as resolve24 } from "path";
15146
15146
  // package.json
15147
- var version = "0.10.73";
15147
+ var version = "0.10.75";
15148
15148
  var package_default = {
15149
15149
  name: "@hasna/skills",
15150
15150
  version,
@@ -20212,11 +20212,19 @@ function planAgentIntegration(options) {
20212
20212
  const executable2 = discoveryExecutable(agent) ?? (prior ? recordedDiscoveryExecutable(agent, prior) : null);
20213
20213
  executables.set(agent, executable2);
20214
20214
  const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? { ...policy.bridge.discovery[agent], ...agent === "codex" ? { codexDisabledPluginSkills: policy.bridge.codexPluginSkills ?? [] } : {} } : undefined;
20215
- if (retainedReview) {
20216
- assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
20217
- retainedAgents.push(agent);
20215
+ try {
20216
+ if (retainedReview) {
20217
+ assertRetainedReviewState(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
20218
+ retainedAgents.push(agent);
20219
+ }
20220
+ return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
20221
+ } catch (error) {
20222
+ if (isDiscoveryRootUnresolved(error) || error instanceof SumiPathResolverError || error instanceof NativeSkillCopiesDriftError)
20223
+ throw error;
20224
+ if (!retainedReview)
20225
+ throw error;
20226
+ throw new Error(`NATIVE_SKILL_DRIFT: ${error.message.replace(/^NATIVE_SKILL_DRIFT: /, "")}. This agent reuses a saved discovery review and that review no longer verifies, so a fresh explicit review is required: skills hook install --agent ${agent} --discovery-inputs <review> --apply.`);
20218
20227
  }
20219
- return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
20220
20228
  });
20221
20229
  const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) : null;
20222
20230
  const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
@@ -20970,7 +20978,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
20970
20978
  assertCurrent();
20971
20979
  recheckRootAliases(aliases);
20972
20980
  for (const agent of plan.retainedReviewChecks?.agents ?? [])
20973
- assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
20981
+ assertRetainedReviewState(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
20974
20982
  if (plan.reviewArtifactMigration) {
20975
20983
  const verified = planReviewedArtifactMigration(plan.reviewArtifactMigration);
20976
20984
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.discoveryAfter) !== JSON.stringify(plan.discoveryAfter))
@@ -21013,6 +21021,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
21013
21021
  if (!plan.changes.length) {
21014
21022
  assertCurrent();
21015
21023
  recheckRootAliases(aliases);
21024
+ for (const agent of plan.retainedReviewChecks?.agents ?? [])
21025
+ assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
21016
21026
  return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
21017
21027
  }
21018
21028
  assertSafePath(backupRoot);
@@ -21062,6 +21072,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
21062
21072
  }
21063
21073
  for (const agent of plan.managedAgentChecks?.agents ?? [])
21064
21074
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.projectDir ?? plan.managedAgentChecks.home });
21075
+ for (const agent of plan.retainedReviewChecks?.agents ?? [])
21076
+ assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
21065
21077
  atomicWrite2(join31(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha4(change.before), afterHash: sha4(change.after) })), backups, ...plan.cacheWitnessUpgrade ? { cacheWitnessUpgrade: plan.cacheWitnessUpgrade } : {}, ...plan.retainedReviewArtifacts?.length ? { retainedReviewArtifacts: plan.retainedReviewArtifacts.map((item) => ({ path: item.path, ...item.artifact })) } : {}, ...aliases.length ? { rootAliases: aliases } : {} }) + `
21066
21078
  `);
21067
21079
  } catch (error) {
@@ -21393,7 +21405,7 @@ enabled = false
21393
21405
  function assertManagedAgentBridge(agent, options = {}) {
21394
21406
  return assertManagedAgentBridgeWithDiscovery(agent, options);
21395
21407
  }
21396
- function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
21408
+ function assertManagedBridgeCustody(agent, options = {}, provenDiscovery) {
21397
21409
  const home = resolve23(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
21398
21410
  assertSafePath(join31(dataDir2, "agent-policy.json"));
21399
21411
  const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
@@ -21410,7 +21422,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
21410
21422
  if (!isOwnedCliBridge(expected, [expected]))
21411
21423
  throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
21412
21424
  const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
21413
- const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep14}.agents${sep14}skills${sep14}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
21414
21425
  const discovery = provenDiscovery ?? binding.discovery?.[agent];
21415
21426
  assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
21416
21427
  for (const source of discovery?.sources ?? [])
@@ -21425,11 +21436,30 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
21425
21436
  throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
21426
21437
  if (options.profileId !== undefined && options.profileId !== profile)
21427
21438
  throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
21439
+ return { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery, configPath, config, command, profile };
21440
+ }
21441
+ function assertUnrepairableBridgeProtection(custody, agent) {
21442
+ if (agent !== "hermes")
21443
+ return;
21444
+ const { home, dataDir: dataDir2, binding, config, command, profile } = custody;
21445
+ const supervisor = binding.supervisors?.hermes;
21446
+ if (supervisor?.path !== join31(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
21447
+ throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
21448
+ assertHermesProtection(home, config, command, profile, supervisor);
21449
+ }
21450
+ function assertRetainedReviewState(agent, options) {
21451
+ const custody = assertManagedBridgeCustody(agent, options);
21452
+ assertUnrepairableBridgeProtection(custody, agent);
21453
+ assertManagedNativeDiscovery(custody, agent, options);
21454
+ }
21455
+
21456
+ class NativeSkillCopiesDriftError extends Error {
21457
+ }
21458
+ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
21459
+ const custody = assertManagedBridgeCustody(agent, options, provenDiscovery);
21460
+ const { home, aliases, expected, discovery, config, command, profile } = custody;
21428
21461
  if (agent === "hermes") {
21429
- const supervisor = binding.supervisors?.hermes;
21430
- if (supervisor?.path !== join31(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
21431
- throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
21432
- assertHermesProtection(home, config, command, profile, supervisor);
21462
+ assertUnrepairableBridgeProtection(custody, agent);
21433
21463
  } else if (agent === "sumi") {
21434
21464
  if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(join31(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js")), command, profile))
21435
21465
  throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
@@ -21451,6 +21481,21 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
21451
21481
  if (agent === "gemini" && (config.hooksConfig?.enabled === false || config.skills?.enabled !== true || !["antigravity-support", "skill-creator"].every((name) => config.skills?.disabled?.includes(name)) || config.skills?.disabled?.includes(CLI_BRIDGE_NAME)))
21452
21482
  throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
21453
21483
  }
21484
+ if (agent === "codex") {
21485
+ const codexConfig = Bun.TOML.parse(readOptional(canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases)) ?? "");
21486
+ if (codexConfig.skills?.bundled?.enabled !== false)
21487
+ throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
21488
+ const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join31(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
21489
+ if ([...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
21490
+ throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
21491
+ }
21492
+ if (agent === "gemini" && discovery && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
21493
+ throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
21494
+ return assertManagedNativeDiscovery(custody, agent, options);
21495
+ }
21496
+ function assertManagedNativeDiscovery(custody, agent, options) {
21497
+ const { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery } = custody;
21498
+ const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep14}.agents${sep14}skills${sep14}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
21454
21499
  const codexPath = canonicalAgentPath(join31(home, ".codex", "config.toml"), aliases);
21455
21500
  const vendorCache = canonicalAgentPath(join31(home, ".codex", "plugins", "cache"), aliases);
21456
21501
  const witnessTolerance = agent === "codex" && options.codexPromptHook === true ? {
@@ -21461,11 +21506,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
21461
21506
  if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
21462
21507
  throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
21463
21508
  const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
21464
- if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
21465
- throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
21466
- const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join31(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
21467
- if (agent === "codex" && [...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
21468
- throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
21469
21509
  if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join31(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
21470
21510
  throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
21471
21511
  const disabledPlugin = (entry) => {
@@ -21502,8 +21542,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
21502
21542
  };
21503
21543
  if (!discovery || discovery.agent !== agent)
21504
21544
  throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
21505
- if (agent === "gemini" && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
21506
- throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
21507
21545
  try {
21508
21546
  verifyAgentDiscovery(agent === "codex" ? { ...discovery, codexDisabledPluginSkills: binding.codexPluginSkills ?? [] } : discovery, options.codexDiscoveryRecovery, aliases, witnessTolerance);
21509
21547
  if (agent === "sumi" && discovery.method === "reviewed")
@@ -21558,7 +21596,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
21558
21596
  return false;
21559
21597
  }
21560
21598
  }, read: (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)) });
21561
- throw new Error(`NATIVE_SKILL_DRIFT: ${blocking.length} unexpected native skill copies were found: ${nativeSkillPathList(blocking.map((entry) => entry.path))}. ${remediation}${adapterRefusal ? ` Native policy adapter refused: ${adapterRefusal}` : ""}`);
21599
+ throw new NativeSkillCopiesDriftError(`NATIVE_SKILL_DRIFT: ${blocking.length} unexpected native skill copies were found: ${nativeSkillPathList(blocking.map((entry) => entry.path))}. ${remediation}${adapterRefusal ? ` Native policy adapter refused: ${adapterRefusal}` : ""}`);
21562
21600
  }
21563
21601
  recheckRootAliases(aliases);
21564
21602
  const discoveryChanges = (witnessTolerance?.changes ?? []).filter((change, index, all) => all.findIndex((other) => other.kind === change.kind && other.path === change.path) === index);
@@ -22689,6 +22727,115 @@ function readIfExists(path) {
22689
22727
  return null;
22690
22728
  }
22691
22729
 
22730
+ // src/lib/search.ts
22731
+ function editDistance(a, b) {
22732
+ if (a === b)
22733
+ return 0;
22734
+ if (a.length === 0)
22735
+ return b.length;
22736
+ if (b.length === 0)
22737
+ return a.length;
22738
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
22739
+ const curr = new Array(b.length + 1);
22740
+ for (let i = 1;i <= a.length; i++) {
22741
+ curr[0] = i;
22742
+ for (let j = 1;j <= b.length; j++) {
22743
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
22744
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
22745
+ }
22746
+ prev.splice(0, prev.length, ...curr);
22747
+ }
22748
+ return prev[b.length];
22749
+ }
22750
+ function fuzzyMatchScore(word, target) {
22751
+ if (target.includes(word))
22752
+ return 1;
22753
+ const tokens = target.split(/[\s\-_]+/).filter(Boolean);
22754
+ for (const token of tokens) {
22755
+ if (token.startsWith(word))
22756
+ return 0.6;
22757
+ }
22758
+ if (word.length >= 3) {
22759
+ const maxDist = word.length <= 3 ? 1 : 2;
22760
+ for (const token of tokens) {
22761
+ if (Math.abs(token.length - word.length) <= maxDist) {
22762
+ const dist = editDistance(word, token);
22763
+ if (dist <= maxDist)
22764
+ return 0.4;
22765
+ }
22766
+ }
22767
+ }
22768
+ return 0;
22769
+ }
22770
+ function searchSkills(query, registry = loadRegistry()) {
22771
+ const words = query.toLowerCase().split(/\s+/).filter(Boolean);
22772
+ if (words.length === 0)
22773
+ return [];
22774
+ const scored = [];
22775
+ for (const skill of registry) {
22776
+ const nameLower = skill.name.toLowerCase();
22777
+ const displayNameLower = skill.displayName.toLowerCase();
22778
+ const descriptionLower = skill.description.toLowerCase();
22779
+ const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
22780
+ const tagsCombined = tagsLower.join(" ");
22781
+ let score = 0;
22782
+ let allWordsMatch = true;
22783
+ for (const word of words) {
22784
+ let wordScore = 0;
22785
+ const nameMatch = fuzzyMatchScore(word, nameLower);
22786
+ if (nameMatch > 0)
22787
+ wordScore += 10 * nameMatch;
22788
+ const displayMatch = fuzzyMatchScore(word, displayNameLower);
22789
+ if (displayMatch > 0)
22790
+ wordScore += 7 * displayMatch;
22791
+ const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
22792
+ if (tagMatch > 0)
22793
+ wordScore += 5 * tagMatch;
22794
+ const descMatch = fuzzyMatchScore(word, descriptionLower);
22795
+ if (descMatch > 0)
22796
+ wordScore += 2 * descMatch;
22797
+ if (wordScore === 0) {
22798
+ allWordsMatch = false;
22799
+ break;
22800
+ }
22801
+ score += wordScore;
22802
+ }
22803
+ if (allWordsMatch && score > 0) {
22804
+ scored.push({ skill, score });
22805
+ }
22806
+ }
22807
+ scored.sort((a, b) => b.score - a.score);
22808
+ return scored.map((s) => s.skill);
22809
+ }
22810
+ function findCloseSkillNames(query, names, maxResults = 3) {
22811
+ if (maxResults <= 0)
22812
+ return [];
22813
+ const q = query.trim().toLowerCase();
22814
+ if (q.length === 0)
22815
+ return [];
22816
+ const tokens = q.split("-").filter(Boolean);
22817
+ const ranked = [];
22818
+ if (q.length >= 3) {
22819
+ for (const name of names) {
22820
+ const candidate = name.toLowerCase();
22821
+ const rank = candidate === q ? 0 : candidate.endsWith(`-${q}`) || candidate.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate.split("-").includes(token)) ? 2 : undefined;
22822
+ if (rank !== undefined)
22823
+ ranked.push([name, rank]);
22824
+ }
22825
+ }
22826
+ ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
22827
+ const found = ranked.slice(0, maxResults).map(([name]) => name);
22828
+ if (found.length === maxResults)
22829
+ return found;
22830
+ const taken = new Set(found);
22831
+ const threshold = Math.max(3, Math.floor(q.length / 2));
22832
+ 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));
22833
+ return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
22834
+ }
22835
+ function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
22836
+ return findCloseSkillNames(query, registry.map((skill) => skill.name), maxResults);
22837
+ }
22838
+
22692
22839
  // src/lib/selected-document.ts
22693
22840
  var DOCUMENT_PATHS = ["SKILL.md", "README.md", "CLAUDE.md"];
22694
22841
  function readSelectedDocument(entries, file) {
@@ -22805,11 +22952,31 @@ function exactProfileSelection(spec, profile) {
22805
22952
  const at = spec.lastIndexOf("@");
22806
22953
  const slug = at > 0 ? spec.slice(0, at) : spec;
22807
22954
  const version2 = at > 0 ? spec.slice(at + 1) : undefined;
22808
- const selection = profile.selections.find((entry) => (entry.slug === slug || entry.aliases?.includes(slug)) && (version2 === undefined || version2 === entry.version));
22809
- if (!selection || version2 === "")
22810
- throw new SkillSelectionError("SKILL_NOT_SELECTED", "The requested exact skill version is not selected by this profile or project lock.");
22955
+ const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
22956
+ if (version2 === "")
22957
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
22958
+ if (named.length === 0)
22959
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
22960
+ const selection = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
22961
+ if (!selection)
22962
+ throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
22811
22963
  return selection;
22812
22964
  }
22965
+ var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
22966
+ function emptyVersionRefusal(spec) {
22967
+ return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
22968
+ }
22969
+ function wrongVersionRefusal(slug, version2, named) {
22970
+ const versions = [...new Set(named.map((entry) => entry.version))].sort();
22971
+ const canonical9 = named[0].slug;
22972
+ return `Skill '${slug}' is selected at version '${versions.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions[0]}'.`;
22973
+ }
22974
+ function unknownNameRefusal(slug, profile) {
22975
+ const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
22976
+ const close = findCloseSkillNames(slug, names);
22977
+ const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
22978
+ 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}`;
22979
+ }
22813
22980
 
22814
22981
  // src/lib/selected-manifest.ts
22815
22982
  function jsonEntry(entries, path) {
@@ -24133,6 +24300,12 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
24133
24300
  }
24134
24301
  var CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
24135
24302
  var CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
24303
+ function claudeControlSource(source) {
24304
+ return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename15(source.path)) && !source.path.includes(`${sep15}.claude${sep15}plugins${sep15}cache${sep15}`);
24305
+ }
24306
+ function retainedAbsentClaudeRoot(absentRoots, source) {
24307
+ return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
24308
+ }
24136
24309
  function codexInstallationRoots(binding) {
24137
24310
  const proof = binding.codexInstallationInputs;
24138
24311
  if (proof === undefined)
@@ -24542,7 +24715,10 @@ function resolveAgentDiscovery(options) {
24542
24715
  if (!review.sources.some((source) => source.path === canonical9(configPath)))
24543
24716
  throw new Error("Discovery review must include the agent configuration source");
24544
24717
  const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
24718
+ const absentRoots = absentRetiredClaudeRoots(supplied);
24545
24719
  const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
24720
+ if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
24721
+ return source;
24546
24722
  if (agent === "claude" && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && source.reviewArtifact === undefined && [undefined, "bytes", "path-bytes"].includes(source.hashMode) && isClaudePluginCacheOrphanMarker(source.path)) {
24547
24723
  return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
24548
24724
  }
@@ -24575,6 +24751,11 @@ function resolveAgentDiscovery(options) {
24575
24751
  });
24576
24752
  if (agent === "claude")
24577
24753
  assertReviewedClaudeCacheClosures(home, config, review, canonical9);
24754
+ for (const root2 of absentRoots) {
24755
+ safe6(root2);
24756
+ if (lstatSync32(root2, { throwIfNoEntry: false }))
24757
+ throw new Error("Retired native materialization changed during discovery review");
24758
+ }
24578
24759
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
24579
24760
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual9(candidate2, source)) === index);
24580
24761
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
@@ -25302,92 +25483,6 @@ var CATEGORIES = [
25302
25483
  "Event Management"
25303
25484
  ];
25304
25485
  var BASIC_SKILL_NAMES = [];
25305
-
25306
- // src/lib/search.ts
25307
- function editDistance(a, b) {
25308
- if (a === b)
25309
- return 0;
25310
- if (a.length === 0)
25311
- return b.length;
25312
- if (b.length === 0)
25313
- return a.length;
25314
- const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
25315
- const curr = new Array(b.length + 1);
25316
- for (let i = 1;i <= a.length; i++) {
25317
- curr[0] = i;
25318
- for (let j = 1;j <= b.length; j++) {
25319
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
25320
- curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
25321
- }
25322
- prev.splice(0, prev.length, ...curr);
25323
- }
25324
- return prev[b.length];
25325
- }
25326
- function fuzzyMatchScore(word, target) {
25327
- if (target.includes(word))
25328
- return 1;
25329
- const tokens = target.split(/[\s\-_]+/).filter(Boolean);
25330
- for (const token of tokens) {
25331
- if (token.startsWith(word))
25332
- return 0.6;
25333
- }
25334
- if (word.length >= 3) {
25335
- const maxDist = word.length <= 3 ? 1 : 2;
25336
- for (const token of tokens) {
25337
- if (Math.abs(token.length - word.length) <= maxDist) {
25338
- const dist = editDistance(word, token);
25339
- if (dist <= maxDist)
25340
- return 0.4;
25341
- }
25342
- }
25343
- }
25344
- return 0;
25345
- }
25346
- function searchSkills(query, registry = loadRegistry()) {
25347
- const words = query.toLowerCase().split(/\s+/).filter(Boolean);
25348
- if (words.length === 0)
25349
- return [];
25350
- const scored = [];
25351
- for (const skill of registry) {
25352
- const nameLower = skill.name.toLowerCase();
25353
- const displayNameLower = skill.displayName.toLowerCase();
25354
- const descriptionLower = skill.description.toLowerCase();
25355
- const tagsLower = skill.tags.map((t2) => t2.toLowerCase());
25356
- const tagsCombined = tagsLower.join(" ");
25357
- let score = 0;
25358
- let allWordsMatch = true;
25359
- for (const word of words) {
25360
- let wordScore = 0;
25361
- const nameMatch = fuzzyMatchScore(word, nameLower);
25362
- if (nameMatch > 0)
25363
- wordScore += 10 * nameMatch;
25364
- const displayMatch = fuzzyMatchScore(word, displayNameLower);
25365
- if (displayMatch > 0)
25366
- wordScore += 7 * displayMatch;
25367
- const tagMatch = Math.max(...tagsLower.map((t2) => fuzzyMatchScore(word, t2)), fuzzyMatchScore(word, tagsCombined));
25368
- if (tagMatch > 0)
25369
- wordScore += 5 * tagMatch;
25370
- const descMatch = fuzzyMatchScore(word, descriptionLower);
25371
- if (descMatch > 0)
25372
- wordScore += 2 * descMatch;
25373
- if (wordScore === 0) {
25374
- allWordsMatch = false;
25375
- break;
25376
- }
25377
- score += wordScore;
25378
- }
25379
- if (allWordsMatch && score > 0) {
25380
- scored.push({ skill, score });
25381
- }
25382
- }
25383
- scored.sort((a, b) => b.score - a.score);
25384
- return scored.map((s) => s.skill);
25385
- }
25386
- function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
25387
- const q = query.toLowerCase();
25388
- const scored = registry.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);
25389
- return scored.slice(0, maxResults).map((s) => s.name);
25390
- }
25391
25486
  // src/lib/registry.ts
25392
25487
  function isBasicSkillName(name) {
25393
25488
  return BASIC_SKILL_NAMES.includes(name);
@@ -52,6 +52,16 @@ export declare function ensurePortableSkillFiles(skillPath: string, manifest: Po
52
52
  * locally, so `port` must never fabricate executable stubs (package.json, bin,
53
53
  * src/index.ts, tsconfig.json, AGENTS.md). Apart from aligning declared names
54
54
  * after an import rename, it preserves the copied prose and metadata.
55
+ *
56
+ * "Metadata" includes what the candidate declared in skill.json. An instruction
57
+ * skill may legitimately bundle a helper command (and inputs for it) — the
58
+ * validator permits a package.json bin on an instruction skill for exactly that
59
+ * reason — so declared `commands` and `inputs` are carried through verbatim and
60
+ * only an absent declaration becomes an explicit empty array. Forcing them to []
61
+ * here silently erased the declaration on disk: writeSkillJsonWithHash() merges
62
+ * the rebuilt manifest over the copied candidate skill.json, so the candidate's
63
+ * own array was destroyed and the manifest was left self-inconsistent
64
+ * (runtime.entrypoint still naming an entry no command declared).
55
65
  */
56
66
  export declare function ensureInstructionSkillFiles(skillPath: string, manifest: PortableSkillManifest): PortableSkillManifest;
57
67
  export declare function copySkillDirectory(source: string, destination: string): void;
@@ -11,6 +11,24 @@ export declare function editDistance(a: string, b: string): number;
11
11
  * Search skills by name, description, and tags using fuzzy matching.
12
12
  */
13
13
  export declare function searchSkills(query: string, registry?: SkillMeta[]): SkillMeta[];
14
+ /**
15
+ * Bounded close canonical names for a refused name, strongest evidence first:
16
+ *
17
+ * 0. the exact name;
18
+ * 1. a hyphen-delimited containment — the candidate owns the query as a whole
19
+ * leading or trailing segment (`fleet-refresh` → `instructions-fleet-refresh`);
20
+ * 2. a multi-word token subset of the candidate;
21
+ * 3. the edit-distance rule that was the only signal before.
22
+ *
23
+ * Containment is what edit distance cannot see, and it is the case that cost
24
+ * real time: the canonical name that owns a shorter name as its trailing
25
+ * segment is 13 edits from the short name, so a caller naming the procedure by
26
+ * its last words was told the procedure did not exist (Agents Audit A27). The
27
+ * distance pass still runs and fills any slots containment leaves, so a
28
+ * distance-only miss — including every query under three characters — keeps the
29
+ * answer it has always had.
30
+ */
31
+ export declare function findCloseSkillNames(query: string, names: readonly string[], maxResults?: number): string[];
14
32
  /**
15
33
  * Find skills with names similar to the given query (for "did you mean?" suggestions).
16
34
  */
@@ -13,6 +13,50 @@ export interface ResolvedSelectedRun {
13
13
  cacheDir: string;
14
14
  }
15
15
  export declare function resolveSelectedRun(spec: string, profileId: string, options?: SelectionResolverOptions): Promise<ResolvedSelectedRun>;
16
+ /**
17
+ * The operation an instruction skill declares, if it declares one.
18
+ *
19
+ * An instruction skill is prose-first: agents follow SKILL.md, and `skills run`
20
+ * refuses it. Its manifest may still declare the one operation it ships — the
21
+ * first `commands` entry, naming the helper — together with an explicit runtime
22
+ * contract in `runtime` (`runtime` plus its `version`/`timeout` policy). Such a
23
+ * skill is runnable through that declared operation, and only through it: the
24
+ * executed file is exactly the entry the selected command declares, it must be a
25
+ * regular file in the selected bundle, and a `runtime.entrypoint` that disagrees
26
+ * with it is an inconsistent declaration that refuses (see
27
+ * {@link instructionEntrypointMismatch}) rather than quietly running the other
28
+ * file. An instruction skill that declares no operation — no command, a
29
+ * non-string or empty entry, or no runtime contract — keeps the prose-only
30
+ * refusal (`skills load`, never `skills run`), so an instruction bundle is never
31
+ * mistaken for a runnable stub.
32
+ */
33
+ export declare function declaredOperation(manifest: Record<string, any>): {
34
+ entry: string;
35
+ } | null;
36
+ /**
37
+ * The entry the instruction skill's declared command names, when it names one.
38
+ * The first `commands` entry is the operation: nothing else in the manifest
39
+ * selects what an instruction skill runs.
40
+ */
41
+ export declare function instructionCommandEntry(manifest: Record<string, any>): string | null;
42
+ /**
43
+ * A declared `runtime.entrypoint` that disagrees with the declared command entry,
44
+ * for an instruction skill. The command entry is the authority, so a disagreement
45
+ * is an inconsistent declaration: it refuses, and the disagreeing file never runs.
46
+ */
47
+ export declare function instructionEntrypointMismatch(manifest: Record<string, any>): string | null;
48
+ /**
49
+ * Why an instruction skill cannot run locally, if it cannot. `isBundledFile` says
50
+ * whether a path is a regular file in the selected bundle, so a declared command
51
+ * entry that is not there is refused by name instead of being substituted. The
52
+ * CLI, the MCP tool and the local executor all ask this one function, so the
53
+ * reason and the code cannot drift between them. A runnable instruction skill
54
+ * (declared command, agreeing runtime, entry present in the bundle) returns null.
55
+ */
56
+ export declare function instructionRunRefusal(manifest: Record<string, any>, isBundledFile: (path: string) => boolean): {
57
+ code: string;
58
+ message: string;
59
+ } | null;
16
60
  export interface SelectedLocalRunOptions {
17
61
  args?: string[];
18
62
  input?: unknown;
@@ -54,6 +98,7 @@ export declare function executeSelectedLocal(selected: ResolvedSelectedRun, opti
54
98
  selection: ResolvedSkillSelection;
55
99
  target: "local";
56
100
  runtime: any;
101
+ entrypoint: string;
57
102
  inputDigest: string;
58
103
  runDirectory: string;
59
104
  }>;
@@ -0,0 +1,108 @@
1
+ import { type SelectionCacheOptions } from "./selection-cache.js";
2
+ /** Bounded so one receipt can never carry an unbounded drift list. */
3
+ export declare const PROVENANCE_DRIFT_LIMIT = 200;
4
+ export type SelectionProvenanceStatus = "current" | "behind" | "pinned" | "unverified";
5
+ export type SelectionProvenanceSource = "local-receipt" | "authority-receipt" | "session-receipt";
6
+ export type SelectionProvenanceReference = "authority" | "synced-snapshot" | "unavailable";
7
+ /**
8
+ * The fixed diagnostic vocabulary. A reader's exception text is never carried,
9
+ * only one of these codes, so a foreign body or a credential inside an error
10
+ * string cannot reach a receipt that may be shared.
11
+ */
12
+ export type SelectionProvenanceReason = "CURRENT_SCHEMA_MATCH" | "REVISION_BEHIND" | "PINNED_BY_DESIGN" | "IDENTITY_MISMATCH" | "INCOMPLETE_RECEIPT" | "DIFFERENT_PROFILE" | "NO_LOCAL_RECEIPT" | "NO_SESSION_NAMED" | "NO_SESSION_RECEIPT" | "NO_STATION_RECEIPT" | "READ_REFUSED" | "REFERENCE_FROM_AUTHORITY" | "REFERENCE_NOT_AUTHORITY" | "REFERENCE_UNAVAILABLE" | "NO_STATION_REPORTED";
13
+ export interface SelectionProvenanceVersion {
14
+ slug: string;
15
+ version: string;
16
+ bundleDigest?: string;
17
+ }
18
+ export interface SelectionProvenanceConsumer {
19
+ kind: "station" | "session";
20
+ source: SelectionProvenanceSource;
21
+ /** The local host for a local receipt, the requested station only for the authority receipt. */
22
+ id: string;
23
+ /** Whether `id` names the host running this command or a station named on the command line. */
24
+ idKind: "local-host" | "requested-station";
25
+ profileId?: string;
26
+ profileRevision?: string;
27
+ /** Compared, never assumed: a matching revision under another identity is not current. */
28
+ authority?: string;
29
+ workspaceId?: string;
30
+ observedAt?: string;
31
+ selectionCount?: number;
32
+ /** Only for a session: how many of its selections it really loaded. */
33
+ loadedCount?: number;
34
+ /** Only for a session: the exact versions it really loaded. */
35
+ consumed?: SelectionProvenanceVersion[];
36
+ status: SelectionProvenanceStatus;
37
+ reasonCode: SelectionProvenanceReason;
38
+ reason: string;
39
+ }
40
+ export interface SelectionProvenanceDrift {
41
+ source: SelectionProvenanceSource;
42
+ id: string;
43
+ slug: string;
44
+ selectedVersion: string;
45
+ observedVersion: string;
46
+ }
47
+ export interface SelectionProvenanceReceipt {
48
+ schemaVersion: 1;
49
+ readOnly: true;
50
+ selectionProfile: string;
51
+ selected: {
52
+ profileId: string;
53
+ profileRevision?: string;
54
+ selectionCount?: number;
55
+ authority?: string;
56
+ workspaceId?: string;
57
+ source: SelectionProvenanceReference;
58
+ observedAt?: string;
59
+ reasonCode: SelectionProvenanceReason;
60
+ reason: string;
61
+ };
62
+ consumers: SelectionProvenanceConsumer[];
63
+ drift: SelectionProvenanceDrift[];
64
+ driftTruncated: boolean;
65
+ /**
66
+ * What this receipt deliberately does not attest. Version and digest evidence
67
+ * for published payloads, native discovery, native locks and live consumer
68
+ * adoption are outside this command and stay unverified here rather than being
69
+ * implied by a clean-looking result.
70
+ */
71
+ notAttested: string[];
72
+ }
73
+ export interface SelectionProvenanceInput {
74
+ profileId: string;
75
+ /** The station whose authority-held sync receipt is read. */
76
+ stationId: string;
77
+ /** The host this command runs on. Never conflated with `stationId`. */
78
+ localStationId: string;
79
+ sessionId?: string;
80
+ /** Read the reference from the locally synced snapshot; never ask the authority. */
81
+ cached?: boolean;
82
+ }
83
+ /** A station receipt reduced to what this receipt compares. */
84
+ export interface ProvenanceStationObservation {
85
+ profileId: string;
86
+ profileRevision: string;
87
+ appliedAt: string;
88
+ workspaceId?: string;
89
+ selections: readonly {
90
+ slug: string;
91
+ version: string;
92
+ }[];
93
+ }
94
+ export interface SelectionProvenanceOptions extends SelectionCacheOptions {
95
+ /** Injected by the SDK and tests; defaults to the authenticated reads. */
96
+ readSelected?: (profileId: string) => Promise<{
97
+ profileId: string;
98
+ profileRevision: string;
99
+ authority?: string;
100
+ workspaceId?: string;
101
+ selections: readonly {
102
+ slug: string;
103
+ version: string;
104
+ }[];
105
+ }>;
106
+ readStation?: (stationId: string) => Promise<ProvenanceStationObservation>;
107
+ }
108
+ export declare function readSelectionProvenance(input: SelectionProvenanceInput, options?: SelectionProvenanceOptions): Promise<SelectionProvenanceReceipt>;