@hasna/skills 0.10.92 → 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.92",
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;
@@ -65996,8 +66052,8 @@ function resolveDiscovery(options, exclusion) {
65996
66052
  const configPath = exclusion?.nativeHome === undefined ? agentDiscoveryConfigPath(home, agent) : join46(nativeHome, agent === "codex" ? "config.toml" : "settings.json");
65997
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"]);
65998
66054
  const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
65999
- const unresolved = (detail) => {
66000
- 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);
66001
66057
  };
66002
66058
  let opaqueClosureVerified = false;
66003
66059
  const verifyOpaqueClosure = (declarations) => {
@@ -66011,6 +66067,17 @@ function resolveDiscovery(options, exclusion) {
66011
66067
  verifyDiscovery(retained, undefined, undefined, undefined, exclusion?.narrowed, canonical9(configPath));
66012
66068
  opaqueClosureVerified = true;
66013
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
+ };
66014
66081
  if (exclusion) {
66015
66082
  if (configText !== null && agent !== "codex" && agent !== "hermes")
66016
66083
  parseNativeJsonObject(configText);
@@ -66082,7 +66149,7 @@ function resolveDiscovery(options, exclusion) {
66082
66149
  unresolved(`non-enumerable ${label} require a current settings review`);
66083
66150
  };
66084
66151
  if (hasHooks)
66085
- verifyCurrentOpaqueSettings("hooks");
66152
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("hooks"));
66086
66153
  if (agent === "claude") {
66087
66154
  if (config.env !== undefined) {
66088
66155
  if (!object6(config.env) || Object.keys(config.env).length > 512)
@@ -66095,7 +66162,7 @@ function resolveDiscovery(options, exclusion) {
66095
66162
  if (hasFunctionHookEnvironment) {
66096
66163
  if (!["0", "1"].includes(config.env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS))
66097
66164
  unresolved("malformed Claude env.CLAUDE_CODE_ENABLE_FUNCTION_HOOKS flag");
66098
- verifyCurrentOpaqueSettings("function-hook environment controls");
66165
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("function-hook environment controls"));
66099
66166
  }
66100
66167
  }
66101
66168
  if (config.pluginConfigs !== undefined) {
@@ -66122,7 +66189,7 @@ function resolveDiscovery(options, exclusion) {
66122
66189
  }
66123
66190
  hasPluginConfigs = Object.keys(config.pluginConfigs).length > 0;
66124
66191
  if (hasPluginConfigs)
66125
- verifyCurrentOpaqueSettings("plugin configurations");
66192
+ reviewRemedy(canonical9(configPath), () => verifyCurrentOpaqueSettings("plugin configurations"));
66126
66193
  }
66127
66194
  if (config.enabledPlugins !== undefined && !object6(config.enabledPlugins))
66128
66195
  unresolved("enabledPlugins must be an object");
@@ -66234,7 +66301,7 @@ function resolveDiscovery(options, exclusion) {
66234
66301
  if (Object.keys(config.enabledPlugins ?? config.plugins ?? config.extensions ?? {}).length > 512)
66235
66302
  unresolved("unbounded native registrations");
66236
66303
  if (hasHooks || hasPluginConfigs || hasFunctionHookEnvironment)
66237
- verifyOpaqueClosure([]);
66304
+ reviewRemedy(canonical9(configPath), () => verifyOpaqueClosure([]));
66238
66305
  }
66239
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))
66240
66307
  throw new Error("Invalid --discovery-inputs version or agents");
@@ -66487,40 +66554,41 @@ function resolveDiscovery(options, exclusion) {
66487
66554
  if (exclusion && Object.keys(manifest).some((key2) => /skill|plugin|hook|marketplace|extension|instruction/i.test(key2) && !["skills", "hooks"].includes(key2)))
66488
66555
  unresolved("unknown plugin discovery controls");
66489
66556
  const hooks = witness(join46(root2, "hooks/hooks.json"));
66490
- if (manifest.hooks !== undefined || hooks !== null) {
66491
- if (!exclusion || exclusion.retained?.method !== "reviewed")
66492
- unresolved("plugin hooks require a separate no-skill-injection review");
66493
- const required = [manifestPath, ...hooks !== null ? [join46(root2, "hooks/hooks.json")] : []];
66494
- const scoped2 = agent === "codex" && codex !== undefined ? [] : undefined;
66495
- if (manifest.hooks !== undefined) {
66496
- if (scoped2 && manifest.hooks && typeof manifest.hooks === "object" && !Array.isArray(manifest.hooks)) {
66497
- const inline = codexPluginHookHandlers(manifest.hooks);
66498
- if (!inline)
66499
- unresolved("unsupported inline plugin hooks");
66500
- scoped2.push(inline);
66501
- } else {
66502
- if (typeof manifest.hooks !== "string")
66503
- unresolved("unsupported plugin hooks target");
66504
- const hook = resolve29(root2, manifest.hooks);
66505
- if (!hook.startsWith(root2 + sep14))
66506
- unresolved("plugin hooks target escapes its root");
66507
- required.push(hook);
66508
- if (scoped2)
66509
- scoped2.push(readCodexHookDeclaration(witness(hook), hook));
66510
- }
66511
- }
66512
- if (scoped2 && hooks !== null)
66513
- scoped2.push(readCodexHookDeclaration(hooks, join46(root2, "hooks/hooks.json")));
66514
- for (const path of required) {
66515
- const saved = exclusion.retained.sources.filter((source) => source.path === canonical9(path) && source.sha256 !== null && source.format === undefined && source.fields === undefined);
66516
- if (saved.length ? saved.some((source) => projected(source, undefined, budget3) !== source.sha256) : !(scoped2 && reviewedInstalledCopy(root2, path, codex)))
66517
- unresolved("plugin hooks require current independently reviewed sources");
66518
- }
66519
- if (scoped2 && scoped2.every((item) => item !== null && item.commands === 0)) {
66520
- verifyCodexPluginHookClosure(root2, codex, codex.source, new Set(scoped2.flatMap((item) => [...item.servers])), required.map((path) => canonical9(path)));
66521
- } else
66522
- verifyOpaqueClosure(required.map((path) => canonical9(path)));
66523
- }
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
+ });
66524
66592
  const commands = join46(root2, "commands");
66525
66593
  safe6(commands);
66526
66594
  if (agent === "claude" && (manifest.commands !== undefined || lstatSync35(commands, { throwIfNoEntry: false })?.isDirectory() && entries(commands).length))
@@ -66630,7 +66698,7 @@ function resolveDiscovery(options, exclusion) {
66630
66698
  unresolved("native discovery roots exceed their bound");
66631
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))) } : {} };
66632
66700
  }
66633
- 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) => {
66634
66702
  try {
66635
66703
  return realpathSync12(path);
66636
66704
  } catch {
@@ -66667,6 +66735,18 @@ var init_agent_discovery = __esm(() => {
66667
66735
  init_codex_semantic_cache_witness();
66668
66736
  init_agent_discovery_directories();
66669
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
+ };
66670
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"]);
66671
66751
  CLAUDE_CONTROL_FILES = new Set(["settings.json", "settings.local.json", "installed_plugins.json", "known_marketplaces.json"]);
66672
66752
  ADOPTABLE_SETTINGS_MATCHES = Object.freeze(["exact", "screen-reader-receipt"]);
@@ -66705,6 +66785,11 @@ function physicalPath(path) {
66705
66785
  }
66706
66786
  return resolve30(realpathSync13(cursor), ...suffix);
66707
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
+ }
66708
66793
  function contains2(root2, path) {
66709
66794
  const boundary = root2.endsWith(sep15) ? root2 : root2 + sep15;
66710
66795
  return path === root2 || path.startsWith(boundary);
@@ -66805,11 +66890,18 @@ function assertPortableAuthoringPath(path, options = {}) {
66805
66890
  if (native) {
66806
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.`);
66807
66892
  }
66808
- const roots = configuredRoots(home, options.homeDir !== undefined);
66809
- for (const value of selected) {
66810
- if (physicalPath(value.path) === physicalPath(join47(home, `.${value.agent}`)))
66811
- continue;
66812
- 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;
66813
66905
  }
66814
66906
  for (const root2 of roots) {
66815
66907
  if (candidates.some((candidate2) => [resolve30(root2), physicalPath(root2)].some((nativeRoot) => contains2(nativeRoot, candidate2) || contains2(candidate2, nativeRoot)))) {
@@ -122461,10 +122553,403 @@ var init_completion = __esm(() => {
122461
122553
  categoryNames = CATEGORIES.map((c) => c);
122462
122554
  });
122463
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
+
122464
122949
  // src/lib/portable-snapshot-filter.ts
122465
- import { readdirSync as readdirSync27, statSync as statSync23 } from "fs";
122950
+ import { readdirSync as readdirSync28, statSync as statSync23 } from "fs";
122466
122951
  import { homedir as homedir12 } from "os";
122467
- import { join as join73, sep as sep21 } from "path";
122952
+ import { join as join74, sep as sep22 } from "path";
122468
122953
  function isExcludedSkillFileName(fileName) {
122469
122954
  if (EXCLUDE_FILE_NAMES.has(fileName)) {
122470
122955
  return true;
@@ -122487,27 +122972,27 @@ function isPortableWithinSkill(relativeParts) {
122487
122972
  function homePathFor(definition, homesRoot) {
122488
122973
  const home = homesRoot ?? homedir12();
122489
122974
  if (definition.subClass === "skills" || definition.subClass === "custom") {
122490
- return join73(skillsDataRootForHome(home), definition.name);
122975
+ return join74(skillsDataRootForHome(home), definition.name);
122491
122976
  }
122492
122977
  if (definition.agent === "opencode") {
122493
- return join73(home, ".config", "opencode", "skills");
122978
+ return join74(home, ".config", "opencode", "skills");
122494
122979
  }
122495
- return join73(home, `.${definition.agent}`, "skills");
122980
+ return join74(home, `.${definition.agent}`, "skills");
122496
122981
  }
122497
122982
  function destinationFor(definition, stationId, relativePath) {
122498
- const category = definition.subClass === "agent-homes" ? join73("agent-homes", definition.agent ?? "") : definition.name;
122499
- 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));
122500
122985
  }
122501
122986
  function walkEntries(absoluteRoot) {
122502
122987
  let entries;
122503
122988
  try {
122504
- entries = readdirSync27(absoluteRoot, { withFileTypes: true });
122989
+ entries = readdirSync28(absoluteRoot, { withFileTypes: true });
122505
122990
  } catch {
122506
122991
  return [];
122507
122992
  }
122508
122993
  const output2 = [];
122509
122994
  for (const entry of entries) {
122510
- const childFull = join73(absoluteRoot, entry.name);
122995
+ const childFull = join74(absoluteRoot, entry.name);
122511
122996
  if (entry.isSymbolicLink()) {
122512
122997
  output2.push({ kind: "symlink", relativePath: entry.name, fullPath: childFull });
122513
122998
  continue;
@@ -122518,7 +123003,7 @@ function walkEntries(absoluteRoot) {
122518
123003
  }
122519
123004
  const nested = walkEntries(childFull);
122520
123005
  for (const item of nested) {
122521
- output2.push({ ...item, relativePath: join73(entry.name, item.relativePath) });
123006
+ output2.push({ ...item, relativePath: join74(entry.name, item.relativePath) });
122522
123007
  }
122523
123008
  continue;
122524
123009
  }
@@ -122611,22 +123096,22 @@ var init_portable_snapshot_filter = __esm(() => {
122611
123096
  });
122612
123097
 
122613
123098
  // src/lib/station-snapshot.ts
122614
- import { createHash as createHash47 } from "crypto";
123099
+ import { createHash as createHash48 } from "crypto";
122615
123100
  import {
122616
123101
  copyFileSync as copyFileSync2,
122617
- mkdirSync as mkdirSync30,
122618
- readFileSync as readFileSync45,
123102
+ mkdirSync as mkdirSync31,
123103
+ readFileSync as readFileSync46,
122619
123104
  statSync as statSync24,
122620
- writeFileSync as writeFileSync28
123105
+ writeFileSync as writeFileSync29
122621
123106
  } from "fs";
122622
- 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";
122623
123108
  function validateStationId(stationId) {
122624
123109
  if (!/^[a-z0-9-]+$/.test(stationId)) {
122625
123110
  throw new StationSnapshotError("INVALID_STATION", `station id must be a slug, got: ${stationId}`);
122626
123111
  }
122627
123112
  }
122628
123113
  function sha256File(filePath) {
122629
- return createHash47("sha256").update(readFileSync45(filePath)).digest("hex");
123114
+ return createHash48("sha256").update(readFileSync46(filePath)).digest("hex");
122630
123115
  }
122631
123116
  function scanHome(definition, homesRoot) {
122632
123117
  const homePath = homePathFor(definition, homesRoot);
@@ -122634,7 +123119,7 @@ function scanHome(definition, homesRoot) {
122634
123119
  const portable = [];
122635
123120
  const skipped = [];
122636
123121
  for (const entry of entries) {
122637
- const relativeParts = entry.relativePath.split(sep22);
123122
+ const relativeParts = entry.relativePath.split(sep23);
122638
123123
  const fileName = relativeParts[relativeParts.length - 1];
122639
123124
  if (entry.kind === "symlink") {
122640
123125
  skipped.push({ relativePath: entry.relativePath, reason: "symlink" });
@@ -122697,7 +123182,7 @@ function humanHomes(scanned) {
122697
123182
  }));
122698
123183
  }
122699
123184
  function writeStationSnapshot(options) {
122700
- const repoRoot = resolve39(options.repoRoot ?? process.cwd());
123185
+ const repoRoot = resolve40(options.repoRoot ?? process.cwd());
122701
123186
  const { scanned, plans, totalBytes } = planStationSnapshot(options);
122702
123187
  const manifestFiles = plans.map((plan) => ({
122703
123188
  relativePath: plan.source.relativePath,
@@ -122723,9 +123208,9 @@ function writeStationSnapshot(options) {
122723
123208
  const conflicts = [];
122724
123209
  const untouched = [];
122725
123210
  for (const plan of plans) {
122726
- const destination = resolve39(repoRoot, plan.destination);
122727
- const destinationRelative = relative15(repoRoot, destination);
122728
- 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)) {
122729
123214
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
122730
123215
  }
122731
123216
  let existingDigest = null;
@@ -122746,8 +123231,8 @@ function writeStationSnapshot(options) {
122746
123231
  }
122747
123232
  let written = 0;
122748
123233
  for (const plan of untouched) {
122749
- const destination = resolve39(repoRoot, plan.destination);
122750
- mkdirSync30(dirname47(destination), { recursive: true });
123234
+ const destination = resolve40(repoRoot, plan.destination);
123235
+ mkdirSync31(dirname48(destination), { recursive: true });
122751
123236
  copyFileSync2(plan.source.fullPath, destination);
122752
123237
  written += 1;
122753
123238
  }
@@ -122765,9 +123250,9 @@ function writeStationSnapshot(options) {
122765
123250
  },
122766
123251
  files: manifestFiles
122767
123252
  };
122768
- const manifestPath = resolve39(repoRoot, "resources", options.stationId, "skills", "sync-manifest.json");
122769
- mkdirSync30(dirname47(manifestPath), { recursive: true });
122770
- 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)}
122771
123256
  `);
122772
123257
  return {
122773
123258
  ...base3,
@@ -122798,8 +123283,8 @@ var exports_create_sync_config = {};
122798
123283
  __export(exports_create_sync_config, {
122799
123284
  registerCreateSync: () => registerCreateSync
122800
123285
  });
122801
- import { existsSync as existsSync35 } from "fs";
122802
- import { join as join74 } from "path";
123286
+ import { existsSync as existsSync36 } from "fs";
123287
+ import { join as join75 } from "path";
122803
123288
  function registerCreateSync(parent) {
122804
123289
  const configCmd = parent.command("config").description("Manage skills configuration");
122805
123290
  configCmd.command("show", { isDefault: true }).option("--json", "Output as JSON", false).description("Show current merged configuration").action((options) => {
@@ -122879,15 +123364,15 @@ function registerCreateSync(parent) {
122879
123364
  const pp = getConfigPath("project");
122880
123365
  if (options.json) {
122881
123366
  console.log(JSON.stringify({
122882
- global: { path: gp, exists: existsSync35(gp) },
122883
- project: { path: pp, exists: existsSync35(pp) }
123367
+ global: { path: gp, exists: existsSync36(gp) },
123368
+ project: { path: pp, exists: existsSync36(pp) }
122884
123369
  }, null, 2));
122885
123370
  return;
122886
123371
  }
122887
- console.log(`${source_default.cyan("global")}: ${gp}${existsSync35(gp) ? source_default.green(" (exists)") : source_default.dim(" (not found)")}`);
122888
- 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)")}`);
122889
123374
  });
122890
- 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));
122891
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));
122892
123377
  }
122893
123378
  function handleCreate(name, options) {
@@ -122909,7 +123394,7 @@ function handleCreate(name, options) {
122909
123394
  console.log(source_default.green(`\u2713 Created custom skill '${result2.name}' at ${result2.path}`));
122910
123395
  console.log(source_default.dim(` Category: ${result2.manifest.category}`));
122911
123396
  console.log(source_default.dim(` Tags: ${result2.manifest.tags?.join(", ")}`));
122912
- 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")}`);
122913
123398
  console.log(` ${source_default.cyan("Prepare:")} skills prepare ${result2.name} --version <new-semver>`);
122914
123399
  }
122915
123400
  } catch (error61) {
@@ -123188,6 +123673,7 @@ var init_create_sync_config = __esm(() => {
123188
123673
  init_config();
123189
123674
  init_auth_store();
123190
123675
  init_portable_skills();
123676
+ init_portable_skills2();
123191
123677
  init_registry();
123192
123678
  init_profile_sync();
123193
123679
  init_managed_policy();
@@ -123198,9 +123684,9 @@ var init_create_sync_config = __esm(() => {
123198
123684
  });
123199
123685
 
123200
123686
  // src/lib/claude-hook-disable.ts
123201
- import { createHash as createHash48, randomUUID as randomUUID15 } from "crypto";
123202
- 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";
123203
- 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";
123204
123690
  import { KernelLock as KernelLock2 } from "@hasna/contracts/kernel-lock";
123205
123691
  function fail6(reason2) {
123206
123692
  throw new Error(`CLAUDE_HOOK_DISABLE_${reason2}`);
@@ -123213,26 +123699,26 @@ function serialize(value) {
123213
123699
  return value.kind === "string" ? JSON.stringify(value.value) : value.value;
123214
123700
  }
123215
123701
  function path(value) {
123216
- if (!isAbsolute44(value) || resolve40(value) !== value || /[\x00-\x1f\x7f]/.test(value))
123702
+ if (!isAbsolute45(value) || resolve41(value) !== value || /[\x00-\x1f\x7f]/.test(value))
123217
123703
  fail6("PATH_INVALID");
123218
123704
  }
123219
123705
  function parents3(file2) {
123220
123706
  safeParents(file2);
123221
123707
  const observed = [];
123222
- for (let directory3 = dirname48(file2);; directory3 = dirname48(directory3)) {
123223
- const stat3 = lstatSync47(directory3, { bigint: true });
123224
- 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) });
123225
123711
  observed.push([directory3, identity6(stat3)]);
123226
- if (directory3 === dirname48(directory3))
123712
+ if (directory3 === dirname49(directory3))
123227
123713
  break;
123228
123714
  }
123229
123715
  return () => {
123230
123716
  safeParents(file2);
123231
123717
  for (const [directory3, expected] of observed) {
123232
- const stat3 = lstatSync47(directory3, { bigint: true });
123718
+ const stat3 = lstatSync48(directory3, { bigint: true });
123233
123719
  if (identity6(stat3) !== expected)
123234
123720
  fail6("PARENT_CHANGED");
123235
- assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname48(file2) });
123721
+ assertNativePathAcl(directory3, stat3, { allowDenyOnly: true, creationParent: directory3 === dirname49(file2) });
123236
123722
  }
123237
123723
  };
123238
123724
  }
@@ -123240,24 +123726,24 @@ function executable2(command, expectedSha256) {
123240
123726
  path(command);
123241
123727
  if (!digest6(expectedSha256))
123242
123728
  fail6("COMMAND_DIGEST_INVALID");
123243
- const parentCheck = parents3(command), alias = lstatSync47(command, { bigint: true });
123729
+ const parentCheck = parents3(command), alias = lstatSync48(command, { bigint: true });
123244
123730
  const resolved = resolveLauncherCommand(command);
123245
123731
  if (alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
123246
123732
  fail6("COMMAND_UNVERIFIED");
123247
123733
  const commandFile = snapshot(resolved.physical, false, { readOnlyPackage: true });
123248
123734
  if (commandFile.sha256 !== expectedSha256)
123249
123735
  fail6("COMMAND_CHANGED");
123250
- const entry = resolved.entry, packageRoot = dirname48(dirname48(entry));
123251
- 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)
123252
123738
  fail6("COMMAND_UNVERIFIED");
123253
123739
  const entryFile = snapshot(entry, false, { readOnlyPackage: true });
123254
- const manifest = snapshot(join75(packageRoot, "package.json"), false, { readOnlyPackage: true });
123740
+ const manifest = snapshot(join76(packageRoot, "package.json"), false, { readOnlyPackage: true });
123255
123741
  const parsed = parseNativeJsonObject(manifest.text);
123256
123742
  if (string6(field3(parsed, "name")) !== "@hasna/skills" || string6(field3(field3(parsed, "bin"), "skills")) !== "bin/index.js")
123257
123743
  fail6("COMMAND_UNVERIFIED");
123258
123744
  return () => {
123259
123745
  parentCheck();
123260
- const current = lstatSync47(command, { bigint: true });
123746
+ const current = lstatSync48(command, { bigint: true });
123261
123747
  if (movedIdentity(current) !== movedIdentity(alias) || current.ctimeNs !== alias.ctimeNs || alias.isSymbolicLink() && directAliasTarget(command) !== resolved.physical)
123262
123748
  fail6("COMMAND_CHANGED");
123263
123749
  unchanged2(commandFile);
@@ -123310,13 +123796,13 @@ function disableClaudeHooks(options) {
123310
123796
  removed: [],
123311
123797
  settingsWitness: "unchanged"
123312
123798
  };
123313
- const exists = lstatSync47(options.settings, { throwIfNoEntry: false });
123799
+ const exists = lstatSync48(options.settings, { throwIfNoEntry: false });
123314
123800
  if (!exists) {
123315
123801
  if (options.expectedSettingsSha256 !== "absent" || options.expectedMode !== "absent")
123316
123802
  fail6("TARGET_CHANGED");
123317
123803
  checkCommand();
123318
123804
  checkParents();
123319
- if (lstatSync47(options.settings, { throwIfNoEntry: false }))
123805
+ if (lstatSync48(options.settings, { throwIfNoEntry: false }))
123320
123806
  fail6("TARGET_CHANGED");
123321
123807
  return base3;
123322
123808
  }
@@ -123379,7 +123865,7 @@ function disableClaudeHooks(options) {
123379
123865
  if (options.preimageDir === options.settings || options.preimageDir.startsWith(options.settings + "/"))
123380
123866
  fail6("PREIMAGE_INVALID");
123381
123867
  const checkPreimageParents = parents3(options.preimageDir);
123382
- 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" });
123383
123869
  let exchanged = false, preimage, displaced, receipt;
123384
123870
  try {
123385
123871
  if (!lock.trySync(1000))
@@ -123389,15 +123875,15 @@ function disableClaudeHooks(options) {
123389
123875
  checkParents();
123390
123876
  checkPreimageParents();
123391
123877
  unchanged2(before2);
123392
- mkdirSync31(options.preimageDir, { mode: 448 });
123393
- if ((lstatSync47(options.preimageDir).mode & 511) !== 448)
123878
+ mkdirSync32(options.preimageDir, { mode: 448 });
123879
+ if ((lstatSync48(options.preimageDir).mode & 511) !== 448)
123394
123880
  fail6("PREIMAGE_INVALID");
123395
- preimage = join75(options.preimageDir, "settings.preimage.json");
123881
+ preimage = join76(options.preimageDir, "settings.preimage.json");
123396
123882
  save(preimage, before2.bytes);
123397
123883
  const preserved = snapshot(preimage, true), checkPreservationRoot = parents3(preimage);
123398
123884
  if (preserved.sha256 !== before2.sha256 || !preserved.bytes.equals(before2.bytes))
123399
123885
  fail6("PRESERVATION_FAILED");
123400
- displaced = join75(dirname48(options.settings), `.settings.skills-disable-${randomUUID15()}.json`);
123886
+ displaced = join76(dirname49(options.settings), `.settings.skills-disable-${randomUUID16()}.json`);
123401
123887
  save(displaced, after2);
123402
123888
  const fd = openSync32(displaced, constants32.O_RDONLY | constants32.O_NOFOLLOW);
123403
123889
  try {
@@ -123411,7 +123897,7 @@ function disableClaudeHooks(options) {
123411
123897
  assertNativePathAcl(displaced, staged.stat);
123412
123898
  if (staged.sha256 !== result2.plannedAfterSha256)
123413
123899
  fail6("STAGING_CHANGED");
123414
- receipt = join75(options.preimageDir, "receipt.json");
123900
+ receipt = join76(options.preimageDir, "receipt.json");
123415
123901
  const preparationBytes = `${JSON.stringify({
123416
123902
  ...result2,
123417
123903
  state: "prepared",
@@ -123447,14 +123933,14 @@ function disableClaudeHooks(options) {
123447
123933
  unchanged2(preserved);
123448
123934
  if (movedIdentity(displacedOriginal.stat) !== movedIdentity(before2.stat) || displacedOriginal.sha256 !== before2.sha256 || movedIdentity(current.stat) !== movedIdentity(staged.stat) || current.sha256 !== staged.sha256)
123449
123935
  fail6("POST_WRITE_CHANGED");
123450
- 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);
123451
123937
  try {
123452
123938
  fsyncSync11(directory3);
123453
123939
  } finally {
123454
123940
  closeSync32(directory3);
123455
123941
  }
123456
123942
  const applied = { ...result2, state: "applied", mutationOccurred: true, afterSha256: staged.sha256, preimage, displaced, receipt };
123457
- const completed = join75(options.preimageDir, "completed.json");
123943
+ const completed = join76(options.preimageDir, "completed.json");
123458
123944
  const completionBytes = `${JSON.stringify({ ...applied, receipt: completed })}
123459
123945
  `;
123460
123946
  save(completed, completionBytes);
@@ -123486,7 +123972,7 @@ function disableClaudeHooks(options) {
123486
123972
  }
123487
123973
  }
123488
123974
  }
123489
- 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}`;
123490
123976
  var init_claude_hook_disable = __esm(() => {
123491
123977
  init_agent_adapters();
123492
123978
  init_claude_settings_witness();
@@ -123497,8 +123983,8 @@ var init_claude_hook_disable = __esm(() => {
123497
123983
  });
123498
123984
 
123499
123985
  // src/lib/agent-install-selection.ts
123500
- import { lstatSync as lstatSync48 } from "fs";
123501
- 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";
123502
123988
  import { homedir as homedir13 } from "os";
123503
123989
  function detectedIntegrationAgents(options = {}) {
123504
123990
  const home = options.home ?? homedir13(), env3 = options.env ?? process.env;
@@ -123511,7 +123997,7 @@ function detectedIntegrationAgents(options = {}) {
123511
123997
  throw new Error("Invalid existing bridge agent inventory");
123512
123998
  const present2 = (path2) => {
123513
123999
  try {
123514
- return lstatSync48(path2, { throwIfNoEntry: false }) !== undefined;
124000
+ return lstatSync49(path2, { throwIfNoEntry: false }) !== undefined;
123515
124001
  } catch {
123516
124002
  throw new Error("Agent presence cannot be inspected; review native configuration before hook installation");
123517
124003
  }
@@ -123521,13 +124007,13 @@ function detectedIntegrationAgents(options = {}) {
123521
124007
  return true;
123522
124008
  if (agent === "sumi") {
123523
124009
  const xdg = env3.XDG_CONFIG_HOME;
123524
- const xdgSumi = xdg?.trim() ? resolve41(options.cwd ?? process.cwd(), xdg === "~" ? home : xdg.startsWith("~/") || xdg.startsWith("~\\") ? resolve41(home, xdg.slice(2)) : xdg, "sumi") : null;
123525
- 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);
123526
124012
  }
123527
124013
  if (agent === "hermes" && env3.HERMES_HOME !== undefined)
123528
124014
  return true;
123529
124015
  const adapter = AGENT_ADAPTERS[agent];
123530
- 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))));
123531
124017
  });
123532
124018
  if (!selected.length && !options.allowEmpty)
123533
124019
  throw new Error(`No configured or detected agents; select --agent <agent> explicitly (${INTEGRATION_AGENTS.join(", ")}).`);
@@ -123541,11 +124027,11 @@ var init_agent_install_selection = __esm(() => {
123541
124027
  });
123542
124028
 
123543
124029
  // src/lib/codex-hook-trust-identity.ts
123544
- import { lstatSync as lstatSync49, realpathSync as realpathSync22 } from "fs";
123545
- 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";
123546
124032
  import { isDeepStrictEqual as isDeepStrictEqual20 } from "util";
123547
124033
  function commandPhysical(command) {
123548
- 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);
123549
124035
  const physical = realpathSync22(command);
123550
124036
  let spelled;
123551
124037
  try {
@@ -123556,7 +124042,7 @@ function commandPhysical(command) {
123556
124042
  safeParents(spelled);
123557
124043
  let direct = false;
123558
124044
  try {
123559
- const leaf = lstatSync49(spelled);
124045
+ const leaf = lstatSync50(spelled);
123560
124046
  direct = leaf.isSymbolicLink() ? trustedAncestorOwner(leaf.uid) && directAliasTarget(spelled) === physical : spelled === physical;
123561
124047
  } catch {}
123562
124048
  need8(direct, COMMAND_ALIAS_UNSAFE);
@@ -123568,7 +124054,7 @@ function assertPinnedRuntime(runtime) {
123568
124054
  } catch {
123569
124055
  need8(false, "SKILLS_LAUNCHER_UNVERIFIED");
123570
124056
  }
123571
- need8(resolve42(runtime) === runtime, "SKILLS_LAUNCHER_UNVERIFIED");
124057
+ need8(resolve43(runtime) === runtime, "SKILLS_LAUNCHER_UNVERIFIED");
123572
124058
  safeParents(runtime);
123573
124059
  }
123574
124060
  function skillsCommandTarget(command) {
@@ -123600,12 +124086,12 @@ function targetUnchanged(command, target) {
123600
124086
  assertPinnedRuntime(target.runtime);
123601
124087
  }
123602
124088
  function inspectRecordedSkillsCli(command, expected) {
123603
- need8(isAbsolute45(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
124089
+ need8(isAbsolute46(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
123604
124090
  const target = skillsCommandTarget(command), resolved = target.entry;
123605
124091
  const cli = snapshot(resolved, false, { readOnlyPackage: true });
123606
- 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 });
123607
124093
  const pkg = JSON.parse(manifest.text);
123608
- 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");
123609
124095
  need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
123610
124096
  const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
123611
124097
  need8(isDeepStrictEqual20(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
@@ -123619,16 +124105,16 @@ function bindSkillsCli(command, reviewed) {
123619
124105
  const normal = Bun.which("skills", { PATH: process.env.PATH });
123620
124106
  need8(normal, "SKILLS_COMMAND_UNAVAILABLE");
123621
124107
  const normalTarget = skillsCommandTarget(normal), resolved = normalTarget.entry;
123622
- const bound = isAbsolute45(command) ? command : command === "skills" ? normal : undefined;
124108
+ const bound = isAbsolute46(command) ? command : command === "skills" ? normal : undefined;
123623
124109
  need8(bound, "SKILLS_COMMAND_MISMATCH");
123624
124110
  const boundTarget = skillsCommandTarget(bound);
123625
124111
  need8(boundTarget.entry === resolved, "SKILLS_COMMAND_MISMATCH");
123626
124112
  const entrypoint = reviewed?.path ?? process.argv[1];
123627
124113
  need8(entrypoint && realpathSync22(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
123628
124114
  const cli = snapshot(resolved, false, { readOnlyPackage: true });
123629
- 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 });
123630
124116
  const pkg = JSON.parse(manifest.text);
123631
- 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");
123632
124118
  if (reviewed)
123633
124119
  need8(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
123634
124120
  need8((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
@@ -123647,7 +124133,7 @@ function captureSkillsCliProducerIdentity(command) {
123647
124133
  const bound = bindSkillsCli(command);
123648
124134
  const target = skillsCommandTarget(command);
123649
124135
  const cli = snapshot(target.entry, false, { readOnlyPackage: true });
123650
- 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 });
123651
124137
  const pkg = JSON.parse(manifest.text);
123652
124138
  need8(pkg.name === "@hasna/skills" && pkg.version === bound.receipt.version && target.entry === bound.receipt.path, "SKILLS_PACKAGE_MISMATCH");
123653
124139
  const launcher = target.launcher ?? cli;
@@ -123701,23 +124187,23 @@ var init_codex_hook_policy_recovery = __esm(() => {
123701
124187
  });
123702
124188
 
123703
124189
  // src/lib/agent-codex-trust.ts
123704
- import { lstatSync as lstatSync50, mkdirSync as mkdirSync32, readdirSync as readdirSync28, readFileSync as readFileSync46, realpathSync as realpathSync23, statSync as statSync25 } from "fs";
123705
- 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";
123706
124192
  import { execFileSync as execFileSync2 } from "child_process";
123707
- 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";
123708
124194
  import { homedir as homedir14 } from "os";
123709
124195
  import { isDeepStrictEqual as isDeepStrictEqual22 } from "util";
123710
124196
  function verifyReleaseArtifact(proof, skillsCli) {
123711
- 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");
123712
124198
  const tarPath = realpathSync23(proof.packageTarPath);
123713
124199
  need8(statSync25(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
123714
- const tarBytes = readFileSync46(tarPath);
123715
- need8(tarBytes.byteLength === proof.packageTarBytes && createHash49("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
123716
- const packageRoot = dirname51(dirname51(realpathSync23(skillsCli.receipt.path)));
123717
- const manifest = readFileSync46(join78(packageRoot, "package.json"));
123718
- need8(createHash49("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
123719
- need8(createHash49("sha256").update(readFileSync46(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
123720
- 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)]];
123721
124207
  for (const [member, expected] of members2) {
123722
124208
  let actual;
123723
124209
  try {
@@ -123731,26 +124217,26 @@ function verifyReleaseArtifact(proof, skillsCli) {
123731
124217
  async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
123732
124218
  let rpc;
123733
124219
  try {
123734
- const home = resolve43(options.home ?? homedir14()), dataDir3 = resolve43(options.dataDir ?? getDataDirReadOnly());
123735
- 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);
123736
124222
  need8(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
123737
- const dir = lstatSync50(journal, { throwIfNoEntry: false });
124223
+ const dir = lstatSync51(journal, { throwIfNoEntry: false });
123738
124224
  need8(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
123739
- need8(!lstatSync50(join78(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
123740
- need8(lstatSync50(join78(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
123741
- const stoppedFile = snapshot(join78(journal, "stopped.json"), true);
123742
- const intentFile = snapshot(join78(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
123743
- 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);
123744
124230
  const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
123745
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");
123746
- 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 }));
123747
124233
  const admitted = intent.admitted ?? intent.hooks;
123748
124234
  const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
123749
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 };
123750
124236
  need8(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
123751
124237
  need8(isDeepStrictEqual22(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
123752
124238
  need8(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
123753
- const policyCurrent = snapshot(join78(dataDir3, "agent-policy.json"), true);
124239
+ const policyCurrent = snapshot(join79(dataDir3, "agent-policy.json"), true);
123754
124240
  const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
123755
124241
  const recovery = claudeRecovery ?? additionsRecovery;
123756
124242
  need8(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
@@ -123767,17 +124253,17 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123767
124253
  if (supersededBinding || recovery) {
123768
124254
  need8(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
123769
124255
  const proof = options.releaseProof;
123770
- 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");
123771
124257
  }
123772
124258
  const currentConfig = snapshot(intent.configPath, true);
123773
124259
  const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
123774
- const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join78(home, ".codex"), hooksPath = join78(physicalRoot, "hooks.json");
123775
- 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");
123776
124262
  const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
123777
- need8([join78(home, ".codex/config.toml"), join78(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
123778
- 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);
123779
124265
  const hooksCurrent = snapshot(hooksPath);
123780
- 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;
123781
124267
  const managedHooks = [...intent.hooks, ...additions?.hooks ?? []];
123782
124268
  const managedKeys = managedHooks.map((h) => h.key);
123783
124269
  const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
@@ -123841,7 +124327,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123841
124327
  need8(isDeepStrictEqual22(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
123842
124328
  }
123843
124329
  for (const wanted of intent.hooks) {
123844
- 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));
123845
124331
  need8(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
123846
124332
  const h = matches2[0];
123847
124333
  const trusted = h.trustStatus === "trusted";
@@ -123890,7 +124376,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123890
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) };
123891
124377
  if (preview2)
123892
124378
  return receipt;
123893
- save(join78(journal, "receipt.json"), json2(receipt));
124379
+ save(join79(journal, "receipt.json"), json2(receipt));
123894
124380
  return receipt;
123895
124381
  } catch (error61) {
123896
124382
  const message = error61 instanceof Error && error61.message.startsWith("CODEX_HOOK_TRUST_") ? error61.message : "CODEX_HOOK_TRUST_RECONCILE_REFUSED";
@@ -123900,21 +124386,21 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
123900
124386
  }
123901
124387
  }
123902
124388
  async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRpc) {
123903
- const home = resolve43(options.home ?? homedir14());
123904
- 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);
123905
124391
  }
123906
124392
  async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123907
124393
  let rpc, journal;
123908
124394
  try {
123909
- const home = resolve43(options.home ?? homedir14()), dataDir3 = resolve43(options.dataDir ?? getDataDirReadOnly());
124395
+ const home = resolve44(options.home ?? homedir14()), dataDir3 = resolve44(options.dataDir ?? getDataDirReadOnly());
123910
124396
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home });
123911
- 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);
123912
124398
  const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
123913
124399
  const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
123914
- const physicalRoot = alias?.target ?? join78(home, ".codex"), hooksPath = join78(physicalRoot, "hooks.json"), configPath = join78(physicalRoot, "config.toml");
123915
- 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");
123916
124402
  const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
123917
- const nativeConfigPath = join78(codexHome ?? join78(home, ".codex"), "config.toml");
124403
+ const nativeConfigPath = join79(codexHome ?? join79(home, ".codex"), "config.toml");
123918
124404
  const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
123919
124405
  const declarations = JSON.parse(hooksFile.text).hooks, expected = [];
123920
124406
  for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
@@ -123923,25 +124409,25 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123923
124409
  need8(Array.isArray(groups), "DECLARATION_CHANGED");
123924
124410
  const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
123925
124411
  need8(matches2.length === 1 && isDeepStrictEqual22(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
123926
- 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 });
123927
124413
  }
123928
124414
  codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
123929
124415
  rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
123930
124416
  need8(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
123931
124417
  const list2 = async () => {
123932
124418
  const response = await rpc.request("hooks/list", { cwds: [home] });
123933
- const nativeRoots = [...new Set([physicalRoot, join78(home, ".codex")])];
123934
- 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`));
123935
124421
  const entry = response?.data?.[0];
123936
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");
123937
124423
  return response.data[0].hooks;
123938
124424
  };
123939
124425
  const admit = (hooks) => expected.map((wanted) => {
123940
- const canonicalKey = wanted.key.replace(join78(home, ".codex/hooks.json"), hooksPath);
124426
+ const canonicalKey = wanted.key.replace(join79(home, ".codex/hooks.json"), hooksPath);
123941
124427
  const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
123942
124428
  need8(matches2.length === 1, "AMBIGUOUS_IDENTITY");
123943
124429
  const h = matches2[0];
123944
- 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");
123945
124431
  need8(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
123946
124432
  need8(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
123947
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 };
@@ -123967,27 +124453,27 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
123967
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) };
123968
124454
  if (options.apply)
123969
124455
  need8(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
123970
- const parent = join78(dataDir3, "native-hook-trust");
123971
- const st = lstatSync50(parent, { throwIfNoEntry: false });
124456
+ const parent = join79(dataDir3, "native-hook-trust");
124457
+ const st = lstatSync51(parent, { throwIfNoEntry: false });
123972
124458
  if (st) {
123973
124459
  need8(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
123974
- const priorJournals = readdirSync28(parent);
124460
+ const priorJournals = readdirSync29(parent);
123975
124461
  need8(priorJournals.length < 1000, "JOURNAL_BOUND");
123976
124462
  for (const name of priorJournals) {
123977
124463
  need8(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
123978
- 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");
123979
124465
  }
123980
124466
  }
123981
124467
  if (!options.apply || !planned.length)
123982
124468
  return baseReceipt;
123983
124469
  if (!st)
123984
- mkdirSync32(parent, { mode: 448 });
123985
- journal = join78(parent, randomUUID16());
123986
- mkdirSync32(journal, { mode: 448 });
123987
- save(join78(journal, "config.before.toml"), configFile.bytes);
123988
- save(join78(journal, "hooks.before.json"), hooksFile.bytes);
123989
- save(join78(journal, "policy.before.json"), policyFile.bytes);
123990
- 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) }));
123991
124477
  need8(isDeepStrictEqual22(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
123992
124478
  checkInputs();
123993
124479
  let result2;
@@ -124003,7 +124489,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
124003
124489
  for (const h of planned)
124004
124490
  expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
124005
124491
  need8(isDeepStrictEqual22(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys4) === preservationText, "PRESERVATION_FAILED");
124006
- const savedConfig = snapshot(join78(journal, "config.before.toml"), true);
124492
+ const savedConfig = snapshot(join79(journal, "config.before.toml"), true);
124007
124493
  need8(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
124008
124494
  const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
124009
124495
  unchanged2(policyFile);
@@ -124019,13 +124505,13 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
124019
124505
  skillsCli.recheck();
124020
124506
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
124021
124507
  const receipt = { ...baseReceipt, applied: true, nativeEligible: true, bindingRefreshRequired: !!discoveryTransition, ...discoveryTransition ? { discoveryTransition } : {}, nativeConfigVersion: result2.version, journal, beforeSha256: configFile.sha256, afterSha256: after2.sha256, unrelatedSettingsAndCommentsPreserved: true };
124022
- save(join78(journal, "receipt.json"), json2(receipt));
124508
+ save(join79(journal, "receipt.json"), json2(receipt));
124023
124509
  return receipt;
124024
124510
  } catch (error61) {
124025
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";
124026
124512
  if (journal) {
124027
124513
  try {
124028
- 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) }));
124029
124515
  } catch {}
124030
124516
  }
124031
124517
  throw new Error(message);
@@ -124034,7 +124520,7 @@ async function enrollCodexNativeHooksUnlocked(options, connect, admission) {
124034
124520
  }
124035
124521
  }
124036
124522
  var json2 = (value) => JSON.stringify(value, null, 2) + `
124037
- `, 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) => {
124038
124524
  const copy = structuredClone(value);
124039
124525
  if (copy?.bridge?.commands && Object.hasOwn(copy.bridge.commands, "codex"))
124040
124526
  copy.bridge.commands.codex = "<reviewed-codex-binding>";
@@ -124058,9 +124544,9 @@ var init_agent_codex_trust = __esm(() => {
124058
124544
  });
124059
124545
 
124060
124546
  // src/lib/codex-native-trust.ts
124061
- import { createHash as createHash50 } from "crypto";
124062
- import { readFileSync as readFileSync47 } from "fs";
124063
- 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";
124064
124550
  function refuse7(detail) {
124065
124551
  throw new Error(`NATIVE_SKILL_POLICY_TRUST_REFUSED: ${detail}`);
124066
124552
  }
@@ -124079,15 +124565,15 @@ function planCodexNativeTrust(options) {
124079
124565
  refuse7("digests must be unique");
124080
124566
  if (typeof expectedPolicySha256 !== "string" || !HEX_DIGEST2.test(expectedPolicySha256))
124081
124567
  refuse7("--expected-policy-sha256 must be lowercase SHA-256 hex");
124082
- 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");
124083
124569
  assertOperatorTrustRoot(dataDir3);
124084
- const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
124085
- if (!snapshot2)
124570
+ const snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
124571
+ if (!snapshot3)
124086
124572
  refuse7("no managed policy; run skills hook install first");
124087
- const policySha256Before = sha7(snapshot2.text);
124573
+ const policySha256Before = sha7(snapshot3.text);
124088
124574
  if (policySha256Before !== expectedPolicySha256)
124089
124575
  refuse7("the managed policy bytes differ from --expected-policy-sha256; read the current file and pass its SHA-256");
124090
- const policy = snapshot2.value;
124576
+ const policy = snapshot3.value;
124091
124577
  if (!policy.bridge || typeof policy.bridge !== "object" || Array.isArray(policy.bridge))
124092
124578
  refuse7("the managed policy has no bridge binding; run skills hook install first");
124093
124579
  const current = parseCodexNativePolicyTrust(policy.bridge.codexNativePolicy);
@@ -124096,7 +124582,7 @@ function planCodexNativeTrust(options) {
124096
124582
  const next = { ...policy, bridge: { ...policy.bridge, codexNativePolicy: { executableDigests } } };
124097
124583
  assertAgentPolicyCollections(next);
124098
124584
  const after2 = serializeManagedSkillPolicy(next);
124099
- 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 } };
124100
124586
  return { schema: CODEX_NATIVE_TRUST_PLAN_SCHEMA, dataDir: dataDir3, policyPath, platform: platform2, digestsBefore: [...current.executableDigests[platform2] ?? []], digestsAfter: [...digests], policySha256Before, policySha256After: sha7(after2), plan };
124101
124587
  }
124102
124588
  function previewCodexNativeTrust(plan) {
@@ -124117,7 +124603,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
124117
124603
  const backupPath = result2.backups[0];
124118
124604
  if (result2.backups.length !== 1 || typeof backupPath !== "string")
124119
124605
  throw new Error("NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the pre-change backup was not recorded");
124120
- const backupSha256 = sha7(readFileSync47(backupPath));
124606
+ const backupSha256 = sha7(readFileSync48(backupPath));
124121
124607
  if (backupSha256 !== plan.policySha256Before)
124122
124608
  throw new Error(`NATIVE_SKILL_POLICY_TRUST_WRITE_UNVERIFIED: the backup at ${backupPath} does not read back as the pre-change policy`);
124123
124609
  receipt.backup = { path: backupPath, sha256: backupSha256, verified: true };
@@ -124131,7 +124617,7 @@ function applyCodexNativeTrust(plan, expectedPolicySha256) {
124131
124617
  receipt.readback = { policySha256: sha7(readback.text), digests, verified: true };
124132
124618
  return receipt;
124133
124619
  }
124134
- 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");
124135
124621
  var init_codex_native_trust = __esm(() => {
124136
124622
  init_config();
124137
124623
  init_managed_policy();
@@ -124143,9 +124629,9 @@ var init_codex_native_trust = __esm(() => {
124143
124629
  });
124144
124630
 
124145
124631
  // src/lib/claude-prospective-review.ts
124146
- import { createHash as createHash51 } from "crypto";
124147
- import { lstatSync as lstatSync51, readFileSync as readFileSync48, readdirSync as readdirSync29, realpathSync as realpathSync24 } from "fs";
124148
- 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";
124149
124635
  import { isDeepStrictEqual as isDeepStrictEqual23 } from "util";
124150
124636
  function hashClaudeDiscoveryBinding(binding) {
124151
124637
  return sha8(JSON.stringify(binding));
@@ -124196,7 +124682,7 @@ function keys4(value, expected, name) {
124196
124682
  refuse8(`${name} has an unsupported shape`);
124197
124683
  }
124198
124684
  function abs(path2, name) {
124199
- 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)
124200
124686
  refuse8(`${name} must be a normalized absolute path`);
124201
124687
  }
124202
124688
  function validUtcTimestamp(value) {
@@ -124204,27 +124690,27 @@ function validUtcTimestamp(value) {
124204
124690
  }
124205
124691
  function safeFile(path2, name) {
124206
124692
  abs(path2, name);
124207
- for (let at2 = dirname52(path2);; at2 = dirname52(at2)) {
124208
- const stat4 = lstatSync51(at2, { throwIfNoEntry: false });
124693
+ for (let at2 = dirname53(path2);; at2 = dirname53(at2)) {
124694
+ const stat4 = lstatSync52(at2, { throwIfNoEntry: false });
124209
124695
  if (!stat4?.isDirectory() || stat4.isSymbolicLink())
124210
124696
  refuse8(`${name} traverses a symlink or non-directory`);
124211
- if (at2 === dirname52(at2))
124697
+ if (at2 === dirname53(at2))
124212
124698
  break;
124213
124699
  }
124214
- const stat3 = lstatSync51(path2, { throwIfNoEntry: false });
124700
+ const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
124215
124701
  if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > MAX_CANDIDATE_BYTES)
124216
124702
  refuse8(`${name} is not a bounded regular file`);
124217
- return readFileSync48(path2);
124703
+ return readFileSync49(path2);
124218
124704
  }
124219
124705
  function canonicalDir(path2, name) {
124220
124706
  abs(path2, name);
124221
- if (realpathSync24(path2) !== path2 || !lstatSync51(path2).isDirectory())
124707
+ if (realpathSync24(path2) !== path2 || !lstatSync52(path2).isDirectory())
124222
124708
  refuse8(`${name} is not a canonical directory`);
124223
- for (let at2 = path2;; at2 = dirname52(at2)) {
124224
- const stat3 = lstatSync51(at2);
124709
+ for (let at2 = path2;; at2 = dirname53(at2)) {
124710
+ const stat3 = lstatSync52(at2);
124225
124711
  if (!stat3.isDirectory() || stat3.isSymbolicLink())
124226
124712
  refuse8(`${name} traverses a symlink`);
124227
- if (at2 === dirname52(at2))
124713
+ if (at2 === dirname53(at2))
124228
124714
  break;
124229
124715
  }
124230
124716
  }
@@ -124233,17 +124719,17 @@ function captureClaudeProspectiveCandidateClosure(root2) {
124233
124719
  const entries = [];
124234
124720
  let bytes = 0;
124235
124721
  const walk2 = (directory3) => {
124236
- const beforeDirectory = lstatSync51(directory3);
124237
- const names = readdirSync29(directory3).sort();
124722
+ const beforeDirectory = lstatSync52(directory3);
124723
+ const names = readdirSync30(directory3).sort();
124238
124724
  if (new Set(names).size !== names.length)
124239
124725
  refuse8("candidate contains ambiguous directory entries");
124240
124726
  for (const name of names) {
124241
124727
  if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\") || /[\x00-\x1f\x7f]/.test(name))
124242
124728
  refuse8("candidate contains an unsupported path member");
124243
- 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("/");
124244
124730
  if (entries.length >= MAX_CANDIDATE_FILES)
124245
124731
  refuse8("candidate closure exceeds its member limit");
124246
- const stat3 = lstatSync51(path2);
124732
+ const stat3 = lstatSync52(path2);
124247
124733
  if (stat3.isSymbolicLink())
124248
124734
  refuse8("candidate closure contains a symlink");
124249
124735
  if (stat3.isDirectory()) {
@@ -124253,14 +124739,14 @@ function captureClaudeProspectiveCandidateClosure(root2) {
124253
124739
  bytes += stat3.size;
124254
124740
  if (bytes > MAX_CANDIDATE_BYTES)
124255
124741
  refuse8("candidate closure exceeds its byte limit");
124256
- const content = safeFile(path2, "candidate member"), after2 = lstatSync51(path2);
124742
+ const content = safeFile(path2, "candidate member"), after2 = lstatSync52(path2);
124257
124743
  if (!after2.isFile() || after2.dev !== stat3.dev || after2.ino !== stat3.ino || after2.size !== stat3.size || after2.mtimeMs !== stat3.mtimeMs || after2.ctimeMs !== stat3.ctimeMs)
124258
124744
  refuse8("candidate member changed while being read");
124259
124745
  entries.push([rel, "file", sha8(content)]);
124260
124746
  } else
124261
124747
  refuse8("candidate closure contains a special file");
124262
124748
  }
124263
- const afterDirectory = lstatSync51(directory3);
124749
+ const afterDirectory = lstatSync52(directory3);
124264
124750
  if (!afterDirectory.isDirectory() || afterDirectory.dev !== beforeDirectory.dev || afterDirectory.ino !== beforeDirectory.ino || afterDirectory.mtimeMs !== beforeDirectory.mtimeMs || afterDirectory.ctimeMs !== beforeDirectory.ctimeMs)
124265
124751
  refuse8("candidate directory changed while being read");
124266
124752
  };
@@ -124271,9 +124757,9 @@ function captureClaudeProspectiveCandidateClosure(root2) {
124271
124757
  function captureClaudeInstallerCandidate(root2, policyPath) {
124272
124758
  const closureSha256 = captureClaudeProspectiveCandidateClosure(root2);
124273
124759
  const dataDir3 = getDataDirReadOnly();
124274
- if (policyPath !== join80(dataDir3, "agent-policy.json"))
124760
+ if (policyPath !== join81(dataDir3, "agent-policy.json"))
124275
124761
  refuse8("Skills policy path differs from the package-owned resolver");
124276
- const catalogPath = join80(root2, ".claude-plugin/marketplace.json");
124762
+ const catalogPath = join81(root2, ".claude-plugin/marketplace.json");
124277
124763
  const catalog = jsonFile(catalogPath, "candidate catalog");
124278
124764
  if (!catalog || !Array.isArray(catalog.plugins))
124279
124765
  refuse8("candidate catalog is invalid");
@@ -124284,13 +124770,13 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
124284
124770
  if (names.has(row2.name))
124285
124771
  refuse8("candidate catalog repeats a plugin name");
124286
124772
  names.add(row2.name);
124287
- 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))
124288
124774
  refuse8("candidate catalog plugin source is invalid");
124289
- const pluginRoot = resolve45(root2, row2.source);
124290
- const relativeRoot = relative16(root2, pluginRoot);
124291
- 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))
124292
124778
  refuse8("candidate plugin source escapes the candidate root");
124293
- const path2 = join80(pluginRoot, ".claude-plugin/plugin.json");
124779
+ const path2 = join81(pluginRoot, ".claude-plugin/plugin.json");
124294
124780
  return { plugin: row2.name, ...captureClaudePluginManifestFile(path2) };
124295
124781
  });
124296
124782
  const policy = readManagedSkillPolicySnapshot(dataDir3);
@@ -124307,7 +124793,7 @@ function captureClaudeInstallerCandidate(root2, policyPath) {
124307
124793
  }
124308
124794
  function readWitness(path2, expected, name) {
124309
124795
  abs(path2, `${name} path`);
124310
- const stat3 = lstatSync51(path2, { throwIfNoEntry: false });
124796
+ const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
124311
124797
  if (expected === null) {
124312
124798
  if (stat3)
124313
124799
  refuse8(`${name} was absent at review but is now present`);
@@ -124359,7 +124845,7 @@ function unreviewedProjectSettingsRefusal(path2, expected, name) {
124359
124845
  return null;
124360
124846
  }
124361
124847
  function jsonFile(path2, name) {
124362
- const stat3 = lstatSync51(path2, { throwIfNoEntry: false });
124848
+ const stat3 = lstatSync52(path2, { throwIfNoEntry: false });
124363
124849
  if (!stat3)
124364
124850
  return null;
124365
124851
  let value;
@@ -124389,9 +124875,9 @@ function assertNoUnprojectedSkillPayload(root2, catalogPath, marketplace, plugin
124389
124875
  if (entries[0].skills !== undefined || entries[0].commands !== undefined)
124390
124876
  refuse8("candidate marketplace entry declares native skill or command payloads");
124391
124877
  const walk2 = (directory3) => {
124392
- for (const name of readdirSync29(directory3).sort()) {
124393
- const path2 = join80(directory3, name), rel = relative16(root2, path2).split(sep23).join("/");
124394
- 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);
124395
124881
  if (stat3.isDirectory())
124396
124882
  walk2(path2);
124397
124883
  else if (stat3.isFile()) {
@@ -124436,10 +124922,10 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124436
124922
  abs(request2.skills.dataDir, "Skills data directory");
124437
124923
  if (!SHA2562.test(request2.skills.policySha256) || !SHA2562.test(request2.skills.discoverySha256))
124438
124924
  refuse8("invalid Skills policy or discovery SHA-256");
124439
- const snapshot2 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
124440
- if (!snapshot2 || sha8(snapshot2.text) !== request2.skills.policySha256)
124925
+ const snapshot3 = readManagedSkillPolicySnapshot(request2.skills.dataDir);
124926
+ if (!snapshot3 || sha8(snapshot3.text) !== request2.skills.policySha256)
124441
124927
  refuse8("Skills policy preimage changed or is absent");
124442
- const aliases = snapshot2.value.bridge?.rootAliases ?? [];
124928
+ const aliases = snapshot3.value.bridge?.rootAliases ?? [];
124443
124929
  if (!Array.isArray(aliases))
124444
124930
  refuse8("Skills policy root aliases are invalid");
124445
124931
  try {
@@ -124448,16 +124934,16 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124448
124934
  refuse8("Skills policy root alias changed or is invalid");
124449
124935
  }
124450
124936
  const canonical9 = (path2) => canonicalAgentPath(path2, aliases);
124451
- const configRoot = canonical9(join80(request2.native.home, ".claude"));
124937
+ const configRoot = canonical9(join81(request2.native.home, ".claude"));
124452
124938
  canonicalDir(configRoot, "Claude config root");
124453
124939
  if (request2.native.configRoot !== configRoot)
124454
124940
  refuse8("Claude config root differs from the current resolver and reviewed root alias");
124455
- const pluginRoot = join80(configRoot, "plugins");
124941
+ const pluginRoot = join81(configRoot, "plugins");
124456
124942
  if (process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR)
124457
124943
  refuse8("CLAUDE_CODE_PLUGIN_CACHE_DIR overrides are unsupported by this prospective schema; bind and review the actual cache root first");
124458
124944
  if (request2.native.pluginRoot !== pluginRoot)
124459
124945
  refuse8("Claude plugin root differs from the supported standard plugins directory; Cowork and custom cache roots require a separately bound schema");
124460
- const pluginRootStat = lstatSync51(pluginRoot, { throwIfNoEntry: false });
124946
+ const pluginRootStat = lstatSync52(pluginRoot, { throwIfNoEntry: false });
124461
124947
  if (pluginRootStat && (!pluginRootStat.isDirectory() || pluginRootStat.isSymbolicLink()))
124462
124948
  refuse8("Claude plugin root is not a regular directory");
124463
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");
@@ -124475,9 +124961,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124475
124961
  if (hasOwn2(candidate2, "settingsMarketplaceSource") && (candidate2.settingsMarketplaceSource !== "directory" || request2.scope.kind !== "user" || !measuredSettingsWriter))
124476
124962
  refuse8("settings marketplace source operation requires directory source and a measured Claude user-scope writer");
124477
124963
  abs(candidate2.catalogPath, "catalog path");
124478
- if (!candidate2.catalogPath.startsWith(candidate2.root + sep23))
124964
+ if (!candidate2.catalogPath.startsWith(candidate2.root + sep24))
124479
124965
  refuse8("catalog must be inside the frozen candidate root");
124480
- if (candidate2.catalogPath !== join80(candidate2.root, ".claude-plugin", "marketplace.json"))
124966
+ if (candidate2.catalogPath !== join81(candidate2.root, ".claude-plugin", "marketplace.json"))
124481
124967
  refuse8("marketplace catalog must be at the frozen root's .claude-plugin/marketplace.json path");
124482
124968
  if (!SHA2562.test(candidate2.catalogSha256) || !SHA2562.test(candidate2.closureSha256))
124483
124969
  refuse8("candidate digests must be lowercase SHA-256");
@@ -124519,11 +125005,11 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124519
125005
  if (Buffer.byteLength(canonicalJson3(selected.optionsPatch), "utf8") > 16 * 1024)
124520
125006
  refuse8("selected plugin optionsPatch exceeds its byte bound");
124521
125007
  abs(selected.manifestPath, "manifest path");
124522
- 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"))
124523
125009
  refuse8("plugin manifest must be under a plugin root inside the frozen marketplace closure");
124524
- const pluginRoot2 = dirname52(dirname52(selected.manifestPath));
124525
- const relativePluginRoot = relative16(candidate2.root, pluginRoot2);
124526
- 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))
124527
125013
  refuse8("plugin root must be a child of the frozen marketplace root");
124528
125014
  canonicalDir(pluginRoot2, "candidate plugin root");
124529
125015
  if (!SHA2562.test(selected.manifestSha256))
@@ -124555,9 +125041,9 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124555
125041
  refuse8("selected plugins collide after native cache path sanitization");
124556
125042
  sanitizedCachePaths.add(sanitizedCachePath);
124557
125043
  const sourcePath = selectedEntry.source;
124558
- const sourceRelativePath = relative16(candidate2.root, pluginRoot2).split(sep23).join("/");
125044
+ const sourceRelativePath = relative17(candidate2.root, pluginRoot2).split(sep24).join("/");
124559
125045
  const expectedSourcePath = sourceRelativePath === "." ? "./" : `./${sourceRelativePath}`;
124560
- 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)
124561
125047
  refuse8("marketplace source does not resolve to the frozen plugin closure; external source mapping is not supported by this schema");
124562
125048
  const entry = captureClaudeMarketplaceEntry(candidate2.catalogPath, candidate2.marketplace, selected.plugin);
124563
125049
  if (entry.sha256.length !== 64)
@@ -124577,12 +125063,12 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124577
125063
  for (const name of ["settings", "projectSettings", "projectSettingsLocal", "marketplaces", "installedPlugins"])
124578
125064
  keys4(request2.preimages[name], ["path", "sha256"], `${name} preimage`);
124579
125065
  const expectedPaths = {
124580
- settings: request2.scope.kind === "project" ? canonical9(join80(request2.scope.projectPath, ".claude/settings.json")) : canonical9(join80(request2.native.home, ".claude/settings.json")),
124581
- userSettings: canonical9(join80(request2.native.home, ".claude/settings.json")),
124582
- projectSettings: canonical9(join80(request2.scope.projectPath, ".claude/settings.json")),
124583
- projectSettingsLocal: canonical9(join80(request2.scope.projectPath, ".claude/settings.local.json")),
124584
- marketplaces: canonical9(join80(request2.native.home, ".claude/plugins/known_marketplaces.json")),
124585
- 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"))
124586
125072
  };
124587
125073
  for (const name of Object.keys(expectedPaths)) {
124588
125074
  const witness = request2.preimages[name];
@@ -124617,8 +125103,8 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124617
125103
  if (hashClaudeDiscoveryBinding(binding) !== request2.skills.discoverySha256)
124618
125104
  refuse8("Skills discovery preimage differs from its reviewed binding");
124619
125105
  assertProjectDiscovery("claude", [request2.scope.projectPath], request2.native.home, canonical9, binding);
124620
- const stored = snapshot2.value.bridge?.discovery?.claude;
124621
- 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)
124622
125108
  refuse8("Skills policy does not bind the reviewed current Claude discovery");
124623
125109
  assertExemptUnchanged();
124624
125110
  const delta = request2.delta;
@@ -124715,7 +125201,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124715
125201
  const manifestVersion = manifest.version;
124716
125202
  const sanitizeComponent = (value) => value.replace(/[^A-Za-z0-9_-]/g, "-");
124717
125203
  const sanitizedVersion = manifestVersion === "." || manifestVersion === ".." ? "-" : manifestVersion.replace(/[^A-Za-z0-9._-]/g, "-");
124718
- const expectedInstallPath = join80(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
125204
+ const expectedInstallPath = join81(pluginRoot, "cache", sanitizeComponent(candidate2.marketplace), sanitizeComponent(plugin), sanitizedVersion);
124719
125205
  if (expectedInstallPath.length > 4096)
124720
125206
  refuse8("candidate native cache path exceeds its bound");
124721
125207
  const targetRegistration = {
@@ -124854,7 +125340,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124854
125340
  { name: "project-settings-local", path: expectedPaths.projectSettingsLocal, sha256: request2.preimages.projectSettingsLocal.sha256 },
124855
125341
  { name: "known-marketplaces", path: expectedPaths.marketplaces, sha256: request2.preimages.marketplaces.sha256 },
124856
125342
  { name: "installed-plugins", path: expectedPaths.installedPlugins, sha256: request2.preimages.installedPlugins.sha256 },
124857
- { 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 }
124858
125344
  ],
124859
125345
  ownedDelta: { settingsEnabledPlugins: selectedPluginIds, selectedPluginOptions: selectedOptionPaths, functionHooksEnv: candidate2.functionHooksEnv ?? null, knownMarketplace: candidate2.marketplace, installedPlugins: selectedPluginIds, unrelatedGraph: "preserved-by-complete-semantic-documents" },
124860
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",
@@ -124862,7 +125348,7 @@ function reviewClaudeProspectiveCandidate(request2, requestFileSha256) {
124862
125348
  limitation: "validation-only; native installer atomicity and crash recovery are not established"
124863
125349
  };
124864
125350
  }
124865
- 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;
124866
125352
  var init_claude_prospective_review = __esm(() => {
124867
125353
  init_agent_discovery();
124868
125354
  init_agent_integration();
@@ -124896,7 +125382,7 @@ var init_claude_prospective_review = __esm(() => {
124896
125382
 
124897
125383
  // src/lib/codex-plugin-reconcile.ts
124898
125384
  import { realpathSync as realpathSync25 } from "fs";
124899
- import { join as join81, resolve as resolve46 } from "path";
125385
+ import { join as join82, resolve as resolve47 } from "path";
124900
125386
  function boundedMessage(text5) {
124901
125387
  const line = text5.replace(/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g, " ").trim();
124902
125388
  return line.length > 800 ? `${line.slice(0, 797)}...` : line;
@@ -124930,20 +125416,20 @@ async function runCodexPluginReconcile(options) {
124930
125416
  const mode = options.apply ? "apply" : "preview";
124931
125417
  const base3 = emptyResult(mode);
124932
125418
  try {
124933
- 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();
124934
125420
  if (options.codexHome !== undefined) {
124935
125421
  const canonical9 = (path2) => {
124936
125422
  try {
124937
125423
  return realpathSync25(path2);
124938
125424
  } catch {
124939
- return resolve46(path2);
125425
+ return resolve47(path2);
124940
125426
  }
124941
125427
  };
124942
- if (canonical9(options.codexHome) !== canonical9(join81(home, ".codex")))
125428
+ if (canonical9(options.codexHome) !== canonical9(join82(home, ".codex")))
124943
125429
  throw new CodexPluginReconcileRefusal("CODEX_RECONCILE_CODEX_HOME_UNSUPPORTED", "CODEX_HOME names a Codex home other than the one the Skills binding reviews");
124944
125430
  }
124945
125431
  const capture2 = options.captureCatalog ?? captureCodexNativeSkillCatalog;
124946
- const remoteReceipts = captureCodexRemoteReceiptSnapshot(join81(home, ".codex/plugins/cache"));
125432
+ const remoteReceipts = captureCodexRemoteReceiptSnapshot(join82(home, ".codex/plugins/cache"));
124947
125433
  const captured = await capture2({ command: options.codexCommand ?? "codex", home, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, cwd: projectDir });
124948
125434
  const catalog = captured && typeof captured === "object" ? { ...captured, remoteReceipts } : captured;
124949
125435
  base3.codexVersion = typeof catalog?.version === "string" ? catalog.version : null;
@@ -124968,7 +125454,7 @@ async function runCodexPluginReconcile(options) {
124968
125454
  if (!options.apply)
124969
125455
  return { ...reported, outcome: "planned", changes };
124970
125456
  const applied = await applyCodexPluginReconcile(plan, { ...options.codexCommand === undefined ? {} : { codexCommand: options.codexCommand }, ...options.codexHome === undefined ? {} : { codexHome: options.codexHome }, ...options.connect ? { connect: options.connect } : {} });
124971
- 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 };
124972
125458
  } catch (error61) {
124973
125459
  return { ...base3, ...classifyCodexReconcileError(error61) };
124974
125460
  }
@@ -125004,23 +125490,23 @@ var init_codex_plugin_reconcile = __esm(() => {
125004
125490
  });
125005
125491
 
125006
125492
  // src/lib/claude-installer-binding.ts
125007
- import { createHash as createHash52 } from "crypto";
125008
- import { join as join82 } from "path";
125493
+ import { createHash as createHash53 } from "crypto";
125494
+ import { join as join83 } from "path";
125009
125495
  function readPolicy(dataDir3) {
125010
- let snapshot2;
125496
+ let snapshot3;
125011
125497
  try {
125012
- snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
125498
+ snapshot3 = readManagedSkillPolicySnapshot(dataDir3);
125013
125499
  } catch {
125014
125500
  return refuse9("POLICY_UNREADABLE");
125015
125501
  }
125016
- return snapshot2 ?? refuse9("POLICY_UNAVAILABLE");
125502
+ return snapshot3 ?? refuse9("POLICY_UNAVAILABLE");
125017
125503
  }
125018
125504
  function verifyClaudeInstallerDiscoveryBinding(policyPath) {
125019
125505
  const dataDir3 = getDataDirReadOnly();
125020
- if (policyPath !== join82(dataDir3, "agent-policy.json"))
125506
+ if (policyPath !== join83(dataDir3, "agent-policy.json"))
125021
125507
  refuse9("POLICY_PATH_MISMATCH");
125022
- const snapshot2 = readPolicy(dataDir3);
125023
- const bridge = snapshot2.value.bridge;
125508
+ const snapshot3 = readPolicy(dataDir3);
125509
+ const bridge = snapshot3.value.bridge;
125024
125510
  const binding = object14(bridge) && object14(bridge.discovery) ? bridge.discovery.claude : undefined;
125025
125511
  if (!object14(binding))
125026
125512
  return refuse9("BINDING_UNAVAILABLE");
@@ -125062,12 +125548,12 @@ function verifyClaudeInstallerDiscoveryBinding(policyPath) {
125062
125548
  ...directories.flatMap((row2) => row2.status === "drift" ? [{ kind: "directory", index: row2.index, path: row2.path, reason: row2.reason }] : []),
125063
125549
  ...accepted ? [] : [{ kind: "binding", reason: "DISCOVERY_REFUSED" }]
125064
125550
  ];
125065
- if (readPolicy(dataDir3).text !== snapshot2.text)
125551
+ if (readPolicy(dataDir3).text !== snapshot3.text)
125066
125552
  refuse9("POLICY_CHANGED");
125067
125553
  return {
125068
125554
  schema: CLAUDE_DISCOVERY_BINDING_VERIFICATION_SCHEMA,
125069
125555
  ok: failures3.length === 0,
125070
- policySha256: createHash52("sha256").update(snapshot2.text).digest("hex"),
125556
+ policySha256: createHash53("sha256").update(snapshot3.text).digest("hex"),
125071
125557
  discoverySha256: hashClaudeDiscoveryBinding(binding),
125072
125558
  agent: "claude",
125073
125559
  method: binding.method,
@@ -125096,8 +125582,8 @@ var init_claude_installer_binding = __esm(() => {
125096
125582
  });
125097
125583
 
125098
125584
  // src/cli/commands/claude-installer-capture.ts
125099
- import { closeSync as closeSync33, constants as constants33, fstatSync as fstatSync32, lstatSync as lstatSync52, openSync as openSync33, readSync as readSync25 } from "fs";
125100
- 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";
125101
125587
  function need11(condition, code2) {
125102
125588
  if (!condition)
125103
125589
  throw new CaptureRefusal(code2);
@@ -125111,11 +125597,11 @@ function exactKeys5(value, expected, operation) {
125111
125597
  }
125112
125598
  }
125113
125599
  function canonicalPath2(value) {
125114
- 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");
125115
125601
  }
125116
125602
  function readRequest2(path2) {
125117
125603
  canonicalPath2(path2);
125118
- const before2 = lstatSync52(path2, { throwIfNoEntry: false });
125604
+ const before2 = lstatSync53(path2, { throwIfNoEntry: false });
125119
125605
  if (!before2?.isFile() || before2.isSymbolicLink() || before2.size > MAX_REQUEST_BYTES)
125120
125606
  throw new CaptureRefusal("INVALID_REQUEST_FILE: --request must name a regular non-symlink JSON file no larger than 64 KiB");
125121
125607
  const fd = openSync33(path2, constants33.O_RDONLY | (constants33.O_NOFOLLOW ?? 0) | constants33.O_NONBLOCK);
@@ -125130,7 +125616,7 @@ function readRequest2(path2) {
125130
125616
  break;
125131
125617
  length += count;
125132
125618
  }
125133
- const after2 = fstatSync32(fd), current = lstatSync52(path2);
125619
+ const after2 = fstatSync32(fd), current = lstatSync53(path2);
125134
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");
125135
125621
  try {
125136
125622
  return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length)));
@@ -125292,10 +125778,10 @@ __export(exports_agent_integration, {
125292
125778
  MANAGED_HOOK_CONTEXT_MAX_SKILLS: () => MANAGED_HOOK_CONTEXT_MAX_SKILLS,
125293
125779
  MANAGED_HOOK_CONTEXT_MAX_CHARS: () => MANAGED_HOOK_CONTEXT_MAX_CHARS
125294
125780
  });
125295
- import { existsSync as existsSync36, lstatSync as lstatSync53, readFileSync as readFileSync49, readSync as readSync26, writeFileSync as writeFileSync29 } from "fs";
125296
- 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";
125297
125783
  import { homedir as homedir15 } from "os";
125298
- 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";
125299
125785
  function boundedEnvInteger(name, fallback, [min2, max2]) {
125300
125786
  const raw = process.env[name];
125301
125787
  if (raw === undefined || !/^[0-9]{1,6}$/.test(raw.trim()))
@@ -125479,9 +125965,9 @@ function registerAgentIntegration(parent) {
125479
125965
  });
125480
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) => {
125481
125967
  try {
125482
- if (!isAbsolute49(options.cwd) || !isAbsolute49(options.output))
125968
+ if (!isAbsolute50(options.cwd) || !isAbsolute50(options.output))
125483
125969
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_ABSOLUTE_PATH_REQUIRED");
125484
- if (existsSync36(options.output))
125970
+ if (existsSync37(options.output))
125485
125971
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
125486
125972
  let admission;
125487
125973
  const catalog = await captureCodexNativeSkillCatalog({
@@ -125497,14 +125983,14 @@ function registerAgentIntegration(parent) {
125497
125983
  const bytes = JSON.stringify(catalog, null, 2) + `
125498
125984
  `;
125499
125985
  try {
125500
- writeFileSync29(options.output, bytes, { flag: "wx", mode: 384 });
125986
+ writeFileSync30(options.output, bytes, { flag: "wx", mode: 384 });
125501
125987
  } catch (error61) {
125502
125988
  if (error61 && typeof error61 === "object" && "code" in error61 && error61.code === "EEXIST")
125503
125989
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_OUTPUT_EXISTS");
125504
125990
  throw error61;
125505
125991
  }
125506
- const written = lstatSync53(options.output);
125507
- 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)
125508
125994
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_READBACK_FAILED");
125509
125995
  const receipt = {
125510
125996
  version: catalog.version,
@@ -125512,7 +125998,7 @@ function registerAgentIntegration(parent) {
125512
125998
  skillCount: catalog.skills.length,
125513
125999
  output: options.output,
125514
126000
  bytes: Buffer.byteLength(bytes),
125515
- sha256: createHash53("sha256").update(bytes).digest("hex"),
126001
+ sha256: createHash54("sha256").update(bytes).digest("hex"),
125516
126002
  ...codexAdmissionReceipt(admission)
125517
126003
  };
125518
126004
  await writeCliOutput(options.json ? JSON.stringify(receipt) : `Captured ${receipt.skillCount} native Codex skill(s) in ${receipt.output}.`);
@@ -125546,7 +126032,7 @@ function registerAgentIntegration(parent) {
125546
126032
  if (!options.reviewedHookSources)
125547
126033
  throw new Error("Codex V6 requires a bounded --reviewed-hook-sources JSON array");
125548
126034
  const { hashNativeJsonControls: hashNativeJsonControls2, readNativeSettingsWitnessFile: readNativeSettingsWitnessFile2 } = await Promise.resolve().then(() => (init_claude_settings_witness(), exports_claude_settings_witness));
125549
- const metadata2 = resolve48(options.reviewedHookSources);
126035
+ const metadata2 = resolve49(options.reviewedHookSources);
125550
126036
  const text5 = readNativeSettingsWitnessFile2(metadata2, { remaining: 1048576 }, basename27(metadata2));
125551
126037
  hashNativeJsonControls2(`{"sources":${text5}}`);
125552
126038
  await writeCliOutput(JSON.stringify(captureCodexSettingsV6(options.path, JSON.parse(text5)), null, 2));
@@ -125581,14 +126067,14 @@ function registerAgentIntegration(parent) {
125581
126067
  });
125582
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) => {
125583
126069
  try {
125584
- if (!isAbsolute49(options.request))
126070
+ if (!isAbsolute50(options.request))
125585
126071
  throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: --request must be an absolute path");
125586
- const stat3 = lstatSync53(options.request);
126072
+ const stat3 = lstatSync54(options.request);
125587
126073
  if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 1024 * 1024)
125588
126074
  throw new Error("CLAUDE_CANDIDATE_REVIEW_REFUSED: request must be a regular file no larger than 1 MiB");
125589
- const requestBytes = readFileSync49(options.request);
126075
+ const requestBytes = readFileSync50(options.request);
125590
126076
  const request2 = JSON.parse(requestBytes.toString("utf8"));
125591
- const receipt = reviewClaudeProspectiveCandidate(request2, createHash53("sha256").update(requestBytes).digest("hex"));
126077
+ const receipt = reviewClaudeProspectiveCandidate(request2, createHash54("sha256").update(requestBytes).digest("hex"));
125592
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.`);
125593
126079
  } catch (error61) {
125594
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");
@@ -125660,8 +126146,8 @@ function registerAgentIntegration(parent) {
125660
126146
  });
125661
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) => {
125662
126148
  try {
125663
- const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync49(options.discoveryInputs, "utf8")) : undefined;
125664
- 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 });
125665
126151
  const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
125666
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) };
125667
126153
  if (options.json)
@@ -125723,7 +126209,7 @@ function registerAgentIntegration(parent) {
125723
126209
  try {
125724
126210
  if (agents(options.agent).length !== 1)
125725
126211
  throw new Error("A hook invocation requires one agent");
125726
- const inputBytes = readFileSync49(0), inputText = inputBytes.toString("utf8");
126212
+ const inputBytes = readFileSync50(0), inputText = inputBytes.toString("utf8");
125727
126213
  if (inputText.length > 1024 * 1024)
125728
126214
  throw new Error("Hook input is too large");
125729
126215
  let input3 = JSON.parse(inputText);
@@ -125736,12 +126222,12 @@ function registerAgentIntegration(parent) {
125736
126222
  input3.hook_event_name = event;
125737
126223
  const projects = [process.cwd()];
125738
126224
  if (input3.cwd !== undefined) {
125739
- 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"))
125740
126226
  throw new Error("Invalid native hook working directory");
125741
126227
  projects.push(input3.cwd);
125742
126228
  }
125743
126229
  if (options.agent === "cursor") {
125744
- 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")))
125745
126231
  throw new Error("Invalid Cursor workspace roots");
125746
126232
  projects.push(...input3.workspace_roots);
125747
126233
  input3.cwd ??= input3.workspace_roots[0] ?? process.cwd();
@@ -125889,7 +126375,7 @@ ${driftWarning}` : unavailable2, ...output2 }));
125889
126375
  try {
125890
126376
  if (options.agent && !INTEGRATION_AGENTS.includes(options.agent))
125891
126377
  throw new Error(`Unsupported migration agent: ${options.agent}`);
125892
- const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync49(options.discoveryInputs, "utf8")) : undefined;
126378
+ const discoveryInputs = options.discoveryInputs ? JSON.parse(readFileSync50(options.discoveryInputs, "utf8")) : undefined;
125893
126379
  if (options.targetManifest && (options.includeUnmanaged || options.includeVendor))
125894
126380
  throw new Error("--target-manifest cannot be combined with broad native migration selectors");
125895
126381
  const targetManifest = options.targetManifest ? readNativeMigrationTargetManifest(options.targetManifest) : undefined;
@@ -126084,7 +126570,7 @@ var exports_profiles = {};
126084
126570
  __export(exports_profiles, {
126085
126571
  registerProfiles: () => registerProfiles
126086
126572
  });
126087
- 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";
126088
126574
  function readProfileInput(path2) {
126089
126575
  const fd = openSync34(path2, constants34.O_RDONLY | constants34.O_NONBLOCK);
126090
126576
  try {
@@ -126112,7 +126598,7 @@ function registerProfiles(parent) {
126112
126598
  try {
126113
126599
  const profile = await readSkillProfile(id3), text5 = JSON.stringify(profile, null, 2);
126114
126600
  if (options.save)
126115
- writeFileSync30(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
126601
+ writeFileSync31(options.save, JSON.stringify(profile), { mode: 384, flag: "wx" });
126116
126602
  await writeCliOutput(options.json ? text5 : `${profile.id} at ${profile.revision}: ${profile.selections.map((skill) => `${skill.slug}@${skill.version}`).join(", ")}`);
126117
126603
  } catch (error61) {
126118
126604
  console.error(error61.message);
@@ -126522,7 +127008,7 @@ import {
126522
127008
  fstatSync as fstatSync34,
126523
127009
  openSync as openSync35,
126524
127010
  readSync as readSync28,
126525
- writeFileSync as writeFileSync31
127011
+ writeFileSync as writeFileSync32
126526
127012
  } from "fs";
126527
127013
  function readInput(path2) {
126528
127014
  const fd = openSync35(path2, constants35.O_RDONLY | constants35.O_NONBLOCK);
@@ -126555,7 +127041,7 @@ function registerGrants(parent) {
126555
127041
  try {
126556
127042
  const policy = await readExecutionGrantPolicy(profile, options.revision);
126557
127043
  if (options.save)
126558
- writeFileSync31(options.save, JSON.stringify(policy, null, 2), {
127044
+ writeFileSync32(options.save, JSON.stringify(policy, null, 2), {
126559
127045
  mode: 384,
126560
127046
  flag: "wx"
126561
127047
  });
@@ -126582,28 +127068,28 @@ var init_grants = __esm(() => {
126582
127068
  });
126583
127069
 
126584
127070
  // src/lib/station-hydrate.ts
126585
- import { createHash as createHash54 } from "crypto";
127071
+ import { createHash as createHash55 } from "crypto";
126586
127072
  import {
126587
127073
  copyFileSync as copyFileSync3,
126588
- mkdirSync as mkdirSync33,
126589
- readdirSync as readdirSync30,
126590
- readFileSync as readFileSync50,
127074
+ mkdirSync as mkdirSync34,
127075
+ readdirSync as readdirSync31,
127076
+ readFileSync as readFileSync51,
126591
127077
  statSync as statSync26,
126592
- writeFileSync as writeFileSync32
127078
+ writeFileSync as writeFileSync33
126593
127079
  } from "fs";
126594
- 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";
126595
127081
  function fail7(code2, message, detail = []) {
126596
127082
  throw new StationSnapshotError(code2, message, detail);
126597
127083
  }
126598
127084
  function snapshotRootFor(repoRoot, stationId) {
126599
- return join83(repoRoot, "resources", stationId, "skills");
127085
+ return join84(repoRoot, "resources", stationId, "skills");
126600
127086
  }
126601
127087
  function readSnapshotManifest(repoRoot, stationId) {
126602
127088
  const snapshotRoot = snapshotRootFor(repoRoot, stationId);
126603
- const manifestPath = join83(snapshotRoot, "sync-manifest.json");
127089
+ const manifestPath = join84(snapshotRoot, "sync-manifest.json");
126604
127090
  let manifest;
126605
127091
  try {
126606
- manifest = JSON.parse(readFileSync50(manifestPath, "utf8"));
127092
+ manifest = JSON.parse(readFileSync51(manifestPath, "utf8"));
126607
127093
  } catch (error61) {
126608
127094
  fail7("MANIFEST_UNREADABLE", `cannot read snapshot manifest: ${manifestPath}: ${error61.message}`);
126609
127095
  }
@@ -126627,10 +127113,10 @@ function planStationHydration(stationId, repoRoot) {
126627
127113
  const hashMismatches = [];
126628
127114
  const skippedByRule = [];
126629
127115
  for (const agent of SYNC_AGENTS) {
126630
- const agentRoot = join83(snapshotRoot, "agent-homes", agent);
127116
+ const agentRoot = join84(snapshotRoot, "agent-homes", agent);
126631
127117
  let identEntries;
126632
127118
  try {
126633
- identEntries = readdirSync30(agentRoot, { withFileTypes: true });
127119
+ identEntries = readdirSync31(agentRoot, { withFileTypes: true });
126634
127120
  } catch {
126635
127121
  continue;
126636
127122
  }
@@ -126638,10 +127124,10 @@ function planStationHydration(stationId, repoRoot) {
126638
127124
  if (!identEntry.isDirectory() || identEntry.name.startsWith(".")) {
126639
127125
  continue;
126640
127126
  }
126641
- const identRoot = join83(agentRoot, identEntry.name);
127127
+ const identRoot = join84(agentRoot, identEntry.name);
126642
127128
  const entries = walkEntries(identRoot);
126643
127129
  for (const entry of entries) {
126644
- const relativeParts = [identEntry.name, ...entry.relativePath.split(sep24)];
127130
+ const relativeParts = [identEntry.name, ...entry.relativePath.split(sep25)];
126645
127131
  if (entry.kind === "symlink") {
126646
127132
  symlinks.push({ ident: identEntry.name, agent, relativePath: entry.relativePath });
126647
127133
  continue;
@@ -126665,8 +127151,8 @@ function planStationHydration(stationId, repoRoot) {
126665
127151
  });
126666
127152
  continue;
126667
127153
  }
126668
- const withinIdent = relativeParts.slice(1).join(sep24);
126669
- const homeRelative = relativeParts.join(sep24);
127154
+ const withinIdent = relativeParts.slice(1).join(sep25);
127155
+ const homeRelative = relativeParts.join(sep25);
126670
127156
  if (REFUSED_SCANNER_FLAGGED.has(homeRelative)) {
126671
127157
  skippedByRule.push({
126672
127158
  ident: identEntry.name,
@@ -126739,7 +127225,7 @@ function planStationHydration(stationId, repoRoot) {
126739
127225
  for (const copy of copies) {
126740
127226
  let isStub = false;
126741
127227
  try {
126742
- isStub = isPointerSkillMd(readFileSync50(copy.fullPath, "utf8"));
127228
+ isStub = isPointerSkillMd(readFileSync51(copy.fullPath, "utf8"));
126743
127229
  } catch {
126744
127230
  isStub = false;
126745
127231
  }
@@ -126785,12 +127271,12 @@ function skillSha256(skill) {
126785
127271
  return sha256File(skill.files[0].winner.fullPath);
126786
127272
  }
126787
127273
  const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
126788
- return createHash54("sha256").update(joined.sort().join(`
127274
+ return createHash55("sha256").update(joined.sort().join(`
126789
127275
  `)).digest("hex");
126790
127276
  }
126791
127277
  function writeStationHydration(options) {
126792
- const repoRoot = resolve49(options.repoRoot ?? process.cwd());
126793
- const cacheRoot = resolve49(options.cacheRoot ?? resolveCorpusRoot());
127278
+ const repoRoot = resolve50(options.repoRoot ?? process.cwd());
127279
+ const cacheRoot = resolve50(options.cacheRoot ?? resolveCorpusRoot());
126794
127280
  const plan = planStationHydration(options.stationId, repoRoot);
126795
127281
  const resultSkills = plan.winners.map((skill) => ({
126796
127282
  ident: skill.ident,
@@ -126823,7 +127309,7 @@ function writeStationHydration(options) {
126823
127309
  const toWrite = [];
126824
127310
  for (const skill of plan.winners) {
126825
127311
  for (const file2 of skill.files) {
126826
- const destination = join83(cacheRoot, skill.ident, file2.withinIdent);
127312
+ const destination = join84(cacheRoot, skill.ident, file2.withinIdent);
126827
127313
  const digest7 = sha256File(file2.winner.fullPath);
126828
127314
  let existingDigest = null;
126829
127315
  try {
@@ -126844,7 +127330,7 @@ function writeStationHydration(options) {
126844
127330
  }
126845
127331
  let written = 0;
126846
127332
  for (const entry of toWrite) {
126847
- mkdirSync33(dirname53(entry.destination), { recursive: true });
127333
+ mkdirSync34(dirname54(entry.destination), { recursive: true });
126848
127334
  copyFileSync3(entry.fullPath, entry.destination);
126849
127335
  written += 1;
126850
127336
  }
@@ -126865,9 +127351,9 @@ function writeStationHydration(options) {
126865
127351
  },
126866
127352
  skills: resultSkills
126867
127353
  };
126868
- const hydrationManifestPath = join83(dirname53(cacheRoot), `hydration-${options.stationId}.json`);
126869
- mkdirSync33(dirname53(hydrationManifestPath), { recursive: true });
126870
- 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)}
126871
127357
  `);
126872
127358
  return {
126873
127359
  ...base3,
@@ -126941,389 +127427,6 @@ var init_hydrate = __esm(() => {
126941
127427
  init_station_hydrate();
126942
127428
  });
126943
127429
 
126944
- // src/lib/prepare-skill.ts
126945
- import { createHash as createHash55, randomUUID as randomUUID17 } from "crypto";
126946
- 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";
126947
- import { tmpdir as tmpdir2 } from "os";
126948
- import { dirname as dirname54, isAbsolute as isAbsolute50, join as join84, relative as relative17, resolve as resolve50, sep as sep25 } from "path";
126949
- function prepareSkill(name, options) {
126950
- const normalized2 = normalizePortableSkillName(name);
126951
- if (!new RegExp(SEMVER_PATTERN).test(options.version))
126952
- throw new Error("--version must be a semantic version, for example 0.2.0.");
126953
- const path2 = join84(getPortableSkillsRootReadOnly(options), normalized2);
126954
- assertPortableAuthoringPath(path2, options);
126955
- const before2 = snapshot2(path2);
126956
- const manifestFile = before2.files.find((file2) => file2.path === "skill.json");
126957
- if (!manifestFile)
126958
- throw new Error(`Skill '${normalized2}' needs skill.json. Import the source with skills port first.`);
126959
- let manifest;
126960
- try {
126961
- const parsed = JSON.parse(manifestFile.bytes.toString("utf8"));
126962
- if (!isRecord9(parsed))
126963
- throw new Error("not an object");
126964
- manifest = parsed;
126965
- } catch {
126966
- throw new Error("skill.json must contain a JSON object; no files were changed.");
126967
- }
126968
- const previousVersion = manifest.version;
126969
- if (typeof previousVersion !== "string" || !new RegExp(SEMVER_PATTERN).test(previousVersion)) {
126970
- throw new Error("skill.json must declare its current semantic version before preparation.");
126971
- }
126972
- if (manifest.kind !== undefined && manifest.kind !== "instruction" && manifest.kind !== "executable") {
126973
- throw new Error("skill.json has an invalid kind; correct it before preparation.");
126974
- }
126975
- const kind = options.kind ?? manifest.kind;
126976
- if (kind !== "instruction" && kind !== "executable") {
126977
- 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.");
126978
- }
126979
- const document3 = before2.files.find((file2) => file2.path === "SKILL.md");
126980
- const documentKind = document3 ? parseSkillFrontmatter(document3.bytes.toString("utf8"))?.kind : undefined;
126981
- if (documentKind !== undefined && documentKind !== kind) {
126982
- 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.");
126983
- }
126984
- if (manifest.provenance !== undefined && !isRecord9(manifest.provenance))
126985
- throw new Error("skill.json provenance must be an object.");
126986
- const provenance = manifest.provenance;
126987
- const candidate2 = { ...manifest, version: options.version, kind, provenance: { ...provenance } };
126988
- const staging = mkdtempSync7(join84(tmpdir2(), "skills-prepare-"));
126989
- try {
126990
- for (const directory3 of before2.directories)
126991
- mkdirSync34(join84(staging, directory3), { recursive: true });
126992
- for (const file2 of before2.files) {
126993
- const destination = join84(staging, file2.path);
126994
- mkdirSync34(dirname54(destination), { recursive: true });
126995
- writeFileSync33(destination, file2.bytes, { mode: file2.mode });
126996
- }
126997
- const stagedManifest = join84(staging, "skill.json");
126998
- writeFileSync33(stagedManifest, `${JSON.stringify(candidate2, null, 2)}
126999
- `);
127000
- const contentHash = computeContentHash(staging);
127001
- const changed3 = options.version !== previousVersion || kind !== manifest.kind || contentHash !== provenance?.content_hash;
127002
- if (changed3 && compareVersions(options.version, previousVersion) <= 0) {
127003
- throw new Error(`Changed content requires a version greater than ${previousVersion}; pass --version <new-semver>.`);
127004
- }
127005
- candidate2.provenance.content_hash = contentHash;
127006
- const candidateBytes = `${JSON.stringify(candidate2, null, 2)}
127007
- `;
127008
- writeFileSync33(stagedManifest, candidateBytes);
127009
- validateRawCandidate(candidate2);
127010
- validateRuntimeEntrypoint(candidate2, staging);
127011
- const validation = validatePortableSkillDirectory(normalized2, staging);
127012
- if (!validation.valid)
127013
- throw new Error(`Skill '${normalized2}' cannot be prepared:
127014
- ${validation.issues.map((issue2) => `${issue2.code}: ${issue2.message}`).join(`
127015
- `)}`);
127016
- assertPackedCanonicalCoverage(staging);
127017
- if (snapshot2(path2).identity !== before2.identity)
127018
- throw new Error("Skill changed during preparation; review the draft and run prepare again.");
127019
- if (changed3 && !options.dryRun) {
127020
- const temporary = join84(path2, `.skill-prepare-${randomUUID17()}.tmp`);
127021
- try {
127022
- writeFileSync33(temporary, candidateBytes, { flag: "wx", mode: manifestFile.mode });
127023
- chmodSync5(temporary, manifestFile.mode);
127024
- renameSync18(temporary, join84(path2, "skill.json"));
127025
- } finally {
127026
- rmSync10(temporary, { force: true });
127027
- }
127028
- }
127029
- return { name: normalized2, path: path2, previousVersion, version: options.version, kind, contentHash, changed: changed3, written: changed3 && !options.dryRun, warnings: validation.warnings };
127030
- } finally {
127031
- rmSync10(staging, { recursive: true, force: true });
127032
- }
127033
- }
127034
- function assertPackedCanonicalCoverage(root2) {
127035
- const canonicalPaths = new Set(collectBundleFiles(root2).map((file2) => file2.rel));
127036
- const packedPaths = new Set(collectSkillBundleEntries(root2).map((entry) => entry.path));
127037
- const omitted = [...canonicalPaths].filter((path2) => !packedPaths.has(path2)).sort();
127038
- if (omitted.length === 0)
127039
- return;
127040
- const rendered = omitted.slice(0, MAX_PACK_PARITY_PATHS).map(escapedRelativePath).join(", ");
127041
- const suffix = omitted.length > MAX_PACK_PARITY_PATHS ? ` (+${omitted.length - MAX_PACK_PARITY_PATHS} more)` : "";
127042
- 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.");
127043
- }
127044
- function escapedRelativePath(path2) {
127045
- const bounded2 = path2.length > MAX_PACK_PARITY_PATH_CHARS - 8 ? `${path2.slice(0, MAX_PACK_PARITY_PATH_CHARS - 8)}...` : path2;
127046
- return JSON.stringify(bounded2);
127047
- }
127048
- function validateRuntimeEntrypoint(candidate2, root2) {
127049
- if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
127050
- return;
127051
- const entry = candidate2.runtime.entrypoint;
127052
- if (typeof entry !== "string")
127053
- return;
127054
- const target = resolve50(root2, entry), rel = relative17(root2, target);
127055
- if (isAbsolute50(entry) || /^[a-zA-Z]:/.test(entry) || entry.includes("\\") || !rel || rel === ".." || rel.startsWith(`..${sep25}`) || isAbsolute50(rel) || !existsSync37(target) || !lstatSync54(target).isFile()) {
127056
- throw new Error("Executable runtime.entrypoint must name an existing regular file inside the skill directory.");
127057
- }
127058
- }
127059
- function isRecord9(value) {
127060
- return typeof value === "object" && value !== null && !Array.isArray(value);
127061
- }
127062
- function validateRawCandidate(candidate2) {
127063
- for (const field4 of ["standard", "name", "description", "version"]) {
127064
- if (typeof candidate2[field4] !== "string" || !candidate2[field4].trim())
127065
- throw new Error(`skill.json ${field4} is required.`);
127066
- }
127067
- for (const field4 of ["displayName", "category"]) {
127068
- if (candidate2[field4] !== undefined && (typeof candidate2[field4] !== "string" || !candidate2[field4].trim()))
127069
- throw new Error(`Invalid skill.json ${field4}.`);
127070
- }
127071
- if (candidate2.tags !== undefined && (!Array.isArray(candidate2.tags) || candidate2.tags.some((tag) => typeof tag !== "string" || !tag.trim())))
127072
- throw new Error("Invalid skill.json tags.");
127073
- const issues = validatePortableManifestContract(candidate2, { strict: true });
127074
- if (issues.length)
127075
- throw new Error(`Invalid skill.json: ${issues.map((issue2) => issue2.code).join(", ")}`);
127076
- validateRuntimeEntrypointCompatibility(candidate2);
127077
- for (const field4 of ["inputs", "commands"]) {
127078
- const items = candidate2[field4];
127079
- if (items === undefined)
127080
- continue;
127081
- if (!Array.isArray(items))
127082
- throw new Error(`skill.json ${field4} must be an array.`);
127083
- for (const item of items) {
127084
- if (!isRecord9(item) || typeof item.name !== "string" || !item.name.trim())
127085
- throw new Error(`Invalid skill.json ${field4} name.`);
127086
- if (field4 === "inputs" && (typeof item.type !== "string" || !item.type.trim() || item.required !== undefined && typeof item.required !== "boolean"))
127087
- throw new Error("Invalid skill.json input type or required flag.");
127088
- if (field4 === "commands") {
127089
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(item.name))
127090
- throw new Error("Invalid skill.json command name.");
127091
- for (const target of ["entry", "command"])
127092
- if (item[target] !== undefined && (typeof item[target] !== "string" || !item[target].trim()))
127093
- throw new Error(`Invalid skill.json command ${target}.`);
127094
- if (!item.entry && !item.command)
127095
- throw new Error("A skill.json command needs entry or command.");
127096
- if (item.args !== undefined && (!Array.isArray(item.args) || item.args.some((arg) => typeof arg !== "string")))
127097
- throw new Error("Invalid skill.json command args.");
127098
- }
127099
- }
127100
- }
127101
- }
127102
- function validateRuntimeEntrypointCompatibility(candidate2) {
127103
- if (candidate2.kind !== "executable" || !isRecord9(candidate2.runtime))
127104
- return;
127105
- const runtime = candidate2.runtime.runtime;
127106
- const entrypoint = candidate2.runtime.entrypoint;
127107
- if (typeof runtime !== "string" || typeof entrypoint !== "string")
127108
- return;
127109
- const extension = entrypoint.toLowerCase().split(".").pop();
127110
- if (extension === "py" && runtime !== "python3") {
127111
- throw new Error("Invalid skill.json: runtime.entrypoint with .py extension requires runtime python3.");
127112
- }
127113
- }
127114
- function compareVersions(left, right) {
127115
- const parts = (value) => value.split("+")[0].split(/-(.*)/s).slice(0, 2);
127116
- const [a, ap] = parts(left), [b, bp] = parts(right);
127117
- const ac = a.split(".").map(BigInt), bc = b.split(".").map(BigInt);
127118
- for (let i = 0;i < 3; i++)
127119
- if (ac[i] !== bc[i])
127120
- return ac[i] > bc[i] ? 1 : -1;
127121
- if (ap === undefined || bp === undefined)
127122
- return ap === bp ? 0 : ap === undefined ? 1 : -1;
127123
- const aa = ap.split("."), bb = bp.split(".");
127124
- for (let i = 0;i < Math.max(aa.length, bb.length); i++) {
127125
- const x = aa[i], y = bb[i];
127126
- if (x === undefined || y === undefined)
127127
- return x === y ? 0 : x === undefined ? -1 : 1;
127128
- if (x === y)
127129
- continue;
127130
- const xn = /^\d+$/.test(x), yn = /^\d+$/.test(y);
127131
- if (xn && yn)
127132
- return BigInt(x) > BigInt(y) ? 1 : -1;
127133
- if (xn !== yn)
127134
- return xn ? -1 : 1;
127135
- return x > y ? 1 : -1;
127136
- }
127137
- return 0;
127138
- }
127139
- function snapshot2(root2) {
127140
- const rootStat = lstatSync54(root2);
127141
- if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
127142
- throw new Error("Skill root must be a real directory, not a symlink.");
127143
- const files = [], directories = [];
127144
- const hash8 = createHash55("sha256").update(`${rootStat.dev}:${rootStat.ino}\x00`);
127145
- let count = 0, bytes = 0;
127146
- const walk2 = (relative18) => {
127147
- for (const name of readdirSync31(join84(root2, relative18)).sort()) {
127148
- if (IGNORED.has(name.toLowerCase()) || !relative18 && ROOT_IGNORED.has(name.toLowerCase()))
127149
- continue;
127150
- const path2 = relative18 ? `${relative18}/${name}` : name;
127151
- if (++count > 20000)
127152
- throw new Error("Skill exceeds the preparation entry limit (20000).");
127153
- const absolute = join84(root2, path2), stat3 = lstatSync54(absolute);
127154
- if (stat3.isSymbolicLink())
127155
- throw new Error(`Skill contains a symlink: ${path2}. No files were changed.`);
127156
- if (stat3.isDirectory() && name === "node_modules")
127157
- continue;
127158
- hash8.update(JSON.stringify([path2, stat3.dev, stat3.ino, stat3.mode]));
127159
- if (stat3.isDirectory()) {
127160
- directories.push(path2);
127161
- walk2(path2);
127162
- } else if (stat3.isFile()) {
127163
- bytes += stat3.size;
127164
- if (bytes > 50000000)
127165
- throw new Error("Skill exceeds the preparation size limit (50000000 bytes).");
127166
- const content = readFileSync51(absolute);
127167
- if (content.length !== stat3.size)
127168
- throw new Error("Skill changed during preparation.");
127169
- files.push({ path: path2, bytes: content, mode: stat3.mode & 511 });
127170
- hash8.update(`\x00${content.length}\x00`).update(content);
127171
- } else
127172
- throw new Error(`Unsupported skill entry: ${path2}.`);
127173
- hash8.update("\x00");
127174
- }
127175
- };
127176
- walk2("");
127177
- return { files, directories, identity: hash8.digest("hex") };
127178
- }
127179
- var MAX_PACK_PARITY_PATHS = 8, MAX_PACK_PARITY_PATH_CHARS = 120, IGNORED, ROOT_IGNORED;
127180
- var init_prepare_skill = __esm(() => {
127181
- init_portable_skills();
127182
- init_skill_contract();
127183
- init_skill_hash();
127184
- init_skill_bundle();
127185
- init_skill_validation();
127186
- init_authoring_path();
127187
- IGNORED = new Set([".git", ".ds_store", ".system"]);
127188
- ROOT_IGNORED = new Set(["dist", "build", ".turbo", ".skills-dependency-preparation"]);
127189
- });
127190
-
127191
- // src/cli/commands/portable-skills.ts
127192
- var exports_portable_skills = {};
127193
- __export(exports_portable_skills, {
127194
- registerPortableSkillCommands: () => registerPortableSkillCommands
127195
- });
127196
- function registerPortableSkillCommands(parent) {
127197
- 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) => {
127198
- try {
127199
- const result2 = prepareSkill(name, { ...options, kind: options.kind === undefined ? undefined : parseSkillKind2(options.kind) });
127200
- if (result2.written)
127201
- clearRegistryCache();
127202
- if (options.json)
127203
- await writeCliOutput(JSON.stringify(result2, null, 2));
127204
- else {
127205
- console.log(source_default.green(`${result2.written ? "Prepared" : options.dryRun ? "Previewed" : "Already prepared"} ${result2.name}@${result2.version} (${result2.kind})`));
127206
- console.log(source_default.dim(` Content hash: ${result2.contentHash}`));
127207
- console.log(source_default.dim(` Manifest: ${result2.path}/skill.json`));
127208
- if (result2.written)
127209
- console.log(source_default.dim(` Publish: skills push ${result2.name}`));
127210
- }
127211
- } catch (error61) {
127212
- if (options.json)
127213
- await writeCliOutput(JSON.stringify({ error: error61.message }, null, 2));
127214
- else
127215
- console.error(source_default.red(error61.message));
127216
- process.exitCode = 1;
127217
- }
127218
- });
127219
- 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) => {
127220
- try {
127221
- const kind = parseSkillKind2(options.kind);
127222
- const result2 = scaffoldPortableSkill(name, {
127223
- description: options.description,
127224
- overwrite: options.overwrite,
127225
- kind
127226
- });
127227
- clearRegistryCache();
127228
- if (options.json) {
127229
- console.log(JSON.stringify({ ...result2, manifest: result2.manifest }, null, 2));
127230
- return;
127231
- }
127232
- console.log(source_default.green(`\u2713 Created ${kind} skill '${result2.name}'`));
127233
- console.log(source_default.dim(` Path: ${result2.path}`));
127234
- if (kind === "instruction") {
127235
- console.log(source_default.dim(` Edit the prose in: ${result2.path}/SKILL.md`));
127236
- console.log(source_default.dim(` Instruction skills are consumed by agents, not run with 'skills run'.`));
127237
- } else {
127238
- console.log(source_default.dim(` Agent instructions: ${result2.path}/AGENTS.md`));
127239
- console.log(source_default.dim(` Validate: skills validate ${result2.name}`));
127240
- console.log(source_default.dim(` Run: skills run ${result2.name} --help`));
127241
- }
127242
- } catch (error61) {
127243
- writePortableError(error61, options.json);
127244
- }
127245
- });
127246
- 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) => {
127247
- if (options.all) {
127248
- handleBulkPort(path2, options);
127249
- return;
127250
- }
127251
- try {
127252
- const result2 = portPortableSkill(path2, {
127253
- name: options.name,
127254
- overwrite: options.overwrite,
127255
- allowShadow: options.allowShadow
127256
- });
127257
- clearRegistryCache();
127258
- const validation = validatePortableSkillDirectory(result2.name, result2.path);
127259
- const payload = { ...result2, valid: validation.valid, issues: validation.issues, warnings: validation.warnings };
127260
- if (!validation.valid)
127261
- process.exitCode = 1;
127262
- if (options.json) {
127263
- console.log(JSON.stringify(payload, null, 2));
127264
- return;
127265
- }
127266
- 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.`));
127267
- console.log(source_default.dim(` Path: ${result2.path}`));
127268
- console.log(source_default.dim(` Valid: ${validation.valid ? "yes" : "no"}`));
127269
- if (!validation.valid) {
127270
- for (const issue2 of validation.issues)
127271
- console.log(source_default.red(` \u2022 ${issue2.message}`));
127272
- }
127273
- } catch (error61) {
127274
- writePortableError(error61, options.json);
127275
- }
127276
- });
127277
- }
127278
- function handleBulkPort(path2, options) {
127279
- if (options.name) {
127280
- writePortableError(new Error("--name cannot be used with --all"), options.json);
127281
- return;
127282
- }
127283
- try {
127284
- const summary = portPortableSkillDirectory(path2, { overwrite: options.overwrite, allowShadow: options.allowShadow });
127285
- clearRegistryCache();
127286
- if (options.json) {
127287
- console.log(JSON.stringify(summary, null, 2));
127288
- if (summary.failed > 0)
127289
- process.exitCode = 1;
127290
- return;
127291
- }
127292
- console.log(source_default.green(`\u2713 Imported ${summary.succeeded}/${summary.total} skill(s) from ${summary.root}`));
127293
- for (const entry of summary.imported)
127294
- console.log(source_default.dim(` \u2022 ${entry.name} \u2192 ${entry.path}`));
127295
- if (summary.skipped.length > 0) {
127296
- console.log(source_default.yellow(` Skipped ${summary.skipped.length}:`));
127297
- for (const entry of summary.skipped)
127298
- console.log(source_default.yellow(` \u2022 ${entry.sourcePath}: ${entry.reason}`));
127299
- process.exitCode = 1;
127300
- }
127301
- } catch (error61) {
127302
- writePortableError(error61, options.json);
127303
- }
127304
- }
127305
- function parseSkillKind2(value) {
127306
- if (value === undefined || value === "executable")
127307
- return "executable";
127308
- if (value === "instruction")
127309
- return "instruction";
127310
- throw new Error(`Invalid --kind '${value}'. Use 'executable' or 'instruction'.`);
127311
- }
127312
- function writePortableError(error61, json3) {
127313
- const message = error61.message;
127314
- if (json3)
127315
- console.log(JSON.stringify({ error: message }, null, 2));
127316
- else
127317
- console.error(source_default.red(message));
127318
- process.exitCode = 1;
127319
- }
127320
- var init_portable_skills2 = __esm(() => {
127321
- init_source();
127322
- init_registry();
127323
- init_prepare_skill();
127324
- init_portable_skills();
127325
- });
127326
-
127327
127430
  // src/cli/commands/metadata.ts
127328
127431
  var exports_metadata = {};
127329
127432
  __export(exports_metadata, {