@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/README.md +77 -0
- package/bin/index.js +684 -142
- package/bin/mcp.js +215 -104
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/cli/commands/provenance.d.ts +18 -0
- package/dist/cli/stray-arguments.d.ts +29 -0
- package/dist/index.js +206 -111
- package/dist/lib/portable-skills-files.d.ts +10 -0
- package/dist/lib/search.d.ts +18 -0
- package/dist/lib/selected-run.d.ts +45 -0
- package/dist/lib/selection-provenance.d.ts +108 -0
- package/dist/runtime-prerequisites.js +1 -1
- package/dist/sdk/index.js +172 -33
- package/docs/plugin-admission.md +13 -9
- package/docs/skill-standard.md +18 -4
- package/package.json +1 -1
|
@@ -49,7 +49,7 @@ var __require = import.meta.require;
|
|
|
49
49
|
// src/runtime-prerequisites.ts
|
|
50
50
|
import { isAbsolute as isAbsolute7 } from "path";
|
|
51
51
|
// package.json
|
|
52
|
-
var version = "0.10.
|
|
52
|
+
var version = "0.10.75";
|
|
53
53
|
var package_default = {
|
|
54
54
|
name: "@hasna/skills",
|
|
55
55
|
version,
|
package/dist/sdk/index.js
CHANGED
|
@@ -32371,7 +32371,7 @@ var init_event_sink = __esm(() => {
|
|
|
32371
32371
|
init_dist();
|
|
32372
32372
|
});
|
|
32373
32373
|
// package.json
|
|
32374
|
-
var version = "0.10.
|
|
32374
|
+
var version = "0.10.75";
|
|
32375
32375
|
var package_default = {
|
|
32376
32376
|
name: "@hasna/skills",
|
|
32377
32377
|
version,
|
|
@@ -46491,6 +46491,12 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
|
|
|
46491
46491
|
}
|
|
46492
46492
|
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"]);
|
|
46493
46493
|
var CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
|
|
46494
|
+
function claudeControlSource(source) {
|
|
46495
|
+
return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename12(source.path)) && !source.path.includes(`${sep13}.claude${sep13}plugins${sep13}cache${sep13}`);
|
|
46496
|
+
}
|
|
46497
|
+
function retainedAbsentClaudeRoot(absentRoots, source) {
|
|
46498
|
+
return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
|
|
46499
|
+
}
|
|
46494
46500
|
function codexInstallationRoots(binding) {
|
|
46495
46501
|
const proof = binding.codexInstallationInputs;
|
|
46496
46502
|
if (proof === undefined)
|
|
@@ -46900,7 +46906,10 @@ function resolveAgentDiscovery(options) {
|
|
|
46900
46906
|
if (!review.sources.some((source) => source.path === canonical9(configPath)))
|
|
46901
46907
|
throw new Error("Discovery review must include the agent configuration source");
|
|
46902
46908
|
const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
|
|
46909
|
+
const absentRoots = absentRetiredClaudeRoots(supplied);
|
|
46903
46910
|
const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
|
|
46911
|
+
if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
|
|
46912
|
+
return source;
|
|
46904
46913
|
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)) {
|
|
46905
46914
|
return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
|
|
46906
46915
|
}
|
|
@@ -46933,6 +46942,11 @@ function resolveAgentDiscovery(options) {
|
|
|
46933
46942
|
});
|
|
46934
46943
|
if (agent === "claude")
|
|
46935
46944
|
assertReviewedClaudeCacheClosures(home, config, review, canonical9);
|
|
46945
|
+
for (const root2 of absentRoots) {
|
|
46946
|
+
safe6(root2);
|
|
46947
|
+
if (lstatSync28(root2, { throwIfNoEntry: false }))
|
|
46948
|
+
throw new Error("Retired native materialization changed during discovery review");
|
|
46949
|
+
}
|
|
46936
46950
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
46937
46951
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual8(candidate2, source)) === index);
|
|
46938
46952
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
@@ -48259,11 +48273,19 @@ function planAgentIntegration(options) {
|
|
|
48259
48273
|
const executable2 = discoveryExecutable(agent) ?? (prior ? recordedDiscoveryExecutable(agent, prior) : null);
|
|
48260
48274
|
executables.set(agent, executable2);
|
|
48261
48275
|
const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? { ...policy.bridge.discovery[agent], ...agent === "codex" ? { codexDisabledPluginSkills: policy.bridge.codexPluginSkills ?? [] } : {} } : undefined;
|
|
48262
|
-
|
|
48263
|
-
|
|
48264
|
-
|
|
48276
|
+
try {
|
|
48277
|
+
if (retainedReview) {
|
|
48278
|
+
assertRetainedReviewState(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
|
|
48279
|
+
retainedAgents.push(agent);
|
|
48280
|
+
}
|
|
48281
|
+
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
48282
|
+
} catch (error) {
|
|
48283
|
+
if (isDiscoveryRootUnresolved(error) || error instanceof SumiPathResolverError || error instanceof NativeSkillCopiesDriftError)
|
|
48284
|
+
throw error;
|
|
48285
|
+
if (!retainedReview)
|
|
48286
|
+
throw error;
|
|
48287
|
+
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.`);
|
|
48265
48288
|
}
|
|
48266
|
-
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
|
|
48267
48289
|
});
|
|
48268
48290
|
const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join33(home, ".codex", "config.toml"), aliases)) : null;
|
|
48269
48291
|
const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
|
|
@@ -49017,7 +49039,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
49017
49039
|
assertCurrent();
|
|
49018
49040
|
recheckRootAliases(aliases);
|
|
49019
49041
|
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
49020
|
-
|
|
49042
|
+
assertRetainedReviewState(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
49021
49043
|
if (plan.reviewArtifactMigration) {
|
|
49022
49044
|
const verified = planReviewedArtifactMigration(plan.reviewArtifactMigration);
|
|
49023
49045
|
if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.discoveryAfter) !== JSON.stringify(plan.discoveryAfter))
|
|
@@ -49060,6 +49082,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
49060
49082
|
if (!plan.changes.length) {
|
|
49061
49083
|
assertCurrent();
|
|
49062
49084
|
recheckRootAliases(aliases);
|
|
49085
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
49086
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
49063
49087
|
return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
49064
49088
|
}
|
|
49065
49089
|
assertSafePath(backupRoot);
|
|
@@ -49109,6 +49133,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
|
|
|
49109
49133
|
}
|
|
49110
49134
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
49111
49135
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.projectDir ?? plan.managedAgentChecks.home });
|
|
49136
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
49137
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
49112
49138
|
atomicWrite2(join33(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha5(change.before), afterHash: sha5(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 } : {} }) + `
|
|
49113
49139
|
`);
|
|
49114
49140
|
} catch (error) {
|
|
@@ -49440,7 +49466,7 @@ enabled = false
|
|
|
49440
49466
|
function assertManagedAgentBridge(agent, options = {}) {
|
|
49441
49467
|
return assertManagedAgentBridgeWithDiscovery(agent, options);
|
|
49442
49468
|
}
|
|
49443
|
-
function
|
|
49469
|
+
function assertManagedBridgeCustody(agent, options = {}, provenDiscovery) {
|
|
49444
49470
|
const home = resolve27(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
49445
49471
|
assertSafePath(join33(dataDir2, "agent-policy.json"));
|
|
49446
49472
|
const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
|
|
@@ -49457,7 +49483,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
49457
49483
|
if (!isOwnedCliBridge(expected, [expected]))
|
|
49458
49484
|
throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
|
|
49459
49485
|
const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
|
|
49460
|
-
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";
|
|
49461
49486
|
const discovery = provenDiscovery ?? binding.discovery?.[agent];
|
|
49462
49487
|
assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
|
|
49463
49488
|
for (const source of discovery?.sources ?? [])
|
|
@@ -49472,11 +49497,30 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
49472
49497
|
throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
|
|
49473
49498
|
if (options.profileId !== undefined && options.profileId !== profile)
|
|
49474
49499
|
throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
|
|
49500
|
+
return { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery, configPath, config, command, profile };
|
|
49501
|
+
}
|
|
49502
|
+
function assertUnrepairableBridgeProtection(custody, agent) {
|
|
49503
|
+
if (agent !== "hermes")
|
|
49504
|
+
return;
|
|
49505
|
+
const { home, dataDir: dataDir2, binding, config, command, profile } = custody;
|
|
49506
|
+
const supervisor = binding.supervisors?.hermes;
|
|
49507
|
+
if (supervisor?.path !== join33(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha5(renderHermesSupervisor(command, profile)))
|
|
49508
|
+
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
49509
|
+
assertHermesProtection(home, config, command, profile, supervisor);
|
|
49510
|
+
}
|
|
49511
|
+
function assertRetainedReviewState(agent, options) {
|
|
49512
|
+
const custody = assertManagedBridgeCustody(agent, options);
|
|
49513
|
+
assertUnrepairableBridgeProtection(custody, agent);
|
|
49514
|
+
assertManagedNativeDiscovery(custody, agent, options);
|
|
49515
|
+
}
|
|
49516
|
+
|
|
49517
|
+
class NativeSkillCopiesDriftError extends Error {
|
|
49518
|
+
}
|
|
49519
|
+
function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
|
|
49520
|
+
const custody = assertManagedBridgeCustody(agent, options, provenDiscovery);
|
|
49521
|
+
const { home, aliases, expected, discovery, config, command, profile } = custody;
|
|
49475
49522
|
if (agent === "hermes") {
|
|
49476
|
-
|
|
49477
|
-
if (supervisor?.path !== join33(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha5(renderHermesSupervisor(command, profile)))
|
|
49478
|
-
throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
|
|
49479
|
-
assertHermesProtection(home, config, command, profile, supervisor);
|
|
49523
|
+
assertUnrepairableBridgeProtection(custody, agent);
|
|
49480
49524
|
} else if (agent === "sumi") {
|
|
49481
49525
|
if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(join33(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js")), command, profile))
|
|
49482
49526
|
throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
|
|
@@ -49498,6 +49542,21 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
49498
49542
|
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)))
|
|
49499
49543
|
throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
|
|
49500
49544
|
}
|
|
49545
|
+
if (agent === "codex") {
|
|
49546
|
+
const codexConfig = Bun.TOML.parse(readOptional(canonicalAgentPath(join33(home, ".codex", "config.toml"), aliases)) ?? "");
|
|
49547
|
+
if (codexConfig.skills?.bundled?.enabled !== false)
|
|
49548
|
+
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
49549
|
+
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join33(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
49550
|
+
if ([...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
|
|
49551
|
+
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
49552
|
+
}
|
|
49553
|
+
if (agent === "gemini" && discovery && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
|
|
49554
|
+
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
49555
|
+
return assertManagedNativeDiscovery(custody, agent, options);
|
|
49556
|
+
}
|
|
49557
|
+
function assertManagedNativeDiscovery(custody, agent, options) {
|
|
49558
|
+
const { home, dataDir: dataDir2, binding, aliases, expected, roots, discovery } = custody;
|
|
49559
|
+
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";
|
|
49501
49560
|
const codexPath = canonicalAgentPath(join33(home, ".codex", "config.toml"), aliases);
|
|
49502
49561
|
const vendorCache = canonicalAgentPath(join33(home, ".codex", "plugins", "cache"), aliases);
|
|
49503
49562
|
const witnessTolerance = agent === "codex" && options.codexPromptHook === true ? {
|
|
@@ -49508,11 +49567,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
49508
49567
|
if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
|
|
49509
49568
|
throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
|
|
49510
49569
|
const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
|
|
49511
|
-
if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
|
|
49512
|
-
throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
|
|
49513
|
-
const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join33(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
|
|
49514
|
-
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))
|
|
49515
|
-
throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
|
|
49516
49570
|
if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join33(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
|
|
49517
49571
|
throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
|
|
49518
49572
|
const disabledPlugin = (entry) => {
|
|
@@ -49549,8 +49603,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
49549
49603
|
};
|
|
49550
49604
|
if (!discovery || discovery.agent !== agent)
|
|
49551
49605
|
throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
|
|
49552
|
-
if (agent === "gemini" && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
|
|
49553
|
-
throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
|
|
49554
49606
|
try {
|
|
49555
49607
|
verifyAgentDiscovery(agent === "codex" ? { ...discovery, codexDisabledPluginSkills: binding.codexPluginSkills ?? [] } : discovery, options.codexDiscoveryRecovery, aliases, witnessTolerance);
|
|
49556
49608
|
if (agent === "sumi" && discovery.method === "reviewed")
|
|
@@ -49605,7 +49657,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
|
|
|
49605
49657
|
return false;
|
|
49606
49658
|
}
|
|
49607
49659
|
}, read: (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)) });
|
|
49608
|
-
throw new
|
|
49660
|
+
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}` : ""}`);
|
|
49609
49661
|
}
|
|
49610
49662
|
recheckRootAliases(aliases);
|
|
49611
49663
|
const discoveryChanges = (witnessTolerance?.changes ?? []).filter((change, index, all) => all.findIndex((other) => other.kind === change.kind && other.path === change.path) === index);
|
|
@@ -50460,8 +50512,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
|
|
|
50460
50512
|
displayName: manifest.displayName ?? displayName(manifest.name),
|
|
50461
50513
|
category: manifest.category ?? "Development Tools",
|
|
50462
50514
|
tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
|
|
50463
|
-
inputs: [],
|
|
50464
|
-
commands: []
|
|
50515
|
+
inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
|
|
50516
|
+
commands: Array.isArray(manifest.commands) ? manifest.commands : []
|
|
50465
50517
|
};
|
|
50466
50518
|
if (!existsSync9(join35(skillPath, "SKILL.md"))) {
|
|
50467
50519
|
writeFileSync6(join35(skillPath, "SKILL.md"), renderSkillMd(next));
|
|
@@ -51861,10 +51913,33 @@ function searchSkills(query, registry = loadRegistry()) {
|
|
|
51861
51913
|
scored.sort((a, b) => b.score - a.score);
|
|
51862
51914
|
return scored.map((s) => s.skill);
|
|
51863
51915
|
}
|
|
51916
|
+
function findCloseSkillNames(query, names, maxResults = 3) {
|
|
51917
|
+
if (maxResults <= 0)
|
|
51918
|
+
return [];
|
|
51919
|
+
const q = query.trim().toLowerCase();
|
|
51920
|
+
if (q.length === 0)
|
|
51921
|
+
return [];
|
|
51922
|
+
const tokens = q.split("-").filter(Boolean);
|
|
51923
|
+
const ranked = [];
|
|
51924
|
+
if (q.length >= 3) {
|
|
51925
|
+
for (const name of names) {
|
|
51926
|
+
const candidate2 = name.toLowerCase();
|
|
51927
|
+
const rank = candidate2 === q ? 0 : candidate2.endsWith(`-${q}`) || candidate2.startsWith(`${q}-`) ? 1 : tokens.length > 1 && tokens.every((token) => candidate2.split("-").includes(token)) ? 2 : undefined;
|
|
51928
|
+
if (rank !== undefined)
|
|
51929
|
+
ranked.push([name, rank]);
|
|
51930
|
+
}
|
|
51931
|
+
}
|
|
51932
|
+
ranked.sort((a, b) => a[1] - b[1] || a[0].length - b[0].length || a[0].localeCompare(b[0]));
|
|
51933
|
+
const found = ranked.slice(0, maxResults).map(([name]) => name);
|
|
51934
|
+
if (found.length === maxResults)
|
|
51935
|
+
return found;
|
|
51936
|
+
const taken = new Set(found);
|
|
51937
|
+
const threshold = Math.max(3, Math.floor(q.length / 2));
|
|
51938
|
+
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));
|
|
51939
|
+
return [...found, ...byDistance.slice(0, maxResults - found.length).map((entry) => entry.name)];
|
|
51940
|
+
}
|
|
51864
51941
|
function findSimilarSkills(query, maxResults = 3, registry = loadRegistry()) {
|
|
51865
|
-
|
|
51866
|
-
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);
|
|
51867
|
-
return scored.slice(0, maxResults).map((s) => s.name);
|
|
51942
|
+
return findCloseSkillNames(query, registry.map((skill) => skill.name), maxResults);
|
|
51868
51943
|
}
|
|
51869
51944
|
// src/lib/registry.ts
|
|
51870
51945
|
function isBasicSkillName(name) {
|
|
@@ -52885,11 +52960,31 @@ function exactProfileSelection(spec, profile) {
|
|
|
52885
52960
|
const at = spec.lastIndexOf("@");
|
|
52886
52961
|
const slug = at > 0 ? spec.slice(0, at) : spec;
|
|
52887
52962
|
const version2 = at > 0 ? spec.slice(at + 1) : undefined;
|
|
52888
|
-
const
|
|
52889
|
-
if (
|
|
52890
|
-
throw new SkillSelectionError("SKILL_NOT_SELECTED",
|
|
52963
|
+
const named = profile.selections.filter((entry) => entry.slug === slug || entry.aliases?.includes(slug));
|
|
52964
|
+
if (version2 === "")
|
|
52965
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", emptyVersionRefusal(spec));
|
|
52966
|
+
if (named.length === 0)
|
|
52967
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", unknownNameRefusal(slug, profile));
|
|
52968
|
+
const selection2 = version2 === undefined ? named[0] : named.find((entry) => entry.version === version2);
|
|
52969
|
+
if (!selection2)
|
|
52970
|
+
throw new SkillSelectionError("SKILL_NOT_SELECTED", wrongVersionRefusal(slug, version2, named));
|
|
52891
52971
|
return selection2;
|
|
52892
52972
|
}
|
|
52973
|
+
var ACCEPTED_FORM = "Accepted form: '<slug>' or '<slug>@<version>'.";
|
|
52974
|
+
function emptyVersionRefusal(spec) {
|
|
52975
|
+
return `The requested name '${spec}' has an empty version. ${ACCEPTED_FORM}`;
|
|
52976
|
+
}
|
|
52977
|
+
function wrongVersionRefusal(slug, version2, named) {
|
|
52978
|
+
const versions = [...new Set(named.map((entry) => entry.version))].sort();
|
|
52979
|
+
const canonical9 = named[0].slug;
|
|
52980
|
+
return `Skill '${slug}' is selected at version '${versions.join("', '")}', but '${version2}' was requested. ` + `Accepted form: '${canonical9}@${versions[0]}'.`;
|
|
52981
|
+
}
|
|
52982
|
+
function unknownNameRefusal(slug, profile) {
|
|
52983
|
+
const names = profile.selections.flatMap((entry) => [entry.slug, ...entry.aliases ?? []]);
|
|
52984
|
+
const close = findCloseSkillNames(slug, names);
|
|
52985
|
+
const hint = close.length > 0 ? ` Did you mean: ${close.join(", ")}?` : "";
|
|
52986
|
+
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}`;
|
|
52987
|
+
}
|
|
52893
52988
|
|
|
52894
52989
|
// src/lib/selected-run.ts
|
|
52895
52990
|
async function resolveSelectedRun(spec, profileId, options = {}) {
|
|
@@ -52898,12 +52993,51 @@ async function resolveSelectedRun(spec, profileId, options = {}) {
|
|
|
52898
52993
|
const entries = await readSelectedEntries(selection2, context, options);
|
|
52899
52994
|
return { selection: selection2, profileId: context.receipt.profile.profileId, entries, ...describeEntries(entries), cacheDir: selectionCacheRoot(options) };
|
|
52900
52995
|
}
|
|
52996
|
+
function declaredOperation(manifest) {
|
|
52997
|
+
const entry = instructionCommandEntry(manifest);
|
|
52998
|
+
if (!entry)
|
|
52999
|
+
return null;
|
|
53000
|
+
const runtime = manifest.runtime;
|
|
53001
|
+
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
|
|
53002
|
+
return null;
|
|
53003
|
+
if (runtime.runtime !== "bun" && runtime.runtime !== "node" && runtime.runtime !== "python3")
|
|
53004
|
+
return null;
|
|
53005
|
+
if (instructionEntrypointMismatch(manifest))
|
|
53006
|
+
return null;
|
|
53007
|
+
return { entry };
|
|
53008
|
+
}
|
|
53009
|
+
function instructionCommandEntry(manifest) {
|
|
53010
|
+
const commands = Array.isArray(manifest.commands) ? manifest.commands : [];
|
|
53011
|
+
const command = commands[0];
|
|
53012
|
+
if (!command || typeof command !== "object" || typeof command.entry !== "string" || command.entry.length === 0)
|
|
53013
|
+
return null;
|
|
53014
|
+
return command.entry;
|
|
53015
|
+
}
|
|
53016
|
+
function instructionEntrypointMismatch(manifest) {
|
|
53017
|
+
const entry = instructionCommandEntry(manifest);
|
|
53018
|
+
const declared = manifest.runtime;
|
|
53019
|
+
if (!entry || !declared || typeof declared !== "object" || Array.isArray(declared))
|
|
53020
|
+
return null;
|
|
53021
|
+
const runtimeEntrypoint = declared.entrypoint;
|
|
53022
|
+
return typeof runtimeEntrypoint === "string" && runtimeEntrypoint !== entry ? runtimeEntrypoint : null;
|
|
53023
|
+
}
|
|
53024
|
+
function instructionRunRefusal(manifest, isBundledFile) {
|
|
53025
|
+
const declaredEntry = instructionCommandEntry(manifest);
|
|
53026
|
+
if (declaredEntry && !isBundledFile(declaredEntry))
|
|
53027
|
+
return { code: "LOCAL_ENTRYPOINT_MISSING", message: `This selected instruction skill declares its operation entry as ${declaredEntry}, which is not a regular file in the selected bundle; a different runtime.entrypoint never substitutes for it.` };
|
|
53028
|
+
const mismatch = instructionEntrypointMismatch(manifest);
|
|
53029
|
+
if (mismatch)
|
|
53030
|
+
return { code: "LOCAL_ENTRYPOINT_MISMATCH", message: `This selected instruction skill declares its operation entry as ${declaredEntry}, but its runtime.entrypoint is ${mismatch}; the two must agree.` };
|
|
53031
|
+
if (!declaredOperation(manifest))
|
|
53032
|
+
return { code: "INSTRUCTION_SKILL", message: "This selected skill contains instructions. Use skills load instead of skills run." };
|
|
53033
|
+
return null;
|
|
53034
|
+
}
|
|
52901
53035
|
async function prepareSelectedSecretBindings(selected, cwd = process.cwd()) {
|
|
52902
53036
|
const entries = await readCachedSelection(selected.selection, { cacheDir: selected.cacheDir });
|
|
52903
53037
|
if (!entries)
|
|
52904
53038
|
throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected execution bundle is not cached.");
|
|
52905
53039
|
const { kind, manifest } = describeEntries(entries);
|
|
52906
|
-
if (kind !== "executable")
|
|
53040
|
+
if (kind !== "executable" && !declaredOperation(manifest))
|
|
52907
53041
|
throw new SkillSelectionError("INSTRUCTION_SKILL", "Instruction skills do not accept execution secret bindings.");
|
|
52908
53042
|
const names = declaredSecretNames(manifest.runtime?.env);
|
|
52909
53043
|
if (!names.length)
|
|
@@ -52916,8 +53050,12 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
52916
53050
|
if (!entries)
|
|
52917
53051
|
throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected local execution bundle is not cached.");
|
|
52918
53052
|
const { kind, manifest, packageJson } = describeEntries(entries);
|
|
52919
|
-
|
|
52920
|
-
|
|
53053
|
+
const operation = declaredOperation(manifest);
|
|
53054
|
+
if (kind === "instruction") {
|
|
53055
|
+
const refusal2 = instructionRunRefusal(manifest, (path) => entries.some((entry) => entry.path === path));
|
|
53056
|
+
if (refusal2)
|
|
53057
|
+
throw new SkillSelectionError(refusal2.code, refusal2.message);
|
|
53058
|
+
}
|
|
52921
53059
|
const runtime = manifest.runtime ?? {};
|
|
52922
53060
|
if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
|
|
52923
53061
|
throw new SkillSelectionError("INVALID_SKILL_MANIFEST", "The selected runtime declaration is invalid.");
|
|
@@ -52936,9 +53074,9 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
52936
53074
|
const executable2 = runtimeName === "bun" ? process.execPath : Bun.which(runtimeName);
|
|
52937
53075
|
if (!executable2)
|
|
52938
53076
|
throw new SkillSelectionError("LOCAL_RUNTIME_UNAVAILABLE", "The selected runtime is not installed on this station.");
|
|
52939
|
-
const declaredEntry = runtime.entrypoint ?? (typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin && typeof packageJson.bin === "object" ? Object.values(packageJson.bin)[0] : undefined);
|
|
53077
|
+
const declaredEntry = (kind === "instruction" ? operation?.entry : undefined) ?? runtime.entrypoint ?? (typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin && typeof packageJson.bin === "object" ? Object.values(packageJson.bin)[0] : undefined);
|
|
52940
53078
|
if (typeof declaredEntry !== "string" || !entries.some((entry) => entry.path === declaredEntry))
|
|
52941
|
-
throw new SkillSelectionError("LOCAL_ENTRYPOINT_MISSING", "The selected bundle must declare a regular-file entrypoint in its runtime manifest or package bin.");
|
|
53079
|
+
throw new SkillSelectionError("LOCAL_ENTRYPOINT_MISSING", kind === "instruction" ? "The selected instruction skill must declare the operation entrypoint it ships in its manifest, and that declared entry must be a regular file in the selected bundle; a different runtime.entrypoint never substitutes for it." : "The selected bundle must declare a regular-file entrypoint in its runtime manifest or package bin.");
|
|
52942
53080
|
const args = options.args ?? [];
|
|
52943
53081
|
if (args.length > 128 || args.some((arg) => typeof arg !== "string" || arg.length > 16384 || arg.includes("\x00")))
|
|
52944
53082
|
throw new SkillSelectionError("INVALID_RUN_INPUT", "Local skill arguments exceed their limits.");
|
|
@@ -53013,6 +53151,7 @@ async function executeSelectedLocal(selected, options = {}) {
|
|
|
53013
53151
|
selection: selection2,
|
|
53014
53152
|
target: "local",
|
|
53015
53153
|
runtime: runtimeName,
|
|
53154
|
+
entrypoint: declaredEntry,
|
|
53016
53155
|
inputDigest: `sha256:${createHash32("sha256").update(input).digest("hex")}`,
|
|
53017
53156
|
runDirectory,
|
|
53018
53157
|
...executionGrant ? { executionGrant } : {},
|
package/docs/plugin-admission.md
CHANGED
|
@@ -354,7 +354,8 @@ What the software currently enforces is narrower:
|
|
|
354
354
|
non-null hash. A non-string `hooks` value and a `hooks` target outside the
|
|
355
355
|
plugin root are refused. A manifest listed as `claude-plugin-manifest-v1`
|
|
356
356
|
(the form Skills writes into the stored policy) or as `path-bytes` triggers
|
|
357
|
-
no hook-file requirement
|
|
357
|
+
no hook-file requirement, and neither does a manifest the absent retired
|
|
358
|
+
version rule below retains. Nothing else is required.
|
|
358
359
|
- A `hooks/hooks.json` that is absent at review time and appears later is
|
|
359
360
|
detected if the review pins it with `sha256: null` or covers it with a
|
|
360
361
|
reviewed `directories` membership witness on the plugin root or its `hooks/`
|
|
@@ -383,7 +384,7 @@ What the software currently enforces is narrower:
|
|
|
383
384
|
`captureDiscoveryPathSources` (including the installer `path-bytes`
|
|
384
385
|
operation) produces a `claude-plugin-orphaned-at-v1` witness, and a fresh
|
|
385
386
|
explicit review converts a plain byte or `path-bytes` witness of it to that
|
|
386
|
-
form. The witness binds the marker's presence or absence, not its bytes or
|
|
387
|
+
form, unless the absent retired version rule below retains it. The witness binds the marker's presence or absence, not its bytes or
|
|
387
388
|
inode. Every capture and check still requires a regular, non-symlink file
|
|
388
389
|
owned by the current user, at most 32 bytes, matching `^[1-9][0-9]{12}$`.
|
|
389
390
|
The name stays in `directories` membership, so other added or removed names
|
|
@@ -392,17 +393,20 @@ What the software currently enforces is narrower:
|
|
|
392
393
|
that binds the marker's bytes keeps that meaning and needs one fresh review.
|
|
393
394
|
- An entirely absent retired Claude version root exempts only its plain byte
|
|
394
395
|
sources (`hashMode` omitted or `"bytes"`, with no `format`, `fields` or
|
|
395
|
-
`managedPlugins`)
|
|
396
|
-
|
|
396
|
+
`managedPlugins`), its `.claude-plugin/plugin.json` as a
|
|
397
|
+
`claude-plugin-manifest-v1` witness and a present
|
|
398
|
+
`claude-plugin-orphaned-at-v1` marker. Such a source with a non-null
|
|
399
|
+
`sha256` is also the only
|
|
397
400
|
thing that identifies the root, and the root qualifies only while it stays
|
|
398
401
|
entirely absent and the reviewed settings and registry select a different,
|
|
399
402
|
verified version. Every other source under that root is re-hashed as usual,
|
|
400
403
|
and one whose file existed at review no longer matches its hash, so the
|
|
401
|
-
check refuses.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
the root reappears, its sources are
|
|
404
|
+
check refuses. A `directories` witness that covers the retired root is not
|
|
405
|
+
exempt either. An explicit review (`--discovery-inputs`) keeps each exempt
|
|
406
|
+
source exactly as listed: it does not project a byte-witnessed manifest,
|
|
407
|
+
require its hook files or convert a byte-witnessed marker, so a historical
|
|
408
|
+
byte witness keeps its meaning. If the root reappears, its sources are
|
|
409
|
+
checked again.
|
|
406
410
|
- It is detection-only. Later checks re-hash the stored sources and re-walk any
|
|
407
411
|
reviewed `directories` witnesses. Drift makes the
|
|
408
412
|
Skills hooks refuse with `NATIVE_SKILL_DRIFT`; it does not stop, unload or
|
package/docs/skill-standard.md
CHANGED
|
@@ -91,10 +91,24 @@ existing Codewith conventions):
|
|
|
91
91
|
`package.json`, a non-empty `bin`, and `src/index.ts`. `skills run` executes it.
|
|
92
92
|
- `kind: instruction` — a `SKILL.md`-primary prose skill for coding agents.
|
|
93
93
|
`package.json`, `bin`, and `src/` are all optional. Instruction skills may still
|
|
94
|
-
bundle optional helper scripts (a `bin`/`src` is permitted, not forbidden)
|
|
95
|
-
|
|
96
|
-
instruction skill
|
|
97
|
-
|
|
94
|
+
bundle optional helper scripts (a `bin`/`src` is permitted, not forbidden) and
|
|
95
|
+
are consumed by agents via `SKILL.md`. `skills run` refuses a prose-only
|
|
96
|
+
instruction skill with a clear "not runnable — instruction skill" error instead
|
|
97
|
+
of executing a stub.
|
|
98
|
+
- An instruction skill may instead declare the one operation it ships: the first
|
|
99
|
+
`commands` entry, naming its helper, together with an explicit `runtime`
|
|
100
|
+
contract. `skills run` then executes exactly that declared entrypoint from the
|
|
101
|
+
selected bundle — the same immutable object, digest receipt, argument,
|
|
102
|
+
timeout, output and no-ambient-credential contract an executable skill gets —
|
|
103
|
+
and still refuses the skill when it declares no operation or no runtime. The
|
|
104
|
+
declared command entry is the authority: it must be a regular file in the
|
|
105
|
+
selected bundle, and a `runtime.entrypoint` that disagrees with it is an
|
|
106
|
+
inconsistent declaration that refuses (`LOCAL_ENTRYPOINT_MISMATCH`) rather than
|
|
107
|
+
running the other file. A declared entry missing from the bundle refuses
|
|
108
|
+
(`LOCAL_ENTRYPOINT_MISSING`) and is never substituted, and the mutable
|
|
109
|
+
authoring corpus is never executed. Executable skills keep their own
|
|
110
|
+
precedence (`runtime.entrypoint`, then `package.json` bin) unchanged, and cloud
|
|
111
|
+
execution keeps requiring an executable skill version.
|
|
98
112
|
|
|
99
113
|
New scaffolds always record their selected kind. Legacy local readers retain
|
|
100
114
|
their executable fallback, while the legacy publication API retains its
|