@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/index.js CHANGED
@@ -36888,7 +36888,7 @@ var package_default;
36888
36888
  var init_package = __esm(() => {
36889
36889
  package_default = {
36890
36890
  name: "@hasna/skills",
36891
- version: "0.10.73",
36891
+ version: "0.10.74",
36892
36892
  description: "Skills library for AI coding agents",
36893
36893
  type: "module",
36894
36894
  bin: {
@@ -39496,8 +39496,8 @@ function ensureInstructionSkillFiles(skillPath, manifest) {
39496
39496
  displayName: manifest.displayName ?? displayName(manifest.name),
39497
39497
  category: manifest.category ?? "Development Tools",
39498
39498
  tags: manifest.tags?.length ? manifest.tags : ["custom", manifest.name],
39499
- inputs: [],
39500
- commands: []
39499
+ inputs: Array.isArray(manifest.inputs) ? manifest.inputs : [],
39500
+ commands: Array.isArray(manifest.commands) ? manifest.commands : []
39501
39501
  };
39502
39502
  if (!existsSync10(join10(skillPath, "SKILL.md"))) {
39503
39503
  writeFileSync2(join10(skillPath, "SKILL.md"), renderSkillMd(next));
@@ -58192,11 +58192,19 @@ function planAgentIntegration(options) {
58192
58192
  const executable2 = discoveryExecutable(agent) ?? (prior ? recordedDiscoveryExecutable(agent, prior) : null);
58193
58193
  executables.set(agent, executable2);
58194
58194
  const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? { ...policy.bridge.discovery[agent], ...agent === "codex" ? { codexDisabledPluginSkills: policy.bridge.codexPluginSkills ?? [] } : {} } : undefined;
58195
- if (retainedReview) {
58196
- assertManagedAgentBridge(agent, { home, dataDir: dataDir3, projectDir: options.projectDir ?? home });
58197
- retainedAgents.push(agent);
58195
+ try {
58196
+ if (retainedReview) {
58197
+ assertRetainedReviewState(agent, { home, dataDir: dataDir3, projectDir: options.projectDir ?? home });
58198
+ retainedAgents.push(agent);
58199
+ }
58200
+ return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
58201
+ } catch (error) {
58202
+ if (isDiscoveryRootUnresolved(error) || error instanceof SumiPathResolverError || error instanceof NativeSkillCopiesDriftError)
58203
+ throw error;
58204
+ if (!retainedReview)
58205
+ throw error;
58206
+ 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.`);
58198
58207
  }
58199
- return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases), executable: executable2 });
58200
58208
  });
58201
58209
  const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join34(home, ".codex", "config.toml"), aliases)) : null;
58202
58210
  const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
@@ -58867,7 +58875,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
58867
58875
  assertCurrent();
58868
58876
  recheckRootAliases(aliases);
58869
58877
  for (const agent of plan.retainedReviewChecks?.agents ?? [])
58870
- assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
58878
+ assertRetainedReviewState(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
58871
58879
  if (plan.reviewArtifactMigration) {
58872
58880
  const verified = planReviewedArtifactMigration(plan.reviewArtifactMigration);
58873
58881
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.discoveryAfter) !== JSON.stringify(plan.discoveryAfter))
@@ -58910,6 +58918,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
58910
58918
  if (!plan.changes.length) {
58911
58919
  assertCurrent();
58912
58920
  recheckRootAliases(aliases);
58921
+ for (const agent of plan.retainedReviewChecks?.agents ?? [])
58922
+ assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
58913
58923
  return { changed: [], backups, ...aliases.length ? { rootAliases: aliases } : {} };
58914
58924
  }
58915
58925
  assertSafePath(backupRoot);
@@ -58959,6 +58969,8 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
58959
58969
  }
58960
58970
  for (const agent of plan.managedAgentChecks?.agents ?? [])
58961
58971
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.projectDir ?? plan.managedAgentChecks.home });
58972
+ for (const agent of plan.retainedReviewChecks?.agents ?? [])
58973
+ assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
58962
58974
  atomicWrite2(join34(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 } : {} }) + `
58963
58975
  `);
58964
58976
  } catch (error) {
@@ -59299,7 +59311,7 @@ function codexPromptDriftWarning(report) {
59299
59311
  function assertManagedAgentBridge(agent, options = {}) {
59300
59312
  return assertManagedAgentBridgeWithDiscovery(agent, options);
59301
59313
  }
59302
- function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
59314
+ function assertManagedBridgeCustody(agent, options = {}, provenDiscovery) {
59303
59315
  const home = resolve24(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
59304
59316
  assertSafePath(join34(dataDir3, "agent-policy.json"));
59305
59317
  const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
@@ -59316,7 +59328,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
59316
59328
  if (!isOwnedCliBridge(expected, [expected]))
59317
59329
  throw new Error("NATIVE_SKILL_DRIFT: the native Skills bridge is missing or modified; repair it before continuing");
59318
59330
  const roots = projectAncestorDirectories([options.projectDir ?? process.cwd(), ...options.projectDirs ?? [], ...agent === "hermes" && process.env.TERMINAL_CWD ? [process.cwd()] : []]);
59319
- const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep13}.agents${sep13}skills${sep13}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
59320
59331
  const discovery = provenDiscovery ?? binding.discovery?.[agent];
59321
59332
  assertProjectDiscovery(agent, [...roots], home, (path) => canonicalAgentPath(path, aliases), discovery);
59322
59333
  for (const source of discovery?.sources ?? [])
@@ -59331,11 +59342,27 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
59331
59342
  throw new Error("NATIVE_SKILL_DRIFT: the native hook command/profile binding is missing");
59332
59343
  if (options.profileId !== undefined && options.profileId !== profile)
59333
59344
  throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
59345
+ return { home, dataDir: dataDir3, binding, aliases, expected, roots, discovery, configPath, config, command, profile };
59346
+ }
59347
+ function assertUnrepairableBridgeProtection(custody, agent) {
59348
+ if (agent !== "hermes")
59349
+ return;
59350
+ const { home, dataDir: dataDir3, binding, config, command, profile } = custody;
59351
+ const supervisor = binding.supervisors?.hermes;
59352
+ if (supervisor?.path !== join34(dataDir3, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
59353
+ throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
59354
+ assertHermesProtection(home, config, command, profile, supervisor);
59355
+ }
59356
+ function assertRetainedReviewState(agent, options) {
59357
+ const custody = assertManagedBridgeCustody(agent, options);
59358
+ assertUnrepairableBridgeProtection(custody, agent);
59359
+ assertManagedNativeDiscovery(custody, agent, options);
59360
+ }
59361
+ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
59362
+ const custody = assertManagedBridgeCustody(agent, options, provenDiscovery);
59363
+ const { home, aliases, expected, discovery, config, command, profile } = custody;
59334
59364
  if (agent === "hermes") {
59335
- const supervisor = binding.supervisors?.hermes;
59336
- if (supervisor?.path !== join34(dataDir3, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha4(renderHermesSupervisor(command, profile)))
59337
- throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
59338
- assertHermesProtection(home, config, command, profile, supervisor);
59365
+ assertUnrepairableBridgeProtection(custody, agent);
59339
59366
  } else if (agent === "sumi") {
59340
59367
  if (JSON.stringify(config.permissions?.filter((rule) => rule.action === "skill")) !== JSON.stringify(SUMI_SKILL_PERMISSIONS) || !isManagedSumiPlugin(readOptional(join34(sumiConfigDirectory(home, process.env, options.projectDir ?? process.cwd()), "plugins", "skills-cli.js")), command, profile))
59341
59368
  throw new Error("NATIVE_SKILL_DRIFT: Sumi bridge protection changed; run skills hook install");
@@ -59357,6 +59384,21 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
59357
59384
  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)))
59358
59385
  throw new Error("NATIVE_SKILL_DRIFT: Gemini bridge or bundled-skill protection changed; run skills hook install");
59359
59386
  }
59387
+ if (agent === "codex") {
59388
+ const codexConfig = Bun.TOML.parse(readOptional(canonicalAgentPath(join34(home, ".codex", "config.toml"), aliases)) ?? "");
59389
+ if (codexConfig.skills?.bundled?.enabled !== false)
59390
+ throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
59391
+ const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join34(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
59392
+ if ([...setting(expected), ...(codexConfig.skills?.config ?? []).filter((entry) => typeof entry.name === "string" && entry.name.trim() === CLI_BRIDGE_NAME)].some((entry) => entry.enabled === false))
59393
+ throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
59394
+ }
59395
+ if (agent === "gemini" && discovery && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
59396
+ throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
59397
+ return assertManagedNativeDiscovery(custody, agent, options);
59398
+ }
59399
+ function assertManagedNativeDiscovery(custody, agent, options) {
59400
+ const { home, dataDir: dataDir3, binding, aliases, expected, roots, discovery } = custody;
59401
+ const visible = (entry) => entry.agent === agent || ["codex", "gemini", "opencode", "hermes", "sumi"].includes(agent) && entry.path.includes(`${sep13}.agents${sep13}skills${sep13}`) || ["opencode", "sumi"].includes(agent) && entry.agent === "claude";
59360
59402
  const codexPath = canonicalAgentPath(join34(home, ".codex", "config.toml"), aliases);
59361
59403
  const vendorCache = canonicalAgentPath(join34(home, ".codex", "plugins", "cache"), aliases);
59362
59404
  const witnessTolerance = agent === "codex" && options.codexPromptHook === true ? {
@@ -59367,11 +59409,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
59367
59409
  if (options.codexDiscoveryRecovery && (agent !== "codex" || options.codexDiscoveryRecovery.configPath !== codexPath))
59368
59410
  throw new Error("NATIVE_SKILL_DRIFT: Codex trust recovery names a different configuration root");
59369
59411
  const codexConfig = agent === "codex" ? Bun.TOML.parse(readOptional(codexPath) ?? "") : {};
59370
- if (agent === "codex" && codexConfig.skills?.bundled?.enabled !== false)
59371
- throw new Error("NATIVE_SKILL_DRIFT: Codex bundled skill reseeding is not disabled (skills.bundled.enabled); run skills hook install");
59372
- const setting = (path) => (codexConfig.skills?.config ?? []).filter((entry) => typeof entry.path === "string" && [path, join34(path, "SKILL.md")].includes(canonicalAgentPath(entry.path, aliases)));
59373
- 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))
59374
- throw new Error("NATIVE_SKILL_DRIFT: the Codex CLI bridge is disabled; run skills hook install");
59375
59412
  if (agent === "codex" && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join34(home, ".codex/plugins/cache"), aliases), binding.codexPluginSkills ?? [], (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)), codexConfig.skills?.config ?? []))
59376
59413
  throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; run skills hook install with a fresh discovery review");
59377
59414
  const disabledPlugin = (entry) => {
@@ -59408,8 +59445,6 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
59408
59445
  };
59409
59446
  if (!discovery || discovery.agent !== agent)
59410
59447
  throw new Error("NATIVE_SKILL_DRIFT: native discovery coverage is missing; run skills hook install");
59411
- if (agent === "gemini" && !discovery.builtinNames?.every((name) => config.skills.disabled.includes(name)))
59412
- throw new Error("NATIVE_SKILL_DRIFT: an installed Gemini builtin is not disabled");
59413
59448
  try {
59414
59449
  verifyAgentDiscovery(agent === "codex" ? { ...discovery, codexDisabledPluginSkills: binding.codexPluginSkills ?? [] } : discovery, options.codexDiscoveryRecovery, aliases, witnessTolerance);
59415
59450
  if (agent === "sumi" && discovery.method === "reviewed")
@@ -59464,7 +59499,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
59464
59499
  return false;
59465
59500
  }
59466
59501
  }, read: (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)) });
59467
- 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}` : ""}`);
59502
+ 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}` : ""}`);
59468
59503
  }
59469
59504
  recheckRootAliases(aliases);
59470
59505
  const discoveryChanges = (witnessTolerance?.changes ?? []).filter((change, index, all) => all.findIndex((other) => other.kind === change.kind && other.path === change.path) === index);
@@ -59505,7 +59540,7 @@ function assertNativeExportAllowed(dataDir3 = getDataDirReadOnly()) {
59505
59540
  if (requiresCliSkillLoading(dataDir3))
59506
59541
  throw new Error("NATIVE_SKILL_EXPORT_DISABLED: this station loads skills through the Skills CLI; use skills sync --selection-profile <id>");
59507
59542
  }
59508
- var sha4 = (data) => createHash26("sha256").update(data).digest("hex"), HOOK_EVENTS, ROOTS, SKILLS_LOADING_POLICY = "Skills loading policy: discover skills with `skills list` or `skills search`, and read selected instructions with `skills load <slug>`. Use `skills sync` to refresh the shared profile. The only native skill is the owned skills-cli bridge. Create and edit payload skills in the Skills authoring workspace; do not copy payload instructions into native discovery folders. Context loading does not authorize execution; use `skills run` only when the task calls for running a skill.";
59543
+ var sha4 = (data) => createHash26("sha256").update(data).digest("hex"), HOOK_EVENTS, ROOTS, NativeSkillCopiesDriftError, SKILLS_LOADING_POLICY = "Skills loading policy: discover skills with `skills list` or `skills search`, and read selected instructions with `skills load <slug>`. Use `skills sync` to refresh the shared profile. The only native skill is the owned skills-cli bridge. Create and edit payload skills in the Skills authoring workspace; do not copy payload instructions into native discovery folders. Context loading does not authorize execution; use `skills run` only when the task calls for running a skill.";
59509
59544
  var init_agent_integration = __esm(() => {
59510
59545
  init_sumi_settings_witness();
59511
59546
  init_codex_corpus_write();
@@ -59534,6 +59569,8 @@ var init_agent_integration = __esm(() => {
59534
59569
  init_retained_review_artifacts();
59535
59570
  HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
59536
59571
  ROOTS = NATIVE_SKILL_ROOTS;
59572
+ NativeSkillCopiesDriftError = class NativeSkillCopiesDriftError extends Error {
59573
+ };
59537
59574
  });
59538
59575
 
59539
59576
  // src/lib/home-migration.ts
@@ -62715,6 +62752,9 @@ function verifyAgentDiscovery(binding, codexRecovery, reviewedRootAliases, witne
62715
62752
  function claudeControlSource(source) {
62716
62753
  return CLAUDE_CONTROL_WITNESS_MODES.has(source.hashMode ?? "") || CLAUDE_CONTROL_FILES.has(basename16(source.path)) && !source.path.includes(`${sep14}.claude${sep14}plugins${sep14}cache${sep14}`);
62717
62754
  }
62755
+ function retainedAbsentClaudeRoot(absentRoots, source) {
62756
+ return claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
62757
+ }
62718
62758
  function classifyClaudeDiscoverySources(binding) {
62719
62759
  if (!binding || binding.agent !== "claude" || !Array.isArray(binding.sources))
62720
62760
  throw new Error("Invalid Claude discovery binding");
@@ -62735,7 +62775,7 @@ function classifyClaudeDiscoverySources(binding) {
62735
62775
  };
62736
62776
  const budget2 = discoveryByteBudget();
62737
62777
  return binding.sources.map((source) => {
62738
- const root2 = claudeControlSource(source) ? undefined : retainedAbsentRoot(absentRoots, source);
62778
+ const root2 = retainedAbsentClaudeRoot(absentRoots, source);
62739
62779
  if (root2 !== undefined)
62740
62780
  return { status: "retained-absent", root: root2, witness: retainableAbsentWitness(source) };
62741
62781
  let current;
@@ -63150,7 +63190,10 @@ function resolveAgentDiscovery(options) {
63150
63190
  if (!review.sources.some((source) => source.path === canonical9(configPath)))
63151
63191
  throw new Error("Discovery review must include the agent configuration source");
63152
63192
  const reviewedSources = supersedesSettingsProjection(agent, review.sources, canonical9(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical9(configPath), source)) : sources;
63193
+ const absentRoots = absentRetiredClaudeRoots(supplied);
63153
63194
  const reviewedSourcesWithManifestProjection = review.sources.map((source) => {
63195
+ if (retainedAbsentClaudeRoot(absentRoots, source) !== undefined)
63196
+ return source;
63154
63197
  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)) {
63155
63198
  return { path: source.path, hashMode: CLAUDE_ORPHAN_MARKER_MODE, sha256: hashClaudePluginCacheOrphanMarker(source.path) };
63156
63199
  }
@@ -63183,6 +63226,11 @@ function resolveAgentDiscovery(options) {
63183
63226
  });
63184
63227
  if (agent === "claude")
63185
63228
  assertReviewedClaudeCacheClosures(home, config, review, canonical9);
63229
+ for (const root2 of absentRoots) {
63230
+ safe6(root2);
63231
+ if (lstatSync32(root2, { throwIfNoEntry: false }))
63232
+ throw new Error("Retired native materialization changed during discovery review");
63233
+ }
63186
63234
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
63187
63235
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual13(candidate2, source)) === index);
63188
63236
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
@@ -94457,7 +94505,11 @@ var exports_selected_run = {};
94457
94505
  __export(exports_selected_run, {
94458
94506
  resolveSelectedRun: () => resolveSelectedRun,
94459
94507
  prepareSelectedSecretBindings: () => prepareSelectedSecretBindings,
94460
- executeSelectedLocal: () => executeSelectedLocal
94508
+ instructionRunRefusal: () => instructionRunRefusal,
94509
+ instructionEntrypointMismatch: () => instructionEntrypointMismatch,
94510
+ instructionCommandEntry: () => instructionCommandEntry,
94511
+ executeSelectedLocal: () => executeSelectedLocal,
94512
+ declaredOperation: () => declaredOperation
94461
94513
  });
94462
94514
  import { createHash as createHash36 } from "crypto";
94463
94515
  import { spawn as spawn3 } from "child_process";
@@ -94470,12 +94522,51 @@ async function resolveSelectedRun(spec, profileId, options = {}) {
94470
94522
  const entries = await readSelectedEntries(selection2, context, options);
94471
94523
  return { selection: selection2, profileId: context.receipt.profile.profileId, entries, ...describeEntries(entries), cacheDir: selectionCacheRoot(options) };
94472
94524
  }
94525
+ function declaredOperation(manifest) {
94526
+ const entry = instructionCommandEntry(manifest);
94527
+ if (!entry)
94528
+ return null;
94529
+ const runtime = manifest.runtime;
94530
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
94531
+ return null;
94532
+ if (runtime.runtime !== "bun" && runtime.runtime !== "node" && runtime.runtime !== "python3")
94533
+ return null;
94534
+ if (instructionEntrypointMismatch(manifest))
94535
+ return null;
94536
+ return { entry };
94537
+ }
94538
+ function instructionCommandEntry(manifest) {
94539
+ const commands = Array.isArray(manifest.commands) ? manifest.commands : [];
94540
+ const command = commands[0];
94541
+ if (!command || typeof command !== "object" || typeof command.entry !== "string" || command.entry.length === 0)
94542
+ return null;
94543
+ return command.entry;
94544
+ }
94545
+ function instructionEntrypointMismatch(manifest) {
94546
+ const entry = instructionCommandEntry(manifest);
94547
+ const declared = manifest.runtime;
94548
+ if (!entry || !declared || typeof declared !== "object" || Array.isArray(declared))
94549
+ return null;
94550
+ const runtimeEntrypoint = declared.entrypoint;
94551
+ return typeof runtimeEntrypoint === "string" && runtimeEntrypoint !== entry ? runtimeEntrypoint : null;
94552
+ }
94553
+ function instructionRunRefusal(manifest, isBundledFile) {
94554
+ const declaredEntry = instructionCommandEntry(manifest);
94555
+ if (declaredEntry && !isBundledFile(declaredEntry))
94556
+ 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.` };
94557
+ const mismatch = instructionEntrypointMismatch(manifest);
94558
+ if (mismatch)
94559
+ 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.` };
94560
+ if (!declaredOperation(manifest))
94561
+ return { code: "INSTRUCTION_SKILL", message: "This selected skill contains instructions. Use skills load instead of skills run." };
94562
+ return null;
94563
+ }
94473
94564
  async function prepareSelectedSecretBindings(selected, cwd2 = process.cwd()) {
94474
94565
  const entries = await readCachedSelection(selected.selection, { cacheDir: selected.cacheDir });
94475
94566
  if (!entries)
94476
94567
  throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected execution bundle is not cached.");
94477
94568
  const { kind, manifest } = describeEntries(entries);
94478
- if (kind !== "executable")
94569
+ if (kind !== "executable" && !declaredOperation(manifest))
94479
94570
  throw new SkillSelectionError("INSTRUCTION_SKILL", "Instruction skills do not accept execution secret bindings.");
94480
94571
  const names = declaredSecretNames(manifest.runtime?.env);
94481
94572
  if (!names.length)
@@ -94488,8 +94579,12 @@ async function executeSelectedLocal(selected, options = {}) {
94488
94579
  if (!entries)
94489
94580
  throw new SkillSelectionError("CACHED_BUNDLE_MISSING", "The selected local execution bundle is not cached.");
94490
94581
  const { kind, manifest, packageJson } = describeEntries(entries);
94491
- if (kind === "instruction")
94492
- throw new SkillSelectionError("INSTRUCTION_SKILL", "This selected skill contains instructions. Use skills load instead of skills run.");
94582
+ const operation = declaredOperation(manifest);
94583
+ if (kind === "instruction") {
94584
+ const refusal3 = instructionRunRefusal(manifest, (path) => entries.some((entry) => entry.path === path));
94585
+ if (refusal3)
94586
+ throw new SkillSelectionError(refusal3.code, refusal3.message);
94587
+ }
94493
94588
  const runtime = manifest.runtime ?? {};
94494
94589
  if (!runtime || typeof runtime !== "object" || Array.isArray(runtime))
94495
94590
  throw new SkillSelectionError("INVALID_SKILL_MANIFEST", "The selected runtime declaration is invalid.");
@@ -94508,9 +94603,9 @@ async function executeSelectedLocal(selected, options = {}) {
94508
94603
  const executable2 = runtimeName === "bun" ? process.execPath : Bun.which(runtimeName);
94509
94604
  if (!executable2)
94510
94605
  throw new SkillSelectionError("LOCAL_RUNTIME_UNAVAILABLE", "The selected runtime is not installed on this station.");
94511
- const declaredEntry = runtime.entrypoint ?? (typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin && typeof packageJson.bin === "object" ? Object.values(packageJson.bin)[0] : undefined);
94606
+ 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);
94512
94607
  if (typeof declaredEntry !== "string" || !entries.some((entry) => entry.path === declaredEntry))
94513
- throw new SkillSelectionError("LOCAL_ENTRYPOINT_MISSING", "The selected bundle must declare a regular-file entrypoint in its runtime manifest or package bin.");
94608
+ 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.");
94514
94609
  const args = options.args ?? [];
94515
94610
  if (args.length > 128 || args.some((arg) => typeof arg !== "string" || arg.length > 16384 || arg.includes("\x00")))
94516
94611
  throw new SkillSelectionError("INVALID_RUN_INPUT", "Local skill arguments exceed their limits.");
@@ -94585,6 +94680,7 @@ async function executeSelectedLocal(selected, options = {}) {
94585
94680
  selection: selection2,
94586
94681
  target: "local",
94587
94682
  runtime: runtimeName,
94683
+ entrypoint: declaredEntry,
94588
94684
  inputDigest: `sha256:${createHash36("sha256").update(input2).digest("hex")}`,
94589
94685
  runDirectory,
94590
94686
  ...executionGrant ? { executionGrant } : {},
@@ -113378,13 +113474,18 @@ function registerOperationTools(server) {
113378
113474
  return mcpError("CONFLICTING_EXECUTION_MODES", "Use remote for the configured server catalog and quote, or target for selected execution, not both.");
113379
113475
  if (managed && !remote) {
113380
113476
  try {
113381
- const { resolveSelectedRun: resolveSelectedRun2, executeSelectedLocal: executeSelectedLocal2 } = await Promise.resolve().then(() => (init_selected_run(), exports_selected_run));
113477
+ const { resolveSelectedRun: resolveSelectedRun2, executeSelectedLocal: executeSelectedLocal2, instructionRunRefusal: instructionRunRefusal2 } = await Promise.resolve().then(() => (init_selected_run(), exports_selected_run));
113382
113478
  const resolved = await resolveSelectedRun2(name, selectedProfileId(), { projectDir: process.cwd() });
113383
- if (resolved.kind === "instruction")
113384
- return mcpError("INSTRUCTION_SKILL", "This selected skill contains instructions. Load it with skills load instead of running it.");
113479
+ if (resolved.kind === "instruction") {
113480
+ const refusal3 = instructionRunRefusal2(resolved.manifest, (path) => resolved.entries.some((entry) => entry.path === path));
113481
+ if (refusal3)
113482
+ return mcpError(refusal3.code, refusal3.message);
113483
+ }
113385
113484
  if (files?.length)
113386
113485
  return mcpError("SELECTED_INPUT_REQUIRED", "Selected executions require their declared JSON input; use the CLI cloud execution input contract for files.");
113387
113486
  if (target === "cloud") {
113487
+ if (resolved.kind === "instruction")
113488
+ 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.");
113388
113489
  if (args?.length || quoteReceipt || maxCredits !== undefined || maxCostCents !== undefined)
113389
113490
  return mcpError("SELECTED_INPUT_REQUIRED", "The selected cloud execution contract accepts declared JSON input, not legacy run arguments or quote receipts.");
113390
113491
  const { CloudExecutionClient: CloudExecutionClient2 } = await Promise.resolve().then(() => (init_cloud_executions(), exports_cloud_executions));
@@ -117550,8 +117651,11 @@ async function handleRun(name, args2, options, separatorBeforeSkill) {
117550
117651
  projectDir: process.cwd(),
117551
117652
  sessionId: options.session
117552
117653
  }) : undefined;
117553
- if (selected?.kind === "instruction")
117554
- throw new Error("This selected skill contains instructions. Use skills load instead of skills run.");
117654
+ if (selected?.kind === "instruction") {
117655
+ const refusal3 = instructionRunRefusal(selected.manifest, (path) => selected.entries.some((entry) => entry.path === path));
117656
+ if (refusal3)
117657
+ throw new Error(refusal3.message);
117658
+ }
117555
117659
  if (selected) {
117556
117660
  slug2 = selected.selection.slug;
117557
117661
  version2 = selected.selection.version;
@@ -117576,6 +117680,8 @@ async function handleRun(name, args2, options, separatorBeforeSkill) {
117576
117680
  process.exitCode = result4.exitCode;
117577
117681
  return;
117578
117682
  }
117683
+ if (selected?.kind === "instruction")
117684
+ throw new Error("Cloud execution requires an executable skill version; run the declared operation locally on a managed station, or use skills load for the instructions.");
117579
117685
  if (args2.length || options.remote || options.file?.length)
117580
117686
  throw new Error("Cloud execution accepts structured --input only");
117581
117687
  if (!version2 || options.input === undefined)
@@ -121866,6 +121972,369 @@ var init_profiles = __esm(() => {
121866
121972
  init_remote_permissions();
121867
121973
  });
121868
121974
 
121975
+ // src/lib/selection-provenance.ts
121976
+ function label(value) {
121977
+ return value !== undefined && SAFE_LABEL2.test(value) ? value : "an unnamed value";
121978
+ }
121979
+ async function attempt2(read3) {
121980
+ try {
121981
+ return { ok: true, value: await read3() };
121982
+ } catch {
121983
+ return { ok: false, code: "READ_REFUSED" };
121984
+ }
121985
+ }
121986
+ function sameRevision(observed, reference) {
121987
+ return observed !== undefined && reference !== undefined && observed === reference;
121988
+ }
121989
+ function sameIdentity2(observed, reference) {
121990
+ if (observed.profileId !== undefined && reference.profileId !== undefined && observed.profileId !== reference.profileId)
121991
+ return false;
121992
+ if (observed.authority !== undefined && reference.authority !== undefined && observed.authority !== reference.authority)
121993
+ return false;
121994
+ if (observed.workspaceId !== undefined && reference.workspaceId !== undefined && observed.workspaceId !== reference.workspaceId)
121995
+ return false;
121996
+ return true;
121997
+ }
121998
+ function supportedIdentityValue(value) {
121999
+ return typeof value === "string" && value.trim() !== "";
122000
+ }
122001
+ function uncomparableFields(observed, reference, required2) {
122002
+ return required2.filter((field3) => !supportedIdentityValue(observed[field3]) || !supportedIdentityValue(reference[field3]));
122003
+ }
122004
+ function supportedIdentityFields(source) {
122005
+ const out = {};
122006
+ for (const field3 of ["profileId", "profileRevision", "authority", "workspaceId"]) {
122007
+ const value = source[field3];
122008
+ if (supportedIdentityValue(value))
122009
+ out[field3] = value;
122010
+ }
122011
+ return out;
122012
+ }
122013
+ function revisionReason(labelText, observed, reference, status) {
122014
+ const seen = label(observed);
122015
+ if (status === "current")
122016
+ return `${labelText} revision ${seen} is the selected revision`;
122017
+ return `${labelText} revision ${seen} is not the selected revision ${label(reference)}`;
122018
+ }
122019
+ async function resolveReference(input3, options) {
122020
+ const fromSnapshot = (reasonCode, because) => {
122021
+ let receipt;
122022
+ try {
122023
+ receipt = readSelectionProfile(input3.profileId, options);
122024
+ } catch {
122025
+ return { profileId: input3.profileId, source: "unavailable", reasonCode: "REFERENCE_UNAVAILABLE", reason: `${because} The locally synced snapshot is unreadable too.`, selections: noSelections };
122026
+ }
122027
+ if (!receipt)
122028
+ return { profileId: input3.profileId, source: "unavailable", reasonCode: "REFERENCE_UNAVAILABLE", reason: `${because} Nothing is synced locally either, so no consumer can be compared.`, selections: noSelections };
122029
+ return {
122030
+ profileId: receipt.profile.profileId,
122031
+ ...supportedIdentityFields({ profileRevision: receipt.profile.profileRevision, authority: receipt.profile.authority, workspaceId: receipt.profile.workspaceId }),
122032
+ selectionCount: receipt.profile.selections.length,
122033
+ observedAt: receipt.verifiedAt,
122034
+ source: "synced-snapshot",
122035
+ reasonCode,
122036
+ reason: `${because} Comparisons use the locally synced snapshot (posted work is not proof of adoption), so every consumer below is reported unverified rather than current.`,
122037
+ selections: receipt.profile.selections
122038
+ };
122039
+ };
122040
+ if (input3.cached)
122041
+ return fromSnapshot("REFERENCE_NOT_AUTHORITY", "--cached was requested, so the authority was not read.");
122042
+ const read3 = options.readSelected ?? (async (profileId) => {
122043
+ const client2 = await createProfileClient();
122044
+ const profile2 = await client2.resolveProfile(profileId);
122045
+ return { profileId: profile2.profileId, profileRevision: profile2.profileRevision, authority: profile2.authority, workspaceId: profile2.workspaceId, selections: profile2.selections };
122046
+ });
122047
+ const attempted = await attempt2(() => read3(input3.profileId));
122048
+ if (!attempted.ok)
122049
+ return fromSnapshot("READ_REFUSED", "The authority did not answer for the selected profile.");
122050
+ const profile = attempted.value;
122051
+ if (!profile?.profileId || !profile.profileRevision || !Array.isArray(profile.selections)) {
122052
+ return { profileId: input3.profileId, source: "unavailable", reasonCode: "REFERENCE_UNAVAILABLE", reason: "The authority returned a selection profile this client does not recognise, so no consumer can be compared.", selections: noSelections };
122053
+ }
122054
+ return {
122055
+ profileId: profile.profileId,
122056
+ profileRevision: profile.profileRevision,
122057
+ selectionCount: profile.selections.length,
122058
+ ...profile.authority === undefined ? {} : { authority: profile.authority },
122059
+ ...profile.workspaceId === undefined ? {} : { workspaceId: profile.workspaceId },
122060
+ source: "authority",
122061
+ reasonCode: "REFERENCE_FROM_AUTHORITY",
122062
+ reason: `Read from the authority for profile ${label(profile.profileId)}.`,
122063
+ selections: profile.selections
122064
+ };
122065
+ }
122066
+ function activationConsumer(input3, reference, options) {
122067
+ const base3 = {
122068
+ kind: "station",
122069
+ source: "local-receipt",
122070
+ id: input3.localStationId,
122071
+ idKind: "local-host",
122072
+ status: "unverified",
122073
+ reasonCode: "NO_LOCAL_RECEIPT",
122074
+ reason: ""
122075
+ };
122076
+ const refuse10 = (reasonCode, reason2) => ({ consumer: { ...base3, reasonCode, reason: reason2 }, compared: noSelections });
122077
+ let receipt;
122078
+ try {
122079
+ receipt = readSelectionProfile(input3.profileId, options);
122080
+ } catch {
122081
+ return refuse10("READ_REFUSED", `This machine's activated snapshot for profile ${label(input3.profileId)} could not be read.`);
122082
+ }
122083
+ if (!receipt)
122084
+ return refuse10("NO_LOCAL_RECEIPT", `This machine holds no activated snapshot for profile ${label(input3.profileId)}.`);
122085
+ const profile = receipt.profile;
122086
+ const observed = {
122087
+ ...base3,
122088
+ ...supportedIdentityFields({ profileId: profile.profileId, profileRevision: profile.profileRevision, authority: profile.authority, workspaceId: profile.workspaceId }),
122089
+ observedAt: receipt.verifiedAt,
122090
+ selectionCount: profile.selections.length
122091
+ };
122092
+ if (!sameIdentity2(observed, reference)) {
122093
+ return { consumer: { ...observed, status: "unverified", reasonCode: "IDENTITY_MISMATCH", reason: `This machine's activated snapshot is not the selected selection's identity: it differs in profile, authority or workspace, so it is not current whatever its revision.` }, compared: noSelections };
122094
+ }
122095
+ if (reference.source !== "authority") {
122096
+ return { consumer: { ...observed, status: "unverified", reasonCode: reference.reasonCode, reason: `${reference.reason} This snapshot alone cannot say whether the machine is current.` }, compared: noSelections };
122097
+ }
122098
+ const missingActivation = uncomparableFields(observed, reference, REQUIRED_IDENTITY["local-receipt"]);
122099
+ if (missingActivation.length > 0) {
122100
+ return { consumer: { ...observed, status: "unverified", reasonCode: "INCOMPLETE_RECEIPT", reason: `This machine's activated snapshot does not record ${missingActivation.join(", ")}, so its identity and revision cannot be compared; it is reported unverified rather than current or behind.` }, compared: noSelections };
122101
+ }
122102
+ const status = sameRevision(profile.profileRevision, reference.profileRevision) ? "current" : "behind";
122103
+ return {
122104
+ consumer: {
122105
+ ...observed,
122106
+ status,
122107
+ reasonCode: status === "current" ? "CURRENT_SCHEMA_MATCH" : "REVISION_BEHIND",
122108
+ reason: status === "current" ? revisionReason("This machine's activated snapshot", profile.profileRevision, reference.profileRevision, "current") : `${revisionReason("This machine's activated snapshot", profile.profileRevision, reference.profileRevision, "behind")}. Run skills sync to activate the selected revision here.`
122109
+ },
122110
+ compared: profile.selections
122111
+ };
122112
+ }
122113
+ async function stationConsumer(input3, reference, options) {
122114
+ const base3 = {
122115
+ kind: "station",
122116
+ source: "authority-receipt",
122117
+ id: input3.stationId,
122118
+ idKind: "requested-station",
122119
+ status: "unverified",
122120
+ reasonCode: "NO_STATION_REPORTED",
122121
+ reason: ""
122122
+ };
122123
+ const refuse10 = (reasonCode, reason2) => ({ consumer: { ...base3, reasonCode, reason: reason2 }, compared: noSelections });
122124
+ if (reference.source !== "authority") {
122125
+ return refuse10("REFERENCE_NOT_AUTHORITY", "The station receipt was not compared: this receipt has no authenticated selection to compare it with.");
122126
+ }
122127
+ const read3 = options.readStation ?? (async (stationId) => {
122128
+ const state = await readStationSkillState(stationId);
122129
+ return { profileId: state.profileId, profileRevision: state.profileRevision, appliedAt: state.appliedAt, workspaceId: state.workspaceId, selections: state.selections };
122130
+ });
122131
+ const attempted = await attempt2(() => read3(input3.stationId));
122132
+ if (!attempted.ok)
122133
+ return refuse10("READ_REFUSED", `The authority did not answer for station ${label(input3.stationId)}.`);
122134
+ const station = attempted.value;
122135
+ const observed = {
122136
+ ...base3,
122137
+ ...supportedIdentityFields({ profileId: station.profileId, profileRevision: station.profileRevision, workspaceId: station.workspaceId }),
122138
+ observedAt: station.appliedAt,
122139
+ selectionCount: station.selections.length
122140
+ };
122141
+ if (!sameIdentity2(observed, reference)) {
122142
+ return { consumer: { ...observed, status: "unverified", reasonCode: "IDENTITY_MISMATCH", reason: `Station ${label(input3.stationId)} last applied a different selection identity: it differs in profile or workspace from the selected profile.` }, compared: noSelections };
122143
+ }
122144
+ const missing2 = uncomparableFields(observed, reference, REQUIRED_IDENTITY["authority-receipt"]);
122145
+ if (missing2.length > 0) {
122146
+ return { consumer: { ...observed, status: "unverified", reasonCode: "INCOMPLETE_RECEIPT", reason: `The receipt the authority holds for station ${label(input3.stationId)} does not record ${missing2.join(", ")}, so its identity and revision cannot be compared; it is reported unverified rather than current or behind.` }, compared: noSelections };
122147
+ }
122148
+ if (sameRevision(station.profileRevision, reference.profileRevision)) {
122149
+ return { consumer: { ...observed, status: "current", reasonCode: "CURRENT_SCHEMA_MATCH", reason: revisionReason(`The receipt the authority holds for station ${label(input3.stationId)}`, station.profileRevision, reference.profileRevision, "current") }, compared: station.selections };
122150
+ }
122151
+ return {
122152
+ consumer: {
122153
+ ...observed,
122154
+ status: "behind",
122155
+ reasonCode: "REVISION_BEHIND",
122156
+ reason: `${revisionReason(`The receipt the authority holds for station ${label(input3.stationId)}`, station.profileRevision, reference.profileRevision, "behind")}. Only an explicit skills sync writes this record, so report it before treating the machine as adrift.`
122157
+ },
122158
+ compared: station.selections
122159
+ };
122160
+ }
122161
+ function sessionConsumer(input3, reference, options) {
122162
+ const unnamed = {
122163
+ kind: "session",
122164
+ source: "session-receipt",
122165
+ id: "unnamed",
122166
+ idKind: "local-host",
122167
+ status: "unverified",
122168
+ reasonCode: "NO_SESSION_NAMED",
122169
+ reason: "No session was named, so its pin and load receipts were not read; pass --session <id>."
122170
+ };
122171
+ if (!input3.sessionId)
122172
+ return { consumer: unnamed, compared: noSelections };
122173
+ const base3 = {
122174
+ kind: "session",
122175
+ source: "session-receipt",
122176
+ id: input3.sessionId,
122177
+ idKind: "local-host",
122178
+ status: "unverified",
122179
+ reasonCode: "NO_SESSION_RECEIPT",
122180
+ reason: ""
122181
+ };
122182
+ const refuse10 = (reasonCode, reason2) => ({ consumer: { ...base3, reasonCode, reason: reason2 }, compared: noSelections });
122183
+ let session;
122184
+ try {
122185
+ session = inspectSkillSession(input3.sessionId, options);
122186
+ } catch {
122187
+ return refuse10("READ_REFUSED", `The session receipt for the named session could not be read.`);
122188
+ }
122189
+ const consumed = session.selections.filter((selection2) => selection2.loaded).map((selection2) => ({ slug: selection2.slug, version: selection2.version, bundleDigest: selection2.bundleDigest }));
122190
+ const observed = {
122191
+ ...base3,
122192
+ ...supportedIdentityFields({ profileId: session.profileId, profileRevision: session.profileRevision, authority: session.authority, workspaceId: session.workspaceId }),
122193
+ observedAt: session.verifiedAt,
122194
+ selectionCount: session.selectionCount,
122195
+ loadedCount: session.loadedCount,
122196
+ consumed
122197
+ };
122198
+ if (!sameIdentity2(observed, reference)) {
122199
+ return { consumer: { ...observed, status: "unverified", reasonCode: "IDENTITY_MISMATCH", reason: "The session pin is not the selected selection's identity: it differs in profile, authority or workspace, so it is not current whatever its revision." }, compared: noSelections };
122200
+ }
122201
+ if (reference.source !== "authority") {
122202
+ return { consumer: { ...observed, status: "unverified", reasonCode: reference.reasonCode, reason: `${reference.reason} This pin alone cannot say whether the session is current.` }, compared: noSelections };
122203
+ }
122204
+ const missingPin = uncomparableFields(observed, reference, REQUIRED_IDENTITY["session-receipt"]);
122205
+ if (missingPin.length > 0) {
122206
+ return { consumer: { ...observed, status: "unverified", reasonCode: "INCOMPLETE_RECEIPT", reason: `The session receipt does not record ${missingPin.join(", ")}, so its identity and revision cannot be compared; it is reported unverified rather than current, pinned or behind.` }, compared: noSelections };
122207
+ }
122208
+ if (sameRevision(session.profileRevision, reference.profileRevision)) {
122209
+ return {
122210
+ consumer: { ...observed, status: "current", reasonCode: "CURRENT_SCHEMA_MATCH", reason: `${revisionReason("The session pin", session.profileRevision, reference.profileRevision, "current")}; it loaded ${session.loadedCount} of ${session.selectionCount} selections.` },
122211
+ compared: consumed
122212
+ };
122213
+ }
122214
+ return {
122215
+ consumer: {
122216
+ ...observed,
122217
+ status: "pinned",
122218
+ reasonCode: "PINNED_BY_DESIGN",
122219
+ reason: `${revisionReason("The session pin", session.profileRevision, reference.profileRevision, "pinned")}. A session keeps its revision when the profile moves on, so this is custody by design; skills sessions reconcile moves it intentionally.`
122220
+ },
122221
+ compared: consumed
122222
+ };
122223
+ }
122224
+ function driftFor(result2, reference) {
122225
+ if (reference.source !== "authority" || result2.compared.length === 0)
122226
+ return [];
122227
+ if (result2.consumer.status === "unverified" && result2.consumer.reasonCode === "IDENTITY_MISMATCH")
122228
+ return [];
122229
+ const selected = new Map(reference.selections.map((selection2) => [selection2.slug, selection2.version]));
122230
+ return result2.compared.filter((selection2) => selected.has(selection2.slug) && selected.get(selection2.slug) !== selection2.version).map((selection2) => ({ source: result2.consumer.source, id: result2.consumer.id, slug: selection2.slug, selectedVersion: selected.get(selection2.slug), observedVersion: selection2.version })).sort((left, right) => left.slug.localeCompare(right.slug) || left.source.localeCompare(right.source));
122231
+ }
122232
+ async function readSelectionProvenance(input3, options = {}) {
122233
+ if (!input3.profileId?.trim())
122234
+ throw new Error("A selection profile id is required to read selection provenance.");
122235
+ if (!input3.stationId?.trim() || input3.stationId.length > LABEL_MAX_CHARS)
122236
+ throw new Error("A station id is required to read selection provenance.");
122237
+ if (!input3.localStationId?.trim() || input3.localStationId.length > LABEL_MAX_CHARS)
122238
+ throw new Error("The local host id is required to read selection provenance.");
122239
+ const reference = await resolveReference(input3, options);
122240
+ const results2 = [activationConsumer(input3, reference, options), await stationConsumer(input3, reference, options), sessionConsumer(input3, reference, options)];
122241
+ const drift = results2.flatMap((result2) => driftFor(result2, reference));
122242
+ return {
122243
+ schemaVersion: 1,
122244
+ readOnly: true,
122245
+ selectionProfile: input3.profileId,
122246
+ selected: {
122247
+ profileId: reference.profileId,
122248
+ ...supportedIdentityValue(reference.profileRevision) ? { profileRevision: reference.profileRevision, selectionCount: reference.selectionCount } : {},
122249
+ ...supportedIdentityValue(reference.authority) ? { authority: reference.authority } : {},
122250
+ ...supportedIdentityValue(reference.workspaceId) ? { workspaceId: reference.workspaceId } : {},
122251
+ ...reference.observedAt === undefined ? {} : { observedAt: reference.observedAt },
122252
+ source: reference.source,
122253
+ reasonCode: reference.reasonCode,
122254
+ reason: reference.reason
122255
+ },
122256
+ consumers: results2.map((result2) => result2.consumer),
122257
+ drift: drift.slice(0, PROVENANCE_DRIFT_LIMIT),
122258
+ driftTruncated: drift.length > PROVENANCE_DRIFT_LIMIT,
122259
+ notAttested: [
122260
+ "Published version and digest evidence for skill payloads: read it with skills versions and skills review-export.",
122261
+ "Native discovery, native locks and project locks: read them with skills hook check and skills sessions recover-lock.",
122262
+ "Live consumer adoption: a receipt shows what a session loaded, never that an agent honoured it."
122263
+ ]
122264
+ };
122265
+ }
122266
+ var PROVENANCE_DRIFT_LIMIT = 200, SAFE_LABEL2, LABEL_MAX_CHARS = 200, noSelections, REQUIRED_IDENTITY;
122267
+ var init_selection_provenance = __esm(() => {
122268
+ init_session_reconciliation();
122269
+ init_profile_admin();
122270
+ init_profile_client();
122271
+ init_selection_cache();
122272
+ SAFE_LABEL2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
122273
+ noSelections = [];
122274
+ REQUIRED_IDENTITY = {
122275
+ "local-receipt": ["profileId", "profileRevision", "authority", "workspaceId"],
122276
+ "session-receipt": ["profileId", "profileRevision", "authority", "workspaceId"],
122277
+ "authority-receipt": ["profileId", "profileRevision", "workspaceId"]
122278
+ };
122279
+ });
122280
+
122281
+ // src/cli/commands/provenance.ts
122282
+ var exports_provenance = {};
122283
+ __export(exports_provenance, {
122284
+ registerProvenance: () => registerProvenance,
122285
+ provenanceLines: () => provenanceLines
122286
+ });
122287
+ import { hostname as hostname5 } from "os";
122288
+ function provenanceLines(receipt) {
122289
+ const selected = receipt.selected;
122290
+ const lines = [
122291
+ `Selection profile ${receipt.selectionProfile}`,
122292
+ `Selected: ${selected.profileRevision ?? "unavailable"} (${selected.selectionCount ?? 0} selections, ${selected.source})`
122293
+ ];
122294
+ for (const consumer of receipt.consumers)
122295
+ lines.push(...consumerLines(consumer));
122296
+ if (receipt.drift.length > 0) {
122297
+ lines.push(`Drift (${receipt.drift.length}${receipt.driftTruncated ? ", truncated" : ""}):`);
122298
+ for (const row2 of receipt.drift)
122299
+ lines.push(`- ${row2.source} ${row2.id}: ${row2.slug} ${row2.selectedVersion} -> ${row2.observedVersion}`);
122300
+ }
122301
+ return lines;
122302
+ }
122303
+ function consumerLines(consumer) {
122304
+ const label2 = consumer.kind === "session" ? "Pinned" : consumer.source === "local-receipt" ? "Activated" : "Applied";
122305
+ const identity6 = consumer.kind === "session" ? `session ${consumer.id}` : consumer.idKind === "local-host" ? `this machine (${consumer.id})` : `station ${consumer.id}`;
122306
+ const lines = [`${label2}: ${identity6} ${consumer.profileRevision ?? "no receipt"} [${consumer.status}] ${consumer.reasonCode}`, ` ${consumer.reason}`];
122307
+ if (consumer.kind === "session" && consumer.consumed) {
122308
+ lines.push(`Consumed: ${consumer.loadedCount ?? 0} of ${consumer.selectionCount ?? 0} selections loaded`);
122309
+ for (const selection2 of consumer.consumed)
122310
+ lines.push(`- ${selection2.slug}@${selection2.version} ${selection2.bundleDigest}`);
122311
+ }
122312
+ return lines;
122313
+ }
122314
+ function registerProvenance(parent) {
122315
+ parent.command("provenance").description("Read-only receipt of the selected, activated, applied, pinned and actually loaded skill versions").option("--selection-profile <id>", "Selection profile to read (default: the managed profile)").option("--station <id>", "Station whose sync receipt to read (default: HASNA_STATION, then this host)").option("--session <id>", "Also report one session's pin and the versions it actually loaded").option("--cached", "Read the reference from the locally synced snapshot and never ask the authority", false).option("--json", "Return the receipt as JSON", false).action(async (options) => {
122316
+ try {
122317
+ const localStationId = process.env.HASNA_STATION ?? hostname5();
122318
+ const receipt = await readSelectionProvenance({
122319
+ profileId: selectedProfileId(options.selectionProfile),
122320
+ localStationId,
122321
+ stationId: options.station ?? localStationId,
122322
+ ...options.session === undefined ? {} : { sessionId: options.session },
122323
+ ...options.cached ? { cached: true } : {}
122324
+ });
122325
+ await writeCliOutput(options.json ? JSON.stringify(receipt) : provenanceLines(receipt).join(`
122326
+ `));
122327
+ } catch (error61) {
122328
+ console.error(error61.message);
122329
+ process.exitCode = 1;
122330
+ }
122331
+ });
122332
+ }
122333
+ var init_provenance = __esm(() => {
122334
+ init_selection_provenance();
122335
+ init_context();
122336
+ });
122337
+
121869
122338
  // src/cli/commands/grants.ts
121870
122339
  var exports_grants = {};
121871
122340
  __export(exports_grants, {
@@ -123312,7 +123781,7 @@ __export(exports_publish, {
123312
123781
  });
123313
123782
  import { execFileSync as execFileSync3 } from "child_process";
123314
123783
  import { existsSync as existsSync39, readFileSync as readFileSync52 } from "fs";
123315
- import { hostname as hostname5 } from "os";
123784
+ import { hostname as hostname6 } from "os";
123316
123785
  import { join as join79 } from "path";
123317
123786
  function registerPublish(parent) {
123318
123787
  parent.command("push").argument("<name>", "Name of a skill in the local corpus (~/.hasna/skills/installed or the migrated ~/.hasna/skills/skills)").option("--version <version>", "Override the version recorded on the instance").option("--force-new-version", "If name@version already exists with different content, publish as the next patch version", false).option("--dry-run", "Pack and validate without uploading", false).option("--json", "Output result as JSON", false).description("Publish a local skill to the configured skills instance").action(async (name, options) => {
@@ -123496,7 +123965,7 @@ function buildVersionManifest(skillDir, packed) {
123496
123965
  unpackedByteSize: packed.unpackedByteSize,
123497
123966
  bundleSha256: packed.sha256,
123498
123967
  provenance: {
123499
- machine: hostname5(),
123968
+ machine: hostname6(),
123500
123969
  agent: process.env.SKILLS_AGENT_ID ?? process.env.HASNA_AGENT_ID ?? process.env.AGENT_ID ?? null,
123501
123970
  cliVersion: package_default.version,
123502
123971
  gitRemote: sanitizeGitRemote(gitValue(skillDir, ["remote", "get-url", "origin"])),
@@ -123875,9 +124344,9 @@ function printWhoami(payload) {
123875
124344
  console.log(source_default.dim(`Auth: ${payload.authSource}`));
123876
124345
  const permissions = recordField(payload.permissions);
123877
124346
  if (permissions) {
123878
- const label = (value) => value === true ? "allowed" : value === false ? "denied" : "unknown";
123879
- console.log(`Publish: ${label(permissions.publish)}`);
123880
- console.log(`Profiles write: ${label(permissions.profilesWrite)}`);
124347
+ const label2 = (value) => value === true ? "allowed" : value === false ? "denied" : "unknown";
124348
+ console.log(`Publish: ${label2(permissions.publish)}`);
124349
+ console.log(`Profiles write: ${label2(permissions.profilesWrite)}`);
123881
124350
  }
123882
124351
  if (payload.offline)
123883
124352
  console.log(source_default.dim("(offline \u2014 showing cached info)"));
@@ -124503,7 +124972,7 @@ function promptInvitationRecoveryCode() {
124503
124972
  }
124504
124973
  function promptInvitationProof(kind) {
124505
124974
  const length = kind === "token" ? 43 : 6;
124506
- const label = kind === "token" ? "invitation token" : "recovery code";
124975
+ const label2 = kind === "token" ? "invitation token" : "recovery code";
124507
124976
  const character = kind === "token" ? /^[A-Za-z0-9_-]$/ : /^\d$/;
124508
124977
  const { stdin, stderr: output2 } = process, wasRaw = stdin.isRaw, wasFlowing = stdin.readableFlowing;
124509
124978
  return new Promise((resolve49) => {
@@ -124534,13 +125003,13 @@ function promptInvitationProof(kind) {
124534
125003
  value = "";
124535
125004
  overflow = false;
124536
125005
  output2.write(`
124537
- Enter exactly ${length} ${label} characters: `);
125006
+ Enter exactly ${length} ${label2} characters: `);
124538
125007
  return;
124539
125008
  }
124540
125009
  if (value.length === length)
124541
125010
  return finish(value);
124542
125011
  output2.write(`
124543
- Enter the complete ${label}: `);
125012
+ Enter the complete ${label2}: `);
124544
125013
  value = "";
124545
125014
  return;
124546
125015
  }
@@ -124555,7 +125024,7 @@ Enter the complete ${label}: `);
124555
125024
  if (value.length === length) {
124556
125025
  overflow = true;
124557
125026
  output2.write(`
124558
- The ${label} is too long. Press Enter to start again.`);
125027
+ The ${label2} is too long. Press Enter to start again.`);
124559
125028
  } else {
124560
125029
  value += text4;
124561
125030
  output2.write("*");
@@ -124563,7 +125032,7 @@ The ${label} is too long. Press Enter to start again.`);
124563
125032
  } else if (text4 && !key2.ctrl && !key2.meta) {
124564
125033
  overflow = true;
124565
125034
  output2.write(`
124566
- The ${label} contains invalid characters. Press Enter to start again.`);
125035
+ The ${label2} contains invalid characters. Press Enter to start again.`);
124567
125036
  }
124568
125037
  };
124569
125038
  const timer = setTimeout(cancel, 5 * 60 * 1000);
@@ -133712,6 +134181,8 @@ var { registerPluginAdmission: registerPluginAdmission2 } = await Promise.resolv
133712
134181
  registerPluginAdmission2(program2);
133713
134182
  var { registerProfiles: registerProfiles2 } = await Promise.resolve().then(() => (init_profiles(), exports_profiles));
133714
134183
  registerProfiles2(program2);
134184
+ var { registerProvenance: registerProvenance2 } = await Promise.resolve().then(() => (init_provenance(), exports_provenance));
134185
+ registerProvenance2(program2);
133715
134186
  var { registerGrants: registerGrants2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
133716
134187
  registerGrants2(program2);
133717
134188
  var { registerHydrate: registerHydrate2 } = await Promise.resolve().then(() => (init_hydrate(), exports_hydrate));