@hasna/skills 0.10.91 → 0.10.93

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.91",
36891
+ version: "0.10.93",
36892
36892
  description: "Skills library for AI coding agents",
36893
36893
  type: "module",
36894
36894
  bin: {
@@ -59021,6 +59021,82 @@ var init_runtime_launcher = __esm(() => {
59021
59021
  PINNED_LAUNCHER_MAX_BYTES = 16 * 1024;
59022
59022
  });
59023
59023
 
59024
+ // src/lib/linux-access-acl.ts
59025
+ function queryAttribute(probe2, errno, fd, attribute) {
59026
+ for (let attempt2 = 0;attempt2 < LINUX_ACL_QUERY_ATTEMPTS; attempt2++) {
59027
+ errno.set?.(0);
59028
+ const result2 = probe2.fgetxattr(fd, attribute);
59029
+ const code = errno.get();
59030
+ if (result2 !== -1 && result2 !== -1n)
59031
+ return "present";
59032
+ if (code === LINUX_ENODATA)
59033
+ return "absent";
59034
+ if (code === LINUX_EOPNOTSUPP)
59035
+ return "unsupported";
59036
+ }
59037
+ return "unverified";
59038
+ }
59039
+ function linuxAccessAclAbsent(probe2, fd, creationParent) {
59040
+ if (probe2.arch !== "x64" && probe2.arch !== "arm64")
59041
+ return false;
59042
+ const errno = probe2.errno;
59043
+ if (!errno)
59044
+ return false;
59045
+ let unsupported = false;
59046
+ for (const attribute of ["system.posix_acl_access", ...creationParent ? ["system.posix_acl_default"] : []]) {
59047
+ const state = queryAttribute(probe2, errno, fd, attribute);
59048
+ if (state === "present" || state === "unverified")
59049
+ return false;
59050
+ if (state === "unsupported")
59051
+ unsupported = true;
59052
+ }
59053
+ if (!unsupported)
59054
+ return true;
59055
+ const type = probe2.filesystemType(fd);
59056
+ return type !== null && LINUX_ACL_FREE_WHEN_UNSUPPORTED.has(type);
59057
+ }
59058
+ function withGlibcAclProbe(use) {
59059
+ try {
59060
+ const { dlopen, ptr, read: read2, FFIType } = __require("bun:ffi");
59061
+ const library = dlopen("libc.so.6", {
59062
+ fgetxattr: { args: ["i32", "ptr", "ptr", "u64"], returns: FFIType.i64_fast },
59063
+ fstatfs: { args: ["i32", "ptr"], returns: "i32" },
59064
+ __errno_location: { args: [], returns: "ptr" }
59065
+ });
59066
+ try {
59067
+ const location = library.symbols.__errno_location();
59068
+ const names = new Map;
59069
+ return use({
59070
+ arch: process.arch,
59071
+ errno: location ? { get: () => read2.i32(location) } : null,
59072
+ fgetxattr: (fd, attribute) => {
59073
+ let name = names.get(attribute);
59074
+ if (!name)
59075
+ names.set(attribute, name = Buffer.from(attribute + "\x00"));
59076
+ return library.symbols.fgetxattr(fd, ptr(name), null, 0);
59077
+ },
59078
+ filesystemType: (fd) => {
59079
+ const statfs = new BigUint64Array(32);
59080
+ return library.symbols.fstatfs(fd, ptr(statfs)) === 0 ? statfs[0] : null;
59081
+ }
59082
+ });
59083
+ } finally {
59084
+ library.close();
59085
+ }
59086
+ } catch {
59087
+ return false;
59088
+ }
59089
+ }
59090
+ var LINUX_ENODATA = 61, LINUX_EOPNOTSUPP = 95, LINUX_ACL_QUERY_ATTEMPTS = 4, LINUX_ACL_FREE_WHEN_UNSUPPORTED;
59091
+ var init_linux_access_acl = __esm(() => {
59092
+ LINUX_ACL_FREE_WHEN_UNSUPPORTED = new Set([
59093
+ 0xef53n,
59094
+ 0x58465342n,
59095
+ 0x9123683en,
59096
+ 0x01021994n
59097
+ ]);
59098
+ });
59099
+
59024
59100
  // src/lib/retained-review-artifacts.ts
59025
59101
  import { createHash as createHash29 } from "crypto";
59026
59102
  import { constants as constants20, closeSync as closeSync18, fstatSync as fstatSync18, fsyncSync as fsyncSync6, lstatSync as lstatSync30, mkdirSync as mkdirSync9, openSync as openSync18, readSync as readSync14, writeSync as writeSync4 } from "fs";
@@ -60069,28 +60145,7 @@ function hookProjectionRefusal(reason2) {
60069
60145
  throw new Error(`CLAUDE_MANAGED_HOOK_${reason2}`);
60070
60146
  }
60071
60147
  function hasNoLinuxAccessAcl(fd, creationParent) {
60072
- try {
60073
- const { dlopen, read: read2, ptr } = __require("bun:ffi");
60074
- const library = dlopen("libc.so.6", {
60075
- fgetxattr: { args: ["i32", "ptr", "ptr", "u64"], returns: "i64" },
60076
- __errno_location: { args: [], returns: "ptr" }
60077
- });
60078
- try {
60079
- const errno = library.symbols.__errno_location();
60080
- if (!errno)
60081
- return false;
60082
- for (const attribute of ["system.posix_acl_access", ...creationParent ? ["system.posix_acl_default"] : []]) {
60083
- const name = Buffer.from(attribute + "\x00");
60084
- if (Number(library.symbols.fgetxattr(fd, ptr(name), null, 0)) !== -1 || read2.i32(errno) !== 61)
60085
- return false;
60086
- }
60087
- return true;
60088
- } finally {
60089
- library.close();
60090
- }
60091
- } catch {
60092
- return false;
60093
- }
60148
+ return withGlibcAclProbe((probe2) => linuxAccessAclAbsent(probe2, fd, creationParent));
60094
60149
  }
60095
60150
  function assertNativePathAcl(path, observed, options = {}) {
60096
60151
  if (process.platform !== "darwin" && process.platform !== "linux")
@@ -60938,7 +60993,7 @@ function planCodexPluginNameReconcile(request) {
60938
60993
  pathDenies: [...pathDenies].sort(),
60939
60994
  ...discovery ? { discovery } : {}
60940
60995
  };
60941
- const plan = planAgentIntegration({ home, dataDir: dataDir3, projectDir, agents: ["codex"], codexNativeCatalog: catalog, codexReconcile: input });
60996
+ const plan = planAgentIntegration({ home, dataDir: dataDir3, projectDir, agents: ["codex"], codexNativeCatalog: catalog, codexReconcile: input, allowRootAliases: aliases.length > 0 });
60942
60997
  assertCodexReconcileScope(plan, { configPath, policyPath, prior, names, remoteIdentities, discovery: discovery ?? savedDiscovery });
60943
60998
  const configChange = plan.changes.find((change) => resolve25(change.path) === configPath);
60944
60999
  const plannedRules = configChange ? Bun.TOML.parse(configChange.after).skills?.config ?? [] : [];
@@ -61227,7 +61282,7 @@ async function applyCodexPluginReconcileUnlocked(plan, options, assertCurrent, a
61227
61282
  const uncertain = (reason2) => new Error(`CODEX_RECONCILE_NATIVE_WRITE_UNCERTAIN: ${reason2}; the policy was not written (private backup: ${backupRoot}); config.toml ${codexConfigStructureEqual(currentText(configChange.path), configChange.after) ? "holds" : "may still receive"} this run's deny rows: ${rows}. Run the reconcile again: it finishes the policy once Codex reports those skills disabled`);
61228
61283
  let result2;
61229
61284
  try {
61230
- result2 = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.after) ?? [], mergeStrategy: "replace" }], filePath: configChange.path, expectedVersion, reloadUserConfig: true });
61285
+ result2 = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.after) ?? [], mergeStrategy: "replace" }], filePath: nativeConfigPath, expectedVersion, reloadUserConfig: true });
61231
61286
  } catch (error) {
61232
61287
  const refused = error instanceof Error && error.message.startsWith("CODEX_HOOK_TRUST_NATIVE_RPC_REFUSED");
61233
61288
  if (refused && !codexConfigStructureEqual(currentText(configChange.path), configChange.after))
@@ -61238,7 +61293,7 @@ async function applyCodexPluginReconcileUnlocked(plan, options, assertCurrent, a
61238
61293
  nativeVersion = result2.version;
61239
61294
  if (!nativeVersion)
61240
61295
  throw uncertain("Codex answered the config write without a valid config version");
61241
- if (result2?.status !== "ok" || result2.filePath !== configChange.path)
61296
+ if (result2?.status !== "ok" || result2.filePath !== nativeConfigPath)
61242
61297
  throw new Error("CODEX_RECONCILE_NATIVE_WRITE_OVERRIDDEN: Codex did not confirm a plain user-layer write of the deny rows (another configuration layer may override them)");
61243
61298
  if (!codexConfigStructureEqual(currentText(configChange.path), configChange.after))
61244
61299
  throw new Error("CODEX_RECONCILE_NATIVE_READBACK_FAILED: config.toml after Codex's write differs from the planned configuration");
@@ -61289,7 +61344,7 @@ async function applyCodexPluginReconcileUnlocked(plan, options, assertCurrent, a
61289
61344
  if (layers?.length !== 1 || layers[0].version !== nativeVersion)
61290
61345
  incomplete = "config.toml changed after this run's write, so its deny rows were left in place";
61291
61346
  else {
61292
- const restored = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.before), mergeStrategy: "replace" }], filePath: configChange.path, expectedVersion: nativeVersion, reloadUserConfig: true });
61347
+ const restored = await rpc.request("config/batchWrite", { edits: [{ keyPath: "skills.config", value: codexSkillRows(configChange.before), mergeStrategy: "replace" }], filePath: nativeConfigPath, expectedVersion: nativeVersion, reloadUserConfig: true });
61293
61348
  if (restored?.status !== "ok" || !codexConfigStructureEqual(currentText(configChange.path), configChange.before))
61294
61349
  incomplete = "Codex did not restore config.toml to its preimage";
61295
61350
  }
@@ -62051,6 +62106,7 @@ var init_agent_integration = __esm(() => {
62051
62106
  init_claude_settings_witness();
62052
62107
  init_codex_hook_trust_files();
62053
62108
  init_runtime_launcher();
62109
+ init_linux_access_acl();
62054
62110
  init_retained_review_artifacts();
62055
62111
  HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
62056
62112
  ROOTS = NATIVE_SKILL_ROOTS;
@@ -65872,9 +65928,94 @@ function nativeDiscoveryAliasTargets(roots, home) {
65872
65928
  }
65873
65929
  return [...targets];
65874
65930
  }
65931
+ function hooksAreManagedBridge(agent, hooks, managed) {
65932
+ const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
65933
+ const exactKeys4 = (value, keys3) => Object.keys(value).sort().join("\x00") === [...keys3].sort().join("\x00");
65934
+ if (!table(hooks) || typeof managed.command !== "string" || !isAbsolute30(managed.command))
65935
+ return false;
65936
+ const events3 = agent === "gemini" ? ["BeforeAgent", "SessionStart", "BeforeTool"] : ["beforeSubmitPrompt", "sessionStart"];
65937
+ for (const [event, entries] of Object.entries(hooks)) {
65938
+ if (!events3.includes(event) || !Array.isArray(entries) || entries.length !== 1)
65939
+ return false;
65940
+ let expected;
65941
+ try {
65942
+ expected = renderAgentHookCommand(managed.command, agent, managed.profile, event);
65943
+ } catch {
65944
+ return false;
65945
+ }
65946
+ const entry = entries[0];
65947
+ if (!table(entry))
65948
+ return false;
65949
+ if (agent === "cursor") {
65950
+ if (!exactKeys4(entry, ["command", "timeout"]) || entry.timeout !== 15 || entry.command !== expected)
65951
+ return false;
65952
+ continue;
65953
+ }
65954
+ if (!exactKeys4(entry, event === "BeforeTool" ? ["hooks", "matcher"] : ["hooks"]) || event === "BeforeTool" && entry.matcher !== "activate_skill" || !Array.isArray(entry.hooks) || entry.hooks.length !== 1)
65955
+ return false;
65956
+ const hook = entry.hooks[0];
65957
+ if (!table(hook) || !exactKeys4(hook, ["command", "timeout", "type"]) || hook.type !== "command" || hook.timeout !== 15000 || hook.command !== expected)
65958
+ return false;
65959
+ }
65960
+ return true;
65961
+ }
65962
+ function sumiPluginLoadsOnlyBuiltins(text3) {
65963
+ for (const match of text3.matchAll(/\b(?:import|require)\s*\(\s*(.?)/g)) {
65964
+ const rest2 = text3.slice(match.index + match[0].length - match[1].length);
65965
+ if (!/^(['"])node:[A-Za-z0-9_/.-]+\1/.test(rest2))
65966
+ return false;
65967
+ }
65968
+ for (const match of text3.matchAll(/\b(?:from|import)\s*(['"])([^'"]*)\1/g))
65969
+ if (!match[2].startsWith("node:"))
65970
+ return false;
65971
+ return true;
65972
+ }
65973
+ function codexRootFreeControls(config) {
65974
+ const table = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date));
65975
+ const adapted = [];
65976
+ const servers = config.mcp_servers;
65977
+ if (table(servers) && Object.keys(servers).length <= AGENT_POLICY_LIMITS.discoveryRoots && Object.entries(servers).every(([name, row]) => name !== "codex_apps" && name !== "_codex_apps" && isCodexLocalStdioMcpServer(name, row)))
65978
+ adapted.push("mcp_servers");
65979
+ const agents = config.agents;
65980
+ if (table(agents) && Object.keys(agents).length <= AGENT_POLICY_LIMITS.fields && Object.values(agents).every((value) => typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
65981
+ adapted.push("agents");
65982
+ return adapted;
65983
+ }
65984
+ function codexPluginHookHandlers(value) {
65985
+ const table = (item) => Boolean(item && typeof item === "object" && !Array.isArray(item));
65986
+ const identifier6 = (item) => typeof item === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(item);
65987
+ const text3 = (item) => typeof item === "string" && item.length <= AGENT_POLICY_LIMITS.pathCharacters && !item.includes("\x00");
65988
+ if (!table(value) || !Object.keys(value).every((key2) => key2 === "hooks" || key2 === "description") || value.description !== undefined && !text3(value.description) || !table(value.hooks) || Object.keys(value.hooks).length > 32)
65989
+ return null;
65990
+ const result2 = { commands: 0, servers: new Set };
65991
+ for (const [event, groups] of Object.entries(value.hooks)) {
65992
+ if (!CODEX_HOOK_EVENTS.has(event) || !Array.isArray(groups) || groups.length > 32)
65993
+ return null;
65994
+ for (const group of groups) {
65995
+ if (!table(group) || !Object.keys(group).every((key2) => key2 === "hooks" || key2 === "matcher") || group.matcher !== undefined && !text3(group.matcher) || !Array.isArray(group.hooks) || group.hooks.length > 32)
65996
+ return null;
65997
+ for (const handler of group.hooks) {
65998
+ if (!table(handler) || handler.timeout !== undefined && !(Number.isSafeInteger(handler.timeout) && handler.timeout >= 0) || handler.statusMessage !== undefined && !text3(handler.statusMessage))
65999
+ return null;
66000
+ if (handler.type === "command") {
66001
+ if (!text3(handler.command) || !handler.command || !Object.keys(handler).every((key2) => ["type", "command", "commandWindows", "command_windows", "timeout", "async", "statusMessage", "additionalContextLimit"].includes(key2)))
66002
+ return null;
66003
+ result2.commands++;
66004
+ } else if (handler.type === "mcp_tool") {
66005
+ if (!identifier6(handler.server) || !identifier6(handler.tool) || handler.input !== undefined && !table(handler.input) || !Object.keys(handler).every((key2) => ["type", "server", "tool", "input", "timeout", "statusMessage"].includes(key2)))
66006
+ return null;
66007
+ result2.servers.add(handler.server);
66008
+ } else
66009
+ return null;
66010
+ }
66011
+ }
66012
+ }
66013
+ return result2;
66014
+ }
65875
66015
  function resolveDiscovery(options, exclusion) {
65876
66016
  const canonical9 = options.canonical ?? resolve29, home = resolve29(options.home), agent = options.agent;
65877
66017
  const sources = [], roots = new Set, builtinNames = [];
66018
+ const sumiLocalPluginTexts = new Map;
65878
66019
  const budget3 = discoveryByteBudget();
65879
66020
  let remainingEntries = AGENT_POLICY_LIMITS.discoveryDirectoryEntries;
65880
66021
  const entries = (path) => {
@@ -65911,8 +66052,8 @@ function resolveDiscovery(options, exclusion) {
65911
66052
  const configPath = exclusion?.nativeHome === undefined ? agentDiscoveryConfigPath(home, agent) : join46(nativeHome, agent === "codex" ? "config.toml" : "settings.json");
65912
66053
  const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : agent === "sumi" ? [...SUMI_DISCOVERY_PROJECTION_FIELDS] : ["version"]);
65913
66054
  const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
65914
- const unresolved = (detail) => {
65915
- throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
66055
+ const unresolved = (detail, source) => {
66056
+ throw new NativeDiscoveryUnresolved(agent, detail, source);
65916
66057
  };
65917
66058
  let opaqueClosureVerified = false;
65918
66059
  const verifyOpaqueClosure = (declarations) => {
@@ -65926,6 +66067,17 @@ function resolveDiscovery(options, exclusion) {
65926
66067
  verifyDiscovery(retained, undefined, undefined, undefined, exclusion?.narrowed, canonical9(configPath));
65927
66068
  opaqueClosureVerified = true;
65928
66069
  };
66070
+ const reviewRemedy = (source, check) => {
66071
+ try {
66072
+ check();
66073
+ } catch (error) {
66074
+ if (error instanceof NativeDiscoveryUnresolved) {
66075
+ error.reviewable = true;
66076
+ error.source ??= source;
66077
+ }
66078
+ throw error;
66079
+ }
66080
+ };
65929
66081
  if (exclusion) {
65930
66082
  if (configText !== null && agent !== "codex" && agent !== "hermes")
65931
66083
  parseNativeJsonObject(configText);
@@ -65951,12 +66103,38 @@ function resolveDiscovery(options, exclusion) {
65951
66103
  const controls = agent === "claude" ? claudeDiscoveryControlKeys(configText ?? "{}") : agent === "codex" ? codexDiscoveryControlKeys(config) : Object.keys(config).filter((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2));
65952
66104
  const fieldKey = (key2) => /^[A-Za-z_$][A-Za-z0-9_$.-]{0,22}$/.test(key2) && !/(?:sk|rk|pk)_(?:live|test)_|whsec_/i.test(key2) ? JSON.stringify(key2) : "[withheld field key]";
65953
66105
  const unsupportedFields = (names, label) => unresolved(`unknown discovery controls have no root-exclusion adapter for ${label}${names.slice(0, 8).map(fieldKey).join(", ")}${names.length > 8 ? ", additional keys withheld" : ""} in ${quotedPath(canonical9(configPath))}`);
65954
- const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2));
66106
+ const adapted = agent === "codex" ? codexRootFreeControls(config) : agent === "opencode" && Array.isArray(config.instructions) && config.instructions.length === 0 ? ["instructions"] : [];
66107
+ const unsupportedControls = controls.filter((key2) => !supported[agent].includes(key2) && !adapted.includes(key2));
65955
66108
  if (unsupportedControls.length)
65956
66109
  unsupportedFields(unsupportedControls, "");
65957
66110
  if (config.hooks !== undefined && !object6(config.hooks))
65958
66111
  unresolved("hooks must be an object");
65959
- const hasHooks = controls.includes("hooks") && config.hooks !== undefined && Object.keys(config.hooks).length > 0;
66112
+ const managedSupervisorOnly = () => {
66113
+ const supervisor = exclusion.managedHook?.supervisor;
66114
+ if (agent !== "hermes" || !supervisor || typeof supervisor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(supervisor.sha256) || !object6(config.hooks))
66115
+ return false;
66116
+ let expected;
66117
+ try {
66118
+ expected = hermesHookDefinitions(supervisor);
66119
+ } catch {
66120
+ return false;
66121
+ }
66122
+ for (const [event, entries2] of Object.entries(config.hooks)) {
66123
+ const want = expected[event];
66124
+ if (!want || !Array.isArray(entries2) || entries2.length !== 1 || !object6(entries2[0]))
66125
+ return false;
66126
+ const entry = entries2[0];
66127
+ if (Object.keys(entry).sort().join("\x00") !== Object.keys(want).sort().join("\x00") || Object.keys(want).some((key2) => entry[key2] !== want[key2]))
66128
+ return false;
66129
+ }
66130
+ try {
66131
+ return hashRawDiscoveryFile(canonical9(supervisor.path), budget3) === supervisor.sha256;
66132
+ } catch {
66133
+ return false;
66134
+ }
66135
+ };
66136
+ const managedBridgeOnly = (agent === "gemini" || agent === "cursor") && exclusion.managedHook !== undefined && hooksAreManagedBridge(agent, config.hooks, exclusion.managedHook) || managedSupervisorOnly();
66137
+ const hasHooks = controls.includes("hooks") && config.hooks !== undefined && Object.keys(config.hooks).length > 0 && !managedBridgeOnly;
65960
66138
  let hasPluginConfigs = false, hasFunctionHookEnvironment = false;
65961
66139
  const verifyCurrentOpaqueSettings = (label) => {
65962
66140
  const retained = exclusion.retained;
@@ -65971,7 +66149,7 @@ function resolveDiscovery(options, exclusion) {
65971
66149
  unresolved(`non-enumerable ${label} require a current settings review`);
65972
66150
  };
65973
66151
  if (hasHooks)
65974
- verifyCurrentOpaqueSettings("hooks");
66152
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("hooks"));
65975
66153
  if (agent === "claude") {
65976
66154
  if (config.env !== undefined) {
65977
66155
  if (!object6(config.env) || Object.keys(config.env).length > 512)
@@ -65984,7 +66162,7 @@ function resolveDiscovery(options, exclusion) {
65984
66162
  if (hasFunctionHookEnvironment) {
65985
66163
  if (!["0", "1"].includes(config.env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS))
65986
66164
  unresolved("malformed Claude env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS flag");
65987
- verifyCurrentOpaqueSettings("function-hook environment controls");
66165
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("function-hook environment controls"));
65988
66166
  }
65989
66167
  }
65990
66168
  if (config.pluginConfigs !== undefined) {
@@ -66011,7 +66189,7 @@ function resolveDiscovery(options, exclusion) {
66011
66189
  }
66012
66190
  hasPluginConfigs = Object.keys(config.pluginConfigs).length > 0;
66013
66191
  if (hasPluginConfigs)
66014
- verifyCurrentOpaqueSettings("plugin configurations");
66192
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("plugin configurations"));
66015
66193
  }
66016
66194
  if (config.enabledPlugins !== undefined && !object6(config.enabledPlugins))
66017
66195
  unresolved("enabledPlugins must be an object");
@@ -66061,7 +66239,9 @@ function resolveDiscovery(options, exclusion) {
66061
66239
  unresolved("malformed Sumi plugins");
66062
66240
  }
66063
66241
  if (agent === "hermes" && config.skills !== undefined) {
66064
- keys3(config.skills, ["external_dirs"], "Hermes skills");
66242
+ keys3(config.skills, ["external_dirs", "creation_nudge_interval"], "Hermes skills");
66243
+ if (config.skills.creation_nudge_interval !== undefined && !(Number.isSafeInteger(config.skills.creation_nudge_interval) && config.skills.creation_nudge_interval >= 0))
66244
+ unresolved("malformed Hermes skills creation_nudge_interval");
66065
66245
  if (config.skills.external_dirs !== undefined)
66066
66246
  paths(typeof config.skills.external_dirs === "string" ? [config.skills.external_dirs] : config.skills.external_dirs, "Hermes external directories");
66067
66247
  }
@@ -66081,10 +66261,21 @@ function resolveDiscovery(options, exclusion) {
66081
66261
  unresolved(`malformed Codex ${key2}`);
66082
66262
  for (const value of Object.values(config.plugins ?? {}))
66083
66263
  keys3(value, ["enabled"], "Codex plugin registration");
66084
- for (const value of Object.values(config.marketplaces ?? {}))
66085
- keys3(value, ["source_type", "source"], "Codex marketplace registration");
66264
+ for (const value of Object.values(config.marketplaces ?? {})) {
66265
+ keys3(value, ["source_type", "source", "last_updated"], "Codex marketplace registration");
66266
+ if (value.last_updated !== undefined && (typeof value.last_updated !== "string" || !CODEX_MARKETPLACE_TIMESTAMP.test(value.last_updated)))
66267
+ unresolved("malformed Codex marketplace refresh time");
66268
+ }
66086
66269
  if (config.skills !== undefined)
66087
- keys3(config.skills, ["config", "bundled"], "Codex skills");
66270
+ keys3(config.skills, ["config", "bundled", "native_allowed_paths"], "Codex skills");
66271
+ if (config.skills?.native_allowed_paths !== undefined) {
66272
+ for (const value of paths(config.skills.native_allowed_paths, "Codex native allowed skill paths")) {
66273
+ if (!isAbsolute30(value))
66274
+ unresolved("relative Codex native allowed skill path");
66275
+ const path = canonical9(value);
66276
+ roots.add(path.endsWith(`${sep14}SKILL.md`) ? dirname29(path) : path);
66277
+ }
66278
+ }
66088
66279
  if (config.skills?.bundled !== undefined) {
66089
66280
  keys3(config.skills.bundled, ["enabled"], "Codex bundled skills");
66090
66281
  if (typeof config.skills.bundled.enabled !== "boolean")
@@ -66110,7 +66301,7 @@ function resolveDiscovery(options, exclusion) {
66110
66301
  if (Object.keys(config.enabledPlugins ?? config.plugins ?? config.extensions ?? {}).length > 512)
66111
66302
  unresolved("unbounded native registrations");
66112
66303
  if (hasHooks || hasPluginConfigs || hasFunctionHookEnvironment)
66113
- verifyOpaqueClosure([]);
66304
+ reviewRemedy(canonical9(configPath), () => verifyOpaqueClosure([]));
66114
66305
  }
66115
66306
  if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
66116
66307
  throw new Error("Invalid --discovery-inputs version or agents");
@@ -66142,7 +66333,9 @@ function resolveDiscovery(options, exclusion) {
66142
66333
  const target = join46(path, name);
66143
66334
  if (!lstatSync35(target).isFile())
66144
66335
  unresolved("local plugin packages need a dedicated discovery adapter");
66145
- witness(target);
66336
+ const text3 = witness(target);
66337
+ if (exclusion && text3 !== null)
66338
+ sumiLocalPluginTexts.set(canonical9(target), text3);
66146
66339
  }
66147
66340
  }
66148
66341
  for (const root2 of roots) {
@@ -66276,38 +66469,126 @@ function resolveDiscovery(options, exclusion) {
66276
66469
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual15(candidate2, source)) === index);
66277
66470
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
66278
66471
  }
66279
- function plugin(root2) {
66472
+ const reviewedManifestAbsence = (root2, manifestPath) => {
66473
+ const retained = exclusion?.retained;
66474
+ if (agent !== "claude" || retained?.method !== "reviewed" || ![join46(root2, "skills"), root2].some((path) => retained.roots.includes(path)))
66475
+ return false;
66476
+ const saved = retained.sources.filter((source) => source.path === canonical9(manifestPath) && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined);
66477
+ return saved.length > 0 && saved.every((source) => projected(source, undefined, budget3) === source.sha256);
66478
+ };
66479
+ const reviewedInstalledCopy = (root2, path, identity6) => {
66480
+ const retained = exclusion?.retained;
66481
+ if (agent !== "codex" || retained?.method !== "reviewed")
66482
+ return false;
66483
+ const inside = relative9(root2, canonical9(path));
66484
+ if (!inside || inside === ".." || inside.startsWith(`..${sep14}`) || isAbsolute30(inside))
66485
+ return false;
66486
+ const parent = canonical9(join46(nativeHome, "plugins/cache", identity6.marketplace, identity6.name));
66487
+ const current = hashRawDiscoveryFile(canonical9(path), budget3);
66488
+ if (current === null)
66489
+ return false;
66490
+ return retained.sources.some((source) => {
66491
+ if (source.sha256 !== current || source.format !== undefined || source.fields !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || !source.path.startsWith(parent + sep14))
66492
+ return false;
66493
+ const [version, ...rest2] = source.path.slice(parent.length + 1).split(sep14);
66494
+ return Boolean(version) && version !== "." && version !== ".." && rest2.join(sep14) === inside;
66495
+ });
66496
+ };
66497
+ const verifyCodexPluginHookClosure = (root2, identity6, marketplaceSource, servers, declarations) => {
66498
+ const retained = exclusion.retained;
66499
+ if (retained.codexSemanticCache || retained.codexInstallationInputs || retained.codexRetiredMaterializations)
66500
+ return verifyOpaqueClosure(declarations);
66501
+ const within2 = (base2, path) => path === base2 || path.startsWith(base2 + sep14);
66502
+ const executables = [];
66503
+ for (const server of servers) {
66504
+ const row = config.mcp_servers?.[server];
66505
+ if (server === "codex_apps" || server === "_codex_apps" || !isCodexLocalStdioMcpServer(server, row))
66506
+ unresolved("plugin hook MCP server is not a configured local stdio server");
66507
+ for (const value of [row.command, ...row.args ?? []])
66508
+ for (const candidate2 of [value, value.slice(value.indexOf("=") + 1)]) {
66509
+ if (!isAbsolute30(candidate2) || /[\0$]/.test(candidate2) || executables.length >= AGENT_POLICY_LIMITS.discoveryRoots)
66510
+ continue;
66511
+ const path = canonical9(candidate2);
66512
+ if (lstatSync35(path, { throwIfNoEntry: false })?.isFile())
66513
+ executables.push(path);
66514
+ }
66515
+ }
66516
+ const cacheRoot = canonical9(join46(nativeHome, "plugins/cache"));
66517
+ const ownCache = canonical9(join46(nativeHome, "plugins/cache", identity6.marketplace, identity6.name));
66518
+ const ownCatalog = canonical9(join46(marketplaceSource, ".agents/plugins/marketplace.json"));
66519
+ const account = canonical9(home);
66520
+ const marketplaceRoots = Object.values(config.marketplaces ?? {}).filter((market) => market?.source_type === "local" && typeof market.source === "string" && isAbsolute30(market.source)).map((market) => canonical9(market.source)).filter((path) => !within2(path, account));
66521
+ const otherPlugin = (path) => !within2(root2, path) && !within2(ownCache, path) && path !== ownCatalog && !executables.includes(path) && (within2(cacheRoot, path) || marketplaceRoots.some((base2) => within2(base2, path)));
66522
+ const sources2 = retained.sources.filter((source) => source.path !== canonical9(configPath) && !otherPlugin(source.path));
66523
+ if (sources2.some((source) => source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || source.reviewArtifact !== undefined || ![undefined, "bytes", "path-bytes", "executable-v1"].includes(source.hashMode)))
66524
+ return verifyOpaqueClosure(declarations);
66525
+ const declared = declarations.map((path) => relative9(root2, path));
66526
+ const declaration = (path) => declarations.includes(path) || path.startsWith(ownCache + sep14) && declared.includes(path.slice(ownCache.length + 1).split(sep14).slice(1).join(sep14));
66527
+ if (!sources2.some((source) => !declaration(source.path) && source.sha256 !== null))
66528
+ unresolved("opaque executable dependency closure is unavailable");
66529
+ for (const source of sources2)
66530
+ if (projected(source, undefined, budget3) !== source.sha256) {
66531
+ throw new NativeDiscoveryWitnessChange("input", source.path, `Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
66532
+ }
66533
+ verifyDiscoveryDirectories((retained.directories ?? []).filter((directory2) => !otherPlugin(directory2.path)));
66534
+ };
66535
+ const readCodexHookDeclaration = (text3, path) => {
66536
+ if (text3 === null)
66537
+ return null;
66538
+ try {
66539
+ return codexPluginHookHandlers(parseConfig(text3, path));
66540
+ } catch {
66541
+ return null;
66542
+ }
66543
+ };
66544
+ function plugin(root2, codex) {
66280
66545
  root2 = canonical9(root2);
66281
66546
  safe6(root2);
66282
66547
  if (exclusion)
66283
66548
  roots.add(root2);
66284
66549
  const manifestPath = join46(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
66285
66550
  const raw = witness(manifestPath, undefined, undefined, agent === "claude" ? "claude-plugin-manifest-v1" : undefined);
66286
- if (raw === null)
66551
+ if (raw === null && !reviewedManifestAbsence(root2, manifestPath))
66287
66552
  unresolved("plugin manifest missing");
66288
- const manifest = parseConfig(raw, manifestPath);
66553
+ const manifest = raw === null ? {} : parseConfig(raw, manifestPath);
66289
66554
  if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
66290
66555
  unresolved("unknown plugin discovery controls");
66291
66556
  const hooks = witness(join46(root2, "hooks/hooks.json"));
66292
- if (manifest.hooks !== undefined || hooks !== null) {
66293
- if (!exclusion || exclusion.retained?.method !== "reviewed")
66294
- unresolved("plugin hooks require a separate no-skill-injection review");
66295
- const required = [manifestPath, ...hooks !== null ? [join46(root2, "hooks/hooks.json")] : []];
66296
- if (manifest.hooks !== undefined) {
66297
- if (typeof manifest.hooks !== "string")
66298
- unresolved("unsupported plugin hooks target");
66299
- const hook = resolve29(root2, manifest.hooks);
66300
- if (!hook.startsWith(root2 + sep14))
66301
- unresolved("plugin hooks target escapes its root");
66302
- required.push(hook);
66303
- }
66304
- for (const path of required) {
66305
- const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
66306
- if (!saved.length || saved.some((source) => projected(source, undefined, budget3) !== source.sha256))
66307
- unresolved("plugin hooks require current independently reviewed sources");
66308
- }
66309
- verifyOpaqueClosure(required.map((path) => canonical9(path)));
66310
- }
66557
+ if (manifest.hooks !== undefined || hooks !== null)
66558
+ reviewRemedy(canonical9(hooks !== null ? join46(root2, "hooks/hooks.json") : manifestPath), () => {
66559
+ if (!exclusion || exclusion.retained?.method !== "reviewed")
66560
+ unresolved("plugin hooks require a separate no-skill-injection review");
66561
+ const required = [manifestPath, ...hooks !== null ? [join46(root2, "hooks/hooks.json")] : []];
66562
+ const scoped2 = agent === "codex" && codex !== undefined ? [] : undefined;
66563
+ if (manifest.hooks !== undefined) {
66564
+ if (scoped2 && manifest.hooks && typeof manifest.hooks === "object" && !Array.isArray(manifest.hooks)) {
66565
+ const inline = codexPluginHookHandlers(manifest.hooks);
66566
+ if (!inline)
66567
+ unresolved("unsupported inline plugin hooks");
66568
+ scoped2.push(inline);
66569
+ } else {
66570
+ if (typeof manifest.hooks !== "string")
66571
+ unresolved("unsupported plugin hooks target");
66572
+ const hook = resolve29(root2, manifest.hooks);
66573
+ if (!hook.startsWith(root2 + sep14))
66574
+ unresolved("plugin hooks target escapes its root");
66575
+ required.push(hook);
66576
+ if (scoped2)
66577
+ scoped2.push(readCodexHookDeclaration(witness(hook), hook));
66578
+ }
66579
+ }
66580
+ if (scoped2 && hooks !== null)
66581
+ scoped2.push(readCodexHookDeclaration(hooks, join46(root2, "hooks/hooks.json")));
66582
+ for (const path of required) {
66583
+ const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
66584
+ if (saved.length ? saved.some((source) => projected(source, undefined, budget3) !== source.sha256) : !(scoped2 && reviewedInstalledCopy(root2, path, codex)))
66585
+ unresolved("plugin hooks require current independently reviewed sources", canonical9(path));
66586
+ }
66587
+ if (scoped2 && scoped2.every((item) => item !== null && item.commands === 0)) {
66588
+ verifyCodexPluginHookClosure(root2, codex, codex.source, new Set(scoped2.flatMap((item) => [...item.servers])), required.map((path) => canonical9(path)));
66589
+ } else
66590
+ verifyOpaqueClosure(required.map((path) => canonical9(path)));
66591
+ });
66311
66592
  const commands = join46(root2, "commands");
66312
66593
  safe6(commands);
66313
66594
  if (agent === "claude" && (manifest.commands !== undefined || lstatSync35(commands, { throwIfNoEntry: false })?.isDirectory() && entries(commands).length))
@@ -66362,7 +66643,7 @@ function resolveDiscovery(options, exclusion) {
66362
66643
  const catalog = parseConfig(catalogText, join46(market.source, ".agents/plugins/marketplace.json")), matches2 = Array.isArray(catalog.plugins) ? catalog.plugins.filter((item) => item.name === name) : [];
66363
66644
  if (matches2.length !== 1 || matches2[0]?.source?.source !== "local" || typeof matches2[0]?.source?.path !== "string")
66364
66645
  unresolved("plugin catalog source is missing or ambiguous");
66365
- plugin(resolve29(market.source, matches2[0].source.path));
66646
+ plugin(resolve29(market.source, matches2[0].source.path), exclusion ? { marketplace, name, source: market.source } : undefined);
66366
66647
  }
66367
66648
  } else if (agent === "opencode") {
66368
66649
  if (witness(join46(home, ".config/opencode/opencode.jsonc")) !== null || witness(join46(home, ".config/opencode/config.json")) !== null)
@@ -66379,7 +66660,11 @@ function resolveDiscovery(options, exclusion) {
66379
66660
  if (lstatSync35(localPlugins, { throwIfNoEntry: false }) && entries(localPlugins).some((name) => name !== "skills-cli.js"))
66380
66661
  unresolved("additional local plugin hooks require review");
66381
66662
  } else if (agent === "sumi") {
66382
- if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join46(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null))
66663
+ const pinnedLocalPlugin = (source) => {
66664
+ const retained = exclusion?.retained, text3 = sumiLocalPluginTexts.get(source.path);
66665
+ return retained?.method === "reviewed" && text3 !== undefined && sumiPluginLoadsOnlyBuiltins(text3) && retained.sources.some((saved) => saved.path === source.path && saved.sha256 === source.sha256 && saved.sha256 !== null && [undefined, "bytes"].includes(saved.hashMode) && saved.format === undefined && saved.fields === undefined && saved.managedPlugins === undefined && saved.reviewArtifact === undefined);
66666
+ };
66667
+ if ((config.plugin?.length ?? 0) > 0 || (config.plugins?.length ?? 0) > 0 || sources.some((source) => source.path !== canonical9(configPath) && source.path !== canonical9(join46(sumiConfigDirectory(home), "sumi.jsonc")) && source.sha256 !== null && !pinnedLocalPlugin(source)))
66383
66668
  unresolved("external or foreign local plugins require review");
66384
66669
  } else if (agent === "hermes") {
66385
66670
  for (const directory2 of [join46(home, ".hermes/plugins"), join46(home, ".hermes/hermes-agent")]) {
@@ -66413,17 +66698,18 @@ function resolveDiscovery(options, exclusion) {
66413
66698
  unresolved("native discovery roots exceed their bound");
66414
66699
  return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join46(home, ".hermes/plugins"), join46(home, ".hermes/hermes-agent")].map((path) => canonical9(path))) } : {} };
66415
66700
  }
66416
- var DISCOVERY_RUNTIME_COMMANDS, DISCOVERY_ROOT_UNRESOLVED = "DISCOVERY_ROOT_UNRESOLVED", quotedPath = (path) => JSON.stringify(path).replace(/[^\x20-\x7e]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`), realpathOrNull = (path) => {
66701
+ var DISCOVERY_RUNTIME_COMMANDS, DISCOVERY_ROOT_UNRESOLVED = "DISCOVERY_ROOT_UNRESOLVED", NativeDiscoveryUnresolved, quotedPath = (path) => JSON.stringify(path).replace(/[^\x20-\x7e]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`), realpathOrNull = (path) => {
66417
66702
  try {
66418
66703
  return realpathSync12(path);
66419
66704
  } catch {
66420
66705
  return null;
66421
66706
  }
66422
- }, digest2 = (text3) => createHash33("sha256").update(text3).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS;
66707
+ }, digest2 = (text3) => createHash33("sha256").update(text3).digest("hex"), CLAUDE_CONTROL_WITNESS_MODES, CLAUDE_CONTROL_FILES, ADOPTABLE_SETTINGS_MATCHES, CLAUDE_PROJECT_DISCOVERY_KEYS, CODEX_MARKETPLACE_TIMESTAMP, CODEX_HOOK_EVENTS;
66423
66708
  var init_agent_discovery = __esm(() => {
66424
66709
  init_sumi_settings_witness();
66425
66710
  init_agent_policy_limits();
66426
66711
  init_agent_hermes();
66712
+ init_agent_adapters();
66427
66713
  init_agent_discovery_directories();
66428
66714
  init_agent_discovery_bytes();
66429
66715
  init_plugin_projection_store();
@@ -66449,10 +66735,24 @@ var init_agent_discovery = __esm(() => {
66449
66735
  init_codex_semantic_cache_witness();
66450
66736
  init_agent_discovery_directories();
66451
66737
  DISCOVERY_RUNTIME_COMMANDS = Object.freeze({ gemini: "gemini" });
66738
+ NativeDiscoveryUnresolved = class NativeDiscoveryUnresolved extends Error {
66739
+ agent;
66740
+ detail;
66741
+ source;
66742
+ reviewable = false;
66743
+ constructor(agent, detail, source) {
66744
+ super(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
66745
+ this.agent = agent;
66746
+ this.detail = detail;
66747
+ this.source = source;
66748
+ }
66749
+ };
66452
66750
  CLAUDE_CONTROL_WITNESS_MODES = new Set(["claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-settings-v5", "claude-marketplace-registry", "claude-marketplace-registry-v2", "claude-marketplace-entry-v1", "claude-plugin-registry"]);
66453
66751
  CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
66454
66752
  ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
66455
66753
  CLAUDE_PROJECT_DISCOVERY_KEYS = Object.freeze(["enabledPlugins", "extraKnownMarketplaces", "additionalMarketplaces", "skillOverrides"]);
66754
+ CODEX_MARKETPLACE_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
66755
+ CODEX_HOOK_EVENTS = new Set(["PreToolUse", "PermissionRequest", "PostToolUse", "PreCompact", "PostCompact", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart", "SubagentStop", "Stop", "Interrupt"]);
66456
66756
  });
66457
66757
 
66458
66758
  // src/lib/authoring-path.ts
@@ -66485,6 +66785,11 @@ function physicalPath(path) {
66485
66785
  }
66486
66786
  return resolve30(realpathSync13(cursor), ...suffix);
66487
66787
  }
66788
+ function portableAuthoringDiscoveryRefusal(error, destination) {
66789
+ const head2 = `Native discovery is unresolved (${error.agent}: ${error.detail}).${error.source ? ` Checked source: ${error.source}.` : ""}`;
66790
+ const review = `skills hook install --agent ${error.agent} --discovery-inputs <reviewed-file> --apply`;
66791
+ return error.reviewable ? `${head2} Portable Skills authoring stays refused: an unreviewed ${error.agent} hook or plugin option could load skills written to ${destination}. This command takes no --discovery-inputs option; review the current ${error.agent} configuration with ${review}, then retry.` : `${head2} Portable Skills authoring stays refused: Skills cannot list every skill root this ${error.agent} configuration registers, so it cannot rule out ${destination}. This command takes no --discovery-inputs option; correct that ${error.agent} configuration entry, then retry. Where a reviewed discovery binding covers it, record one with ${review}.`;
66792
+ }
66488
66793
  function contains2(root2, path) {
66489
66794
  const boundary = root2.endsWith(sep15) ? root2 : root2 + sep15;
66490
66795
  return path === root2 || path.startsWith(boundary);
@@ -66530,7 +66835,10 @@ function configuredRoots(home, explicitHome) {
66530
66835
  if (recorded && (!executable2 || executable2.target !== recorded.target || onPath && onPath.target !== recorded.target) || !executable2 && (binding.builtinNames?.length || binding.agent === "gemini" && binding.sources.some((source) => basename19(source.path) === "package.json"))) {
66531
66836
  throw new Error("Native discovery runtime target is unresolved or changed");
66532
66837
  }
66533
- roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable: executable2, narrowed }));
66838
+ const managedCommand = current.commands?.[agent], managedProfile = current.profiles?.[agent];
66839
+ const supervisor = current.supervisors?.[agent];
66840
+ const managedHook = (binding.agent === "gemini" || binding.agent === "cursor") && typeof managedCommand === "string" && typeof managedProfile === "string" ? { command: managedCommand, profile: managedProfile } : binding.agent === "hermes" && typeof managedCommand === "string" && typeof managedProfile === "string" && supervisor && typeof supervisor === "object" && typeof supervisor.path === "string" && typeof supervisor.runtime === "string" && typeof supervisor.sha256 === "string" ? { command: managedCommand, profile: managedProfile, supervisor: { path: supervisor.path, runtime: supervisor.runtime, sha256: supervisor.sha256 } } : undefined;
66841
+ roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: binding.agent, canonical: physicalPath, retained: binding, executable: executable2, narrowed, ...managedHook ? { managedHook } : {} }));
66534
66842
  }
66535
66843
  };
66536
66844
  if (homePolicy)
@@ -66582,11 +66890,18 @@ function assertPortableAuthoringPath(path, options = {}) {
66582
66890
  if (native) {
66583
66891
  throw new Error(`Refusing portable Skills authoring below native discovery root '${native}': ${lexical}. Set HASNA_SKILLS_DIR to a private authoring corpus outside native roots.`);
66584
66892
  }
66585
- const roots = configuredRoots(home, options.homeDir !== undefined);
66586
- for (const value of selected) {
66587
- if (physicalPath(value.path) === physicalPath(join47(home, `.${value.agent}`)))
66588
- continue;
66589
- roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: value.agent, nativeHome: value.path, canonical: physicalPath, executable: null }));
66893
+ const roots = [];
66894
+ try {
66895
+ roots.push(...configuredRoots(home, options.homeDir !== undefined));
66896
+ for (const value of selected) {
66897
+ if (physicalPath(value.path) === physicalPath(join47(home, `.${value.agent}`)))
66898
+ continue;
66899
+ roots.push(...resolveAgentDiscoveryExclusionRoots({ home, agent: value.agent, nativeHome: value.path, canonical: physicalPath, executable: null }));
66900
+ }
66901
+ } catch (error) {
66902
+ if (error instanceof NativeDiscoveryUnresolved)
66903
+ throw new Error(portableAuthoringDiscoveryRefusal(error, lexical));
66904
+ throw error;
66590
66905
  }
66591
66906
  for (const root2 of roots) {
66592
66907
  if (candidates.some((candidate2) => [resolve30(root2), physicalPath(root2)].some((nativeRoot) => contains2(nativeRoot, candidate2) || contains2(candidate2, nativeRoot)))) {
@@ -122238,10 +122553,403 @@ var init_completion = __esm(() => {
122238
122553
  categoryNames = CATEGORIES.map((c) => c);
122239
122554
  });
122240
122555
 
122556
+ // src/lib/prepare-skill.ts
122557
+ import { createHash as createHash47, randomUUID as randomUUID15 } from "crypto";
122558
+ import { chmodSync as chmodSync5, existsSync as existsSync35, lstatSync as lstatSync47, mkdirSync as mkdirSync30, mkdtempSync as mkdtempSync7, readFileSync as readFileSync45, readdirSync as readdirSync27, renameSync as renameSync18, rmSync as rmSync10, writeFileSync as writeFileSync28 } from "fs";
122559
+ import { tmpdir as tmpdir2 } from "os";
122560
+ import { dirname as dirname47, isAbsolute as isAbsolute43, join as join73, relative as relative15, resolve as resolve39, sep as sep21 } from "path";
122561
+ function prepareSkill(name, options) {
122562
+ const normalized2 = normalizePortableSkillName(name);
122563
+ if (!new RegExp(SEMVER_PATTERN).test(options.version))
122564
+ throw new Error("--version must be a semantic version, for example 0.2.0.");
122565
+ const path = join73(getPortableSkillsRootReadOnly(options), normalized2);
122566
+ assertPortableAuthoringPath(path, options);
122567
+ const before2 = snapshot2(path);
122568
+ const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
122569
+ if (!manifestFile)
122570
+ throw new Error(`Skill '${normalized2}' needs skill.json. Import the source with skills port first.`);
122571
+ let manifest;
122572
+ try {
122573
+ const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
122574
+ if (!isRecord9(parsed))
122575
+ throw new Error("not an object");
122576
+ manifest = parsed;
122577
+ } catch {
122578
+ throw new Error("skill.json must contain a JSON object; no files were changed.");
122579
+ }
122580
+ const previousVersion = manifest.version;
122581
+ if (typeof previousVersion !== "string" || !new RegExp(SEMVER_PATTERN).test(previousVersion)) {
122582
+ throw new Error("skill.json must declare its current semantic version before preparation.");
122583
+ }
122584
+ if (manifest.kind !== undefined && manifest.kind !== "instruction" && manifest.kind !== "executable") {
122585
+ throw new Error("skill.json has an invalid kind; correct it before preparation.");
122586
+ }
122587
+ const kind = options.kind ?? manifest.kind;
122588
+ if (kind !== "instruction" && kind !== "executable") {
122589
+ throw new Error("This legacy manifest has no explicit kind. Pass --kind instruction or --kind executable; helper scripts alone do not determine a skill's kind.");
122590
+ }
122591
+ const document3 = before2.files.find((file2) => file2.path === "SKILL.md");
122592
+ const documentKind = document3 ? parseSkillFrontmatter(document3.bytes.toString("utf8"))?.kind : undefined;
122593
+ if (documentKind !== undefined && documentKind !== kind) {
122594
+ throw new Error("SKILL.md kind conflicts with the candidate skill.json kind. Review and align both declarations (or remove the optional frontmatter kind), then prepare again; no files were changed.");
122595
+ }
122596
+ if (manifest.provenance !== undefined && !isRecord9(manifest.provenance))
122597
+ throw new Error("skill.json provenance must be an object.");
122598
+ const provenance = manifest.provenance;
122599
+ const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
122600
+ const staging = mkdtempSync7(join73(tmpdir2(), "skills-prepare-"));
122601
+ try {
122602
+ for (const directory3 of before2.directories)
122603
+ mkdirSync30(join73(staging, directory3), { recursive: true });
122604
+ for (const file2 of before2.files) {
122605
+ const destination = join73(staging, file2.path);
122606
+ mkdirSync30(dirname47(destination), { recursive: true });
122607
+ writeFileSync28(destination, file2.bytes, { mode: file2.mode });
122608
+ }
122609
+ const stagedManifest = join73(staging, "skill.json");
122610
+ writeFileSync28(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
122611
+ `);
122612
+ const contentHash = computeContentHash(staging);
122613
+ const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
122614
+ if (changed3 && compareVersions(options.version, previousVersion) <= 0) {
122615
+ throw new Error(`Changed content requires a version greater than ${previousVersion}; pass --version <new-semver>.`);
122616
+ }
122617
+ candidate2.provenance.content_hash = contentHash;
122618
+ const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
122619
+ `;
122620
+ writeFileSync28(stagedManifest, candidateBytes);
122621
+ validateRawCandidate(candidate2);
122622
+ validateRuntimeEntrypoint(candidate2, staging);
122623
+ const validation = validatePortableSkillDirectory(normalized2, staging);
122624
+ if (!validation.valid)
122625
+ throw new Error(`Skill '${normalized2}' cannot be prepared:
122626
+ ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
122627
+ `)}`);
122628
+ assertPackedCanonicalCoverage(staging);
122629
+ if (snapshot2(path).identity !== before2.identity)
122630
+ throw new Error("Skill changed during preparation; review the draft and run prepare again.");
122631
+ if (changed3 && !options.dryRun) {
122632
+ const temporary = join73(path, `.skill-prepare-${randomUUID15()}.tmp`);
122633
+ try {
122634
+ writeFileSync28(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
122635
+ chmodSync5(temporary, manifestFile.mode);
122636
+ renameSync18(temporary, join73(path, "skill.json"));
122637
+ } finally {
122638
+ rmSync10(temporary, { force: true });
122639
+ }
122640
+ }
122641
+ return { name: normalized2, path, previousVersion, version: options.version, kind, contentHash, changed: changed3, written: changed3 && !options.dryRun, warnings: validation.warnings };
122642
+ } finally {
122643
+ rmSync10(staging, { recursive: true, force: true });
122644
+ }
122645
+ }
122646
+ function assertPackedCanonicalCoverage(root2) {
122647
+ const canonicalPaths = new Set(collectBundleFiles(root2).map((file2) => file2.rel));
122648
+ const packedPaths = new Set(collectSkillBundleEntries(root2).map((entry) => entry.path));
122649
+ const omitted = [...canonicalPaths].filter((path) => !packedPaths.has(path)).sort();
122650
+ if (omitted.length === 0)
122651
+ return;
122652
+ const rendered = omitted.slice(0, MAX_PACK_PARITY_PATHS).map(escapedRelativePath).join(", ");
122653
+ const suffix = omitted.length > MAX_PACK_PARITY_PATHS ? ` (+${omitted.length - MAX_PACK_PARITY_PATHS} more)` : "";
122654
+ throw new Error(`Skill cannot be prepared because packSkillBundle excludes canonical source path(s): ${rendered}${suffix}. ` + "Rename or remove the excluded source before preparing; no files were changed.");
122655
+ }
122656
+ function escapedRelativePath(path) {
122657
+ const bounded2 = path.length > MAX_PACK_PARITY_PATH_CHARS - 8 ? `${path.slice(0, MAX_PACK_PARITY_PATH_CHARS - 8)}...` : path;
122658
+ return JSON.stringify(bounded2);
122659
+ }
122660
+ function validateRuntimeEntrypoint(candidate2, root2) {
122661
+ if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
122662
+ return;
122663
+ const entry = candidate2.runtime.entrypoint;
122664
+ if (typeof entry !== "string")
122665
+ return;
122666
+ const target = resolve39(root2, entry), rel = relative15(root2, target);
122667
+ if (isAbsolute43(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep21}`) || isAbsolute43(rel) || !existsSync35(target) || !lstatSync47(target).isFile()) {
122668
+ throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
122669
+ }
122670
+ }
122671
+ function isRecord9(value) {
122672
+ return typeof value === "object" && value !== null && !Array.isArray(value);
122673
+ }
122674
+ function validateRawCandidate(candidate2) {
122675
+ for (const field3 of ["standard", "name", "description", "version"]) {
122676
+ if (typeof candidate2[field3] !== "string" || !candidate2[field3].trim())
122677
+ throw new Error(`skill.json ${field3} is required.`);
122678
+ }
122679
+ for (const field3 of ["displayName", "category"]) {
122680
+ if (candidate2[field3] !== undefined && (typeof candidate2[field3] !== "string" || !candidate2[field3].trim()))
122681
+ throw new Error(`Invalid skill.json ${field3}.`);
122682
+ }
122683
+ if (candidate2.tags !== undefined && (!Array.isArray(candidate2.tags) || candidate2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
122684
+ throw new Error("Invalid skill.json tags.");
122685
+ const issues = validatePortableManifestContract(candidate2, { strict: true });
122686
+ if (issues.length)
122687
+ throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
122688
+ validateRuntimeEntrypointCompatibility(candidate2);
122689
+ for (const field3 of ["inputs", "commands"]) {
122690
+ const items = candidate2[field3];
122691
+ if (items === undefined)
122692
+ continue;
122693
+ if (!Array.isArray(items))
122694
+ throw new Error(`skill.json ${field3} must be an array.`);
122695
+ for (const item of items) {
122696
+ if (!isRecord9(item) || typeof item.name !== "string" || !item.name.trim())
122697
+ throw new Error(`Invalid skill.json ${field3} name.`);
122698
+ if (field3 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
122699
+ throw new Error("Invalid skill.json input type or required flag.");
122700
+ if (field3 === "commands") {
122701
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(item.name))
122702
+ throw new Error("Invalid skill.json command name.");
122703
+ for (const target of ["entry", "command"])
122704
+ if (item[target] !== undefined && (typeof item[target] !== "string" || !item[target].trim()))
122705
+ throw new Error(`Invalid skill.json command ${target}.`);
122706
+ if (!item.entry && !item.command)
122707
+ throw new Error("A skill.json command needs entry or command.");
122708
+ if (item.args !== undefined && (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")))
122709
+ throw new Error("Invalid skill.json command args.");
122710
+ }
122711
+ }
122712
+ }
122713
+ }
122714
+ function validateRuntimeEntrypointCompatibility(candidate2) {
122715
+ if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
122716
+ return;
122717
+ const runtime = candidate2.runtime.runtime;
122718
+ const entrypoint = candidate2.runtime.entrypoint;
122719
+ if (typeof runtime !== "string" || typeof entrypoint !== "string")
122720
+ return;
122721
+ const extension = entrypoint.toLowerCase().split(".").pop();
122722
+ if (extension === "py" && runtime !== "python3") {
122723
+ throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
122724
+ }
122725
+ }
122726
+ function compareVersions(left, right) {
122727
+ const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
122728
+ const [a, ap] = parts(left), [b, bp] = parts(right);
122729
+ const ac = a.split(".").map(BigInt), bc = b.split(".").map(BigInt);
122730
+ for (let i = 0;i < 3; i++)
122731
+ if (ac[i] !== bc[i])
122732
+ return ac[i] > bc[i] ? 1 : -1;
122733
+ if (ap === undefined || bp === undefined)
122734
+ return ap === bp ? 0 : ap === undefined ? 1 : -1;
122735
+ const aa = ap.split("."), bb = bp.split(".");
122736
+ for (let i = 0;i < Math.max(aa.length, bb.length); i++) {
122737
+ const x = aa[i], y = bb[i];
122738
+ if (x === undefined || y === undefined)
122739
+ return x === y ? 0 : x === undefined ? -1 : 1;
122740
+ if (x === y)
122741
+ continue;
122742
+ const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
122743
+ if (xn && yn)
122744
+ return BigInt(x) > BigInt(y) ? 1 : -1;
122745
+ if (xn !== yn)
122746
+ return xn ? -1 : 1;
122747
+ return x > y ? 1 : -1;
122748
+ }
122749
+ return 0;
122750
+ }
122751
+ function snapshot2(root2) {
122752
+ const rootStat = lstatSync47(root2);
122753
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
122754
+ throw new Error("Skill root must be a real directory, not a symlink.");
122755
+ const files = [], directories = [];
122756
+ const hash7 = createHash47("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
122757
+ let count = 0, bytes = 0;
122758
+ const walk2 = (relative16) => {
122759
+ for (const name of readdirSync27(join73(root2, relative16)).sort()) {
122760
+ if (IGNORED.has(name.toLowerCase()) || !relative16 && ROOT_IGNORED.has(name.toLowerCase()))
122761
+ continue;
122762
+ const path = relative16 ? `${relative16}/${name}` : name;
122763
+ if (++count > 20000)
122764
+ throw new Error("Skill exceeds the preparation entry limit (20000).");
122765
+ const absolute = join73(root2, path), stat3 = lstatSync47(absolute);
122766
+ if (stat3.isSymbolicLink())
122767
+ throw new Error(`Skill contains a symlink: ${path}. No files were changed.`);
122768
+ if (stat3.isDirectory() && name === "node_modules")
122769
+ continue;
122770
+ hash7.update(JSON.stringify([path, stat3.dev, stat3.ino, stat3.mode]));
122771
+ if (stat3.isDirectory()) {
122772
+ directories.push(path);
122773
+ walk2(path);
122774
+ } else if (stat3.isFile()) {
122775
+ bytes += stat3.size;
122776
+ if (bytes > 50000000)
122777
+ throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
122778
+ const content = readFileSync45(absolute);
122779
+ if (content.length !== stat3.size)
122780
+ throw new Error("Skill changed during preparation.");
122781
+ files.push({ path, bytes: content, mode: stat3.mode & 511 });
122782
+ hash7.update(`\x00${content.length}\x00`).update(content);
122783
+ } else
122784
+ throw new Error(`Unsupported skill entry: ${path}.`);
122785
+ hash7.update("\x00");
122786
+ }
122787
+ };
122788
+ walk2("");
122789
+ return { files, directories, identity: hash7.digest("hex") };
122790
+ }
122791
+ var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
122792
+ var init_prepare_skill = __esm(() => {
122793
+ init_portable_skills();
122794
+ init_skill_contract();
122795
+ init_skill_hash();
122796
+ init_skill_bundle();
122797
+ init_skill_validation();
122798
+ init_authoring_path();
122799
+ IGNORED = new Set([".git", ".ds_store", ".system"]);
122800
+ ROOT_IGNORED = new Set(["dist", "build", ".turbo", ".skills-dependency-preparation"]);
122801
+ });
122802
+
122803
+ // src/cli/commands/portable-skills.ts
122804
+ var exports_portable_skills = {};
122805
+ __export(exports_portable_skills, {
122806
+ registerPortableSkillCommands: () => registerPortableSkillCommands,
122807
+ AUTHORING_NATIVE_HOOKS_HELP: () => AUTHORING_NATIVE_HOOKS_HELP
122808
+ });
122809
+ function registerPortableSkillCommands(parent) {
122810
+ parent.command("prepare").argument("<name>", "Name of a skill in the local corpus").requiredOption("--version <version>", "Reviewed semantic version for this draft").option("--kind <kind>", "Explicit instruction or executable kind; required for legacy kind-less manifests").option("--dry-run", "Validate the candidate and report its hash without changing the draft", false).option("--json", "Output result as JSON", false).description("Validate a reviewed draft and update its manifest version and content hash").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action(async (name, options) => {
122811
+ try {
122812
+ const result2 = prepareSkill(name, { ...options, kind: options.kind === undefined ? undefined : parseSkillKind2(options.kind) });
122813
+ if (result2.written)
122814
+ clearRegistryCache();
122815
+ if (options.json)
122816
+ await writeCliOutput(JSON.stringify(result2, null, 2));
122817
+ else {
122818
+ console.log(source_default.green(`${result2.written ? "Prepared" : options.dryRun ? "Previewed" : "Already prepared"} ${result2.name}@${result2.version} (${result2.kind})`));
122819
+ console.log(source_default.dim(` Content hash: ${result2.contentHash}`));
122820
+ console.log(source_default.dim(` Manifest: ${result2.path}/skill.json`));
122821
+ if (result2.written)
122822
+ console.log(source_default.dim(` Publish: skills push ${result2.name}`));
122823
+ }
122824
+ } catch (error61) {
122825
+ if (options.json)
122826
+ await writeCliOutput(JSON.stringify({ error: error61.message }, null, 2));
122827
+ else
122828
+ console.error(source_default.red(error61.message));
122829
+ process.exitCode = 1;
122830
+ }
122831
+ });
122832
+ parent.command("new").alias("scaffold").argument("<name>", "Portable skill name").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable (default) or instruction (prose only)", "executable").option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--json", "Output result as JSON", false).description("Scaffold a portable skill under ~/.hasna/skills/installed/<name>").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action((name, options) => {
122833
+ try {
122834
+ const kind = parseSkillKind2(options.kind);
122835
+ const result2 = scaffoldPortableSkill(name, {
122836
+ description: options.description,
122837
+ overwrite: options.overwrite,
122838
+ kind
122839
+ });
122840
+ clearRegistryCache();
122841
+ if (options.json) {
122842
+ console.log(JSON.stringify({ ...result2, manifest: result2.manifest }, null, 2));
122843
+ return;
122844
+ }
122845
+ console.log(source_default.green(`\u2713 Created ${kind} skill '${result2.name}'`));
122846
+ console.log(source_default.dim(` Path: ${result2.path}`));
122847
+ if (kind === "instruction") {
122848
+ console.log(source_default.dim(` Edit the prose in: ${result2.path}/SKILL.md`));
122849
+ console.log(source_default.dim(` Instruction skills are consumed by agents, not run with 'skills run'.`));
122850
+ } else {
122851
+ console.log(source_default.dim(` Agent instructions: ${result2.path}/AGENTS.md`));
122852
+ console.log(source_default.dim(` Validate: skills validate ${result2.name}`));
122853
+ console.log(source_default.dim(` Run: skills run ${result2.name} --help`));
122854
+ }
122855
+ } catch (error61) {
122856
+ writePortableError(error61, options.json);
122857
+ }
122858
+ });
122859
+ parent.command("port").alias("add").argument("<path>", "Existing skill folder (or parent directory with --all) to import").option("--name <name>", "Override the imported skill name").option("--all", "Import every skill subfolder of <path> (bulk import)", false).option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--allow-shadow", "Allow an imported name that shadows a bundled official skill", false).option("--json", "Output result as JSON", false).description("Import an existing skill folder into ~/.hasna/skills/installed/<name>").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action((path, options) => {
122860
+ if (options.all) {
122861
+ handleBulkPort(path, options);
122862
+ return;
122863
+ }
122864
+ try {
122865
+ const result2 = portPortableSkill(path, {
122866
+ name: options.name,
122867
+ overwrite: options.overwrite,
122868
+ allowShadow: options.allowShadow
122869
+ });
122870
+ clearRegistryCache();
122871
+ const validation = validatePortableSkillDirectory(result2.name, result2.path);
122872
+ const payload = { ...result2, valid: validation.valid, issues: validation.issues, warnings: validation.warnings };
122873
+ if (!validation.valid)
122874
+ process.exitCode = 1;
122875
+ if (options.json) {
122876
+ console.log(JSON.stringify(payload, null, 2));
122877
+ return;
122878
+ }
122879
+ console.log(validation.valid ? source_default.green(`\u2713 Ported portable skill '${result2.name}'`) : source_default.yellow(`Validation failed for imported files '${result2.name}'; correct the folder before using it.`));
122880
+ console.log(source_default.dim(` Path: ${result2.path}`));
122881
+ console.log(source_default.dim(` Valid: ${validation.valid ? "yes" : "no"}`));
122882
+ if (!validation.valid) {
122883
+ for (const issue2 of validation.issues)
122884
+ console.log(source_default.red(` \u2022 ${issue2.message}`));
122885
+ }
122886
+ } catch (error61) {
122887
+ writePortableError(error61, options.json);
122888
+ }
122889
+ });
122890
+ }
122891
+ function handleBulkPort(path, options) {
122892
+ if (options.name) {
122893
+ writePortableError(new Error("--name cannot be used with --all"), options.json);
122894
+ return;
122895
+ }
122896
+ try {
122897
+ const summary = portPortableSkillDirectory(path, { overwrite: options.overwrite, allowShadow: options.allowShadow });
122898
+ clearRegistryCache();
122899
+ if (options.json) {
122900
+ console.log(JSON.stringify(summary, null, 2));
122901
+ if (summary.failed > 0)
122902
+ process.exitCode = 1;
122903
+ return;
122904
+ }
122905
+ console.log(source_default.green(`\u2713 Imported ${summary.succeeded}/${summary.total} skill(s) from ${summary.root}`));
122906
+ for (const entry of summary.imported)
122907
+ console.log(source_default.dim(` \u2022 ${entry.name} \u2192 ${entry.path}`));
122908
+ if (summary.skipped.length > 0) {
122909
+ console.log(source_default.yellow(` Skipped ${summary.skipped.length}:`));
122910
+ for (const entry of summary.skipped)
122911
+ console.log(source_default.yellow(` \u2022 ${entry.sourcePath}: ${entry.reason}`));
122912
+ process.exitCode = 1;
122913
+ }
122914
+ } catch (error61) {
122915
+ writePortableError(error61, options.json);
122916
+ }
122917
+ }
122918
+ function parseSkillKind2(value) {
122919
+ if (value === undefined || value === "executable")
122920
+ return "executable";
122921
+ if (value === "instruction")
122922
+ return "instruction";
122923
+ throw new Error(`Invalid --kind '${value}'. Use 'executable' or 'instruction'.`);
122924
+ }
122925
+ function writePortableError(error61, json2) {
122926
+ const message = error61.message;
122927
+ if (json2)
122928
+ console.log(JSON.stringify({ error: message }, null, 2));
122929
+ else
122930
+ console.error(source_default.red(message));
122931
+ process.exitCode = 1;
122932
+ }
122933
+ var AUTHORING_NATIVE_HOOKS_HELP = `
122934
+ Native hooks:
122935
+ Authoring refuses while another agent's native hooks, plugin hooks or plugin
122936
+ options are not covered by a current discovery review, because they could
122937
+ load skills from the authoring corpus. The refusal names the agent and the
122938
+ file it checked; review that agent's configuration with
122939
+ skills hook install --agent <agent> --discovery-inputs <reviewed-file> --apply
122940
+ and retry. Authoring commands themselves take no --discovery-inputs option.
122941
+ `;
122942
+ var init_portable_skills2 = __esm(() => {
122943
+ init_source();
122944
+ init_registry();
122945
+ init_prepare_skill();
122946
+ init_portable_skills();
122947
+ });
122948
+
122241
122949
  // src/lib/portable-snapshot-filter.ts
122242
- import { readdirSync as readdirSync27, statSync as statSync23 } from "fs";
122950
+ import { readdirSync as readdirSync28, statSync as statSync23 } from "fs";
122243
122951
  import { homedir as homedir12 } from "os";
122244
- import { join as join73, sep as sep21 } from "path";
122952
+ import { join as join74, sep as sep22 } from "path";
122245
122953
  function isExcludedSkillFileName(fileName) {
122246
122954
  if (EXCLUDE_FILE_NAMES.has(fileName)) {
122247
122955
  return true;
@@ -122264,27 +122972,27 @@ function isPortableWithinSkill(relativeParts) {
122264
122972
  function homePathFor(definition, homesRoot) {
122265
122973
  const home = homesRoot ?? homedir12();
122266
122974
  if (definition.subClass === "skills" || definition.subClass === "custom") {
122267
- return join73(skillsDataRootForHome(home), definition.name);
122975
+ return join74(skillsDataRootForHome(home), definition.name);
122268
122976
  }
122269
122977
  if (definition.agent === "opencode") {
122270
- return join73(home, ".config", "opencode", "skills");
122978
+ return join74(home, ".config", "opencode", "skills");
122271
122979
  }
122272
- return join73(home, `.${definition.agent}`, "skills");
122980
+ return join74(home, `.${definition.agent}`, "skills");
122273
122981
  }
122274
122982
  function destinationFor(definition, stationId, relativePath) {
122275
- const category = definition.subClass === "agent-homes" ? join73("agent-homes", definition.agent ?? "") : definition.name;
122276
- return join73("resources", stationId, "skills", category, ...relativePath.split(sep21));
122983
+ const category = definition.subClass === "agent-homes" ? join74("agent-homes", definition.agent ?? "") : definition.name;
122984
+ return join74("resources", stationId, "skills", category, ...relativePath.split(sep22));
122277
122985
  }
122278
122986
  function walkEntries(absoluteRoot) {
122279
122987
  let entries;
122280
122988
  try {
122281
- entries = readdirSync27(absoluteRoot, { withFileTypes: true });
122989
+ entries = readdirSync28(absoluteRoot, { withFileTypes: true });
122282
122990
  } catch {
122283
122991
  return [];
122284
122992
  }
122285
122993
  const output2 = [];
122286
122994
  for (const entry of entries) {
122287
- const childFull = join73(absoluteRoot, entry.name);
122995
+ const childFull = join74(absoluteRoot, entry.name);
122288
122996
  if (entry.isSymbolicLink()) {
122289
122997
  output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
122290
122998
  continue;
@@ -122295,7 +123003,7 @@ function walkEntries(absoluteRoot) {
122295
123003
  }
122296
123004
  const nested = walkEntries(childFull);
122297
123005
  for (const item of nested) {
122298
- output2.push({ ...item, relativePath: join73(entry.name, item.relativePath) });
123006
+ output2.push({ ...item, relativePath: join74(entry.name, item.relativePath) });
122299
123007
  }
122300
123008
  continue;
122301
123009
  }
@@ -122388,22 +123096,22 @@ var init_portable_snapshot_filter = __esm(() => {
122388
123096
  });
122389
123097
 
122390
123098
  // src/lib/station-snapshot.ts
122391
- import { createHash as createHash47 } from "crypto";
123099
+ import { createHash as createHash48 } from "crypto";
122392
123100
  import {
122393
123101
  copyFileSync as copyFileSync2,
122394
- mkdirSync as mkdirSync30,
122395
- readFileSync as readFileSync45,
123102
+ mkdirSync as mkdirSync31,
123103
+ readFileSync as readFileSync46,
122396
123104
  statSync as statSync24,
122397
- writeFileSync as writeFileSync28
123105
+ writeFileSync as writeFileSync29
122398
123106
  } from "fs";
122399
- import { dirname as dirname47, isAbsolute as isAbsolute43, relative as relative15, resolve as resolve39, sep as sep22 } from "path";
123107
+ import { dirname as dirname48, isAbsolute as isAbsolute44, relative as relative16, resolve as resolve40, sep as sep23 } from "path";
122400
123108
  function validateStationId(stationId) {
122401
123109
  if (!/^[a-z0-9-]+$/.test(stationId)) {
122402
123110
  throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
122403
123111
  }
122404
123112
  }
122405
123113
  function sha256File(filePath) {
122406
- return createHash47("sha256").update(readFileSync45(filePath)).digest("hex");
123114
+ return createHash48("sha256").update(readFileSync46(filePath)).digest("hex");
122407
123115
  }
122408
123116
  function scanHome(definition, homesRoot) {
122409
123117
  const homePath = homePathFor(definition, homesRoot);
@@ -122411,7 +123119,7 @@ function scanHome(definition, homesRoot) {
122411
123119
  const portable = [];
122412
123120
  const skipped = [];
122413
123121
  for (const entry of entries) {
122414
- const relativeParts = entry.relativePath.split(sep22);
123122
+ const relativeParts = entry.relativePath.split(sep23);
122415
123123
  const fileName = relativeParts[relativeParts.length - 1];
122416
123124
  if (entry.kind === "symlink") {
122417
123125
  skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
@@ -122474,7 +123182,7 @@ function humanHomes(scanned) {
122474
123182
  }));
122475
123183
  }
122476
123184
  function writeStationSnapshot(options) {
122477
- const repoRoot = resolve39(options.repoRoot ?? process.cwd());
123185
+ const repoRoot = resolve40(options.repoRoot ?? process.cwd());
122478
123186
  const { scanned, plans, totalBytes } = planStationSnapshot(options);
122479
123187
  const manifestFiles = plans.map((plan) => ({
122480
123188
  relativePath: plan.source.relativePath,
@@ -122500,9 +123208,9 @@ function writeStationSnapshot(options) {
122500
123208
  const conflicts = [];
122501
123209
  const untouched = [];
122502
123210
  for (const plan of plans) {
122503
- const destination = resolve39(repoRoot, plan.destination);
122504
- const destinationRelative = relative15(repoRoot, destination);
122505
- if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep22) || isAbsolute43(destinationRelative)) {
123211
+ const destination = resolve40(repoRoot, plan.destination);
123212
+ const destinationRelative = relative16(repoRoot, destination);
123213
+ if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep23) || isAbsolute44(destinationRelative)) {
122506
123214
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
122507
123215
  }
122508
123216
  let existingDigest = null;
@@ -122523,8 +123231,8 @@ function writeStationSnapshot(options) {
122523
123231
  }
122524
123232
  let written = 0;
122525
123233
  for (const plan of untouched) {
122526
- const destination = resolve39(repoRoot, plan.destination);
122527
- mkdirSync30(dirname47(destination), { recursive: true });
123234
+ const destination = resolve40(repoRoot, plan.destination);
123235
+ mkdirSync31(dirname48(destination), { recursive: true });
122528
123236
  copyFileSync2(plan.source.fullPath, destination);
122529
123237
  written += 1;
122530
123238
  }
@@ -122542,9 +123250,9 @@ function writeStationSnapshot(options) {
122542
123250
  },
122543
123251
  files: manifestFiles
122544
123252
  };
122545
- const manifestPath = resolve39(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
122546
- mkdirSync30(dirname47(manifestPath), { recursive: true });
122547
- writeFileSync28(manifestPath, `${JSON.stringify(manifest, null, 2)}
123253
+ const manifestPath = resolve40(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
123254
+ mkdirSync31(dirname48(manifestPath), { recursive: true });
123255
+ writeFileSync29(manifestPath, `${JSON.stringify(manifest, null, 2)}
122548
123256
  `);
122549
123257
  return {
122550
123258
  ...base3,
@@ -122575,8 +123283,8 @@ var exports_create_sync_config = {};
122575
123283
  __export(exports_create_sync_config, {
122576
123284
  registerCreateSync: () => registerCreateSync
122577
123285
  });
122578
- import { existsSync as existsSync35 } from "fs";
122579
- import { join as join74 } from "path";
123286
+ import { existsSync as existsSync36 } from "fs";
123287
+ import { join as join75 } from "path";
122580
123288
  function registerCreateSync(parent) {
122581
123289
  const configCmd = parent.command("config").description("Manage skills configuration");
122582
123290
  configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
@@ -122656,15 +123364,15 @@ function registerCreateSync(parent) {
122656
123364
  const pp = getConfigPath("project");
122657
123365
  if (options.json) {
122658
123366
  console.log(JSON.stringify({
122659
- global: { path: gp, exists: existsSync35(gp) },
122660
- project: { path: pp, exists: existsSync35(pp) }
123367
+ global: { path: gp, exists: existsSync36(gp) },
123368
+ project: { path: pp, exists: existsSync36(pp) }
122661
123369
  }, null, 2));
122662
123370
  return;
122663
123371
  }
122664
- console.log(`${source_default.cyan("global")}: ${gp}${existsSync35(gp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
122665
- console.log(`${source_default.cyan("project")}: ${pp}${existsSync35(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
123372
+ console.log(`${source_default.cyan("global")}: ${gp}${existsSync36(gp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
123373
+ console.log(`${source_default.cyan("project")}: ${pp}${existsSync36(pp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
122666
123374
  });
122667
- parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable or instruction", "executable").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").action((name, options) => handleCreate(name, options));
123375
+ parent.command("create").argument("<name>", "Skill name (e.g. my-tool)").option("--category <category>", "Skill category", "Development Tools").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable or instruction", "executable").option("--tags <tags>", "Comma-separated tags (e.g. api,testing,automation)").option("--global", "Deprecated; custom skills are always global", false).option("--json", "Output result as JSON", false).description("Scaffold a new custom skill directory").addHelpText("after", AUTHORING_NATIVE_HOOKS_HELP).action((name, options) => handleCreate(name, options));
122668
123376
  parent.command("sync").alias("render").option("--selection-profile <id>", "Consume the exact versions selected by an API profile").option("--profile <id>", "Selection profile (alias for --selection-profile; use before sync for credential profile)").option("--project", "Record exact selected versions in this project's lockfile", false).option("--no-station-report", "Verify and activate the selected profile without reporting station state").argument("[names...]", "Skills to sync (default: every skill in this machine's corpus)").option("--for <agent>", `Target one agent (${SYNC_AGENTS.join(", ")}, or all)`, "all").option("--all", "Sync every corpus skill (the default)", false).option("--source <path>", "Canonical corpus source: a directory of skill folders, or a package root with skills/ (overrides $SKILLS_SOURCE)").option("--dry-run", "Show what would be written without touching any agent folder", false).option("--force", "Adopt an unmanaged skill that already has SKILL.md; other unmarked directories are never overwritten", false).option("--check", "Home drift census (missing-from-home / stray-in-home / diverged); exits non-zero on drift, writes nothing", false).option("--adopt", "Unmarked-home adoption mode: hash unmarked home skills against the corpus; exact matches are marked (dry-run by default)", false).option("--prune", "Prune mode: list (or with --apply, remove) marked home skill dirs that have no canonical corpus entry", false).option("--apply", "Write adoption markers / conflicts ledger, or perform prune removals", false).option("--json", "Output as JSON", false).option("--station <id>", "Per-station snapshot mode: snapshot the installed skill homes into resources/<station>/skills with a v3 sync-manifest (dry-run by default; --populate writes)").option("--populate", "Write the per-station snapshot (station mode; the default is dry-run)", false).option("--repo-root <path>", "Station snapshot destination repo root (default: cwd)").option("--homes-root <dir>", "Build the station snapshot from a staged mirror of the skill homes instead of this machine's $HOME").description("Sync an API selection profile into the Skills cache; native migration flags remain available for older installations").action((names, options) => handleSync(names, options));
122669
123377
  }
122670
123378
  function handleCreate(name, options) {
@@ -122686,7 +123394,7 @@ function handleCreate(name, options) {
122686
123394
  console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
122687
123395
  console.log(source_default.dim(` Category: ${result2.manifest.category}`));
122688
123396
  console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
122689
- console.log(` ${source_default.cyan("Edit:")} ${join74(result2.path, options.kind === "instruction" ? "SKILL.md" : "src/index.ts")}`);
123397
+ console.log(` ${source_default.cyan("Edit:")} ${join75(result2.path, options.kind === "instruction" ? "SKILL.md" : "src/index.ts")}`);
122690
123398
  console.log(` ${source_default.cyan("Prepare:")} skills prepare ${result2.name} --version <new-semver>`);
122691
123399
  }
122692
123400
  } catch (error61) {
@@ -122965,6 +123673,7 @@ var init_create_sync_config = __esm(() => {
122965
123673
  init_config();
122966
123674
  init_auth_store();
122967
123675
  init_portable_skills();
123676
+ init_portable_skills2();
122968
123677
  init_registry();
122969
123678
  init_profile_sync();
122970
123679
  init_managed_policy();
@@ -122975,9 +123684,9 @@ var init_create_sync_config = __esm(() => {
122975
123684
  });
122976
123685
 
122977
123686
  // src/lib/claude-hook-disable.ts
122978
- import { createHash as createHash48, randomUUID as randomUUID15 } from "crypto";
122979
- import { constants as constants32, closeSync as closeSync32, fchownSync as fchownSync2, fchmodSync as fchmodSync4, fsyncSync as fsyncSync11, lstatSync as lstatSync47, mkdirSync as mkdirSync31, openSync as openSync32, realpathSync as realpathSync21 } from "fs";
122980
- import { basename as basename25, dirname as dirname48, isAbsolute as isAbsolute44, join as join75, resolve as resolve40 } from "path";
123687
+ import { createHash as createHash49, randomUUID as randomUUID16 } from "crypto";
123688
+ import { constants as constants32, closeSync as closeSync32, fchownSync as fchownSync2, fchmodSync as fchmodSync4, fsyncSync as fsyncSync11, lstatSync as lstatSync48, mkdirSync as mkdirSync32, openSync as openSync32, realpathSync as realpathSync21 } from "fs";
123689
+ import { basename as basename25, dirname as dirname49, isAbsolute as isAbsolute45, join as join76, resolve as resolve41 } from "path";
122981
123690
  import { KernelLock as KernelLock2 } from "@hasna/contracts/kernel-lock";
122982
123691
  function fail6(reason2) {
122983
123692
  throw new Error(`CLAUDE_HOOK_DISABLE_${reason2}`);
@@ -122990,26 +123699,26 @@ function serialize(value) {
122990
123699
  return value.kind === "string" ? JSON.stringify(value.value) : value.value;
122991
123700
  }
122992
123701
  function path(value) {
122993
- if (!isAbsolute44(value) || resolve40(value) !== value || /[\x00-\x1f\x7f]/.test(value))
123702
+ if (!isAbsolute45(value) || resolve41(value) !== value || /[\x00-\x1f\x7f]/.test(value))
122994
123703
  fail6("PATH_INVALID");
122995
123704
  }
122996
123705
  function parents3(file2) {
122997
123706
  safeParents(file2);
122998
123707
  const observed = [];
122999
- for (let directory3 = dirname48(file2);; directory3 = dirname48(directory3)) {
123000
- const stat3 = lstatSync47(directory3, { bigint: true });
123001
- assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname48(file2) });
123708
+ for (let directory3 = dirname49(file2);; directory3 = dirname49(directory3)) {
123709
+ const stat3 = lstatSync48(directory3, { bigint: true });
123710
+ assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname49(file2) });
123002
123711
  observed.push([directory3, identity6(stat3)]);
123003
- if (directory3 === dirname48(directory3))
123712
+ if (directory3 === dirname49(directory3))
123004
123713
  break;
123005
123714
  }
123006
123715
  return () => {
123007
123716
  safeParents(file2);
123008
123717
  for (const [directory3, expected] of observed) {
123009
- const stat3 = lstatSync47(directory3, { bigint: true });
123718
+ const stat3 = lstatSync48(directory3, { bigint: true });
123010
123719
  if (identity6(stat3) !== expected)
123011
123720
  fail6("PARENT_CHANGED");
123012
- assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname48(file2) });
123721
+ assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname49(file2) });
123013
123722
  }
123014
123723
  };
123015
123724
  }
@@ -123017,24 +123726,24 @@ function executable2(command, expectedSha256) {
123017
123726
  path(command);
123018
123727
  if (!digest6(expectedSha256))
123019
123728
  fail6("COMMAND_DIGEST_INVALID");
123020
- const parentCheck = parents3(command), alias = lstatSync47(command, { bigint: true });
123729
+ const parentCheck = parents3(command), alias = lstatSync48(command, { bigint: true });
123021
123730
  const resolved = resolveLauncherCommand(command);
123022
123731
  if (alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
123023
123732
  fail6("COMMAND_UNVERIFIED");
123024
123733
  const commandFile = snapshot(resolved.physical, false, { readOnlyPackage: true });
123025
123734
  if (commandFile.sha256 !== expectedSha256)
123026
123735
  fail6("COMMAND_CHANGED");
123027
- const entry = resolved.entry, packageRoot = dirname48(dirname48(entry));
123028
- if (entry !== join75(packageRoot, "bin", "index.js") || realpathSync21(entry) !== entry)
123736
+ const entry = resolved.entry, packageRoot = dirname49(dirname49(entry));
123737
+ if (entry !== join76(packageRoot, "bin", "index.js") || realpathSync21(entry) !== entry)
123029
123738
  fail6("COMMAND_UNVERIFIED");
123030
123739
  const entryFile = snapshot(entry, false, { readOnlyPackage: true });
123031
- const manifest = snapshot(join75(packageRoot, "package.json"), false, { readOnlyPackage: true });
123740
+ const manifest = snapshot(join76(packageRoot, "package.json"), false, { readOnlyPackage: true });
123032
123741
  const parsed = parseNativeJsonObject(manifest.text);
123033
123742
  if (string6(field3(parsed, "name")) !== "@hasna/skills" || string6(field3(field3(parsed, "bin"), "skills")) !== "bin/index.js")
123034
123743
  fail6("COMMAND_UNVERIFIED");
123035
123744
  return () => {
123036
123745
  parentCheck();
123037
- const current = lstatSync47(command, { bigint: true });
123746
+ const current = lstatSync48(command, { bigint: true });
123038
123747
  if (movedIdentity(current) !== movedIdentity(alias) || current.ctimeNs !== alias.ctimeNs || alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
123039
123748
  fail6("COMMAND_CHANGED");
123040
123749
  unchanged2(commandFile);
@@ -123087,13 +123796,13 @@ function disableClaudeHooks(options) {
123087
123796
  removed: [],
123088
123797
  settingsWitness: "unchanged"
123089
123798
  };
123090
- const exists = lstatSync47(options.settings, { throwIfNoEntry: false });
123799
+ const exists = lstatSync48(options.settings, { throwIfNoEntry: false });
123091
123800
  if (!exists) {
123092
123801
  if (options.expectedSettingsSha256 !== "absent" || options.expectedMode !== "absent")
123093
123802
  fail6("TARGET_CHANGED");
123094
123803
  checkCommand();
123095
123804
  checkParents();
123096
- if (lstatSync47(options.settings, { throwIfNoEntry: false }))
123805
+ if (lstatSync48(options.settings, { throwIfNoEntry: false }))
123097
123806
  fail6("TARGET_CHANGED");
123098
123807
  return base3;
123099
123808
  }
@@ -123156,7 +123865,7 @@ function disableClaudeHooks(options) {
123156
123865
  if (options.preimageDir === options.settings || options.preimageDir.startsWith(options.settings + "/"))
123157
123866
  fail6("PREIMAGE_INVALID");
123158
123867
  const checkPreimageParents = parents3(options.preimageDir);
123159
- const lock = new KernelLock2(dirname48(options.settings), "skills-claude-hook-disable", { mode: "exclusive" });
123868
+ const lock = new KernelLock2(dirname49(options.settings), "skills-claude-hook-disable", { mode: "exclusive" });
123160
123869
  let exchanged = false, preimage, displaced, receipt;
123161
123870
  try {
123162
123871
  if (!lock.trySync(1000))
@@ -123166,15 +123875,15 @@ function disableClaudeHooks(options) {
123166
123875
  checkParents();
123167
123876
  checkPreimageParents();
123168
123877
  unchanged2(before2);
123169
- mkdirSync31(options.preimageDir, { mode: 448 });
123170
- if ((lstatSync47(options.preimageDir).mode & 511) !== 448)
123878
+ mkdirSync32(options.preimageDir, { mode: 448 });
123879
+ if ((lstatSync48(options.preimageDir).mode & 511) !== 448)
123171
123880
  fail6("PREIMAGE_INVALID");
123172
- preimage = join75(options.preimageDir, "settings.preimage.json");
123881
+ preimage = join76(options.preimageDir, "settings.preimage.json");
123173
123882
  save(preimage, before2.bytes);
123174
123883
  const preserved = snapshot(preimage, true), checkPreservationRoot = parents3(preimage);
123175
123884
  if (preserved.sha256 !== before2.sha256 || !preserved.bytes.equals(before2.bytes))
123176
123885
  fail6("PRESERVATION_FAILED");
123177
- displaced = join75(dirname48(options.settings), `.settings.skills-disable-${randomUUID15()}.json`);
123886
+ displaced = join76(dirname49(options.settings), `.settings.skills-disable-${randomUUID16()}.json`);
123178
123887
  save(displaced, after2);
123179
123888
  const fd = openSync32(displaced, constants32.O_RDONLY | constants32.O_NOFOLLOW);
123180
123889
  try {
@@ -123188,7 +123897,7 @@ function disableClaudeHooks(options) {
123188
123897
  assertNativePathAcl(displaced, staged.stat);
123189
123898
  if (staged.sha256 !== result2.plannedAfterSha256)
123190
123899
  fail6("STAGING_CHANGED");
123191
- receipt = join75(options.preimageDir, "receipt.json");
123900
+ receipt = join76(options.preimageDir, "receipt.json");
123192
123901
  const preparationBytes = `${JSON.stringify({
123193
123902
  ...result2,
123194
123903
  state: "prepared",
@@ -123224,14 +123933,14 @@ function disableClaudeHooks(options) {
123224
123933
  unchanged2(preserved);
123225
123934
  if (movedIdentity(displacedOriginal.stat) !== movedIdentity(before2.stat) || displacedOriginal.sha256 !== before2.sha256 || movedIdentity(current.stat) !== movedIdentity(staged.stat) || current.sha256 !== staged.sha256)
123226
123935
  fail6("POST_WRITE_CHANGED");
123227
- const directory3 = openSync32(dirname48(options.settings), constants32.O_RDONLY | constants32.O_DIRECTORY);
123936
+ const directory3 = openSync32(dirname49(options.settings), constants32.O_RDONLY | constants32.O_DIRECTORY);
123228
123937
  try {
123229
123938
  fsyncSync11(directory3);
123230
123939
  } finally {
123231
123940
  closeSync32(directory3);
123232
123941
  }
123233
123942
  const applied = { ...result2, state: "applied", mutationOccurred: true, afterSha256: staged.sha256, preimage, displaced, receipt };
123234
- const completed = join75(options.preimageDir, "completed.json");
123943
+ const completed = join76(options.preimageDir, "completed.json");
123235
123944
  const completionBytes = `${JSON.stringify({ ...applied, receipt: completed })}
123236
123945
  `;
123237
123946
  save(completed, completionBytes);
@@ -123263,7 +123972,7 @@ function disableClaudeHooks(options) {
123263
123972
  }
123264
123973
  }
123265
123974
  }
123266
- var hash7 = (text5) => createHash48("sha256").update(text5).digest("hex"), digest6 = (value) => /^[a-f0-9]{64}$/.test(value), events3, object12 = (value) => value?.kind === "object" ? value : undefined, field3 = (value, key2) => object12(value)?.entries.find(([name]) => name === key2)?.[1], string6 = (value) => value?.kind === "string" ? value.value : undefined, identity6 = (stat3) => `${stat3.dev}:${stat3.ino}:${stat3.uid}:${stat3.gid}:${stat3.mode}`, movedIdentity = (stat3) => `${identity6(stat3)}:${stat3.nlink}:${stat3.size}:${stat3.mtimeNs}`;
123975
+ var hash7 = (text5) => createHash49("sha256").update(text5).digest("hex"), digest6 = (value) => /^[a-f0-9]{64}$/.test(value), events3, object12 = (value) => value?.kind === "object" ? value : undefined, field3 = (value, key2) => object12(value)?.entries.find(([name]) => name === key2)?.[1], string6 = (value) => value?.kind === "string" ? value.value : undefined, identity6 = (stat3) => `${stat3.dev}:${stat3.ino}:${stat3.uid}:${stat3.gid}:${stat3.mode}`, movedIdentity = (stat3) => `${identity6(stat3)}:${stat3.nlink}:${stat3.size}:${stat3.mtimeNs}`;
123267
123976
  var init_claude_hook_disable = __esm(() => {
123268
123977
  init_agent_adapters();
123269
123978
  init_claude_settings_witness();
@@ -123274,8 +123983,8 @@ var init_claude_hook_disable = __esm(() => {
123274
123983
  });
123275
123984
 
123276
123985
  // src/lib/agent-install-selection.ts
123277
- import { lstatSync as lstatSync48 } from "fs";
123278
- import { dirname as dirname49, join as join76, resolve as resolve41 } from "path";
123986
+ import { lstatSync as lstatSync49 } from "fs";
123987
+ import { dirname as dirname50, join as join77, resolve as resolve42 } from "path";
123279
123988
  import { homedir as homedir13 } from "os";
123280
123989
  function detectedIntegrationAgents(options = {}) {
123281
123990
  const home = options.home ?? homedir13(), env3 = options.env ?? process.env;
@@ -123288,7 +123997,7 @@ function detectedIntegrationAgents(options = {}) {
123288
123997
  throw new Error("Invalid existing bridge agent inventory");
123289
123998
  const present2 = (path2) => {
123290
123999
  try {
123291
- return lstatSync48(path2, { throwIfNoEntry: false }) !== undefined;
124000
+ return lstatSync49(path2, { throwIfNoEntry: false }) !== undefined;
123292
124001
  } catch {
123293
124002
  throw new Error("Agent presence cannot be inspected; review native configuration before hook installation");
123294
124003
  }
@@ -123298,13 +124007,13 @@ function detectedIntegrationAgents(options = {}) {
123298
124007
  return true;
123299
124008
  if (agent === "sumi") {
123300
124009
  const xdg = env3.XDG_CONFIG_HOME;
123301
- const xdgSumi = xdg?.trim() ? resolve41(options.cwd ?? process.cwd(), xdg === "~" ? home : xdg.startsWith("~/") || xdg.startsWith("~\\") ? resolve41(home, xdg.slice(2)) : xdg, "sumi") : null;
123302
- return ["SUMI_HOME", "SUMI_CONFIG_DIR", "SUMI_CONFIG", "SUMI_CONFIG_CONTENT"].some((key2) => env3[key2] !== undefined) || Bun.which("sumi-paths", { PATH: env3.PATH ?? "" }) !== null || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === "sumi" && present2(join76(home, dirname49(root2)))) || present2(join76(home, ".config", "sumi")) || xdgSumi !== null && present2(xdgSumi);
124010
+ const xdgSumi = xdg?.trim() ? resolve42(options.cwd ?? process.cwd(), xdg === "~" ? home : xdg.startsWith("~/") || xdg.startsWith("~\\") ? resolve42(home, xdg.slice(2)) : xdg, "sumi") : null;
124011
+ return ["SUMI_HOME", "SUMI_CONFIG_DIR", "SUMI_CONFIG", "SUMI_CONFIG_CONTENT"].some((key2) => env3[key2] !== undefined) || Bun.which("sumi-paths", { PATH: env3.PATH ?? "" }) !== null || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === "sumi" && present2(join77(home, dirname50(root2)))) || present2(join77(home, ".config", "sumi")) || xdgSumi !== null && present2(xdgSumi);
123303
124012
  }
123304
124013
  if (agent === "hermes" && env3.HERMES_HOME !== undefined)
123305
124014
  return true;
123306
124015
  const adapter = AGENT_ADAPTERS[agent];
123307
- return present2(join76(home, dirname49(adapter.config))) || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === agent && dirname49(root2) === `.${agent}` && present2(join76(home, dirname49(root2))));
124016
+ return present2(join77(home, dirname50(adapter.config))) || NATIVE_SKILL_ROOTS.some(([provider, root2]) => provider === agent && dirname50(root2) === `.${agent}` && present2(join77(home, dirname50(root2))));
123308
124017
  });
123309
124018
  if (!selected.length && !options.allowEmpty)
123310
124019
  throw new Error(`No configured or detected agents; select --agent <agent> explicitly (${INTEGRATION_AGENTS.join(", ")}).`);
@@ -123318,11 +124027,11 @@ var init_agent_install_selection = __esm(() => {
123318
124027
  });
123319
124028
 
123320
124029
  // src/lib/codex-hook-trust-identity.ts
123321
- import { lstatSync as lstatSync49, realpathSync as realpathSync22 } from "fs";
123322
- import { dirname as dirname50, isAbsolute as isAbsolute45, join as join77, resolve as resolve42 } from "path";
124030
+ import { lstatSync as lstatSync50, realpathSync as realpathSync22 } from "fs";
124031
+ import { dirname as dirname51, isAbsolute as isAbsolute46, join as join78, resolve as resolve43 } from "path";
123323
124032
  import { isDeepStrictEqual as isDeepStrictEqual20 } from "util";
123324
124033
  function commandPhysical(command) {
123325
- need8(typeof command === "string" && isAbsolute45(command) && !command.includes("\x00") && resolve42(command) === command, COMMAND_NOT_CANONICAL);
124034
+ need8(typeof command === "string" && isAbsolute46(command) && !command.includes("\x00") && resolve43(command) === command, COMMAND_NOT_CANONICAL);
123326
124035
  const physical = realpathSync22(command);
123327
124036
  let spelled;
123328
124037
  try {
@@ -123333,7 +124042,7 @@ function commandPhysical(command) {
123333
124042
  safeParents(spelled);
123334
124043
  let direct = false;
123335
124044
  try {
123336
- const leaf = lstatSync49(spelled);
124045
+ const leaf = lstatSync50(spelled);
123337
124046
  direct = leaf.isSymbolicLink() ? trustedAncestorOwner(leaf.uid) && directAliasTarget(spelled) === physical : spelled === physical;
123338
124047
  } catch {}
123339
124048
  need8(direct, COMMAND_ALIAS_UNSAFE);
@@ -123345,7 +124054,7 @@ function assertPinnedRuntime(runtime) {
123345
124054
  } catch {
123346
124055
  need8(false, "SKILLS_LAUNCHER_UNVERIFIED");
123347
124056
  }
123348
- need8(resolve42(runtime) === runtime, "SKILLS_LAUNCHER_UNVERIFIED");
124057
+ need8(resolve43(runtime) === runtime, "SKILLS_LAUNCHER_UNVERIFIED");
123349
124058
  safeParents(runtime);
123350
124059
  }
123351
124060
  function skillsCommandTarget(command) {
@@ -123377,12 +124086,12 @@ function targetUnchanged(command, target) {
123377
124086
  assertPinnedRuntime(target.runtime);
123378
124087
  }
123379
124088
  function inspectRecordedSkillsCli(command, expected) {
123380
- need8(isAbsolute45(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
124089
+ need8(isAbsolute46(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
123381
124090
  const target = skillsCommandTarget(command), resolved = target.entry;
123382
124091
  const cli = snapshot(resolved, false, { readOnlyPackage: true });
123383
- const manifest = snapshot(join77(dirname50(dirname50(resolved)), "package.json"), false, { readOnlyPackage: true });
124092
+ const manifest = snapshot(join78(dirname51(dirname51(resolved)), "package.json"), false, { readOnlyPackage: true });
123384
124093
  const pkg = JSON.parse(manifest.text);
123385
- need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join77(dirname50(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
124094
+ need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join78(dirname51(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
123386
124095
  need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
123387
124096
  const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
123388
124097
  need8(isDeepStrictEqual20(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
@@ -123396,16 +124105,16 @@ function bindSkillsCli(command, reviewed) {
123396
124105
  const normal = Bun.which("skills", { PATH: process.env.PATH });
123397
124106
  need8(normal, "SKILLS_COMMAND_UNAVAILABLE");
123398
124107
  const normalTarget = skillsCommandTarget(normal), resolved = normalTarget.entry;
123399
- const bound = isAbsolute45(command) ? command : command === "skills" ? normal : undefined;
124108
+ const bound = isAbsolute46(command) ? command : command === "skills" ? normal : undefined;
123400
124109
  need8(bound, "SKILLS_COMMAND_MISMATCH");
123401
124110
  const boundTarget = skillsCommandTarget(bound);
123402
124111
  need8(boundTarget.entry === resolved, "SKILLS_COMMAND_MISMATCH");
123403
124112
  const entrypoint = reviewed?.path ?? process.argv[1];
123404
124113
  need8(entrypoint && realpathSync22(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
123405
124114
  const cli = snapshot(resolved, false, { readOnlyPackage: true });
123406
- const manifest = snapshot(join77(dirname50(dirname50(resolved)), "package.json"), false, { readOnlyPackage: true });
124115
+ const manifest = snapshot(join78(dirname51(dirname51(resolved)), "package.json"), false, { readOnlyPackage: true });
123407
124116
  const pkg = JSON.parse(manifest.text);
123408
- need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join77(dirname50(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
124117
+ need8(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join78(dirname51(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
123409
124118
  if (reviewed)
123410
124119
  need8(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
123411
124120
  need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
@@ -123424,7 +124133,7 @@ function captureSkillsCliProducerIdentity(command) {
123424
124133
  const bound = bindSkillsCli(command);
123425
124134
  const target = skillsCommandTarget(command);
123426
124135
  const cli = snapshot(target.entry, false, { readOnlyPackage: true });
123427
- const manifest = snapshot(join77(dirname50(dirname50(target.entry)), "package.json"), false, { readOnlyPackage: true });
124136
+ const manifest = snapshot(join78(dirname51(dirname51(target.entry)), "package.json"), false, { readOnlyPackage: true });
123428
124137
  const pkg = JSON.parse(manifest.text);
123429
124138
  need8(pkg.name === "@hasna/skills" && pkg.version === bound.receipt.version && target.entry === bound.receipt.path, "SKILLS_PACKAGE_MISMATCH");
123430
124139
  const launcher = target.launcher ?? cli;
@@ -123478,23 +124187,23 @@ var init_codex_hook_policy_recovery = __esm(() => {
123478
124187
  });
123479
124188
 
123480
124189
  // src/lib/agent-codex-trust.ts
123481
- import { lstatSync as lstatSync50, mkdirSync as mkdirSync32, readdirSync as readdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync23, statSync as statSync25 } from "fs";
123482
- import { createHash as createHash49, randomUUID as randomUUID16 } from "crypto";
124190
+ import { lstatSync as lstatSync51, mkdirSync as mkdirSync33, readdirSync as readdirSync29, readFileSync as readFileSync47, realpathSync as realpathSync23, statSync as statSync25 } from "fs";
124191
+ import { createHash as createHash50, randomUUID as randomUUID17 } from "crypto";
123483
124192
  import { execFileSync as execFileSync2 } from "child_process";
123484
- import { dirname as dirname51, isAbsolute as isAbsolute46, join as join78, resolve as resolve43 } from "path";
124193
+ import { dirname as dirname52, isAbsolute as isAbsolute47, join as join79, resolve as resolve44 } from "path";
123485
124194
  import { homedir as homedir14 } from "os";
123486
124195
  import { isDeepStrictEqual as isDeepStrictEqual22 } from "util";
123487
124196
  function verifyReleaseArtifact(proof, skillsCli) {
123488
- need8(isAbsolute46(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
124197
+ need8(isAbsolute47(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
123489
124198
  const tarPath = realpathSync23(proof.packageTarPath);
123490
124199
  need8(statSync25(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
123491
- const tarBytes = readFileSync46(tarPath);
123492
- need8(tarBytes.byteLength === proof.packageTarBytes && createHash49("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
123493
- const packageRoot = dirname51(dirname51(realpathSync23(skillsCli.receipt.path)));
123494
- const manifest = readFileSync46(join78(packageRoot, "package.json"));
123495
- need8(createHash49("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
123496
- need8(createHash49("sha256").update(readFileSync46(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
123497
- const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync46(skillsCli.receipt.path)]];
124200
+ const tarBytes = readFileSync47(tarPath);
124201
+ need8(tarBytes.byteLength === proof.packageTarBytes && createHash50("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
124202
+ const packageRoot = dirname52(dirname52(realpathSync23(skillsCli.receipt.path)));
124203
+ const manifest = readFileSync47(join79(packageRoot, "package.json"));
124204
+ need8(createHash50("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
124205
+ need8(createHash50("sha256").update(readFileSync47(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
124206
+ const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync47(skillsCli.receipt.path)]];
123498
124207
  for (const [member, expected] of members2) {
123499
124208
  let actual;
123500
124209
  try {
@@ -123508,26 +124217,26 @@ function verifyReleaseArtifact(proof, skillsCli) {
123508
124217
  async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
123509
124218
  let rpc;
123510
124219
  try {
123511
- const home = resolve43(options.home ?? homedir14()), dataDir3 = resolve43(options.dataDir ?? getDataDirReadOnly());
123512
- const parent = resolve43(join78(dataDir3, "native-hook-trust")), journal = resolve43(options.journal);
124220
+ const home = resolve44(options.home ?? homedir14()), dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly());
124221
+ const parent = resolve44(join79(dataDir3, "native-hook-trust")), journal = resolve44(options.journal);
123513
124222
  need8(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
123514
- const dir = lstatSync50(journal, { throwIfNoEntry: false });
124223
+ const dir = lstatSync51(journal, { throwIfNoEntry: false });
123515
124224
  need8(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
123516
- need8(!lstatSync50(join78(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
123517
- need8(lstatSync50(join78(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
123518
- const stoppedFile = snapshot(join78(journal, "stopped.json"), true);
123519
- const intentFile = snapshot(join78(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
123520
- const configBefore = snapshot(join78(journal, "config.before.toml"), true), hooksBefore = snapshot(join78(journal, "hooks.before.json"), true), policyBefore = snapshot(join78(journal, "policy.before.json"), true);
124225
+ need8(!lstatSync51(join79(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
124226
+ need8(lstatSync51(join79(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
124227
+ const stoppedFile = snapshot(join79(journal, "stopped.json"), true);
124228
+ const intentFile = snapshot(join79(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
124229
+ const configBefore = snapshot(join79(journal, "config.before.toml"), true), hooksBefore = snapshot(join79(journal, "hooks.before.json"), true), policyBefore = snapshot(join79(journal, "policy.before.json"), true);
123521
124230
  const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
123522
124231
  need8(intent.version === 1 && /^[a-f0-9]{64}$/.test(intent.planDigest) && typeof intent.configPath === "string" && /^[a-f0-9]{64}$/.test(intent.policySha256) && /^[a-f0-9]{64}$/.test(intent.hooksSha256) && Array.isArray(intent.hooks) && intent.hooks.length > 0 && intent.hooks.length <= 100 && (legacyIntent || typeof intent.home === "string" && intent.home === home && /^[a-f0-9]{64}$/.test(intent.configSha256) && typeof intent.configVersion === "string" && Array.isArray(intent.declarations) && Array.isArray(intent.admitted) && Array.isArray(intent.nativeHooks)), "RECONCILE_INTENT_INVALID");
123523
- const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${join78(home, ".codex/hooks.json")}${hook.key.slice(hook.key.indexOf(".codex/hooks.json") + ".codex/hooks.json".length)}`, command: hook.command }));
124232
+ const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${join79(home, ".codex/hooks.json")}${hook.key.slice(hook.key.indexOf(".codex/hooks.json") + ".codex/hooks.json".length)}`, command: hook.command }));
123524
124233
  const admitted = intent.admitted ?? intent.hooks;
123525
124234
  const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
123526
124235
  const digestInput = { version: 1, home: intent.home ?? home, configPath: intent.configPath, ...intent.nativeConfigPath !== undefined ? { nativeConfigPath: intent.nativeConfigPath } : {}, configSha256, configVersion, policySha256: intent.policySha256, hooksSha256: intent.hooksSha256, skillsCli: intent.skillsCli, nativeVersion: intent.nativeVersion, declarations, admitted };
123527
124236
  need8(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
123528
124237
  need8(isDeepStrictEqual22(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
123529
124238
  need8(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
123530
- const policyCurrent = snapshot(join78(dataDir3, "agent-policy.json"), true);
124239
+ const policyCurrent = snapshot(join79(dataDir3, "agent-policy.json"), true);
123531
124240
  const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
123532
124241
  const recovery = claudeRecovery ?? additionsRecovery;
123533
124242
  need8(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
@@ -123544,17 +124253,17 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123544
124253
  if (supersededBinding || recovery) {
123545
124254
  need8(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
123546
124255
  const proof = options.releaseProof;
123547
- need8(!!proof && isAbsolute46(proof.packageTarPath) && /^[a-f0-9]{64}$/.test(proof.packageTarSha256) && Number.isInteger(proof.packageTarBytes) && proof.packageTarBytes > 0 && /^[a-f0-9]{64}$/.test(proof.manifestSha256) && /^[a-f0-9]{64}$/.test(proof.executableSha256), "RECONCILE_RELEASE_PROVENANCE_REQUIRED");
124256
+ need8(!!proof && isAbsolute47(proof.packageTarPath) && /^[a-f0-9]{64}$/.test(proof.packageTarSha256) && Number.isInteger(proof.packageTarBytes) && proof.packageTarBytes > 0 && /^[a-f0-9]{64}$/.test(proof.manifestSha256) && /^[a-f0-9]{64}$/.test(proof.executableSha256), "RECONCILE_RELEASE_PROVENANCE_REQUIRED");
123548
124257
  }
123549
124258
  const currentConfig = snapshot(intent.configPath, true);
123550
124259
  const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
123551
- const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join78(home, ".codex"), hooksPath = join78(physicalRoot, "hooks.json");
123552
- need8(resolve43(intent.configPath) === resolve43(join78(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
124260
+ const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join79(home, ".codex"), hooksPath = join79(physicalRoot, "hooks.json");
124261
+ need8(resolve44(intent.configPath) === resolve44(join79(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
123553
124262
  const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
123554
- need8([join78(home, ".codex/config.toml"), join78(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
123555
- const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname51(nativeConfigPath);
124263
+ need8([join79(home, ".codex/config.toml"), join79(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
124264
+ const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname52(nativeConfigPath);
123556
124265
  const hooksCurrent = snapshot(hooksPath);
123557
- const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath: join78(dirname51(nativeConfigPath), "hooks.json"), journalHooks: intent.hooks, recordedNativeHooks: intent.nativeHooks }) : undefined;
124266
+ const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath: join79(dirname52(nativeConfigPath), "hooks.json"), journalHooks: intent.hooks, recordedNativeHooks: intent.nativeHooks }) : undefined;
123558
124267
  const managedHooks = [...intent.hooks, ...additions?.hooks ?? []];
123559
124268
  const managedKeys = managedHooks.map((h) => h.key);
123560
124269
  const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
@@ -123618,7 +124327,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123618
124327
  need8(isDeepStrictEqual22(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
123619
124328
  }
123620
124329
  for (const wanted of intent.hooks) {
123621
- const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join78(home, ".codex/hooks.json"), hooksPath));
124330
+ const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join79(home, ".codex/hooks.json"), hooksPath));
123622
124331
  need8(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
123623
124332
  const h = matches2[0];
123624
124333
  const trusted = h.trustStatus === "trusted";
@@ -123667,7 +124376,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123667
124376
  const receipt = { version: 1, status: preview2 ? "recovery-review-required" : supersededBinding ? "superseded" : "reconciled", reconciled: !preview2, supersededBinding, automaticRollback: false, journal, planDigest: intent.planDigest, nativeVersion: rpc.version, nativeStateVerified: !nativeTrustPending, nativeTrustPending, bindingRefreshRequired: !!configDiscoveryRecovery, nativeExecutionVerified: false, configSha256: currentConfig.sha256, hooksSha256: hooksCurrent.sha256, policySha256: policyBefore.sha256, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, previousSkillsCli: intent.skillsCli, skillsCli: skillsCli.receipt, releaseProof: options.releaseProof, nativeInventoryScope: recordedNativeHooks ? "complete" : "declared-managed-hooks-only", unrelatedNativeHooksPreserved: !!recordedNativeHooks, unrelatedSettingsAndCommentsPreserved: true, ...recoveryPlan ? { recoveryPlan, recoveryPlanDigest } : {}, ...codexAdmissionReceipt(rpc.admission) };
123668
124377
  if (preview2)
123669
124378
  return receipt;
123670
- save(join78(journal, "receipt.json"), json2(receipt));
124379
+ save(join79(journal, "receipt.json"), json2(receipt));
123671
124380
  return receipt;
123672
124381
  } catch (error61) {
123673
124382
  const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_RECONCILE_REFUSED";
@@ -123677,21 +124386,21 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123677
124386
  }
123678
124387
  }
123679
124388
  async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRpc) {
123680
- const home = resolve43(options.home ?? homedir14());
123681
- return withCodexCorpusWriteAsync([join78(home, ".codex")], (_assertCurrent, admission) => enrollCodexNativeHooksUnlocked(options, connect, admission), options);
124389
+ const home = resolve44(options.home ?? homedir14());
124390
+ return withCodexCorpusWriteAsync([join79(home, ".codex")], (_assertCurrent, admission) => enrollCodexNativeHooksUnlocked(options, connect, admission), options);
123682
124391
  }
123683
124392
  async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123684
124393
  let rpc, journal;
123685
124394
  try {
123686
- const home = resolve43(options.home ?? homedir14()), dataDir3 = resolve43(options.dataDir ?? getDataDirReadOnly());
124395
+ const home = resolve44(options.home ?? homedir14()), dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly());
123687
124396
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
123688
- const policyFile = snapshot(join78(dataDir3, "agent-policy.json"), true), policy = parseManagedSkillPolicy(policyFile.text);
124397
+ const policyFile = snapshot(join79(dataDir3, "agent-policy.json"), true), policy = parseManagedSkillPolicy(policyFile.text);
123689
124398
  const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
123690
124399
  const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
123691
- const physicalRoot = alias?.target ?? join78(home, ".codex"), hooksPath = join78(physicalRoot, "hooks.json"), configPath = join78(physicalRoot, "config.toml");
123692
- need8(options.codexHome === undefined || isAbsolute46(options.codexHome) && [join78(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
124400
+ const physicalRoot = alias?.target ?? join79(home, ".codex"), hooksPath = join79(physicalRoot, "hooks.json"), configPath = join79(physicalRoot, "config.toml");
124401
+ need8(options.codexHome === undefined || isAbsolute47(options.codexHome) && [join79(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
123693
124402
  const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
123694
- const nativeConfigPath = join78(codexHome ?? join78(home, ".codex"), "config.toml");
124403
+ const nativeConfigPath = join79(codexHome ?? join79(home, ".codex"), "config.toml");
123695
124404
  const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
123696
124405
  const declarations = JSON.parse(hooksFile.text).hooks, expected = [];
123697
124406
  for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
@@ -123700,25 +124409,25 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123700
124409
  need8(Array.isArray(groups), "DECLARATION_CHANGED");
123701
124410
  const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
123702
124411
  need8(matches2.length === 1 && isDeepStrictEqual22(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
123703
- expected.push({ event: nativeEvent, key: `${join78(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
124412
+ expected.push({ event: nativeEvent, key: `${join79(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
123704
124413
  }
123705
124414
  codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
123706
124415
  rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
123707
124416
  need8(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
123708
124417
  const list2 = async () => {
123709
124418
  const response = await rpc.request("hooks/list", { cwds: [home] });
123710
- const nativeRoots = [...new Set([physicalRoot, join78(home, ".codex")])];
123711
- const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${join78(hooksRoot, "hooks.json")} and ${join78(configRoot, "config.toml")}; prefer a single representation for this layer`));
124419
+ const nativeRoots = [...new Set([physicalRoot, join79(home, ".codex")])];
124420
+ const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${join79(hooksRoot, "hooks.json")} and ${join79(configRoot, "config.toml")}; prefer a single representation for this layer`));
123712
124421
  const entry = response?.data?.[0];
123713
124422
  need8(Array.isArray(response?.data) && response.data.length === 1 && entry.cwd === home && Array.isArray(entry.hooks) && entry.hooks.length <= 1e4 && Array.isArray(entry.errors) && entry.errors.length === 0 && Array.isArray(entry.warnings) && entry.warnings.length <= 1 && entry.warnings.every((warning) => typeof warning === "string" && coexistenceWarnings.includes(warning)), "NATIVE_DISCOVERY_REFUSED");
123714
124423
  return response.data[0].hooks;
123715
124424
  };
123716
124425
  const admit = (hooks) => expected.map((wanted) => {
123717
- const canonicalKey = wanted.key.replace(join78(home, ".codex/hooks.json"), hooksPath);
124426
+ const canonicalKey = wanted.key.replace(join79(home, ".codex/hooks.json"), hooksPath);
123718
124427
  const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
123719
124428
  need8(matches2.length === 1, "AMBIGUOUS_IDENTITY");
123720
124429
  const h = matches2[0];
123721
- need8(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath, join78(home, ".codex/hooks.json")].includes(h.sourcePath) && h.source === "user" && h.pluginId === null && h.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(h.currentHash) && typeof h.enabled === "boolean", "NATIVE_IDENTITY_CHANGED");
124430
+ need8(h.eventName === wanted.event && h.handlerType === "command" && h.command === wanted.command && h.matcher === null && h.timeoutSec === 15 && h.async === false && h.statusMessage === null && h.additionalContextLimit === null && [hooksPath, join79(home, ".codex/hooks.json")].includes(h.sourcePath) && h.source === "user" && h.pluginId === null && h.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(h.currentHash) && typeof h.enabled === "boolean", "NATIVE_IDENTITY_CHANGED");
123722
124431
  need8(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
123723
124432
  need8(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
123724
124433
  return { key: h.key, event: wanted.event, command: wanted.command, handlerType: h.handlerType, timeoutSec: h.timeoutSec, sourcePath: h.sourcePath, currentHash: h.currentHash, enabled: h.enabled, trustStatus: h.trustStatus };
@@ -123744,27 +124453,27 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123744
124453
  const baseReceipt = { planDigest, skillsCli: skillsCli.receipt, nativeVersion: rpc.version, nativeConfigPath, ownedNativeProcessId: rpc.processId, agent: "codex", applied: false, planned, nativeEligible: planned.length === 0, bindingRefreshRequired: false, transport: "owned-stdio-process", existingSessionsReloaded: false, nativeExecutionVerified: false, ...codexAdmissionReceipt(admission) };
123745
124454
  if (options.apply)
123746
124455
  need8(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
123747
- const parent = join78(dataDir3, "native-hook-trust");
123748
- const st = lstatSync50(parent, { throwIfNoEntry: false });
124456
+ const parent = join79(dataDir3, "native-hook-trust");
124457
+ const st = lstatSync51(parent, { throwIfNoEntry: false });
123749
124458
  if (st) {
123750
124459
  need8(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
123751
- const priorJournals = readdirSync28(parent);
124460
+ const priorJournals = readdirSync29(parent);
123752
124461
  need8(priorJournals.length < 1000, "JOURNAL_BOUND");
123753
124462
  for (const name of priorJournals) {
123754
124463
  need8(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
123755
- need8(lstatSync50(join78(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
124464
+ need8(lstatSync51(join79(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
123756
124465
  }
123757
124466
  }
123758
124467
  if (!options.apply || !planned.length)
123759
124468
  return baseReceipt;
123760
124469
  if (!st)
123761
- mkdirSync32(parent, { mode: 448 });
123762
- journal = join78(parent, randomUUID16());
123763
- mkdirSync32(journal, { mode: 448 });
123764
- save(join78(journal, "config.before.toml"), configFile.bytes);
123765
- save(join78(journal, "hooks.before.json"), hooksFile.bytes);
123766
- save(join78(journal, "policy.before.json"), policyFile.bytes);
123767
- save(join78(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook), ...codexAdmissionReceipt(admission) }));
124470
+ mkdirSync33(parent, { mode: 448 });
124471
+ journal = join79(parent, randomUUID17());
124472
+ mkdirSync33(journal, { mode: 448 });
124473
+ save(join79(journal, "config.before.toml"), configFile.bytes);
124474
+ save(join79(journal, "hooks.before.json"), hooksFile.bytes);
124475
+ save(join79(journal, "policy.before.json"), policyFile.bytes);
124476
+ save(join79(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook), ...codexAdmissionReceipt(admission) }));
123768
124477
  need8(isDeepStrictEqual22(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
123769
124478
  checkInputs();
123770
124479
  let result2;
@@ -123780,7 +124489,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123780
124489
  for (const h of planned)
123781
124490
  expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
123782
124491
  need8(isDeepStrictEqual22(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys4) === preservationText, "PRESERVATION_FAILED");
123783
- const savedConfig = snapshot(join78(journal, "config.before.toml"), true);
124492
+ const savedConfig = snapshot(join79(journal, "config.before.toml"), true);
123784
124493
  need8(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
123785
124494
  const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
123786
124495
  unchanged2(policyFile);
@@ -123796,13 +124505,13 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123796
124505
  skillsCli.recheck();
123797
124506
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
123798
124507
  const receipt = { ...baseReceipt, applied: true, nativeEligible: true, bindingRefreshRequired: !!discoveryTransition, ...discoveryTransition ? { discoveryTransition } : {}, nativeConfigVersion: result2.version, journal, beforeSha256: configFile.sha256, afterSha256: after2.sha256, unrelatedSettingsAndCommentsPreserved: true };
123799
- save(join78(journal, "receipt.json"), json2(receipt));
124508
+ save(join79(journal, "receipt.json"), json2(receipt));
123800
124509
  return receipt;
123801
124510
  } catch (error61) {
123802
124511
  const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_REFUSED: verify managed bridge installation and native Codex support";
123803
124512
  if (journal) {
123804
124513
  try {
123805
- save(join78(journal, "stopped.json"), json2({ error: message.split(":")[0], automaticRollback: false, reconcileBeforeRetry: true, ...codexAdmissionReceipt(admission) }));
124514
+ save(join79(journal, "stopped.json"), json2({ error: message.split(":")[0], automaticRollback: false, reconcileBeforeRetry: true, ...codexAdmissionReceipt(admission) }));
123806
124515
  } catch {}
123807
124516
  }
123808
124517
  throw new Error(message);
@@ -123811,7 +124520,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123811
124520
  }
123812
124521
  }
123813
124522
  var json2 = (value) => JSON.stringify(value, null, 2) + `
123814
- `, structure3 = (text5) => JSON.parse(JSON.stringify(Bun.TOML.parse(text5))), eventNames, makePlanDigest = (value) => createHash49("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
124523
+ `, structure3 = (text5) => JSON.parse(JSON.stringify(Bun.TOML.parse(text5))), eventNames, makePlanDigest = (value) => createHash50("sha256").update(json2(value)).digest("hex"), safeNativeHook = (hook) => ({ key: hook.key, eventName: hook.eventName, handlerType: hook.handlerType, command: hook.command, matcher: hook.matcher, timeoutSec: hook.timeoutSec, async: hook.async, statusMessage: hook.statusMessage, additionalContextLimit: hook.additionalContextLimit, sourcePath: hook.sourcePath, source: hook.source, pluginId: hook.pluginId, isManaged: hook.isManaged, currentHash: hook.currentHash, enabled: hook.enabled, trustStatus: hook.trustStatus }), withoutCodexCommand = (value) => {
123815
124524
  const copy = structuredClone(value);
123816
124525
  if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
123817
124526
  copy.bridge.commands.codex = "<reviewed-codex-binding>";
@@ -123835,9 +124544,9 @@ var init_agent_codex_trust = __esm(() => {
123835
124544
  });
123836
124545
 
123837
124546
  // src/lib/codex-native-trust.ts
123838
- import { createHash as createHash50 } from "crypto";
123839
- import { readFileSync as readFileSync47 } from "fs";
123840
- import { join as join79, resolve as resolve44 } from "path";
124547
+ import { createHash as createHash51 } from "crypto";
124548
+ import { readFileSync as readFileSync48 } from "fs";
124549
+ import { join as join80, resolve as resolve45 } from "path";
123841
124550
  function refuse7(detail) {
123842
124551
  throw new Error(`NATIVE_SKILL_POLICY_TRUST_REFUSED: ${detail}`);
123843
124552
  }
@@ -123856,15 +124565,15 @@ function planCodexNativeTrust(options) {
123856
124565
  refuse7("digests must be unique");
123857
124566
  if (typeof expectedPolicySha256 !== "string" || !HEX_DIGEST2.test(expectedPolicySha256))
123858
124567
  refuse7("--expected-policy-sha256 must be lowercase SHA-256 hex");
123859
- const dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly()), policyPath = join79(dataDir3, "agent-policy.json");
124568
+ const dataDir3 = resolve45(options.dataDir ?? getDataDirReadOnly()), policyPath = join80(dataDir3, "agent-policy.json");
123860
124569
  assertOperatorTrustRoot(dataDir3);
123861
- const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
123862
- if (!snapshot2)
124570
+ const snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
124571
+ if (!snapshot3)
123863
124572
  refuse7("no managed policy; run skills hook install first");
123864
- const policySha256Before = sha7(snapshot2.text);
124573
+ const policySha256Before = sha7(snapshot3.text);
123865
124574
  if (policySha256Before !== expectedPolicySha256)
123866
124575
  refuse7("the managed policy bytes differ from --expected-policy-sha256; read the current file and pass its SHA-256");
123867
- const policy = snapshot2.value;
124576
+ const policy = snapshot3.value;
123868
124577
  if (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge))
123869
124578
  refuse7("the managed policy has no bridge binding; run skills hook install first");
123870
124579
  const current = parseCodexNativePolicyTrust(policy.bridge.codexNativePolicy);
@@ -123873,7 +124582,7 @@ function planCodexNativeTrust(options) {
123873
124582
  const next = { ...policy, bridge: { ...policy.bridge, codexNativePolicy: { executableDigests } } };
123874
124583
  assertAgentPolicyCollections(next);
123875
124584
  const after2 = serializeManagedSkillPolicy(next);
123876
- const plan = { dataDir: dataDir3, profileId: typeof policy.profileId === "string" ? policy.profileId : "default", changes: after2 === snapshot2.text ? [] : [{ path: policyPath, before: snapshot2.text, after: after2 }], nativeSkills: [], observedPolicy: { path: policyPath, before: snapshot2.text } };
124585
+ const plan = { dataDir: dataDir3, profileId: typeof policy.profileId === "string" ? policy.profileId : "default", changes: after2 === snapshot3.text ? [] : [{ path: policyPath, before: snapshot3.text, after: after2 }], nativeSkills: [], observedPolicy: { path: policyPath, before: snapshot3.text } };
123877
124586
  return { schema: CODEX_NATIVE_TRUST_PLAN_SCHEMA, dataDir: dataDir3, policyPath, platform: platform2, digestsBefore: [...current.executableDigests[platform2] ?? []], digestsAfter: [...digests], policySha256Before, policySha256After: sha7(after2), plan };
123878
124587
  }
123879
124588
  function previewCodexNativeTrust(plan) {
@@ -123894,7 +124603,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
123894
124603
  const backupPath = result2.backups[0];
123895
124604
  if (result2.backups.length !== 1 || typeof backupPath !== "string")
123896
124605
  throw new Error("NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the pre-change backup was not recorded");
123897
- const backupSha256 = sha7(readFileSync47(backupPath));
124606
+ const backupSha256 = sha7(readFileSync48(backupPath));
123898
124607
  if (backupSha256 !== plan.policySha256Before)
123899
124608
  throw new Error(`NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the backup at ${backupPath} does not read back as the pre-change policy`);
123900
124609
  receipt.backup = { path: backupPath, sha256: backupSha256, verified: true };
@@ -123908,7 +124617,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
123908
124617
  receipt.readback = { policySha256: sha7(readback.text), digests, verified: true };
123909
124618
  return receipt;
123910
124619
  }
123911
- var CODEX_NATIVE_TRUST_PLAN_SCHEMA = "skills.codex-native-trust-plan/v1", CODEX_NATIVE_TRUST_RECEIPT_SCHEMA = "skills.codex-native-trust-receipt/v1", PLATFORM_KEY2, HEX_DIGEST2, MAX_DIGESTS = 16, sha7 = (data) => createHash50("sha256").update(data).digest("hex");
124620
+ var CODEX_NATIVE_TRUST_PLAN_SCHEMA = "skills.codex-native-trust-plan/v1", CODEX_NATIVE_TRUST_RECEIPT_SCHEMA = "skills.codex-native-trust-receipt/v1", PLATFORM_KEY2, HEX_DIGEST2, MAX_DIGESTS = 16, sha7 = (data) => createHash51("sha256").update(data).digest("hex");
123912
124621
  var init_codex_native_trust = __esm(() => {
123913
124622
  init_config();
123914
124623
  init_managed_policy();
@@ -123920,9 +124629,9 @@ var init_codex_native_trust = __esm(() => {
123920
124629
  });
123921
124630
 
123922
124631
  // src/lib/claude-prospective-review.ts
123923
- import { createHash as createHash51 } from "crypto";
123924
- import { lstatSync as lstatSync51, readFileSync as readFileSync48, readdirSync as readdirSync29, realpathSync as realpathSync24 } from "fs";
123925
- import { basename as basename26, dirname as dirname52, isAbsolute as isAbsolute47, join as join80, relative as relative16, resolve as resolve45, sep as sep23 } from "path";
124632
+ import { createHash as createHash52 } from "crypto";
124633
+ import { lstatSync as lstatSync52, readFileSync as readFileSync49, readdirSync as readdirSync30, realpathSync as realpathSync24 } from "fs";
124634
+ import { basename as basename26, dirname as dirname53, isAbsolute as isAbsolute48, join as join81, relative as relative17, resolve as resolve46, sep as sep24 } from "path";
123926
124635
  import { isDeepStrictEqual as isDeepStrictEqual23 } from "util";
123927
124636
  function hashClaudeDiscoveryBinding(binding) {
123928
124637
  return sha8(JSON.stringify(binding));
@@ -123973,7 +124682,7 @@ function keys4(value, expected, name) {
123973
124682
  refuse8(`${name} has an unsupported shape`);
123974
124683
  }
123975
124684
  function abs(path2, name) {
123976
- if (typeof path2 !== "string" || !path2 || path2.length > 4096 || /[\x00-\x1f\x7f]/.test(path2) || !isAbsolute47(path2) || resolve45(path2) !== path2)
124685
+ if (typeof path2 !== "string" || !path2 || path2.length > 4096 || /[\x00-\x1f\x7f]/.test(path2) || !isAbsolute48(path2) || resolve46(path2) !== path2)
123977
124686
  refuse8(`${name} must be a normalized absolute path`);
123978
124687
  }
123979
124688
  function validUtcTimestamp(value) {
@@ -123981,27 +124690,27 @@ function validUtcTimestamp(value) {
123981
124690
  }
123982
124691
  function safeFile(path2, name) {
123983
124692
  abs(path2, name);
123984
- for (let at2 = dirname52(path2);; at2 = dirname52(at2)) {
123985
- const stat4 = lstatSync51(at2, { throwIfNoEntry: false });
124693
+ for (let at2 = dirname53(path2);; at2 = dirname53(at2)) {
124694
+ const stat4 = lstatSync52(at2, { throwIfNoEntry: false });
123986
124695
  if (!stat4?.isDirectory() || stat4.isSymbolicLink())
123987
124696
  refuse8(`${name} traverses a symlink or non-directory`);
123988
- if (at2 === dirname52(at2))
124697
+ if (at2 === dirname53(at2))
123989
124698
  break;
123990
124699
  }
123991
- const stat3 = lstatSync51(path2, { throwIfNoEntry: false });
124700
+ const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
123992
124701
  if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > MAX_CANDIDATE_BYTES)
123993
124702
  refuse8(`${name} is not a bounded regular file`);
123994
- return readFileSync48(path2);
124703
+ return readFileSync49(path2);
123995
124704
  }
123996
124705
  function canonicalDir(path2, name) {
123997
124706
  abs(path2, name);
123998
- if (realpathSync24(path2) !== path2 || !lstatSync51(path2).isDirectory())
124707
+ if (realpathSync24(path2) !== path2 || !lstatSync52(path2).isDirectory())
123999
124708
  refuse8(`${name} is not a canonical directory`);
124000
- for (let at2 = path2;; at2 = dirname52(at2)) {
124001
- const stat3 = lstatSync51(at2);
124709
+ for (let at2 = path2;; at2 = dirname53(at2)) {
124710
+ const stat3 = lstatSync52(at2);
124002
124711
  if (!stat3.isDirectory() || stat3.isSymbolicLink())
124003
124712
  refuse8(`${name} traverses a symlink`);
124004
- if (at2 === dirname52(at2))
124713
+ if (at2 === dirname53(at2))
124005
124714
  break;
124006
124715
  }
124007
124716
  }
@@ -124010,17 +124719,17 @@ function captureClaudeProspectiveCandidateClosure(root2) {
124010
124719
  const entries = [];
124011
124720
  let bytes = 0;
124012
124721
  const walk2 = (directory3) => {
124013
- const beforeDirectory = lstatSync51(directory3);
124014
- const names = readdirSync29(directory3).sort();
124722
+ const beforeDirectory = lstatSync52(directory3);
124723
+ const names = readdirSync30(directory3).sort();
124015
124724
  if (new Set(names).size !== names.length)
124016
124725
  refuse8("candidate contains ambiguous directory entries");
124017
124726
  for (const name of names) {
124018
124727
  if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
124019
124728
  refuse8("candidate contains an unsupported path member");
124020
- const path2 = join80(directory3, name), rel = relative16(root2, path2).split(sep23).join("/");
124729
+ const path2 = join81(directory3, name), rel = relative17(root2, path2).split(sep24).join("/");
124021
124730
  if (entries.length >= MAX_CANDIDATE_FILES)
124022
124731
  refuse8("candidate closure exceeds its member limit");
124023
- const stat3 = lstatSync51(path2);
124732
+ const stat3 = lstatSync52(path2);
124024
124733
  if (stat3.isSymbolicLink())
124025
124734
  refuse8("candidate closure contains a symlink");
124026
124735
  if (stat3.isDirectory()) {
@@ -124030,14 +124739,14 @@ function captureClaudeProspectiveCandidateClosure(root2) {
124030
124739
  bytes += stat3.size;
124031
124740
  if (bytes > MAX_CANDIDATE_BYTES)
124032
124741
  refuse8("candidate closure exceeds its byte limit");
124033
- const content = safeFile(path2, "candidate member"), after2 = lstatSync51(path2);
124742
+ const content = safeFile(path2, "candidate member"), after2 = lstatSync52(path2);
124034
124743
  if (!after2.isFile() || after2.dev !== stat3.dev || after2.ino !== stat3.ino || after2.size !== stat3.size || after2.mtimeMs !== stat3.mtimeMs || after2.ctimeMs !== stat3.ctimeMs)
124035
124744
  refuse8("candidate member changed while being read");
124036
124745
  entries.push([rel, "file", sha8(content)]);
124037
124746
  } else
124038
124747
  refuse8("candidate closure contains a special file");
124039
124748
  }
124040
- const afterDirectory = lstatSync51(directory3);
124749
+ const afterDirectory = lstatSync52(directory3);
124041
124750
  if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
124042
124751
  refuse8("candidate directory changed while being read");
124043
124752
  };
@@ -124048,9 +124757,9 @@ function captureClaudeProspectiveCandidateClosure(root2) {
124048
124757
  function captureClaudeInstallerCandidate(root2, policyPath) {
124049
124758
  const closureSha256 = captureClaudeProspectiveCandidateClosure(root2);
124050
124759
  const dataDir3 = getDataDirReadOnly();
124051
- if (policyPath !== join80(dataDir3, "agent-policy.json"))
124760
+ if (policyPath !== join81(dataDir3, "agent-policy.json"))
124052
124761
  refuse8("Skills policy path differs from the package-owned resolver");
124053
- const catalogPath = join80(root2, ".claude-plugin/marketplace.json");
124762
+ const catalogPath = join81(root2, ".claude-plugin/marketplace.json");
124054
124763
  const catalog = jsonFile(catalogPath, "candidate catalog");
124055
124764
  if (!catalog || !Array.isArray(catalog.plugins))
124056
124765
  refuse8("candidate catalog is invalid");
@@ -124061,13 +124770,13 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
124061
124770
  if (names.has(row2.name))
124062
124771
  refuse8("candidate catalog repeats a plugin name");
124063
124772
  names.add(row2.name);
124064
- if (typeof row2.source !== "string" || isAbsolute47(row2.source) || /[\\\x00-\x1f\x7f]/.test(row2.source))
124773
+ if (typeof row2.source !== "string" || isAbsolute48(row2.source) || /[\\\x00-\x1f\x7f]/.test(row2.source))
124065
124774
  refuse8("candidate catalog plugin source is invalid");
124066
- const pluginRoot = resolve45(root2, row2.source);
124067
- const relativeRoot = relative16(root2, pluginRoot);
124068
- if (!relativeRoot || relativeRoot === "." || relativeRoot === ".." || relativeRoot.startsWith(`..${sep23}`) || isAbsolute47(relativeRoot))
124775
+ const pluginRoot = resolve46(root2, row2.source);
124776
+ const relativeRoot = relative17(root2, pluginRoot);
124777
+ if (!relativeRoot || relativeRoot === "." || relativeRoot === ".." || relativeRoot.startsWith(`..${sep24}`) || isAbsolute48(relativeRoot))
124069
124778
  refuse8("candidate plugin source escapes the candidate root");
124070
- const path2 = join80(pluginRoot, ".claude-plugin/plugin.json");
124779
+ const path2 = join81(pluginRoot, ".claude-plugin/plugin.json");
124071
124780
  return { plugin: row2.name, ...captureClaudePluginManifestFile(path2) };
124072
124781
  });
124073
124782
  const policy = readManagedSkillPolicySnapshot(dataDir3);
@@ -124084,7 +124793,7 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
124084
124793
  }
124085
124794
  function readWitness(path2, expected, name) {
124086
124795
  abs(path2, `${name} path`);
124087
- const stat3 = lstatSync51(path2, { throwIfNoEntry: false });
124796
+ const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
124088
124797
  if (expected === null) {
124089
124798
  if (stat3)
124090
124799
  refuse8(`${name} was absent at review but is now present`);
@@ -124136,7 +124845,7 @@ function unreviewedProjectSettingsRefusal(path2, expected, name) {
124136
124845
  return null;
124137
124846
  }
124138
124847
  function jsonFile(path2, name) {
124139
- const stat3 = lstatSync51(path2, { throwIfNoEntry: false });
124848
+ const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
124140
124849
  if (!stat3)
124141
124850
  return null;
124142
124851
  let value;
@@ -124166,9 +124875,9 @@ function assertNoUnprojectedSkillPayload(root2, catalogPath, marketplace, plugin
124166
124875
  if (entries[0].skills !== undefined || entries[0].commands !== undefined)
124167
124876
  refuse8("candidate marketplace entry declares native skill or command payloads");
124168
124877
  const walk2 = (directory3) => {
124169
- for (const name of readdirSync29(directory3).sort()) {
124170
- const path2 = join80(directory3, name), rel = relative16(root2, path2).split(sep23).join("/");
124171
- const stat3 = lstatSync51(path2);
124878
+ for (const name of readdirSync30(directory3).sort()) {
124879
+ const path2 = join81(directory3, name), rel = relative17(root2, path2).split(sep24).join("/");
124880
+ const stat3 = lstatSync52(path2);
124172
124881
  if (stat3.isDirectory())
124173
124882
  walk2(path2);
124174
124883
  else if (stat3.isFile()) {
@@ -124213,10 +124922,10 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124213
124922
  abs(request2.skills.dataDir, "Skills data directory");
124214
124923
  if (!SHA2562.test(request2.skills.policySha256) || !SHA2562.test(request2.skills.discoverySha256))
124215
124924
  refuse8("invalid Skills policy or discovery SHA-256");
124216
- const snapshot2 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
124217
- if (!snapshot2 || sha8(snapshot2.text) !== request2.skills.policySha256)
124925
+ const snapshot3 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
124926
+ if (!snapshot3 || sha8(snapshot3.text) !== request2.skills.policySha256)
124218
124927
  refuse8("Skills policy preimage changed or is absent");
124219
- const aliases = snapshot2.value.bridge?.rootAliases ?? [];
124928
+ const aliases = snapshot3.value.bridge?.rootAliases ?? [];
124220
124929
  if (!Array.isArray(aliases))
124221
124930
  refuse8("Skills policy root aliases are invalid");
124222
124931
  try {
@@ -124225,16 +124934,16 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124225
124934
  refuse8("Skills policy root alias changed or is invalid");
124226
124935
  }
124227
124936
  const canonical9 = (path2) => canonicalAgentPath(path2, aliases);
124228
- const configRoot = canonical9(join80(request2.native.home, ".claude"));
124937
+ const configRoot = canonical9(join81(request2.native.home, ".claude"));
124229
124938
  canonicalDir(configRoot, "Claude config root");
124230
124939
  if (request2.native.configRoot !== configRoot)
124231
124940
  refuse8("Claude config root differs from the current resolver and reviewed root alias");
124232
- const pluginRoot = join80(configRoot, "plugins");
124941
+ const pluginRoot = join81(configRoot, "plugins");
124233
124942
  if (process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR)
124234
124943
  refuse8("CLAUDE_CODE_PLUGIN_CACHE_DIR overrides are unsupported by this prospective schema; bind and review the actual cache root first");
124235
124944
  if (request2.native.pluginRoot !== pluginRoot)
124236
124945
  refuse8("Claude plugin root differs from the supported standard plugins directory; Cowork and custom cache roots require a separately bound schema");
124237
- const pluginRootStat = lstatSync51(pluginRoot, { throwIfNoEntry: false });
124946
+ const pluginRootStat = lstatSync52(pluginRoot, { throwIfNoEntry: false });
124238
124947
  if (pluginRootStat && (!pluginRootStat.isDirectory() || pluginRootStat.isSymbolicLink()))
124239
124948
  refuse8("Claude plugin root is not a regular directory");
124240
124949
  keys4(request2.candidate, ["marketplace", "root", "catalogPath", "catalogSha256", "closureSha256", "plugins", ...hasOwn2(request2.candidate, "releaseManifest") ? ["releaseManifest"] : [], ...hasOwn2(request2.candidate, "functionHooksEnv") ? ["functionHooksEnv"] : [], ...hasOwn2(request2.candidate, "settingsMarketplaceSource") ? ["settingsMarketplaceSource"] : []], "candidate");
@@ -124252,9 +124961,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124252
124961
  if (hasOwn2(candidate2, "settingsMarketplaceSource") && (candidate2.settingsMarketplaceSource !== "directory" || request2.scope.kind !== "user" || !measuredSettingsWriter))
124253
124962
  refuse8("settings marketplace source operation requires directory source and a measured Claude user-scope writer");
124254
124963
  abs(candidate2.catalogPath, "catalog path");
124255
- if (!candidate2.catalogPath.startsWith(candidate2.root + sep23))
124964
+ if (!candidate2.catalogPath.startsWith(candidate2.root + sep24))
124256
124965
  refuse8("catalog must be inside the frozen candidate root");
124257
- if (candidate2.catalogPath !== join80(candidate2.root, ".claude-plugin", "marketplace.json"))
124966
+ if (candidate2.catalogPath !== join81(candidate2.root, ".claude-plugin", "marketplace.json"))
124258
124967
  refuse8("marketplace catalog must be at the frozen root's .claude-plugin/marketplace.json path");
124259
124968
  if (!SHA2562.test(candidate2.catalogSha256) || !SHA2562.test(candidate2.closureSha256))
124260
124969
  refuse8("candidate digests must be lowercase SHA-256");
@@ -124296,11 +125005,11 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124296
125005
  if (Buffer.byteLength(canonicalJson3(selected.optionsPatch), "utf8") > 16 * 1024)
124297
125006
  refuse8("selected plugin optionsPatch exceeds its byte bound");
124298
125007
  abs(selected.manifestPath, "manifest path");
124299
- if (!selected.manifestPath.startsWith(candidate2.root + sep23) || basename26(dirname52(selected.manifestPath)) !== ".claude-plugin" || selected.manifestPath !== join80(dirname52(dirname52(selected.manifestPath)), ".claude-plugin", "plugin.json"))
125008
+ if (!selected.manifestPath.startsWith(candidate2.root + sep24) || basename26(dirname53(selected.manifestPath)) !== ".claude-plugin" || selected.manifestPath !== join81(dirname53(dirname53(selected.manifestPath)), ".claude-plugin", "plugin.json"))
124300
125009
  refuse8("plugin manifest must be under a plugin root inside the frozen marketplace closure");
124301
- const pluginRoot2 = dirname52(dirname52(selected.manifestPath));
124302
- const relativePluginRoot = relative16(candidate2.root, pluginRoot2);
124303
- if (!relativePluginRoot || relativePluginRoot === "." || relativePluginRoot === ".." || relativePluginRoot.startsWith(`..${sep23}`) || isAbsolute47(relativePluginRoot))
125010
+ const pluginRoot2 = dirname53(dirname53(selected.manifestPath));
125011
+ const relativePluginRoot = relative17(candidate2.root, pluginRoot2);
125012
+ if (!relativePluginRoot || relativePluginRoot === "." || relativePluginRoot === ".." || relativePluginRoot.startsWith(`..${sep24}`) || isAbsolute48(relativePluginRoot))
124304
125013
  refuse8("plugin root must be a child of the frozen marketplace root");
124305
125014
  canonicalDir(pluginRoot2, "candidate plugin root");
124306
125015
  if (!SHA2562.test(selected.manifestSha256))
@@ -124332,9 +125041,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124332
125041
  refuse8("selected plugins collide after native cache path sanitization");
124333
125042
  sanitizedCachePaths.add(sanitizedCachePath);
124334
125043
  const sourcePath = selectedEntry.source;
124335
- const sourceRelativePath = relative16(candidate2.root, pluginRoot2).split(sep23).join("/");
125044
+ const sourceRelativePath = relative17(candidate2.root, pluginRoot2).split(sep24).join("/");
124336
125045
  const expectedSourcePath = sourceRelativePath === "." ? "./" : `./${sourceRelativePath}`;
124337
- if (typeof sourcePath !== "string" || isAbsolute47(sourcePath) || sourcePath !== expectedSourcePath || resolve45(dirname52(dirname52(candidate2.catalogPath)), sourcePath) !== pluginRoot2)
125046
+ if (typeof sourcePath !== "string" || isAbsolute48(sourcePath) || sourcePath !== expectedSourcePath || resolve46(dirname53(dirname53(candidate2.catalogPath)), sourcePath) !== pluginRoot2)
124338
125047
  refuse8("marketplace source does not resolve to the frozen plugin closure; external source mapping is not supported by this schema");
124339
125048
  const entry = captureClaudeMarketplaceEntry(candidate2.catalogPath, candidate2.marketplace, selected.plugin);
124340
125049
  if (entry.sha256.length !== 64)
@@ -124354,12 +125063,12 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124354
125063
  for (const name of ["settings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"])
124355
125064
  keys4(request2.preimages[name], ["path", "sha256"], `${name} preimage`);
124356
125065
  const expectedPaths = {
124357
- settings: request2.scope.kind === "project" ? canonical9(join80(request2.scope.projectPath, ".claude/settings.json")) : canonical9(join80(request2.native.home, ".claude/settings.json")),
124358
- userSettings: canonical9(join80(request2.native.home, ".claude/settings.json")),
124359
- projectSettings: canonical9(join80(request2.scope.projectPath, ".claude/settings.json")),
124360
- projectSettingsLocal: canonical9(join80(request2.scope.projectPath, ".claude/settings.local.json")),
124361
- marketplaces: canonical9(join80(request2.native.home, ".claude/plugins/known_marketplaces.json")),
124362
- installedPlugins: canonical9(join80(request2.native.home, ".claude/plugins/installed_plugins.json"))
125066
+ settings: request2.scope.kind === "project" ? canonical9(join81(request2.scope.projectPath, ".claude/settings.json")) : canonical9(join81(request2.native.home, ".claude/settings.json")),
125067
+ userSettings: canonical9(join81(request2.native.home, ".claude/settings.json")),
125068
+ projectSettings: canonical9(join81(request2.scope.projectPath, ".claude/settings.json")),
125069
+ projectSettingsLocal: canonical9(join81(request2.scope.projectPath, ".claude/settings.local.json")),
125070
+ marketplaces: canonical9(join81(request2.native.home, ".claude/plugins/known_marketplaces.json")),
125071
+ installedPlugins: canonical9(join81(request2.native.home, ".claude/plugins/installed_plugins.json"))
124363
125072
  };
124364
125073
  for (const name of Object.keys(expectedPaths)) {
124365
125074
  const witness = request2.preimages[name];
@@ -124394,8 +125103,8 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124394
125103
  if (hashClaudeDiscoveryBinding(binding) !== request2.skills.discoverySha256)
124395
125104
  refuse8("Skills discovery preimage differs from its reviewed binding");
124396
125105
  assertProjectDiscovery("claude", [request2.scope.projectPath], request2.native.home, canonical9, binding);
124397
- const stored = snapshot2.value.bridge?.discovery?.claude;
124398
- if (!stored || !isDeepStrictEqual23(stored, binding) || snapshot2.value.bridge?.home !== request2.native.home)
125106
+ const stored = snapshot3.value.bridge?.discovery?.claude;
125107
+ if (!stored || !isDeepStrictEqual23(stored, binding) || snapshot3.value.bridge?.home !== request2.native.home)
124399
125108
  refuse8("Skills policy does not bind the reviewed current Claude discovery");
124400
125109
  assertExemptUnchanged();
124401
125110
  const delta = request2.delta;
@@ -124492,7 +125201,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124492
125201
  const manifestVersion = manifest.version;
124493
125202
  const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
124494
125203
  const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
124495
- const expectedInstallPath = join80(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
125204
+ const expectedInstallPath = join81(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
124496
125205
  if (expectedInstallPath.length > 4096)
124497
125206
  refuse8("candidate native cache path exceeds its bound");
124498
125207
  const targetRegistration = {
@@ -124631,7 +125340,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124631
125340
  { name: "project-settings-local", path: expectedPaths.projectSettingsLocal, sha256: request2.preimages.projectSettingsLocal.sha256 },
124632
125341
  { name: "known-marketplaces", path: expectedPaths.marketplaces, sha256: request2.preimages.marketplaces.sha256 },
124633
125342
  { name: "installed-plugins", path: expectedPaths.installedPlugins, sha256: request2.preimages.installedPlugins.sha256 },
124634
- { name: "skills-policy", path: join80(request2.skills.dataDir, "agent-policy.json"), sha256: request2.skills.policySha256 }
125343
+ { name: "skills-policy", path: join81(request2.skills.dataDir, "agent-policy.json"), sha256: request2.skills.policySha256 }
124635
125344
  ],
124636
125345
  ownedDelta: { settingsEnabledPlugins: selectedPluginIds, selectedPluginOptions: selectedOptionPaths, functionHooksEnv: candidate2.functionHooksEnv ?? null, knownMarketplace: candidate2.marketplace, installedPlugins: selectedPluginIds, unrelatedGraph: "preserved-by-complete-semantic-documents" },
124637
125346
  identityPrecondition: "Harnesses must resolve the installed Claude home/config/plugin roots and executable path/version/hash plus the installed Skills data directory through their package-owned consumers, then compare every value before trusting this receipt; this validator checks supplied paths and current bytes only and does not establish actual consumer identity",
@@ -124639,7 +125348,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124639
125348
  limitation: "validation-only; native installer atomicity and crash recovery are not established"
124640
125349
  };
124641
125350
  }
124642
- var CLAUDE_PROSPECTIVE_REVIEW_SCHEMA = "skills.claude-plugin-prospective-review/v1", SHA2562, MAX_CANDIDATE_FILES = 16384, MAX_CANDIDATE_BYTES, sha8 = (data) => createHash51("sha256").update(data).digest("hex"), GENERATED_MARKETPLACE_TIME, GENERATED_FRESH_PLUGIN_INSTALLED_AT, GENERATED_PLUGIN_UPDATE_TIME, EXEMPT_PROJECT_SETTINGS, EXEMPT_PROJECT_PERMISSIONS;
125351
+ var CLAUDE_PROSPECTIVE_REVIEW_SCHEMA = "skills.claude-plugin-prospective-review/v1", SHA2562, MAX_CANDIDATE_FILES = 16384, MAX_CANDIDATE_BYTES, sha8 = (data) => createHash52("sha256").update(data).digest("hex"), GENERATED_MARKETPLACE_TIME, GENERATED_FRESH_PLUGIN_INSTALLED_AT, GENERATED_PLUGIN_UPDATE_TIME, EXEMPT_PROJECT_SETTINGS, EXEMPT_PROJECT_PERMISSIONS;
124643
125352
  var init_claude_prospective_review = __esm(() => {
124644
125353
  init_agent_discovery();
124645
125354
  init_agent_integration();
@@ -124673,7 +125382,7 @@ var init_claude_prospective_review = __esm(() => {
124673
125382
 
124674
125383
  // src/lib/codex-plugin-reconcile.ts
124675
125384
  import { realpathSync as realpathSync25 } from "fs";
124676
- import { join as join81, resolve as resolve46 } from "path";
125385
+ import { join as join82, resolve as resolve47 } from "path";
124677
125386
  function boundedMessage(text5) {
124678
125387
  const line = text5.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, " ").trim();
124679
125388
  return line.length > 800 ? `${line.slice(0, 797)}...` : line;
@@ -124707,20 +125416,20 @@ async function runCodexPluginReconcile(options) {
124707
125416
  const mode = options.apply ? "apply" : "preview";
124708
125417
  const base3 = emptyResult(mode);
124709
125418
  try {
124710
- const home = resolve46(options.home), projectDir = resolve46(options.projectDir), dataDir3 = options.dataDir ?? getDataDirReadOnly();
125419
+ const home = resolve47(options.home), projectDir = resolve47(options.projectDir), dataDir3 = options.dataDir ?? getDataDirReadOnly();
124711
125420
  if (options.codexHome !== undefined) {
124712
125421
  const canonical9 = (path2) => {
124713
125422
  try {
124714
125423
  return realpathSync25(path2);
124715
125424
  } catch {
124716
- return resolve46(path2);
125425
+ return resolve47(path2);
124717
125426
  }
124718
125427
  };
124719
- if (canonical9(options.codexHome) !== canonical9(join81(home, ".codex")))
125428
+ if (canonical9(options.codexHome) !== canonical9(join82(home, ".codex")))
124720
125429
  throw new CodexPluginReconcileRefusal("CODEX_RECONCILE_CODEX_HOME_UNSUPPORTED", "CODEX_HOME names a Codex home other than the one the Skills binding reviews");
124721
125430
  }
124722
125431
  const capture2 = options.captureCatalog ?? captureCodexNativeSkillCatalog;
124723
- const remoteReceipts = captureCodexRemoteReceiptSnapshot(join81(home, ".codex/plugins/cache"));
125432
+ const remoteReceipts = captureCodexRemoteReceiptSnapshot(join82(home, ".codex/plugins/cache"));
124724
125433
  const captured = await capture2({ command: options.codexCommand ?? "codex", home, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, cwd: projectDir });
124725
125434
  const catalog = captured && typeof captured === "object" ? { ...captured, remoteReceipts } : captured;
124726
125435
  base3.codexVersion = typeof catalog?.version === "string" ? catalog.version : null;
@@ -124745,7 +125454,7 @@ async function runCodexPluginReconcile(options) {
124745
125454
  if (!options.apply)
124746
125455
  return { ...reported, outcome: "planned", changes };
124747
125456
  const applied = await applyCodexPluginReconcile(plan, { ...options.codexCommand === undefined ? {} : { codexCommand: options.codexCommand }, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, ...options.connect ? { connect: options.connect } : {} });
124748
- return { ...reported, outcome: "applied", changes: applied.changed, ...applied.receipt ? { receipt: resolve46(applied.receipt) } : {}, backups: applied.backups };
125457
+ return { ...reported, outcome: "applied", changes: applied.changed, ...applied.receipt ? { receipt: resolve47(applied.receipt) } : {}, backups: applied.backups };
124749
125458
  } catch (error61) {
124750
125459
  return { ...base3, ...classifyCodexReconcileError(error61) };
124751
125460
  }
@@ -124781,23 +125490,23 @@ var init_codex_plugin_reconcile = __esm(() => {
124781
125490
  });
124782
125491
 
124783
125492
  // src/lib/claude-installer-binding.ts
124784
- import { createHash as createHash52 } from "crypto";
124785
- import { join as join82 } from "path";
125493
+ import { createHash as createHash53 } from "crypto";
125494
+ import { join as join83 } from "path";
124786
125495
  function readPolicy(dataDir3) {
124787
- let snapshot2;
125496
+ let snapshot3;
124788
125497
  try {
124789
- snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
125498
+ snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
124790
125499
  } catch {
124791
125500
  return refuse9("POLICY_UNREADABLE");
124792
125501
  }
124793
- return snapshot2 ?? refuse9("POLICY_UNAVAILABLE");
125502
+ return snapshot3 ?? refuse9("POLICY_UNAVAILABLE");
124794
125503
  }
124795
125504
  function verifyClaudeInstallerDiscoveryBinding(policyPath) {
124796
125505
  const dataDir3 = getDataDirReadOnly();
124797
- if (policyPath !== join82(dataDir3, "agent-policy.json"))
125506
+ if (policyPath !== join83(dataDir3, "agent-policy.json"))
124798
125507
  refuse9("POLICY_PATH_MISMATCH");
124799
- const snapshot2 = readPolicy(dataDir3);
124800
- const bridge = snapshot2.value.bridge;
125508
+ const snapshot3 = readPolicy(dataDir3);
125509
+ const bridge = snapshot3.value.bridge;
124801
125510
  const binding = object14(bridge) && object14(bridge.discovery) ? bridge.discovery.claude : undefined;
124802
125511
  if (!object14(binding))
124803
125512
  return refuse9("BINDING_UNAVAILABLE");
@@ -124839,12 +125548,12 @@ function verifyClaudeInstallerDiscoveryBinding(policyPath) {
124839
125548
  ...directories.flatMap((row2) => row2.status === "drift" ? [{ kind: "directory", index: row2.index, path: row2.path, reason: row2.reason }] : []),
124840
125549
  ...accepted ? [] : [{ kind: "binding", reason: "DISCOVERY_REFUSED" }]
124841
125550
  ];
124842
- if (readPolicy(dataDir3).text !== snapshot2.text)
125551
+ if (readPolicy(dataDir3).text !== snapshot3.text)
124843
125552
  refuse9("POLICY_CHANGED");
124844
125553
  return {
124845
125554
  schema: CLAUDE_DISCOVERY_BINDING_VERIFICATION_SCHEMA,
124846
125555
  ok: failures3.length === 0,
124847
- policySha256: createHash52("sha256").update(snapshot2.text).digest("hex"),
125556
+ policySha256: createHash53("sha256").update(snapshot3.text).digest("hex"),
124848
125557
  discoverySha256: hashClaudeDiscoveryBinding(binding),
124849
125558
  agent: "claude",
124850
125559
  method: binding.method,
@@ -124873,8 +125582,8 @@ var init_claude_installer_binding = __esm(() => {
124873
125582
  });
124874
125583
 
124875
125584
  // src/cli/commands/claude-installer-capture.ts
124876
- import { closeSync as closeSync33, constants as constants33, fstatSync as fstatSync32, lstatSync as lstatSync52, openSync as openSync33, readSync as readSync25 } from "fs";
124877
- import { isAbsolute as isAbsolute48, resolve as resolve47 } from "path";
125585
+ import { closeSync as closeSync33, constants as constants33, fstatSync as fstatSync32, lstatSync as lstatSync53, openSync as openSync33, readSync as readSync25 } from "fs";
125586
+ import { isAbsolute as isAbsolute49, resolve as resolve48 } from "path";
124878
125587
  function need11(condition, code2) {
124879
125588
  if (!condition)
124880
125589
  throw new CaptureRefusal(code2);
@@ -124888,11 +125597,11 @@ function exactKeys5(value, expected, operation) {
124888
125597
  }
124889
125598
  }
124890
125599
  function canonicalPath2(value) {
124891
- need11(typeof value === "string" && isAbsolute48(value) && resolve47(value) === value && !/[\x00-\x1f\x7f]/.test(value), "INVALID_PATH: path fields must be normalized absolute paths without control characters");
125600
+ need11(typeof value === "string" && isAbsolute49(value) && resolve48(value) === value && !/[\x00-\x1f\x7f]/.test(value), "INVALID_PATH: path fields must be normalized absolute paths without control characters");
124892
125601
  }
124893
125602
  function readRequest2(path2) {
124894
125603
  canonicalPath2(path2);
124895
- const before2 = lstatSync52(path2, { throwIfNoEntry: false });
125604
+ const before2 = lstatSync53(path2, { throwIfNoEntry: false });
124896
125605
  if (!before2?.isFile() || before2.isSymbolicLink() || before2.size > MAX_REQUEST_BYTES)
124897
125606
  throw new CaptureRefusal("INVALID_REQUEST_FILE: --request must name a regular non-symlink JSON file no larger than 64 KiB");
124898
125607
  const fd = openSync33(path2, constants33.O_RDONLY | (constants33.O_NOFOLLOW ?? 0) | constants33.O_NONBLOCK);
@@ -124907,7 +125616,7 @@ function readRequest2(path2) {
124907
125616
  break;
124908
125617
  length += count;
124909
125618
  }
124910
- const after2 = fstatSync32(fd), current = lstatSync52(path2);
125619
+ const after2 = fstatSync32(fd), current = lstatSync53(path2);
124911
125620
  need11(length <= MAX_REQUEST_BYTES && length === opened.size && after2.size === opened.size && after2.mtimeMs === opened.mtimeMs && after2.ctimeMs === opened.ctimeMs && current.dev === opened.dev && current.ino === opened.ino && current.mtimeMs === opened.mtimeMs && current.ctimeMs === opened.ctimeMs, "INVALID_REQUEST_FILE: --request must remain unchanged while read");
124912
125621
  try {
124913
125622
  return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)));
@@ -125069,10 +125778,10 @@ __export(exports_agent_integration, {
125069
125778
  MANAGED_HOOK_CONTEXT_MAX_SKILLS: () => MANAGED_HOOK_CONTEXT_MAX_SKILLS,
125070
125779
  MANAGED_HOOK_CONTEXT_MAX_CHARS: () => MANAGED_HOOK_CONTEXT_MAX_CHARS
125071
125780
  });
125072
- import { existsSync as existsSync36, lstatSync as lstatSync53, readFileSync as readFileSync49, readSync as readSync26, writeFileSync as writeFileSync29 } from "fs";
125073
- import { createHash as createHash53 } from "crypto";
125781
+ import { existsSync as existsSync37, lstatSync as lstatSync54, readFileSync as readFileSync50, readSync as readSync26, writeFileSync as writeFileSync30 } from "fs";
125782
+ import { createHash as createHash54 } from "crypto";
125074
125783
  import { homedir as homedir15 } from "os";
125075
- import { basename as basename27, isAbsolute as isAbsolute49, resolve as resolve48 } from "path";
125784
+ import { basename as basename27, isAbsolute as isAbsolute50, resolve as resolve49 } from "path";
125076
125785
  function boundedEnvInteger(name, fallback, [min2, max2]) {
125077
125786
  const raw = process.env[name];
125078
125787
  if (raw === undefined || !/^[0-9]{1,6}$/.test(raw.trim()))
@@ -125256,9 +125965,9 @@ function registerAgentIntegration(parent) {
125256
125965
  });
125257
125966
  hook.command("native-catalog").requiredOption("--cwd <path>", "Absolute project directory observed by native Codex skills/list").requiredOption("--output <file>", "New private file for the projected native catalog").option("--codex-command <path>", "Installed Codex executable used by the native client", "codex").option("--json", "Output bounded capture metadata as JSON", false).description("Capture a reviewed Codex native skill catalog for exact hook enrollment").action(async (options) => {
125258
125967
  try {
125259
- if (!isAbsolute49(options.cwd) || !isAbsolute49(options.output))
125968
+ if (!isAbsolute50(options.cwd) || !isAbsolute50(options.output))
125260
125969
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
125261
- if (existsSync36(options.output))
125970
+ if (existsSync37(options.output))
125262
125971
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
125263
125972
  let admission;
125264
125973
  const catalog = await captureCodexNativeSkillCatalog({
@@ -125274,14 +125983,14 @@ function registerAgentIntegration(parent) {
125274
125983
  const bytes = JSON.stringify(catalog, null, 2) + `
125275
125984
  `;
125276
125985
  try {
125277
- writeFileSync29(options.output, bytes, { flag: "wx", mode: 384 });
125986
+ writeFileSync30(options.output, bytes, { flag: "wx", mode: 384 });
125278
125987
  } catch (error61) {
125279
125988
  if (error61 && typeof error61 === "object" && "code" in error61 && error61.code === "EEXIST")
125280
125989
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
125281
125990
  throw error61;
125282
125991
  }
125283
- const written = lstatSync53(options.output);
125284
- if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 || readFileSync49(options.output, "utf8") !== bytes)
125992
+ const written = lstatSync54(options.output);
125993
+ if (!written.isFile() || written.isSymbolicLink() || (written.mode & 63) !== 0 || readFileSync50(options.output, "utf8") !== bytes)
125285
125994
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
125286
125995
  const receipt = {
125287
125996
  version: catalog.version,
@@ -125289,7 +125998,7 @@ function registerAgentIntegration(parent) {
125289
125998
  skillCount: catalog.skills.length,
125290
125999
  output: options.output,
125291
126000
  bytes: Buffer.byteLength(bytes),
125292
- sha256: createHash53("sha256").update(bytes).digest("hex"),
126001
+ sha256: createHash54("sha256").update(bytes).digest("hex"),
125293
126002
  ...codexAdmissionReceipt(admission)
125294
126003
  };
125295
126004
  await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
@@ -125323,7 +126032,7 @@ function registerAgentIntegration(parent) {
125323
126032
  if (!options.reviewedHookSources)
125324
126033
  throw new Error("Codex V6 requires a bounded --reviewed-hook-sources JSON array");
125325
126034
  const { hashNativeJsonControls: hashNativeJsonControls2, readNativeSettingsWitnessFile: readNativeSettingsWitnessFile2 } = await Promise.resolve().then(() => (init_claude_settings_witness(), exports_claude_settings_witness));
125326
- const metadata2 = resolve48(options.reviewedHookSources);
126035
+ const metadata2 = resolve49(options.reviewedHookSources);
125327
126036
  const text5 = readNativeSettingsWitnessFile2(metadata2, { remaining: 1048576 }, basename27(metadata2));
125328
126037
  hashNativeJsonControls2(`{"sources":${text5}}`);
125329
126038
  await writeCliOutput(JSON.stringify(captureCodexSettingsV6(options.path, JSON.parse(text5)), null, 2));
@@ -125358,14 +126067,14 @@ function registerAgentIntegration(parent) {
125358
126067
  });
125359
126068
  hook.command("review-claude-candidate").requiredOption("--request <file>", "Frozen skills.claude-plugin-prospective-review/v1 JSON request").option("--json", "Return the validation-only receipt as JSON", false).description("Validate a prospective Claude plugin candidate and current preimages without changing native state; Harnesses must compare native and Skills paths against installed package-owned resolvers before trusting the receipt").action(async (options) => {
125360
126069
  try {
125361
- if (!isAbsolute49(options.request))
126070
+ if (!isAbsolute50(options.request))
125362
126071
  throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: --request must be an absolute path");
125363
- const stat3 = lstatSync53(options.request);
126072
+ const stat3 = lstatSync54(options.request);
125364
126073
  if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 1024 * 1024)
125365
126074
  throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: request must be a regular file no larger than 1 MiB");
125366
- const requestBytes = readFileSync49(options.request);
126075
+ const requestBytes = readFileSync50(options.request);
125367
126076
  const request2 = JSON.parse(requestBytes.toString("utf8"));
125368
- const receipt = reviewClaudeProspectiveCandidate(request2, createHash53("sha256").update(requestBytes).digest("hex"));
126077
+ const receipt = reviewClaudeProspectiveCandidate(request2, createHash54("sha256").update(requestBytes).digest("hex"));
125369
126078
  await writeCliOutput(options.json ? JSON.stringify(receipt) : `Claude plugin candidate review accepted for ${receipt.pluginIds.length} selected plugin(s); native state was not changed.`);
125370
126079
  } catch (error61) {
125371
126080
  console.error(error61 instanceof Error && error61.message.startsWith("CLAUDE_CANDIDATE_REVIEW_REFUSED:") ? error61.message : "CLAUDE_CANDIDATE_REVIEW_REFUSED: request or current preimage validation failed");
@@ -125437,8 +126146,8 @@ function registerAgentIntegration(parent) {
125437
126146
  });
125438
126147
  hook.command("install").option("--agent <agent>", `Agent to configure: ${INTEGRATION_AGENTS.join(", ")}, all (default: configured or detected agents; explicit all requires every adapter)`).option("--command <path>", "Skills executable used by the hook (preserves existing binding; new agents use skills)").option("--selection-profile <id>", "Selection profile (preserves existing binding; new agents use default)").option("--include-vendor", "Retained for compatibility; vendor system skills are always inventoried and disabled", false).option("--discovery-inputs <file>", "Advanced reviewed active plugin roots and source hashes for unsupported registrations").option("--codex-skill-denials <file>", "Explicit reviewed remote skill denials: array of exact name, path and document sha256; requires fresh catalog and discovery inputs").option("--codex-native-catalog <file>", "Reviewed supported Codex skill and installed-plugin receipt for exact qualified-name disables").option("--reviewed-cache-alias <path>", "Exact skill-containing vendor cache alias reviewed for this hook plan").option("--allow-root-aliases", "Allow home .claude/.codex aliases to existing directories within this home", false).option("--apply", "Apply the plan, preserving prior configuration in private backups", false).option("--json", "Output a receipt as JSON", false).description("Plan or install one Skills CLI bridge plus native prompt hooks").action(async (options) => {
125439
126148
  try {
125440
- const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync49(options.discoveryInputs, "utf8")) : undefined;
125441
- const plan = planAgentIntegration({ projectDir: process.cwd(), agents: options.agent === undefined ? detectedIntegrationAgents() : agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(readFileSync49(options.codexSkillDenials, "utf8")) : undefined, codexNativeCatalog: options.codexNativeCatalog ? JSON.parse(readFileSync49(options.codexNativeCatalog, "utf8")) : undefined });
126149
+ const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync50(options.discoveryInputs, "utf8")) : undefined;
126150
+ const plan = planAgentIntegration({ projectDir: process.cwd(), agents: options.agent === undefined ? detectedIntegrationAgents() : agents(options.agent), command: options.command, profileId: options.selectionProfile, includeVendor: options.includeVendor, discoveryInputs, allowRootAliases: options.allowRootAliases, reviewedCacheAlias: options.reviewedCacheAlias, codexSkillDenials: options.codexSkillDenials ? JSON.parse(readFileSync50(options.codexSkillDenials, "utf8")) : undefined, codexNativeCatalog: options.codexNativeCatalog ? JSON.parse(readFileSync50(options.codexNativeCatalog, "utf8")) : undefined });
125442
126151
  const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
125443
126152
  const receipt = { codexPluginSkillReview: plan.codexPluginSkillReview, codexPluginSkills: plan.changes.filter((change) => change.path.endsWith("agent-policy.json")).map((change) => JSON.parse(change.after).bridge?.codexPluginSkills ?? []).flat(), applied: options.apply, planned: plan.changes.map((change) => change.path), ...result2, rootAliases: plan.rootAliases ?? [], discovery: plan.discoveryAfter, nativeSkills: plan.nativeSkills.map((entry) => ({ agent: entry.agent, path: entry.path, managed: entry.managed, vendor: entry.vendor, system: entry.system === true, bridge: entry.bridge === true })), requiresNativeRetirement: plan.nativeSkills.some((entry) => !entry.bridge && !entry.system) };
125444
126153
  if (options.json)
@@ -125500,7 +126209,7 @@ function registerAgentIntegration(parent) {
125500
126209
  try {
125501
126210
  if (agents(options.agent).length !== 1)
125502
126211
  throw new Error("A hook invocation requires one agent");
125503
- const inputBytes = readFileSync49(0), inputText = inputBytes.toString("utf8");
126212
+ const inputBytes = readFileSync50(0), inputText = inputBytes.toString("utf8");
125504
126213
  if (inputText.length > 1024 * 1024)
125505
126214
  throw new Error("Hook input is too large");
125506
126215
  let input3 = JSON.parse(inputText);
@@ -125513,12 +126222,12 @@ function registerAgentIntegration(parent) {
125513
126222
  input3.hook_event_name = event;
125514
126223
  const projects = [process.cwd()];
125515
126224
  if (input3.cwd !== undefined) {
125516
- if (typeof input3.cwd !== "string" || !isAbsolute49(input3.cwd) || input3.cwd.includes("\x00"))
126225
+ if (typeof input3.cwd !== "string" || !isAbsolute50(input3.cwd) || input3.cwd.includes("\x00"))
125517
126226
  throw new Error("Invalid native hook working directory");
125518
126227
  projects.push(input3.cwd);
125519
126228
  }
125520
126229
  if (options.agent === "cursor") {
125521
- if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path2) => typeof path2 !== "string" || !isAbsolute49(path2) || path2.includes("\x00")))
126230
+ if (!Array.isArray(input3.workspace_roots) || input3.workspace_roots.length > 32 || input3.workspace_roots.some((path2) => typeof path2 !== "string" || !isAbsolute50(path2) || path2.includes("\x00")))
125522
126231
  throw new Error("Invalid Cursor workspace roots");
125523
126232
  projects.push(...input3.workspace_roots);
125524
126233
  input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
@@ -125666,7 +126375,7 @@ ${driftWarning}` : unavailable2, ...output2 }));
125666
126375
  try {
125667
126376
  if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
125668
126377
  throw new Error(`Unsupported migration agent: ${options.agent}`);
125669
- const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync49(options.discoveryInputs, "utf8")) : undefined;
126378
+ const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync50(options.discoveryInputs, "utf8")) : undefined;
125670
126379
  if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
125671
126380
  throw new Error("--target-manifest cannot be combined with broad native migration selectors");
125672
126381
  const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
@@ -125861,7 +126570,7 @@ var exports_profiles = {};
125861
126570
  __export(exports_profiles, {
125862
126571
  registerProfiles: () => registerProfiles
125863
126572
  });
125864
- import { closeSync as closeSync34, constants as constants34, fstatSync as fstatSync33, openSync as openSync34, readSync as readSync27, writeFileSync as writeFileSync30 } from "fs";
126573
+ import { closeSync as closeSync34, constants as constants34, fstatSync as fstatSync33, openSync as openSync34, readSync as readSync27, writeFileSync as writeFileSync31 } from "fs";
125865
126574
  function readProfileInput(path2) {
125866
126575
  const fd = openSync34(path2, constants34.O_RDONLY | constants34.O_NONBLOCK);
125867
126576
  try {
@@ -125889,7 +126598,7 @@ function registerProfiles(parent) {
125889
126598
  try {
125890
126599
  const profile = await readSkillProfile(id3), text5 = JSON.stringify(profile, null, 2);
125891
126600
  if (options.save)
125892
- writeFileSync30(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
126601
+ writeFileSync31(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
125893
126602
  await writeCliOutput(options.json ? text5 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
125894
126603
  } catch (error61) {
125895
126604
  console.error(error61.message);
@@ -126299,7 +127008,7 @@ import {
126299
127008
  fstatSync as fstatSync34,
126300
127009
  openSync as openSync35,
126301
127010
  readSync as readSync28,
126302
- writeFileSync as writeFileSync31
127011
+ writeFileSync as writeFileSync32
126303
127012
  } from "fs";
126304
127013
  function readInput(path2) {
126305
127014
  const fd = openSync35(path2, constants35.O_RDONLY | constants35.O_NONBLOCK);
@@ -126332,7 +127041,7 @@ function registerGrants(parent) {
126332
127041
  try {
126333
127042
  const policy = await readExecutionGrantPolicy(profile, options.revision);
126334
127043
  if (options.save)
126335
- writeFileSync31(options.save, JSON.stringify(policy, null, 2), {
127044
+ writeFileSync32(options.save, JSON.stringify(policy, null, 2), {
126336
127045
  mode: 384,
126337
127046
  flag: "wx"
126338
127047
  });
@@ -126359,28 +127068,28 @@ var init_grants = __esm(() => {
126359
127068
  });
126360
127069
 
126361
127070
  // src/lib/station-hydrate.ts
126362
- import { createHash as createHash54 } from "crypto";
127071
+ import { createHash as createHash55 } from "crypto";
126363
127072
  import {
126364
127073
  copyFileSync as copyFileSync3,
126365
- mkdirSync as mkdirSync33,
126366
- readdirSync as readdirSync30,
126367
- readFileSync as readFileSync50,
127074
+ mkdirSync as mkdirSync34,
127075
+ readdirSync as readdirSync31,
127076
+ readFileSync as readFileSync51,
126368
127077
  statSync as statSync26,
126369
- writeFileSync as writeFileSync32
127078
+ writeFileSync as writeFileSync33
126370
127079
  } from "fs";
126371
- import { dirname as dirname53, join as join83, resolve as resolve49, sep as sep24 } from "path";
127080
+ import { dirname as dirname54, join as join84, resolve as resolve50, sep as sep25 } from "path";
126372
127081
  function fail7(code2, message, detail = []) {
126373
127082
  throw new StationSnapshotError(code2, message, detail);
126374
127083
  }
126375
127084
  function snapshotRootFor(repoRoot, stationId) {
126376
- return join83(repoRoot, "resources", stationId, "skills");
127085
+ return join84(repoRoot, "resources", stationId, "skills");
126377
127086
  }
126378
127087
  function readSnapshotManifest(repoRoot, stationId) {
126379
127088
  const snapshotRoot = snapshotRootFor(repoRoot, stationId);
126380
- const manifestPath = join83(snapshotRoot, "sync-manifest.json");
127089
+ const manifestPath = join84(snapshotRoot, "sync-manifest.json");
126381
127090
  let manifest;
126382
127091
  try {
126383
- manifest = JSON.parse(readFileSync50(manifestPath, "utf8"));
127092
+ manifest = JSON.parse(readFileSync51(manifestPath, "utf8"));
126384
127093
  } catch (error61) {
126385
127094
  fail7("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
126386
127095
  }
@@ -126404,10 +127113,10 @@ function planStationHydration(stationId, repoRoot) {
126404
127113
  const hashMismatches = [];
126405
127114
  const skippedByRule = [];
126406
127115
  for (const agent of SYNC_AGENTS) {
126407
- const agentRoot = join83(snapshotRoot, "agent-homes", agent);
127116
+ const agentRoot = join84(snapshotRoot, "agent-homes", agent);
126408
127117
  let identEntries;
126409
127118
  try {
126410
- identEntries = readdirSync30(agentRoot, { withFileTypes: true });
127119
+ identEntries = readdirSync31(agentRoot, { withFileTypes: true });
126411
127120
  } catch {
126412
127121
  continue;
126413
127122
  }
@@ -126415,10 +127124,10 @@ function planStationHydration(stationId, repoRoot) {
126415
127124
  if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
126416
127125
  continue;
126417
127126
  }
126418
- const identRoot = join83(agentRoot, identEntry.name);
127127
+ const identRoot = join84(agentRoot, identEntry.name);
126419
127128
  const entries = walkEntries(identRoot);
126420
127129
  for (const entry of entries) {
126421
- const relativeParts = [identEntry.name, ...entry.relativePath.split(sep24)];
127130
+ const relativeParts = [identEntry.name, ...entry.relativePath.split(sep25)];
126422
127131
  if (entry.kind === "symlink") {
126423
127132
  symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
126424
127133
  continue;
@@ -126442,8 +127151,8 @@ function planStationHydration(stationId, repoRoot) {
126442
127151
  });
126443
127152
  continue;
126444
127153
  }
126445
- const withinIdent = relativeParts.slice(1).join(sep24);
126446
- const homeRelative = relativeParts.join(sep24);
127154
+ const withinIdent = relativeParts.slice(1).join(sep25);
127155
+ const homeRelative = relativeParts.join(sep25);
126447
127156
  if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
126448
127157
  skippedByRule.push({
126449
127158
  ident: identEntry.name,
@@ -126516,7 +127225,7 @@ function planStationHydration(stationId, repoRoot) {
126516
127225
  for (const copy of copies) {
126517
127226
  let isStub = false;
126518
127227
  try {
126519
- isStub = isPointerSkillMd(readFileSync50(copy.fullPath, "utf8"));
127228
+ isStub = isPointerSkillMd(readFileSync51(copy.fullPath, "utf8"));
126520
127229
  } catch {
126521
127230
  isStub = false;
126522
127231
  }
@@ -126562,12 +127271,12 @@ function skillSha256(skill) {
126562
127271
  return sha256File(skill.files[0].winner.fullPath);
126563
127272
  }
126564
127273
  const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
126565
- return createHash54("sha256").update(joined.sort().join(`
127274
+ return createHash55("sha256").update(joined.sort().join(`
126566
127275
  `)).digest("hex");
126567
127276
  }
126568
127277
  function writeStationHydration(options) {
126569
- const repoRoot = resolve49(options.repoRoot ?? process.cwd());
126570
- const cacheRoot = resolve49(options.cacheRoot ?? resolveCorpusRoot());
127278
+ const repoRoot = resolve50(options.repoRoot ?? process.cwd());
127279
+ const cacheRoot = resolve50(options.cacheRoot ?? resolveCorpusRoot());
126571
127280
  const plan = planStationHydration(options.stationId, repoRoot);
126572
127281
  const resultSkills = plan.winners.map((skill) => ({
126573
127282
  ident: skill.ident,
@@ -126600,7 +127309,7 @@ function writeStationHydration(options) {
126600
127309
  const toWrite = [];
126601
127310
  for (const skill of plan.winners) {
126602
127311
  for (const file2 of skill.files) {
126603
- const destination = join83(cacheRoot, skill.ident, file2.withinIdent);
127312
+ const destination = join84(cacheRoot, skill.ident, file2.withinIdent);
126604
127313
  const digest7 = sha256File(file2.winner.fullPath);
126605
127314
  let existingDigest = null;
126606
127315
  try {
@@ -126621,7 +127330,7 @@ function writeStationHydration(options) {
126621
127330
  }
126622
127331
  let written = 0;
126623
127332
  for (const entry of toWrite) {
126624
- mkdirSync33(dirname53(entry.destination), { recursive: true });
127333
+ mkdirSync34(dirname54(entry.destination), { recursive: true });
126625
127334
  copyFileSync3(entry.fullPath, entry.destination);
126626
127335
  written += 1;
126627
127336
  }
@@ -126642,9 +127351,9 @@ function writeStationHydration(options) {
126642
127351
  },
126643
127352
  skills: resultSkills
126644
127353
  };
126645
- const hydrationManifestPath = join83(dirname53(cacheRoot), `hydration-${options.stationId}.json`);
126646
- mkdirSync33(dirname53(hydrationManifestPath), { recursive: true });
126647
- writeFileSync32(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
127354
+ const hydrationManifestPath = join84(dirname54(cacheRoot), `hydration-${options.stationId}.json`);
127355
+ mkdirSync34(dirname54(hydrationManifestPath), { recursive: true });
127356
+ writeFileSync33(hydrationManifestPath, `${JSON.stringify(hydration, null, 2)}
126648
127357
  `);
126649
127358
  return {
126650
127359
  ...base3,
@@ -126718,389 +127427,6 @@ var init_hydrate = __esm(() => {
126718
127427
  init_station_hydrate();
126719
127428
  });
126720
127429
 
126721
- // src/lib/prepare-skill.ts
126722
- import { createHash as createHash55, randomUUID as randomUUID17 } from "crypto";
126723
- import { chmodSync as chmodSync5, existsSync as existsSync37, lstatSync as lstatSync54, mkdirSync as mkdirSync34, mkdtempSync as mkdtempSync7, readFileSync as readFileSync51, readdirSync as readdirSync31, renameSync as renameSync18, rmSync as rmSync10, writeFileSync as writeFileSync33 } from "fs";
126724
- import { tmpdir as tmpdir2 } from "os";
126725
- import { dirname as dirname54, isAbsolute as isAbsolute50, join as join84, relative as relative17, resolve as resolve50, sep as sep25 } from "path";
126726
- function prepareSkill(name, options) {
126727
- const normalized2 = normalizePortableSkillName(name);
126728
- if (!new RegExp(SEMVER_PATTERN).test(options.version))
126729
- throw new Error("--version must be a semantic version, for example 0.2.0.");
126730
- const path2 = join84(getPortableSkillsRootReadOnly(options), normalized2);
126731
- assertPortableAuthoringPath(path2, options);
126732
- const before2 = snapshot2(path2);
126733
- const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
126734
- if (!manifestFile)
126735
- throw new Error(`Skill '${normalized2}' needs skill.json. Import the source with skills port first.`);
126736
- let manifest;
126737
- try {
126738
- const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
126739
- if (!isRecord9(parsed))
126740
- throw new Error("not an object");
126741
- manifest = parsed;
126742
- } catch {
126743
- throw new Error("skill.json must contain a JSON object; no files were changed.");
126744
- }
126745
- const previousVersion = manifest.version;
126746
- if (typeof previousVersion !== "string" || !new RegExp(SEMVER_PATTERN).test(previousVersion)) {
126747
- throw new Error("skill.json must declare its current semantic version before preparation.");
126748
- }
126749
- if (manifest.kind !== undefined && manifest.kind !== "instruction" && manifest.kind !== "executable") {
126750
- throw new Error("skill.json has an invalid kind; correct it before preparation.");
126751
- }
126752
- const kind = options.kind ?? manifest.kind;
126753
- if (kind !== "instruction" && kind !== "executable") {
126754
- throw new Error("This legacy manifest has no explicit kind. Pass --kind instruction or --kind executable; helper scripts alone do not determine a skill's kind.");
126755
- }
126756
- const document3 = before2.files.find((file2) => file2.path === "SKILL.md");
126757
- const documentKind = document3 ? parseSkillFrontmatter(document3.bytes.toString("utf8"))?.kind : undefined;
126758
- if (documentKind !== undefined && documentKind !== kind) {
126759
- throw new Error("SKILL.md kind conflicts with the candidate skill.json kind. Review and align both declarations (or remove the optional frontmatter kind), then prepare again; no files were changed.");
126760
- }
126761
- if (manifest.provenance !== undefined && !isRecord9(manifest.provenance))
126762
- throw new Error("skill.json provenance must be an object.");
126763
- const provenance = manifest.provenance;
126764
- const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
126765
- const staging = mkdtempSync7(join84(tmpdir2(), "skills-prepare-"));
126766
- try {
126767
- for (const directory3 of before2.directories)
126768
- mkdirSync34(join84(staging, directory3), { recursive: true });
126769
- for (const file2 of before2.files) {
126770
- const destination = join84(staging, file2.path);
126771
- mkdirSync34(dirname54(destination), { recursive: true });
126772
- writeFileSync33(destination, file2.bytes, { mode: file2.mode });
126773
- }
126774
- const stagedManifest = join84(staging, "skill.json");
126775
- writeFileSync33(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
126776
- `);
126777
- const contentHash = computeContentHash(staging);
126778
- const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
126779
- if (changed3 && compareVersions(options.version, previousVersion) <= 0) {
126780
- throw new Error(`Changed content requires a version greater than ${previousVersion}; pass --version <new-semver>.`);
126781
- }
126782
- candidate2.provenance.content_hash = contentHash;
126783
- const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
126784
- `;
126785
- writeFileSync33(stagedManifest, candidateBytes);
126786
- validateRawCandidate(candidate2);
126787
- validateRuntimeEntrypoint(candidate2, staging);
126788
- const validation = validatePortableSkillDirectory(normalized2, staging);
126789
- if (!validation.valid)
126790
- throw new Error(`Skill '${normalized2}' cannot be prepared:
126791
- ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
126792
- `)}`);
126793
- assertPackedCanonicalCoverage(staging);
126794
- if (snapshot2(path2).identity !== before2.identity)
126795
- throw new Error("Skill changed during preparation; review the draft and run prepare again.");
126796
- if (changed3 && !options.dryRun) {
126797
- const temporary = join84(path2, `.skill-prepare-${randomUUID17()}.tmp`);
126798
- try {
126799
- writeFileSync33(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
126800
- chmodSync5(temporary, manifestFile.mode);
126801
- renameSync18(temporary, join84(path2, "skill.json"));
126802
- } finally {
126803
- rmSync10(temporary, { force: true });
126804
- }
126805
- }
126806
- return { name: normalized2, path: path2, previousVersion, version: options.version, kind, contentHash, changed: changed3, written: changed3 && !options.dryRun, warnings: validation.warnings };
126807
- } finally {
126808
- rmSync10(staging, { recursive: true, force: true });
126809
- }
126810
- }
126811
- function assertPackedCanonicalCoverage(root2) {
126812
- const canonicalPaths = new Set(collectBundleFiles(root2).map((file2) => file2.rel));
126813
- const packedPaths = new Set(collectSkillBundleEntries(root2).map((entry) => entry.path));
126814
- const omitted = [...canonicalPaths].filter((path2) => !packedPaths.has(path2)).sort();
126815
- if (omitted.length === 0)
126816
- return;
126817
- const rendered = omitted.slice(0, MAX_PACK_PARITY_PATHS).map(escapedRelativePath).join(", ");
126818
- const suffix = omitted.length > MAX_PACK_PARITY_PATHS ? ` (+${omitted.length - MAX_PACK_PARITY_PATHS} more)` : "";
126819
- throw new Error(`Skill cannot be prepared because packSkillBundle excludes canonical source path(s): ${rendered}${suffix}. ` + "Rename or remove the excluded source before preparing; no files were changed.");
126820
- }
126821
- function escapedRelativePath(path2) {
126822
- const bounded2 = path2.length > MAX_PACK_PARITY_PATH_CHARS - 8 ? `${path2.slice(0, MAX_PACK_PARITY_PATH_CHARS - 8)}...` : path2;
126823
- return JSON.stringify(bounded2);
126824
- }
126825
- function validateRuntimeEntrypoint(candidate2, root2) {
126826
- if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
126827
- return;
126828
- const entry = candidate2.runtime.entrypoint;
126829
- if (typeof entry !== "string")
126830
- return;
126831
- const target = resolve50(root2, entry), rel = relative17(root2, target);
126832
- if (isAbsolute50(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep25}`) || isAbsolute50(rel) || !existsSync37(target) || !lstatSync54(target).isFile()) {
126833
- throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
126834
- }
126835
- }
126836
- function isRecord9(value) {
126837
- return typeof value === "object" && value !== null && !Array.isArray(value);
126838
- }
126839
- function validateRawCandidate(candidate2) {
126840
- for (const field4 of ["standard", "name", "description", "version"]) {
126841
- if (typeof candidate2[field4] !== "string" || !candidate2[field4].trim())
126842
- throw new Error(`skill.json ${field4} is required.`);
126843
- }
126844
- for (const field4 of ["displayName", "category"]) {
126845
- if (candidate2[field4] !== undefined && (typeof candidate2[field4] !== "string" || !candidate2[field4].trim()))
126846
- throw new Error(`Invalid skill.json ${field4}.`);
126847
- }
126848
- if (candidate2.tags !== undefined && (!Array.isArray(candidate2.tags) || candidate2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
126849
- throw new Error("Invalid skill.json tags.");
126850
- const issues = validatePortableManifestContract(candidate2, { strict: true });
126851
- if (issues.length)
126852
- throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
126853
- validateRuntimeEntrypointCompatibility(candidate2);
126854
- for (const field4 of ["inputs", "commands"]) {
126855
- const items = candidate2[field4];
126856
- if (items === undefined)
126857
- continue;
126858
- if (!Array.isArray(items))
126859
- throw new Error(`skill.json ${field4} must be an array.`);
126860
- for (const item of items) {
126861
- if (!isRecord9(item) || typeof item.name !== "string" || !item.name.trim())
126862
- throw new Error(`Invalid skill.json ${field4} name.`);
126863
- if (field4 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
126864
- throw new Error("Invalid skill.json input type or required flag.");
126865
- if (field4 === "commands") {
126866
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(item.name))
126867
- throw new Error("Invalid skill.json command name.");
126868
- for (const target of ["entry", "command"])
126869
- if (item[target] !== undefined && (typeof item[target] !== "string" || !item[target].trim()))
126870
- throw new Error(`Invalid skill.json command ${target}.`);
126871
- if (!item.entry && !item.command)
126872
- throw new Error("A skill.json command needs entry or command.");
126873
- if (item.args !== undefined && (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")))
126874
- throw new Error("Invalid skill.json command args.");
126875
- }
126876
- }
126877
- }
126878
- }
126879
- function validateRuntimeEntrypointCompatibility(candidate2) {
126880
- if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
126881
- return;
126882
- const runtime = candidate2.runtime.runtime;
126883
- const entrypoint = candidate2.runtime.entrypoint;
126884
- if (typeof runtime !== "string" || typeof entrypoint !== "string")
126885
- return;
126886
- const extension = entrypoint.toLowerCase().split(".").pop();
126887
- if (extension === "py" && runtime !== "python3") {
126888
- throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
126889
- }
126890
- }
126891
- function compareVersions(left, right) {
126892
- const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
126893
- const [a, ap] = parts(left), [b, bp] = parts(right);
126894
- const ac = a.split(".").map(BigInt), bc = b.split(".").map(BigInt);
126895
- for (let i = 0;i < 3; i++)
126896
- if (ac[i] !== bc[i])
126897
- return ac[i] > bc[i] ? 1 : -1;
126898
- if (ap === undefined || bp === undefined)
126899
- return ap === bp ? 0 : ap === undefined ? 1 : -1;
126900
- const aa = ap.split("."), bb = bp.split(".");
126901
- for (let i = 0;i < Math.max(aa.length, bb.length); i++) {
126902
- const x = aa[i], y = bb[i];
126903
- if (x === undefined || y === undefined)
126904
- return x === y ? 0 : x === undefined ? -1 : 1;
126905
- if (x === y)
126906
- continue;
126907
- const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
126908
- if (xn && yn)
126909
- return BigInt(x) > BigInt(y) ? 1 : -1;
126910
- if (xn !== yn)
126911
- return xn ? -1 : 1;
126912
- return x > y ? 1 : -1;
126913
- }
126914
- return 0;
126915
- }
126916
- function snapshot2(root2) {
126917
- const rootStat = lstatSync54(root2);
126918
- if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
126919
- throw new Error("Skill root must be a real directory, not a symlink.");
126920
- const files = [], directories = [];
126921
- const hash8 = createHash55("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
126922
- let count = 0, bytes = 0;
126923
- const walk2 = (relative18) => {
126924
- for (const name of readdirSync31(join84(root2, relative18)).sort()) {
126925
- if (IGNORED.has(name.toLowerCase()) || !relative18 && ROOT_IGNORED.has(name.toLowerCase()))
126926
- continue;
126927
- const path2 = relative18 ? `${relative18}/${name}` : name;
126928
- if (++count > 20000)
126929
- throw new Error("Skill exceeds the preparation entry limit (20000).");
126930
- const absolute = join84(root2, path2), stat3 = lstatSync54(absolute);
126931
- if (stat3.isSymbolicLink())
126932
- throw new Error(`Skill contains a symlink: ${path2}. No files were changed.`);
126933
- if (stat3.isDirectory() && name === "node_modules")
126934
- continue;
126935
- hash8.update(JSON.stringify([path2, stat3.dev, stat3.ino, stat3.mode]));
126936
- if (stat3.isDirectory()) {
126937
- directories.push(path2);
126938
- walk2(path2);
126939
- } else if (stat3.isFile()) {
126940
- bytes += stat3.size;
126941
- if (bytes > 50000000)
126942
- throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
126943
- const content = readFileSync51(absolute);
126944
- if (content.length !== stat3.size)
126945
- throw new Error("Skill changed during preparation.");
126946
- files.push({ path: path2, bytes: content, mode: stat3.mode & 511 });
126947
- hash8.update(`\x00${content.length}\x00`).update(content);
126948
- } else
126949
- throw new Error(`Unsupported skill entry: ${path2}.`);
126950
- hash8.update("\x00");
126951
- }
126952
- };
126953
- walk2("");
126954
- return { files, directories, identity: hash8.digest("hex") };
126955
- }
126956
- var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
126957
- var init_prepare_skill = __esm(() => {
126958
- init_portable_skills();
126959
- init_skill_contract();
126960
- init_skill_hash();
126961
- init_skill_bundle();
126962
- init_skill_validation();
126963
- init_authoring_path();
126964
- IGNORED = new Set([".git", ".ds_store", ".system"]);
126965
- ROOT_IGNORED = new Set(["dist", "build", ".turbo", ".skills-dependency-preparation"]);
126966
- });
126967
-
126968
- // src/cli/commands/portable-skills.ts
126969
- var exports_portable_skills = {};
126970
- __export(exports_portable_skills, {
126971
- registerPortableSkillCommands: () => registerPortableSkillCommands
126972
- });
126973
- function registerPortableSkillCommands(parent) {
126974
- parent.command("prepare").argument("<name>", "Name of a skill in the local corpus").requiredOption("--version <version>", "Reviewed semantic version for this draft").option("--kind <kind>", "Explicit instruction or executable kind; required for legacy kind-less manifests").option("--dry-run", "Validate the candidate and report its hash without changing the draft", false).option("--json", "Output result as JSON", false).description("Validate a reviewed draft and update its manifest version and content hash").action(async (name, options) => {
126975
- try {
126976
- const result2 = prepareSkill(name, { ...options, kind: options.kind === undefined ? undefined : parseSkillKind2(options.kind) });
126977
- if (result2.written)
126978
- clearRegistryCache();
126979
- if (options.json)
126980
- await writeCliOutput(JSON.stringify(result2, null, 2));
126981
- else {
126982
- console.log(source_default.green(`${result2.written ? "Prepared" : options.dryRun ? "Previewed" : "Already prepared"} ${result2.name}@${result2.version} (${result2.kind})`));
126983
- console.log(source_default.dim(` Content hash: ${result2.contentHash}`));
126984
- console.log(source_default.dim(` Manifest: ${result2.path}/skill.json`));
126985
- if (result2.written)
126986
- console.log(source_default.dim(` Publish: skills push ${result2.name}`));
126987
- }
126988
- } catch (error61) {
126989
- if (options.json)
126990
- await writeCliOutput(JSON.stringify({ error: error61.message }, null, 2));
126991
- else
126992
- console.error(source_default.red(error61.message));
126993
- process.exitCode = 1;
126994
- }
126995
- });
126996
- parent.command("new").alias("scaffold").argument("<name>", "Portable skill name").option("--description <description>", "Short description of what the skill does").option("--kind <kind>", "Skill class: executable (default) or instruction (prose only)", "executable").option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--json", "Output result as JSON", false).description("Scaffold a portable skill under ~/.hasna/skills/installed/<name>").action((name, options) => {
126997
- try {
126998
- const kind = parseSkillKind2(options.kind);
126999
- const result2 = scaffoldPortableSkill(name, {
127000
- description: options.description,
127001
- overwrite: options.overwrite,
127002
- kind
127003
- });
127004
- clearRegistryCache();
127005
- if (options.json) {
127006
- console.log(JSON.stringify({ ...result2, manifest: result2.manifest }, null, 2));
127007
- return;
127008
- }
127009
- console.log(source_default.green(`\u2713 Created ${kind} skill '${result2.name}'`));
127010
- console.log(source_default.dim(` Path: ${result2.path}`));
127011
- if (kind === "instruction") {
127012
- console.log(source_default.dim(` Edit the prose in: ${result2.path}/SKILL.md`));
127013
- console.log(source_default.dim(` Instruction skills are consumed by agents, not run with 'skills run'.`));
127014
- } else {
127015
- console.log(source_default.dim(` Agent instructions: ${result2.path}/AGENTS.md`));
127016
- console.log(source_default.dim(` Validate: skills validate ${result2.name}`));
127017
- console.log(source_default.dim(` Run: skills run ${result2.name} --help`));
127018
- }
127019
- } catch (error61) {
127020
- writePortableError(error61, options.json);
127021
- }
127022
- });
127023
- parent.command("port").alias("add").argument("<path>", "Existing skill folder (or parent directory with --all) to import").option("--name <name>", "Override the imported skill name").option("--all", "Import every skill subfolder of <path> (bulk import)", false).option("-o, --overwrite", "Replace an existing portable skill folder", false).option("--allow-shadow", "Allow an imported name that shadows a bundled official skill", false).option("--json", "Output result as JSON", false).description("Import an existing skill folder into ~/.hasna/skills/installed/<name>").action((path2, options) => {
127024
- if (options.all) {
127025
- handleBulkPort(path2, options);
127026
- return;
127027
- }
127028
- try {
127029
- const result2 = portPortableSkill(path2, {
127030
- name: options.name,
127031
- overwrite: options.overwrite,
127032
- allowShadow: options.allowShadow
127033
- });
127034
- clearRegistryCache();
127035
- const validation = validatePortableSkillDirectory(result2.name, result2.path);
127036
- const payload = { ...result2, valid: validation.valid, issues: validation.issues, warnings: validation.warnings };
127037
- if (!validation.valid)
127038
- process.exitCode = 1;
127039
- if (options.json) {
127040
- console.log(JSON.stringify(payload, null, 2));
127041
- return;
127042
- }
127043
- console.log(validation.valid ? source_default.green(`\u2713 Ported portable skill '${result2.name}'`) : source_default.yellow(`Validation failed for imported files '${result2.name}'; correct the folder before using it.`));
127044
- console.log(source_default.dim(` Path: ${result2.path}`));
127045
- console.log(source_default.dim(` Valid: ${validation.valid ? "yes" : "no"}`));
127046
- if (!validation.valid) {
127047
- for (const issue2 of validation.issues)
127048
- console.log(source_default.red(` \u2022 ${issue2.message}`));
127049
- }
127050
- } catch (error61) {
127051
- writePortableError(error61, options.json);
127052
- }
127053
- });
127054
- }
127055
- function handleBulkPort(path2, options) {
127056
- if (options.name) {
127057
- writePortableError(new Error("--name cannot be used with --all"), options.json);
127058
- return;
127059
- }
127060
- try {
127061
- const summary = portPortableSkillDirectory(path2, { overwrite: options.overwrite, allowShadow: options.allowShadow });
127062
- clearRegistryCache();
127063
- if (options.json) {
127064
- console.log(JSON.stringify(summary, null, 2));
127065
- if (summary.failed > 0)
127066
- process.exitCode = 1;
127067
- return;
127068
- }
127069
- console.log(source_default.green(`\u2713 Imported ${summary.succeeded}/${summary.total} skill(s) from ${summary.root}`));
127070
- for (const entry of summary.imported)
127071
- console.log(source_default.dim(` \u2022 ${entry.name} \u2192 ${entry.path}`));
127072
- if (summary.skipped.length > 0) {
127073
- console.log(source_default.yellow(` Skipped ${summary.skipped.length}:`));
127074
- for (const entry of summary.skipped)
127075
- console.log(source_default.yellow(` \u2022 ${entry.sourcePath}: ${entry.reason}`));
127076
- process.exitCode = 1;
127077
- }
127078
- } catch (error61) {
127079
- writePortableError(error61, options.json);
127080
- }
127081
- }
127082
- function parseSkillKind2(value) {
127083
- if (value === undefined || value === "executable")
127084
- return "executable";
127085
- if (value === "instruction")
127086
- return "instruction";
127087
- throw new Error(`Invalid --kind '${value}'. Use 'executable' or 'instruction'.`);
127088
- }
127089
- function writePortableError(error61, json3) {
127090
- const message = error61.message;
127091
- if (json3)
127092
- console.log(JSON.stringify({ error: message }, null, 2));
127093
- else
127094
- console.error(source_default.red(message));
127095
- process.exitCode = 1;
127096
- }
127097
- var init_portable_skills2 = __esm(() => {
127098
- init_source();
127099
- init_registry();
127100
- init_prepare_skill();
127101
- init_portable_skills();
127102
- });
127103
-
127104
127430
  // src/cli/commands/metadata.ts
127105
127431
  var exports_metadata = {};
127106
127432
  __export(exports_metadata, {