@hasna/skills 0.10.73 → 0.10.74

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.73",
26
+ version: "0.10.74",
27
27
  description: "Skills library for AI coding agents",
28
28
  type: "module",
29
29
  bin: {
@@ -22021,8 +22021,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
22021
22021
  displayName: manifest.displayName ?? displayName(manifest.name),
22022
22022
  category: manifest.category ?? "Development Tools",
22023
22023
  tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
22024
- inputs: [],
22025
- commands: []
22024
+ inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
22025
+ commands: Array.isArray(manifest.commands) ? manifest.commands : []
22026
22026
  };
22027
22027
  if (!existsSync6(join9(skillPath, "SKILL.md"))) {
22028
22028
  writeFileSync2(join9(skillPath, "SKILL.md"), renderSkillMd(next));
@@ -37726,6 +37726,12 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
37726
37726
  throw new Error("Retired native materialization changed during discovery verification");
37727
37727
  }
37728
37728
  }
37729
+ function claudeControlSource(source) {
37730
+ return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename12(source.path)) && !source.path.includes(`${sep11}.claude${sep11}plugins${sep11}cache${sep11}`);
37731
+ }
37732
+ function retainedAbsentClaudeRoot(absentRoots, source) {
37733
+ return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
37734
+ }
37729
37735
  function codexInstallationRoots(binding) {
37730
37736
  const proof = binding.codexInstallationInputs;
37731
37737
  if (proof === undefined)
@@ -37921,7 +37927,10 @@ function resolveAgentDiscovery(options) {
37921
37927
  if (!review.sources.some((source) => source.path === canonical9(configPath)))
37922
37928
  throw new Error("Discovery review must include the agent configuration source");
37923
37929
  const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
37930
+ const absentRoots = absentRetiredClaudeRoots(supplied);
37924
37931
  const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
37932
+ if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
37933
+ return source;
37925
37934
  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)) {
37926
37935
  return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
37927
37936
  }
@@ -37954,6 +37963,11 @@ function resolveAgentDiscovery(options) {
37954
37963
  });
37955
37964
  if (agent === "claude")
37956
37965
  assertReviewedClaudeCacheClosures(home, config2, review, canonical9);
37966
+ for (const root of absentRoots) {
37967
+ safe6(root);
37968
+ if (lstatSync23(root, { throwIfNoEntry: false }))
37969
+ throw new Error("Retired native materialization changed during discovery review");
37970
+ }
37957
37971
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
37958
37972
  const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual5(candidate, source)) === index);
37959
37973
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
@@ -46399,7 +46413,11 @@ var exports_selected_run = {};
46399
46413
  __export(exports_selected_run, {
46400
46414
  resolveSelectedRun: () => resolveSelectedRun,
46401
46415
  prepareSelectedSecretBindings: () => prepareSelectedSecretBindings,
46402
- executeSelectedLocal: () => executeSelectedLocal
46416
+ instructionRunRefusal: () => instructionRunRefusal,
46417
+ instructionEntrypointMismatch: () => instructionEntrypointMismatch,
46418
+ instructionCommandEntry: () => instructionCommandEntry,
46419
+ executeSelectedLocal: () => executeSelectedLocal,
46420
+ declaredOperation: () => declaredOperation
46403
46421
  });
46404
46422
  import { createHash as createHash28 } from "crypto";
46405
46423
  import { spawn as spawn2 } from "child_process";
@@ -46412,12 +46430,51 @@ async function resolveSelectedRun(spec, profileId, options = {}) {
46412
46430
  const entries = await readSelectedEntries(selection2, context2, options);
46413
46431
  return { selection: selection2, profileId: context2.receipt.profile.profileId, entries, ...describeEntries(entries), cacheDir: selectionCacheRoot(options) };
46414
46432
  }
46433
+ function declaredOperation(manifest) {
46434
+ const entry = instructionCommandEntry(manifest);
46435
+ if (!entry)
46436
+ return null;
46437
+ const runtime = manifest.runtime;
46438
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
46439
+ return null;
46440
+ if (runtime.runtime !== "bun" && runtime.runtime !== "node" && runtime.runtime !== "python3")
46441
+ return null;
46442
+ if (instructionEntrypointMismatch(manifest))
46443
+ return null;
46444
+ return { entry };
46445
+ }
46446
+ function instructionCommandEntry(manifest) {
46447
+ const commands = Array.isArray(manifest.commands) ? manifest.commands : [];
46448
+ const command = commands[0];
46449
+ if (!command || typeof command !== "object" || typeof command.entry !== "string" || command.entry.length === 0)
46450
+ return null;
46451
+ return command.entry;
46452
+ }
46453
+ function instructionEntrypointMismatch(manifest) {
46454
+ const entry = instructionCommandEntry(manifest);
46455
+ const declared = manifest.runtime;
46456
+ if (!entry || !declared || typeof declared !== "object" || Array.isArray(declared))
46457
+ return null;
46458
+ const runtimeEntrypoint = declared.entrypoint;
46459
+ return typeof runtimeEntrypoint === "string" && runtimeEntrypoint !== entry ? runtimeEntrypoint : null;
46460
+ }
46461
+ function instructionRunRefusal(manifest, isBundledFile) {
46462
+ const declaredEntry = instructionCommandEntry(manifest);
46463
+ if (declaredEntry && !isBundledFile(declaredEntry))
46464
+ 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.` };
46465
+ const mismatch = instructionEntrypointMismatch(manifest);
46466
+ if (mismatch)
46467
+ 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.` };
46468
+ if (!declaredOperation(manifest))
46469
+ return { code: "INSTRUCTION_SKILL", message: "This selected skill contains instructions. Use skills load instead of skills run." };
46470
+ return null;
46471
+ }
46415
46472
  async function prepareSelectedSecretBindings(selected, cwd = process.cwd()) {
46416
46473
  const entries = await readCachedSelection(selected.selection, { cacheDir: selected.cacheDir });
46417
46474
  if (!entries)
46418
46475
  throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected execution bundle is not cached.");
46419
46476
  const { kind, manifest } = describeEntries(entries);
46420
- if (kind !== "executable")
46477
+ if (kind !== "executable" && !declaredOperation(manifest))
46421
46478
  throw new SkillSelectionError("INSTRUCTION_SKILL", "Instruction skills do not accept execution secret bindings.");
46422
46479
  const names = declaredSecretNames(manifest.runtime?.env);
46423
46480
  if (!names.length)
@@ -46430,8 +46487,12 @@ async function executeSelectedLocal(selected, options = {}) {
46430
46487
  if (!entries)
46431
46488
  throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected local execution bundle is not cached.");
46432
46489
  const { kind, manifest, packageJson } = describeEntries(entries);
46433
- if (kind === "instruction")
46434
- throw new SkillSelectionError("INSTRUCTION_SKILL", "This selected skill contains instructions. Use skills load instead of skills run.");
46490
+ const operation = declaredOperation(manifest);
46491
+ if (kind === "instruction") {
46492
+ const refusal3 = instructionRunRefusal(manifest, (path) => entries.some((entry) => entry.path === path));
46493
+ if (refusal3)
46494
+ throw new SkillSelectionError(refusal3.code, refusal3.message);
46495
+ }
46435
46496
  const runtime = manifest.runtime ?? {};
46436
46497
  if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
46437
46498
  throw new SkillSelectionError("INVALID_SKILL_MANIFEST", "The selected runtime declaration is invalid.");
@@ -46450,9 +46511,9 @@ async function executeSelectedLocal(selected, options = {}) {
46450
46511
  const executable = runtimeName === "bun" ? process.execPath : Bun.which(runtimeName);
46451
46512
  if (!executable)
46452
46513
  throw new SkillSelectionError("LOCAL_RUNTIME_UNAVAILABLE", "The selected runtime is not installed on this station.");
46453
- const declaredEntry = runtime.entrypoint ?? (typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin && typeof packageJson.bin === "object" ? Object.values(packageJson.bin)[0] : undefined);
46514
+ 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);
46454
46515
  if (typeof declaredEntry !== "string" || !entries.some((entry) => entry.path === declaredEntry))
46455
- throw new SkillSelectionError("LOCAL_ENTRYPOINT_MISSING", "The selected bundle must declare a regular-file entrypoint in its runtime manifest or package bin.");
46516
+ 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.");
46456
46517
  const args = options.args ?? [];
46457
46518
  if (args.length > 128 || args.some((arg) => typeof arg !== "string" || arg.length > 16384 || arg.includes("\x00")))
46458
46519
  throw new SkillSelectionError("INVALID_RUN_INPUT", "Local skill arguments exceed their limits.");
@@ -46527,6 +46588,7 @@ async function executeSelectedLocal(selected, options = {}) {
46527
46588
  selection: selection2,
46528
46589
  target: "local",
46529
46590
  runtime: runtimeName,
46591
+ entrypoint: declaredEntry,
46530
46592
  inputDigest: `sha256:${createHash28("sha256").update(input2).digest("hex")}`,
46531
46593
  runDirectory,
46532
46594
  ...executionGrant ? { executionGrant } : {},
@@ -46973,13 +47035,18 @@ function registerOperationTools(server) {
46973
47035
  return mcpError("CONFLICTING_EXECUTION_MODES", "Use remote for the configured server catalog and quote, or target for selected execution, not both.");
46974
47036
  if (managed && !remote) {
46975
47037
  try {
46976
- const { resolveSelectedRun: resolveSelectedRun2, executeSelectedLocal: executeSelectedLocal2 } = await Promise.resolve().then(() => (init_selected_run(), exports_selected_run));
47038
+ const { resolveSelectedRun: resolveSelectedRun2, executeSelectedLocal: executeSelectedLocal2, instructionRunRefusal: instructionRunRefusal2 } = await Promise.resolve().then(() => (init_selected_run(), exports_selected_run));
46977
47039
  const resolved = await resolveSelectedRun2(name, selectedProfileId(), { projectDir: process.cwd() });
46978
- if (resolved.kind === "instruction")
46979
- return mcpError("INSTRUCTION_SKILL", "This selected skill contains instructions. Load it with skills load instead of running it.");
47040
+ if (resolved.kind === "instruction") {
47041
+ const refusal3 = instructionRunRefusal2(resolved.manifest, (path) => resolved.entries.some((entry) => entry.path === path));
47042
+ if (refusal3)
47043
+ return mcpError(refusal3.code, refusal3.message);
47044
+ }
46980
47045
  if (files?.length)
46981
47046
  return mcpError("SELECTED_INPUT_REQUIRED", "Selected executions require their declared JSON input; use the CLI cloud execution input contract for files.");
46982
47047
  if (target === "cloud") {
47048
+ if (resolved.kind === "instruction")
47049
+ return mcpError("INSTRUCTION_SKILL", "Cloud execution requires an executable skill version; run the declared operation locally on a managed station, or load the instructions with skills load.");
46983
47050
  if (args?.length || quoteReceipt || maxCredits !== undefined || maxCostCents !== undefined)
46984
47051
  return mcpError("SELECTED_INPUT_REQUIRED", "The selected cloud execution contract accepts declared JSON input, not legacy run arguments or quote receipts.");
46985
47052
  const { CloudExecutionClient: CloudExecutionClient2 } = await Promise.resolve().then(() => (init_cloud_executions(), exports_cloud_executions));
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.73",
28
+ version: "0.10.74",
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.73",
24143
+ version: "0.10.74",
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.73",
24146
+ version: "0.10.74",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
@@ -0,0 +1,18 @@
1
+ import type { Command } from "commander";
2
+ import { type SelectionProvenanceReceipt } from "../../lib/selection-provenance.js";
3
+ export interface ProvenanceCommandOptions {
4
+ selectionProfile?: string;
5
+ station?: string;
6
+ session?: string;
7
+ cached?: boolean;
8
+ json?: boolean;
9
+ }
10
+ /**
11
+ * One read-only receipt for the selection chain: selected, activated, applied,
12
+ * pinned and consumed are named separately, and every consumer carries its
13
+ * status and reason. An exit code here only reports a bad request: a read that
14
+ * could not be answered is recorded as `unverified`, because that is the fact
15
+ * the caller came for.
16
+ */
17
+ export declare function provenanceLines(receipt: SelectionProvenanceReceipt): string[];
18
+ export declare function registerProvenance(parent: Command): void;
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.74";
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);
@@ -24133,6 +24171,12 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
24133
24171
  }
24134
24172
  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
24173
  var CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
24174
+ function claudeControlSource(source) {
24175
+ 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}`);
24176
+ }
24177
+ function retainedAbsentClaudeRoot(absentRoots, source) {
24178
+ return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
24179
+ }
24136
24180
  function codexInstallationRoots(binding) {
24137
24181
  const proof = binding.codexInstallationInputs;
24138
24182
  if (proof === undefined)
@@ -24542,7 +24586,10 @@ function resolveAgentDiscovery(options) {
24542
24586
  if (!review.sources.some((source) => source.path === canonical9(configPath)))
24543
24587
  throw new Error("Discovery review must include the agent configuration source");
24544
24588
  const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
24589
+ const absentRoots = absentRetiredClaudeRoots(supplied);
24545
24590
  const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
24591
+ if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
24592
+ return source;
24546
24593
  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
24594
  return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
24548
24595
  }
@@ -24575,6 +24622,11 @@ function resolveAgentDiscovery(options) {
24575
24622
  });
24576
24623
  if (agent === "claude")
24577
24624
  assertReviewedClaudeCacheClosures(home, config, review, canonical9);
24625
+ for (const root2 of absentRoots) {
24626
+ safe6(root2);
24627
+ if (lstatSync32(root2, { throwIfNoEntry: false }))
24628
+ throw new Error("Retired native materialization changed during discovery review");
24629
+ }
24578
24630
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
24579
24631
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual9(candidate2, source)) === index);
24580
24632
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
@@ -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;
@@ -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>;
@@ -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.73";
52
+ var version = "0.10.74";
53
53
  var package_default = {
54
54
  name: "@hasna/skills",
55
55
  version,