@hasna/skills 0.10.33 → 0.10.35

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
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.10.33",
36871
+ version: "0.10.35",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -52593,18 +52593,80 @@ var init_agent_sumi = __esm(() => {
52593
52593
  ];
52594
52594
  });
52595
52595
 
52596
+ // src/lib/codex-hook-trust-files.ts
52597
+ import { closeSync as closeSync8, constants as constants8, fstatSync as fstatSync8, fsyncSync as fsyncSync2, lstatSync as lstatSync16, openSync as openSync8, readSync as readSync6, writeFileSync as writeFileSync4 } from "fs";
52598
+ import { createHash as createHash12 } from "crypto";
52599
+ import { dirname as dirname11 } from "path";
52600
+ function need5(value, code) {
52601
+ if (!value)
52602
+ throw new Error(`CODEX_HOOK_TRUST_${code}`);
52603
+ }
52604
+ function safeParents(file) {
52605
+ for (let p = dirname11(file);; p = dirname11(p)) {
52606
+ const s = lstatSync16(p);
52607
+ need5(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
52608
+ need5((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
52609
+ if (dirname11(p) === p)
52610
+ break;
52611
+ }
52612
+ }
52613
+ function snapshot(file, privateFile = false, options = {}) {
52614
+ const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
52615
+ safeParents(file);
52616
+ const initial2 = lstatSync16(file, { bigint: true });
52617
+ need5(initial2.isFile() && initial2.uid === BigInt(process.getuid()) && (initial2.nlink === 1n || readOnlyPackage && initial2.nlink > 1n) && (initial2.mode & 0o022n) === 0n && (!privateFile || (initial2.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
52618
+ need5(initial2.size <= 8n * 1024n * 1024n, "FILE_BOUND");
52619
+ const fd = openSync8(file, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
52620
+ try {
52621
+ const before2 = fstatSync8(fd, { bigint: true });
52622
+ need5(sameStat(initial2, before2), "INPUT_CHANGED");
52623
+ const bytes = Buffer.alloc(Number(before2.size) + 1);
52624
+ let length = 0;
52625
+ while (length < bytes.length) {
52626
+ const n2 = readSync6(fd, bytes, length, bytes.length - length, null);
52627
+ if (!n2)
52628
+ break;
52629
+ length += n2;
52630
+ }
52631
+ need5(length === Number(before2.size) && sameStat(before2, fstatSync8(fd, { bigint: true })) && sameStat(before2, lstatSync16(file, { bigint: true })), "INPUT_CHANGED");
52632
+ const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
52633
+ return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before2, sha256: createHash12("sha256").update(content).digest("hex") };
52634
+ } finally {
52635
+ closeSync8(fd);
52636
+ }
52637
+ }
52638
+ function unchanged2(before2) {
52639
+ const after2 = snapshot(before2.file, before2.privateFile, before2.readOnlyPackage ? { readOnlyPackage: true } : {});
52640
+ need5(sameStat(before2.stat, after2.stat) && after2.sha256 === before2.sha256, "INPUT_CHANGED");
52641
+ }
52642
+ function save(file, content) {
52643
+ safeParents(file);
52644
+ const fd = openSync8(file, constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW, 384);
52645
+ try {
52646
+ writeFileSync4(fd, content);
52647
+ fsyncSync2(fd);
52648
+ } finally {
52649
+ closeSync8(fd);
52650
+ }
52651
+ }
52652
+ var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
52653
+ var init_codex_hook_trust_files = () => {};
52654
+
52596
52655
  // src/lib/agent-integration.ts
52597
- import { createHash as createHash12, randomUUID as randomUUID5 } from "crypto";
52656
+ import { createHash as createHash13, randomUUID as randomUUID5 } from "crypto";
52598
52657
  import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
52599
- import { existsSync as existsSync13, lstatSync as lstatSync16, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync13, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync, openSync as openSync8, closeSync as closeSync8, fsyncSync as fsyncSync2, fstatSync as fstatSync8, readSync as readSync6, constants as constants8, linkSync as linkSync2 } from "fs";
52600
- import { dirname as dirname11, join as join23, resolve as resolve12, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
52658
+ import { existsSync as existsSync13, lstatSync as lstatSync17, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync13, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync7, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, chmodSync, openSync as openSync9, closeSync as closeSync9, fsyncSync as fsyncSync3, fstatSync as fstatSync9, readSync as readSync7, constants as constants9, linkSync as linkSync2 } from "fs";
52659
+ import { dirname as dirname12, join as join23, resolve as resolve12, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
52601
52660
  import { homedir as homedir3 } from "os";
52602
- function hasNoDarwinAcl(fd) {
52661
+ function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
52603
52662
  try {
52604
- const { dlopen, read: read2 } = __require("bun:ffi");
52663
+ const { dlopen, read: read2, ptr } = __require("bun:ffi");
52605
52664
  const library = dlopen("/usr/lib/libSystem.B.dylib", {
52606
52665
  acl_get_fd: { args: ["i32"], returns: "ptr" },
52607
52666
  acl_free: { args: ["ptr"], returns: "i32" },
52667
+ acl_valid: { args: ["ptr"], returns: "i32" },
52668
+ acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
52669
+ acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
52608
52670
  __error: { args: [], returns: "ptr" }
52609
52671
  });
52610
52672
  try {
@@ -52613,8 +52675,23 @@ function hasNoDarwinAcl(fd) {
52613
52675
  return false;
52614
52676
  const acl = library.symbols.acl_get_fd(fd);
52615
52677
  if (acl) {
52616
- library.symbols.acl_free(acl);
52617
- return false;
52678
+ try {
52679
+ if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
52680
+ return false;
52681
+ const entry = new BigUint64Array(1), tag = new Int32Array(1);
52682
+ for (let count = 0;count <= 128; count++) {
52683
+ const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
52684
+ if (status !== 0) {
52685
+ return status === -1 && read2.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
52686
+ }
52687
+ const value = read2.ptr(ptr(entry));
52688
+ if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
52689
+ return false;
52690
+ }
52691
+ return false;
52692
+ } finally {
52693
+ library.symbols.acl_free(acl);
52694
+ }
52618
52695
  }
52619
52696
  return (acl === null || acl === 0) && read2.i32(errno) === 2;
52620
52697
  } finally {
@@ -52630,11 +52707,11 @@ function isSystemRootAlias(path) {
52630
52707
  const target = `/private${path}`, link2 = `private${path}`;
52631
52708
  const identity5 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}`;
52632
52709
  try {
52633
- const before2 = lstatSync16(path, { bigint: true });
52710
+ const before2 = lstatSync17(path, { bigint: true });
52634
52711
  if (!before2.isSymbolicLink() || before2.uid !== 0n || readlinkSync4(path) !== link2)
52635
52712
  return false;
52636
52713
  const directories = ["/", "/private", target].map((directory) => {
52637
- const stat = lstatSync16(directory, { bigint: true });
52714
+ const stat = lstatSync17(directory, { bigint: true });
52638
52715
  if (!stat.isDirectory() || stat.uid !== 0n)
52639
52716
  throw new Error("Untrusted system alias directory");
52640
52717
  if (directory !== "/private/tmp") {
@@ -52642,23 +52719,23 @@ function isSystemRootAlias(path) {
52642
52719
  throw new Error("Writable system alias directory");
52643
52720
  } else if ((stat.mode & 0o1000n) === 0n)
52644
52721
  throw new Error("Unprotected system temporary directory");
52645
- const fd = openSync8(directory, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW);
52722
+ const fd = openSync9(directory, constants9.O_RDONLY | constants9.O_DIRECTORY | constants9.O_NOFOLLOW);
52646
52723
  try {
52647
- if (identity5(fstatSync8(fd, { bigint: true })) !== identity5(stat) || !hasNoDarwinAcl(fd) || identity5(fstatSync8(fd, { bigint: true })) !== identity5(stat))
52724
+ if (identity5(fstatSync9(fd, { bigint: true })) !== identity5(stat) || !hasTrustedDarwinAcl(fd) || identity5(fstatSync9(fd, { bigint: true })) !== identity5(stat))
52648
52725
  throw new Error("Unverified system alias directory ACL");
52649
52726
  } finally {
52650
- closeSync8(fd);
52727
+ closeSync9(fd);
52651
52728
  }
52652
52729
  return { directory, identity: identity5(stat) };
52653
52730
  });
52654
- return realpathSync7(path) === target && readlinkSync4(path) === link2 && identity5(lstatSync16(path, { bigint: true })) === identity5(before2) && directories.every((item) => identity5(lstatSync16(item.directory, { bigint: true })) === item.identity);
52731
+ return realpathSync7(path) === target && readlinkSync4(path) === link2 && identity5(lstatSync17(path, { bigint: true })) === identity5(before2) && directories.every((item) => identity5(lstatSync17(item.directory, { bigint: true })) === item.identity);
52655
52732
  } catch {
52656
52733
  return false;
52657
52734
  }
52658
52735
  }
52659
52736
  function canonicalSystemPath(path) {
52660
52737
  const absolute = resolve12(path), root = `/${absolute.split(sep5)[1]}`;
52661
- if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync16(root, { throwIfNoEntry: false })?.isSymbolicLink())
52738
+ if (process.platform !== "darwin" || !["/var", "/tmp", "/etc"].includes(root) || !lstatSync17(root, { throwIfNoEntry: false })?.isSymbolicLink())
52662
52739
  return absolute;
52663
52740
  if (!isSystemRootAlias(root))
52664
52741
  throw new Error(`Refusing symlink path: ${root}`);
@@ -52667,9 +52744,9 @@ function canonicalSystemPath(path) {
52667
52744
  function assertSafePath(path) {
52668
52745
  let cursor = resolve12(path);
52669
52746
  while (true) {
52670
- if (lstatSync16(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
52747
+ if (lstatSync17(cursor, { throwIfNoEntry: false })?.isSymbolicLink() && !isSystemRootAlias(cursor))
52671
52748
  throw new Error(`Refusing symlink path: ${cursor}`);
52672
- const parent = dirname11(cursor);
52749
+ const parent = dirname12(cursor);
52673
52750
  if (parent === cursor)
52674
52751
  return;
52675
52752
  cursor = parent;
@@ -52678,12 +52755,12 @@ function assertSafePath(path) {
52678
52755
  function readRootAlias(home, agent) {
52679
52756
  assertSafePath(home);
52680
52757
  const realHome = realpathSync7(home), alias = join23(realHome, `.${agent}`);
52681
- const stat = lstatSync16(alias, { throwIfNoEntry: false, bigint: true });
52758
+ const stat = lstatSync17(alias, { throwIfNoEntry: false, bigint: true });
52682
52759
  if (!stat?.isSymbolicLink())
52683
52760
  throw new Error(`Agent root alias changed after planning: ${alias}`);
52684
- const link2 = readlinkSync4(alias), targetPath = resolve12(dirname11(alias), link2);
52761
+ const link2 = readlinkSync4(alias), targetPath = resolve12(dirname12(alias), link2);
52685
52762
  assertSafePath(targetPath);
52686
- const targetStat = lstatSync16(targetPath, { throwIfNoEntry: false });
52763
+ const targetStat = lstatSync17(targetPath, { throwIfNoEntry: false });
52687
52764
  if (!targetStat?.isDirectory())
52688
52765
  throw new Error(`Agent root alias must target an existing directory: ${alias}`);
52689
52766
  const target = realpathSync7(targetPath), inside = relative5(realHome, target);
@@ -52696,7 +52773,7 @@ function rootAliases(home, allowed = false) {
52696
52773
  return [];
52697
52774
  const result2 = [];
52698
52775
  for (const agent of ["claude", "codex"])
52699
- if (lstatSync16(join23(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
52776
+ if (lstatSync17(join23(home, `.${agent}`), { throwIfNoEntry: false })?.isSymbolicLink())
52700
52777
  result2.push(readRootAlias(home, agent));
52701
52778
  if (result2.some((item, index) => result2.slice(index + 1).some((other) => item.target === other.target || item.target.startsWith(other.target + sep5) || other.target.startsWith(item.target + sep5))))
52702
52779
  throw new Error("Agent root alias targets must not overlap");
@@ -52731,7 +52808,7 @@ function disabledCodexSkillPaths(config, aliases) {
52731
52808
  continue;
52732
52809
  settings.set(document3, [...settings.get(document3) ?? [], entry.enabled === false]);
52733
52810
  }
52734
- return [...settings].filter(([, values2]) => values2.length === 1 && values2[0] === true).map(([document3]) => dirname11(document3));
52811
+ return [...settings].filter(([, values2]) => values2.length === 1 && values2[0] === true).map(([document3]) => dirname12(document3));
52735
52812
  }
52736
52813
  function recheckRootAliases(aliases) {
52737
52814
  for (const binding of aliases) {
@@ -52744,35 +52821,35 @@ function recheckRootAliases(aliases) {
52744
52821
  }
52745
52822
  function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
52746
52823
  assertSafePath(path);
52747
- const before2 = lstatSync16(path);
52824
+ const before2 = lstatSync17(path);
52748
52825
  if (!before2.isFile() || before2.size > maximum)
52749
52826
  throw new Error("Unsupported or oversized native skill file");
52750
- const descriptor = openSync8(path, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
52827
+ const descriptor = openSync9(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
52751
52828
  try {
52752
- const opened = fstatSync8(descriptor);
52829
+ const opened = fstatSync9(descriptor);
52753
52830
  if (!opened.isFile() || opened.dev !== before2.dev || opened.ino !== before2.ino || opened.size !== before2.size)
52754
52831
  throw new Error("Native skill file changed while reading");
52755
52832
  const bytes = Buffer.allocUnsafe(before2.size + 1);
52756
52833
  let length = 0;
52757
52834
  while (length < bytes.length) {
52758
- const count = readSync6(descriptor, bytes, length, bytes.length - length, null);
52835
+ const count = readSync7(descriptor, bytes, length, bytes.length - length, null);
52759
52836
  if (!count)
52760
52837
  break;
52761
52838
  length += count;
52762
52839
  }
52763
- const after2 = fstatSync8(descriptor), current = lstatSync16(path);
52840
+ const after2 = fstatSync9(descriptor), current = lstatSync17(path);
52764
52841
  if (current.dev !== opened.dev || current.ino !== opened.ino || current.size !== after2.size || current.mtimeMs !== after2.mtimeMs || current.ctimeMs !== after2.ctimeMs || length !== before2.size || after2.size !== before2.size || after2.mtimeMs !== opened.mtimeMs || after2.ctimeMs !== opened.ctimeMs)
52765
52842
  throw new Error("Native skill file changed while reading");
52766
52843
  return bytes.subarray(0, length);
52767
52844
  } finally {
52768
- closeSync8(descriptor);
52845
+ closeSync9(descriptor);
52769
52846
  }
52770
52847
  }
52771
52848
  function treeHash(root) {
52772
- const hash2 = createHash12("sha256");
52849
+ const hash2 = createHash13("sha256");
52773
52850
  let bytes = 0, files = 0;
52774
52851
  function visit2(path) {
52775
- const stat = lstatSync16(path);
52852
+ const stat = lstatSync17(path);
52776
52853
  if (stat.isSymbolicLink())
52777
52854
  throw new Error(`Refusing symlink in skill: ${path}`);
52778
52855
  const rel = relative5(root, path);
@@ -52823,7 +52900,7 @@ function parseNativeMigrationTargetManifest(input) {
52823
52900
  if (!isAbsolute11(item.projectRoot) || resolve12(item.projectRoot) !== item.projectRoot)
52824
52901
  throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
52825
52902
  const projectRoot = canonicalSystemPath(item.projectRoot);
52826
- if (!existsSync13(projectRoot) || !lstatSync16(projectRoot).isDirectory())
52903
+ if (!existsSync13(projectRoot) || !lstatSync17(projectRoot).isDirectory())
52827
52904
  throw new Error(`Native migration target project root must be an existing absolute directory: ${item.projectRoot}`);
52828
52905
  assertSafePath(projectRoot);
52829
52906
  if (isAbsolute11(item.path) || item.path.includes("\\") || item.path.length === 0 || item.path.includes("\x00"))
@@ -52853,7 +52930,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
52853
52930
  if (!isAbsolute11(target.projectRoot) || resolve12(target.projectRoot) !== target.projectRoot)
52854
52931
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
52855
52932
  const projectRoot = canonicalSystemPath(target.projectRoot), normalized = relative5(projectRoot, resolve12(projectRoot, target.path));
52856
- if (!existsSync13(projectRoot) || !lstatSync16(projectRoot).isDirectory())
52933
+ if (!existsSync13(projectRoot) || !lstatSync17(projectRoot).isDirectory())
52857
52934
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
52858
52935
  assertSafePath(projectRoot);
52859
52936
  if (isAbsolute11(target.path) || target.path.includes("\\") || target.path.includes("\x00") || !normalized || normalized === ".." || normalized.startsWith(`..${sep5}`) || normalized !== target.path || !/^[0-9a-f]{64}$/.test(target.treeSha256))
@@ -52882,7 +52959,7 @@ function projectAncestorDirectories(projects) {
52882
52959
  if (depth >= 100)
52883
52960
  throw new Error("NATIVE_SKILL_DRIFT: project ancestor discovery limit exceeded");
52884
52961
  directories.add(path);
52885
- const parent = dirname11(path);
52962
+ const parent = dirname12(path);
52886
52963
  if (parent === path)
52887
52964
  break;
52888
52965
  path = parent;
@@ -52928,12 +53005,12 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52928
53005
  let reviewedCacheAliasFound = false;
52929
53006
  function verifiedSiblingCacheAlias(agent, path, parent) {
52930
53007
  const link2 = readlinkSync4(path), lexicalTarget = resolve12(parent, link2);
52931
- if (isAbsolute11(link2) ? link2 !== lexicalTarget : dirname11(link2) !== ".")
53008
+ if (isAbsolute11(link2) ? link2 !== lexicalTarget : dirname12(link2) !== ".")
52932
53009
  return false;
52933
53010
  const target = canonicalSystemPath(lexicalTarget);
52934
- if (dirname11(target) !== parent)
53011
+ if (dirname12(target) !== parent)
52935
53012
  return false;
52936
- if (!lstatSync16(target, { throwIfNoEntry: false })?.isDirectory())
53013
+ if (!lstatSync17(target, { throwIfNoEntry: false })?.isDirectory())
52937
53014
  return false;
52938
53015
  const scan = cacheScans.get(target);
52939
53016
  if (!scan?.complete || scan.entries > maxAliasProofEntries)
@@ -52958,7 +53035,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
52958
53035
  return scan;
52959
53036
  if (!existsSync13(path))
52960
53037
  return scan;
52961
- const stat = lstatSync16(path);
53038
+ const stat = lstatSync17(path);
52962
53039
  if (stat.isFile()) {
52963
53040
  if (agent === "sumi" && depth === 1 && path.endsWith(".md"))
52964
53041
  throw new Error("NATIVE_SKILL_DRIFT: flat Sumi skill documents require a dedicated migration adapter");
@@ -53007,7 +53084,7 @@ function inventoryNativeSkills(home = homedir3(), options = {}) {
53007
53084
  if (isVendor && !options.includeVendor)
53008
53085
  continue;
53009
53086
  const child = join23(path, name);
53010
- if (isVendor && lstatSync16(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
53087
+ if (isVendor && lstatSync17(child, { throwIfNoEntry: false })?.isSymbolicLink()) {
53011
53088
  const target = statSync8(child, { throwIfNoEntry: false });
53012
53089
  if (target?.isFile() || pluginCache && target?.isDirectory() && verifiedSiblingCacheAlias(agent, child, path)) {
53013
53090
  scan.entries++;
@@ -53341,6 +53418,171 @@ function planAgentIntegration(options) {
53341
53418
  recheckRootAliases(aliases);
53342
53419
  return { dataDir: dataDir3, profileId, changes, nativeSkills, ...observedNativeSources.length ? { observedNativeSources } : {}, ...nextPolicy.bridge.codexPluginSkillReview ? { codexPluginSkillReview: nextPolicy.bridge.codexPluginSkillReview } : {}, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
53343
53420
  }
53421
+ function hookProjectionRefusal(reason) {
53422
+ throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
53423
+ }
53424
+ function projectedHookAcl(path, observed) {
53425
+ if (process.platform !== "darwin")
53426
+ return;
53427
+ const identity5 = (stat) => `${stat.dev}:${stat.ino}:${stat.ctimeNs}:${stat.uid}:${stat.mode}:${stat.nlink}:${stat.size}:${stat.mtimeNs}`;
53428
+ const fd = openSync9(path, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK | (observed.isDirectory() ? constants9.O_DIRECTORY : 0));
53429
+ try {
53430
+ if (identity5(fstatSync9(fd, { bigint: true })) !== identity5(observed) || !hasTrustedDarwinAcl(fd, true) || identity5(fstatSync9(fd, { bigint: true })) !== identity5(observed) || identity5(lstatSync17(path, { bigint: true })) !== identity5(observed))
53431
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53432
+ } finally {
53433
+ closeSync9(fd);
53434
+ }
53435
+ }
53436
+ function projectedHookExecutable(command, expectedResolved) {
53437
+ try {
53438
+ if (!isAbsolute11(command) || /[\0\r\n]/.test(command) || resolve12(command) !== command)
53439
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53440
+ command = canonicalSystemPath(command);
53441
+ const parents2 = (file = command) => {
53442
+ const result2 = [];
53443
+ for (let path = dirname12(file);; path = dirname12(path)) {
53444
+ const stat = lstatSync17(path, { bigint: true });
53445
+ if (!stat.isDirectory() || (stat.mode & 0o022n) !== 0n && !(stat.uid === 0n && (stat.mode & 0o1000n) !== 0n))
53446
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53447
+ projectedHookAcl(path, stat);
53448
+ result2.push(`${path}:${stat.dev}:${stat.ino}:${stat.uid}:${stat.mode}`);
53449
+ if (dirname12(path) === path)
53450
+ break;
53451
+ }
53452
+ return result2;
53453
+ };
53454
+ const parentIdentity = parents2(), link2 = lstatSync17(command, { bigint: true });
53455
+ if (link2.uid !== BigInt(process.getuid()) || !link2.isFile() && !link2.isSymbolicLink())
53456
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53457
+ const linkIdentity = (stat) => `${stat.dev}:${stat.ino}:${stat.mode}:${stat.uid}:${stat.ctimeNs}`;
53458
+ const resolved = realpathSync7(command);
53459
+ if (expectedResolved !== undefined && resolved !== expectedResolved)
53460
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53461
+ const resolvedParentIdentity = parents2(resolved);
53462
+ const manifest = snapshot(join23(dirname12(dirname12(resolved)), "package.json"), false, { readOnlyPackage: true });
53463
+ projectedHookAcl(manifest.file, manifest.stat);
53464
+ const pkg = JSON.parse(manifest.text);
53465
+ if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join23(dirname12(manifest.file), "bin/index.js"))
53466
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53467
+ const cli = snapshot(resolved, false, { readOnlyPackage: true });
53468
+ projectedHookAcl(cli.file, cli.stat);
53469
+ if ((cli.stat.mode & 0o100n) === 0n)
53470
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53471
+ const recheck = () => {
53472
+ try {
53473
+ if (realpathSync7(command) !== resolved || linkIdentity(lstatSync17(command, { bigint: true })) !== linkIdentity(link2) || !isDeepStrictEqual5(parents2(), parentIdentity) || !isDeepStrictEqual5(parents2(resolved), resolvedParentIdentity))
53474
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53475
+ projectedHookAcl(cli.file, cli.stat);
53476
+ projectedHookAcl(manifest.file, manifest.stat);
53477
+ unchanged2(cli);
53478
+ unchanged2(manifest);
53479
+ } catch {
53480
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53481
+ }
53482
+ };
53483
+ recheck();
53484
+ return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
53485
+ } catch {
53486
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53487
+ }
53488
+ }
53489
+ function planClaudeManagedHookProjection(options) {
53490
+ if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
53491
+ hookProjectionRefusal("TARGET_INVALID");
53492
+ const targetSha256 = sha3(options.targetSettings);
53493
+ if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
53494
+ hookProjectionRefusal("TARGET_CHANGED");
53495
+ let target;
53496
+ try {
53497
+ target = jsonObject(options.targetSettings, "target");
53498
+ } catch {
53499
+ hookProjectionRefusal("TARGET_INVALID");
53500
+ }
53501
+ const home = resolve12(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
53502
+ const policyPath = join23(dataDir3, "agent-policy.json");
53503
+ let policyBytes, settingsBytes, settingsPath, command, profile;
53504
+ try {
53505
+ policyBytes = readNativeBytes(policyPath, 1024 * 1024);
53506
+ const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
53507
+ if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
53508
+ hookProjectionRefusal("SOURCE_UNVERIFIED");
53509
+ const binding = snapshot2.value.bridge;
53510
+ settingsPath = canonicalAgentPath(join23(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
53511
+ settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
53512
+ command = binding?.commands?.claude;
53513
+ profile = binding?.profiles?.claude;
53514
+ if (typeof command !== "string" || typeof profile !== "string")
53515
+ hookProjectionRefusal("SOURCE_UNVERIFIED");
53516
+ assertManagedAgentBridge("claude", { home, dataDir: dataDir3, projectDir: options.projectDir, profileId: profile });
53517
+ } catch {
53518
+ hookProjectionRefusal("SOURCE_UNVERIFIED");
53519
+ }
53520
+ const executable = projectedHookExecutable(command);
53521
+ const sourceSha256 = sha3(JSON.stringify({
53522
+ home,
53523
+ policyPath,
53524
+ policy: sha3(policyBytes),
53525
+ settingsPath,
53526
+ settings: sha3(settingsBytes),
53527
+ executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
53528
+ }));
53529
+ if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
53530
+ hookProjectionRefusal("SOURCE_CHANGED");
53531
+ const replacements2 = [];
53532
+ const observed = [executable];
53533
+ if (target.hooks !== undefined) {
53534
+ if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
53535
+ hookProjectionRefusal("TARGET_INVALID");
53536
+ for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
53537
+ const groups = target.hooks[event];
53538
+ if (groups === undefined)
53539
+ continue;
53540
+ if (!Array.isArray(groups))
53541
+ hookProjectionRefusal("TARGET_INVALID");
53542
+ let recognized = 0;
53543
+ for (const [groupIndex, group] of groups.entries()) {
53544
+ if (!group || !Array.isArray(group.hooks))
53545
+ hookProjectionRefusal("TARGET_INVALID");
53546
+ for (const [hookIndex, hook] of group.hooks.entries()) {
53547
+ if (hook?.type !== "command" || typeof hook.command !== "string")
53548
+ continue;
53549
+ if (!hook.command.includes("hook user-prompt --agent claude"))
53550
+ continue;
53551
+ const match = /^('(?:[^']|'\\'')*') hook user-prompt --agent claude --selection-profile ([A-Za-z0-9][A-Za-z0-9._-]{0,127}) --event ([A-Za-z]+)$/.exec(hook.command);
53552
+ if (!match || match[3] !== event)
53553
+ hookProjectionRefusal("UNRECOGNIZED");
53554
+ const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
53555
+ let rendered;
53556
+ try {
53557
+ rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
53558
+ } catch {
53559
+ hookProjectionRefusal("UNRECOGNIZED");
53560
+ }
53561
+ if (rendered !== hook.command)
53562
+ hookProjectionRefusal("UNRECOGNIZED");
53563
+ const legacy = projectedHookExecutable(oldCommand, executable.resolved);
53564
+ if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
53565
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
53566
+ observed.push(legacy);
53567
+ if (++recognized > 1)
53568
+ hookProjectionRefusal("AMBIGUOUS");
53569
+ const replacement = renderAgentHookCommand(command, "claude", profile, event);
53570
+ if (replacement !== hook.command)
53571
+ replacements2.push({ event, groupIndex, hookIndex, beforeSha256: sha3(hook.command), command: replacement });
53572
+ }
53573
+ }
53574
+ }
53575
+ }
53576
+ try {
53577
+ if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
53578
+ hookProjectionRefusal("SOURCE_CHANGED");
53579
+ } catch {
53580
+ hookProjectionRefusal("SOURCE_CHANGED");
53581
+ }
53582
+ for (const item of observed)
53583
+ item.recheck();
53584
+ return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements: replacements2 };
53585
+ }
53344
53586
  function verifyCoordinatedDiscovery(binding, home, aliases) {
53345
53587
  try {
53346
53588
  if (binding.method !== "automatic" && binding.method !== "reviewed")
@@ -53357,10 +53599,10 @@ function verifyCoordinatedDiscovery(binding, home, aliases) {
53357
53599
  }
53358
53600
  function atomicWrite2(path, content) {
53359
53601
  assertSafePath(path);
53360
- mkdirSync5(dirname11(path), { recursive: true, mode: 448 });
53602
+ mkdirSync5(dirname12(path), { recursive: true, mode: 448 });
53361
53603
  const temporary = `${path}.skills-${randomUUID5()}`;
53362
53604
  try {
53363
- writeFileSync4(temporary, content, { mode: 384, flag: "wx" });
53605
+ writeFileSync5(temporary, content, { mode: 384, flag: "wx" });
53364
53606
  renameSync2(temporary, path);
53365
53607
  } finally {
53366
53608
  if (existsSync13(temporary))
@@ -53371,10 +53613,10 @@ function planAgentSettingsWitnessUpgrade(options) {
53371
53613
  if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
53372
53614
  throw new Error("Invalid Codex witness target version");
53373
53615
  const home = resolve12(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
53374
- const snapshot = readManagedSkillPolicySnapshot(dataDir3);
53375
- if (!snapshot || sha3(snapshot.text) !== options.expectedPolicySha256)
53616
+ const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
53617
+ if (!snapshot2 || sha3(snapshot2.text) !== options.expectedPolicySha256)
53376
53618
  throw new Error("Managed policy preimage changed");
53377
- const policy = snapshot.value, aliases = policy.bridge?.rootAliases ?? [];
53619
+ const policy = snapshot2.value, aliases = policy.bridge?.rootAliases ?? [];
53378
53620
  recheckRootAliases(aliases);
53379
53621
  const binding = policy.bridge?.discovery?.[options.agent];
53380
53622
  if (!binding || binding.agent !== options.agent || binding.method !== "reviewed")
@@ -53418,7 +53660,7 @@ function planAgentSettingsWitnessUpgrade(options) {
53418
53660
  const replacement = { ...binding, sources: rebound };
53419
53661
  verifyAgentDiscovery(replacement);
53420
53662
  const after2 = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
53421
- return { dataDir: dataDir3, profileId: policy.profileId, changes: [{ path: join23(dataDir3, "agent-policy.json"), before: snapshot.text, after: after2 }], nativeSkills: [], observedPolicy: { path: join23(dataDir3, "agent-policy.json"), before: snapshot.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
53663
+ return { dataDir: dataDir3, profileId: policy.profileId, changes: [{ path: join23(dataDir3, "agent-policy.json"), before: snapshot2.text, after: after2 }], nativeSkills: [], observedPolicy: { path: join23(dataDir3, "agent-policy.json"), before: snapshot2.text }, observedSettings: { path: configPath, before: settings }, discoveryBefore: [replacement], discoveryAfter: [replacement], rootAliases: aliases, managedAgentChecks: { home, agents: [options.agent] }, settingsWitnessUpgrade: { agent: options.agent, path: configPath, fromHashMode: previous.hashMode ?? "bytes", fromSha256: previous.sha256, toHashMode: next.hashMode, toSha256: next.sha256, reviewedPreimage: options.reviewedPreimage, currentSettingsSha256: options.expectedSettingsSha256, replacedWitnesses: replaced.map((source) => ({ hashMode: source.hashMode ?? "bytes", sha256: source.sha256 })) } };
53422
53664
  }
53423
53665
  function applyAgentIntegration(plan) {
53424
53666
  for (const source of plan.observedNativeSources ?? [])
@@ -53471,7 +53713,7 @@ function applyAgentIntegration(plan) {
53471
53713
  for (const [index, change] of plan.changes.entries())
53472
53714
  if (change.before !== null) {
53473
53715
  const backup = join23(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
53474
- writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
53716
+ writeFileSync5(backup, change.before, { mode: 384, flag: "wx" });
53475
53717
  if (readFileSync13(backup, "utf8") !== change.before)
53476
53718
  throw new Error("Native configuration preservation readback failed");
53477
53719
  backups.push(backup);
@@ -53481,7 +53723,7 @@ function applyAgentIntegration(plan) {
53481
53723
  recheckRootAliases(aliases);
53482
53724
  if (currentText(change.path) !== change.before)
53483
53725
  throw new Error(`Configuration changed after planning: ${change.path}`);
53484
- for (let directory = dirname11(change.path);!existsSync13(directory); directory = dirname11(directory))
53726
+ for (let directory = dirname12(change.path);!existsSync13(directory); directory = dirname12(directory))
53485
53727
  createdDirectories.add(directory);
53486
53728
  const after2 = change.after;
53487
53729
  atomicWrite2(change.path, after2);
@@ -53532,11 +53774,11 @@ function applyAgentIntegration(plan) {
53532
53774
  }
53533
53775
  function syncArchiveDirectory(path) {
53534
53776
  assertSafePath(path);
53535
- const descriptor = openSync8(path, constants8.O_RDONLY | constants8.O_DIRECTORY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
53777
+ const descriptor = openSync9(path, constants9.O_RDONLY | constants9.O_DIRECTORY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
53536
53778
  try {
53537
- fsyncSync2(descriptor);
53779
+ fsyncSync3(descriptor);
53538
53780
  } finally {
53539
- closeSync8(descriptor);
53781
+ closeSync9(descriptor);
53540
53782
  }
53541
53783
  }
53542
53784
  function writeArchiveJournal(path, value) {
@@ -53544,18 +53786,18 @@ function writeArchiveJournal(path, value) {
53544
53786
  const temporary = `${path}.skills-${randomUUID5()}`;
53545
53787
  let descriptor;
53546
53788
  try {
53547
- descriptor = openSync8(temporary, constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW, 384);
53548
- writeFileSync4(descriptor, `${JSON.stringify(value)}
53789
+ descriptor = openSync9(temporary, constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW, 384);
53790
+ writeFileSync5(descriptor, `${JSON.stringify(value)}
53549
53791
  `);
53550
- fsyncSync2(descriptor);
53551
- closeSync8(descriptor);
53792
+ fsyncSync3(descriptor);
53793
+ closeSync9(descriptor);
53552
53794
  descriptor = undefined;
53553
53795
  renameSync2(temporary, path);
53554
- syncArchiveDirectory(dirname11(path));
53796
+ syncArchiveDirectory(dirname12(path));
53555
53797
  } finally {
53556
53798
  if (descriptor !== undefined)
53557
- closeSync8(descriptor);
53558
- if (lstatSync16(temporary, { throwIfNoEntry: false }))
53799
+ closeSync9(descriptor);
53800
+ if (lstatSync17(temporary, { throwIfNoEntry: false }))
53559
53801
  unlinkSync3(temporary);
53560
53802
  }
53561
53803
  }
@@ -53589,7 +53831,7 @@ function archiveNativeSkills(inventory, options) {
53589
53831
  const moved = [];
53590
53832
  const verifyArchive = (entry) => {
53591
53833
  assertSafePath(entry.archive);
53592
- const stat = lstatSync16(entry.archive);
53834
+ const stat = lstatSync17(entry.archive);
53593
53835
  if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
53594
53836
  throw new Error("Archive type changed");
53595
53837
  if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
@@ -53597,14 +53839,14 @@ function archiveNativeSkills(inventory, options) {
53597
53839
  };
53598
53840
  assertSafePath(archiveRoot);
53599
53841
  const created = [];
53600
- for (let path = archiveRoot;!existsSync13(path); path = dirname11(path))
53842
+ for (let path = archiveRoot;!existsSync13(path); path = dirname12(path))
53601
53843
  created.push(path);
53602
53844
  mkdirSync5(archiveRoot, { recursive: true, mode: 448 });
53603
53845
  try {
53604
53846
  for (const directory of created)
53605
53847
  syncArchiveDirectory(directory);
53606
53848
  if (created.length)
53607
- syncArchiveDirectory(dirname11(created.at(-1)));
53849
+ syncArchiveDirectory(dirname12(created.at(-1)));
53608
53850
  writeArchiveJournal(receiptPath, journal);
53609
53851
  for (const [index, entry] of selected.entries()) {
53610
53852
  recheckRootAliases(aliases);
@@ -53616,7 +53858,7 @@ function archiveNativeSkills(inventory, options) {
53616
53858
  writeArchiveJournal(receiptPath, journal);
53617
53859
  renameSync2(move.source, move.archive);
53618
53860
  moved.push(move);
53619
- syncArchiveDirectory(dirname11(move.source));
53861
+ syncArchiveDirectory(dirname12(move.source));
53620
53862
  syncArchiveDirectory(archiveRoot);
53621
53863
  verifyArchive(move);
53622
53864
  move.status = "archived";
@@ -53637,7 +53879,7 @@ function archiveNativeSkills(inventory, options) {
53637
53879
  recheckRootAliases(aliases);
53638
53880
  assertSafePath(entry.source);
53639
53881
  assertSafePath(entry.archive);
53640
- if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
53882
+ if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
53641
53883
  entry.status = "recovery-required";
53642
53884
  entry.conflict = "source-occupied";
53643
53885
  continue;
@@ -53651,10 +53893,10 @@ function archiveNativeSkills(inventory, options) {
53651
53893
  }
53652
53894
  if (entry.discoveryOnly) {
53653
53895
  linkSync2(entry.archive, entry.source);
53654
- syncArchiveDirectory(dirname11(entry.source));
53896
+ syncArchiveDirectory(dirname12(entry.source));
53655
53897
  unlinkSync3(entry.archive);
53656
53898
  } else {
53657
- if (lstatSync16(entry.source, { throwIfNoEntry: false })) {
53899
+ if (lstatSync17(entry.source, { throwIfNoEntry: false })) {
53658
53900
  entry.status = "recovery-required";
53659
53901
  entry.conflict = "source-occupied";
53660
53902
  continue;
@@ -53662,7 +53904,7 @@ function archiveNativeSkills(inventory, options) {
53662
53904
  renameSync2(entry.archive, entry.source);
53663
53905
  }
53664
53906
  entry.status = "restored";
53665
- syncArchiveDirectory(dirname11(entry.source));
53907
+ syncArchiveDirectory(dirname12(entry.source));
53666
53908
  syncArchiveDirectory(archiveRoot);
53667
53909
  } catch {
53668
53910
  entry.status = "recovery-required";
@@ -53687,10 +53929,10 @@ function assertManagedAgentBridge(agent, options = {}) {
53687
53929
  function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscovery) {
53688
53930
  const home = resolve12(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
53689
53931
  assertSafePath(join23(dataDir3, "agent-policy.json"));
53690
- const snapshot = readManagedSkillPolicySnapshot(dataDir3);
53691
- if (!snapshot)
53932
+ const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
53933
+ if (!snapshot2)
53692
53934
  throw new Error("NATIVE_SKILL_DRIFT: install the Skills bridge with skills hook install");
53693
- const binding = snapshot.value.bridge;
53935
+ const binding = snapshot2.value.bridge;
53694
53936
  if (!binding || binding.version !== CLI_BRIDGE_VERSION || binding.digest !== CLI_BRIDGE_DIGEST || binding.home !== home || !Array.isArray(binding.agents) || !binding.agents.includes(agent))
53695
53937
  throw new Error("NATIVE_SKILL_DRIFT: the managed Skills bridge binding is missing or incompatible; run skills hook install");
53696
53938
  const aliases = binding.rootAliases ?? [];
@@ -53846,7 +54088,7 @@ function assertNativeExportAllowed(dataDir3 = getDataDirReadOnly()) {
53846
54088
  if (requiresCliSkillLoading(dataDir3))
53847
54089
  throw new Error("NATIVE_SKILL_EXPORT_DISABLED: this station loads skills through the Skills CLI; use skills sync --selection-profile <id>");
53848
54090
  }
53849
- var sha3 = (data) => createHash12("sha256").update(data).digest("hex"), HOOK_EVENTS, ROOTS, SKILLS_LOADING_POLICY = "Skills loading policy: discover skills with `skills list` or `skills search`, and read selected instructions with `skills load <slug>`. Use `skills sync` to refresh the shared profile. The only native skill is the owned skills-cli bridge. Create and edit payload skills in the Skills authoring workspace; do not copy payload instructions into native discovery folders. Context loading does not authorize execution; use `skills run` only when the task calls for running a skill.";
54091
+ var sha3 = (data) => createHash13("sha256").update(data).digest("hex"), HOOK_EVENTS, ROOTS, SKILLS_LOADING_POLICY = "Skills loading policy: discover skills with `skills list` or `skills search`, and read selected instructions with `skills load <slug>`. Use `skills sync` to refresh the shared profile. The only native skill is the owned skills-cli bridge. Create and edit payload skills in the Skills authoring workspace; do not copy payload instructions into native discovery folders. Context loading does not authorize execution; use `skills run` only when the task calls for running a skill.";
53850
54092
  var init_agent_integration = __esm(() => {
53851
54093
  init_codex_plugin_skill_controls();
53852
54094
  init_codex_native_skill_catalog();
@@ -53862,12 +54104,13 @@ var init_agent_integration = __esm(() => {
53862
54104
  init_agent_sumi();
53863
54105
  init_agent_hermes();
53864
54106
  init_claude_settings_witness();
54107
+ init_codex_hook_trust_files();
53865
54108
  HOOK_EVENTS = ["UserPromptSubmit", "SessionStart", "SubagentStart"];
53866
54109
  ROOTS = NATIVE_SKILL_ROOTS;
53867
54110
  });
53868
54111
 
53869
54112
  // src/lib/home-migration.ts
53870
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync9, renameSync as renameSync3, rmSync, statSync as statSync9, writeFileSync as writeFileSync5 } from "fs";
54113
+ import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync9, renameSync as renameSync3, rmSync, statSync as statSync9, writeFileSync as writeFileSync6 } from "fs";
53871
54114
  import { join as join25 } from "path";
53872
54115
  function layoutMigrationRecordPath(appDir) {
53873
54116
  return join25(appDir, SKILLS_CACHE_DIRNAME, LAYOUT_MIGRATION_RECORD);
@@ -53990,7 +54233,7 @@ function migrateOwnerLayout(options = {}) {
53990
54233
  moved,
53991
54234
  note: "installed/ and legacy flat skill dirs moved into skills/; custom/ retained"
53992
54235
  };
53993
- writeFileSync5(layoutMigrationRecordPath(appDir), `${JSON.stringify(record3, null, 2)}
54236
+ writeFileSync6(layoutMigrationRecordPath(appDir), `${JSON.stringify(record3, null, 2)}
53994
54237
  `);
53995
54238
  return { status: "migrated", moved, created, record: record3 };
53996
54239
  }
@@ -54005,7 +54248,7 @@ var init_home_migration = __esm(() => {
54005
54248
  import {
54006
54249
  cpSync as cpSync2,
54007
54250
  existsSync as existsSync15,
54008
- lstatSync as lstatSync17,
54251
+ lstatSync as lstatSync18,
54009
54252
  mkdirSync as mkdirSync7,
54010
54253
  mkdtempSync,
54011
54254
  readFileSync as readFileSync14,
@@ -54013,17 +54256,17 @@ import {
54013
54256
  renameSync as renameSync4,
54014
54257
  rmSync as rmSync2,
54015
54258
  statSync as statSync10,
54016
- writeFileSync as writeFileSync6
54259
+ writeFileSync as writeFileSync7
54017
54260
  } from "fs";
54018
54261
  import { homedir as homedir4 } from "os";
54019
- import { basename as basename6, dirname as dirname12, join as join26 } from "path";
54262
+ import { basename as basename6, dirname as dirname13, join as join26 } from "path";
54020
54263
  function isSkillsOwnershipMarker(marker) {
54021
54264
  return typeof marker === "object" && marker !== null && Object.hasOwn(marker, "managedBy") && marker.managedBy === SYNC_MARKER_MANAGED_BY;
54022
54265
  }
54023
54266
  function hasSkillsOwnershipMarker(dir) {
54024
54267
  const path = join26(dir, SYNC_MARKER_FILE);
54025
54268
  try {
54026
- if (!lstatSync17(path).isFile())
54269
+ if (!lstatSync18(path).isFile())
54027
54270
  return false;
54028
54271
  const marker = JSON.parse(readFileSync14(path, "utf8"));
54029
54272
  return isSkillsOwnershipMarker(marker);
@@ -54248,7 +54491,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
54248
54491
  const action = dirExists ? "update" : "create";
54249
54492
  if (options.dryRun)
54250
54493
  return { action, path: skillMdPath };
54251
- const parentDir = dirname12(dir);
54494
+ const parentDir = dirname13(dir);
54252
54495
  mkdirSync7(parentDir, { recursive: true });
54253
54496
  const transactionDir = mkdtempSync(join26(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
54254
54497
  const candidateDir = join26(transactionDir, "candidate");
@@ -54270,10 +54513,10 @@ function writeManagedSkillDir(dir, skillMd, options) {
54270
54513
  } else {
54271
54514
  mkdirSync7(candidateDir, { recursive: true });
54272
54515
  }
54273
- writeFileSync6(candidateSkillMdPath, skillMd.endsWith(`
54516
+ writeFileSync7(candidateSkillMdPath, skillMd.endsWith(`
54274
54517
  `) ? skillMd : `${skillMd}
54275
54518
  `);
54276
- writeFileSync6(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
54519
+ writeFileSync7(candidateMarkerPath, `${JSON.stringify(marker, null, 2)}
54277
54520
  `);
54278
54521
  if (dirExists) {
54279
54522
  originalMoved = true;
@@ -54356,7 +54599,7 @@ var init_skill_aliases = __esm(() => {
54356
54599
  });
54357
54600
 
54358
54601
  // src/lib/project-state.ts
54359
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
54602
+ import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
54360
54603
  import { join as join27 } from "path";
54361
54604
  function getProjectStateDir(targetDir = process.cwd()) {
54362
54605
  return join27(targetDir, SKILLS_PROJECT_DIR);
@@ -54392,7 +54635,7 @@ function saveProjectConfig(config, targetDir = process.cwd()) {
54392
54635
  const dir = getProjectStateDir(targetDir);
54393
54636
  mkdirSync8(dir, { recursive: true });
54394
54637
  const normalized = normalizeProjectConfig({ ...config, updatedAt: new Date().toISOString() });
54395
- writeFileSync7(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
54638
+ writeFileSync8(getProjectConfigPath(targetDir), JSON.stringify(normalized, null, 2) + `
54396
54639
  `);
54397
54640
  }
54398
54641
  function pinProjectSkill(name, details = {}, targetDir = process.cwd()) {
@@ -55431,7 +55674,7 @@ var init_selection_resolver = __esm(() => {
55431
55674
  });
55432
55675
 
55433
55676
  // src/lib/plugin-projection.ts
55434
- import { createHash as createHash13 } from "crypto";
55677
+ import { createHash as createHash14 } from "crypto";
55435
55678
  import { posix } from "path";
55436
55679
  function pluginRefusal(message) {
55437
55680
  throw new SkillSelectionError("PLUGIN_ADMISSION_REFUSED", message);
@@ -55624,7 +55867,7 @@ function buildPluginProjection(entries) {
55624
55867
  files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
55625
55868
  return { manifest, payloads: manifest.payloads, files, witnesses: pluginFileWitnesses(files), originalTreeDigest, projectionTreeDigest: pluginTreeDigest(files), removed: removed.length ? pluginFileWitnesses(removed) : [] };
55626
55869
  }
55627
- var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json", PLUGIN_PROJECTION_LIMITS, pluginHash = (bytes) => createHash13("sha256").update(bytes).digest("hex"), MANIFEST_FIELDS;
55870
+ var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json", PLUGIN_PROJECTION_LIMITS, pluginHash = (bytes) => createHash14("sha256").update(bytes).digest("hex"), MANIFEST_FIELDS;
55628
55871
  var init_plugin_projection = __esm(() => {
55629
55872
  init_selection_cache();
55630
55873
  PLUGIN_PROJECTION_LIMITS = Object.freeze({ files: 1024, bytes: 64 * 1024 * 1024, fileBytes: 16 * 1024 * 1024, metadataBytes: 512 * 1024, registrations: 64, history: 128 });
@@ -55632,12 +55875,12 @@ var init_plugin_projection = __esm(() => {
55632
55875
  });
55633
55876
 
55634
55877
  // src/lib/plugin-projection-store.ts
55635
- import { createHash as createHash14, randomUUID as randomUUID6 } from "crypto";
55636
- import { constants as constants9, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync3, lstatSync as lstatSync18, mkdirSync as mkdirSync9, openSync as openSync9, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync5, rmdirSync as rmdirSync3, rmSync as rmSync4, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
55637
- import { dirname as dirname13, isAbsolute as isAbsolute12, join as join30, parse as parse5, resolve as resolve13 } from "path";
55878
+ import { createHash as createHash15, randomUUID as randomUUID6 } from "crypto";
55879
+ import { constants as constants10, closeSync as closeSync10, fchmodSync, fstatSync as fstatSync10, fsyncSync as fsyncSync4, linkSync as linkSync3, lstatSync as lstatSync19, mkdirSync as mkdirSync9, openSync as openSync10, opendirSync as opendirSync4, readSync as readSync8, renameSync as renameSync5, rmdirSync as rmdirSync3, rmSync as rmSync4, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
55880
+ import { dirname as dirname14, isAbsolute as isAbsolute12, join as join30, parse as parse5, resolve as resolve13 } from "path";
55638
55881
  function stat(path) {
55639
55882
  try {
55640
- return lstatSync18(path, { bigint: true });
55883
+ return lstatSync19(path, { bigint: true });
55641
55884
  } catch (error) {
55642
55885
  if (missing(error))
55643
55886
  return null;
@@ -55656,7 +55899,7 @@ function same3(left, right, identityOnly = false) {
55656
55899
  }
55657
55900
  function ancestors(path, create3 = false) {
55658
55901
  canonical5(path);
55659
- const result2 = [], parent = dirname13(path), filesystemRoot = parse5(parent).root;
55902
+ const result2 = [], parent = dirname14(path), filesystemRoot = parse5(parent).root;
55660
55903
  let current = filesystemRoot;
55661
55904
  for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
55662
55905
  if (segment)
@@ -55684,7 +55927,7 @@ function verifyAncestors(chain, identityOnly = true) {
55684
55927
  }
55685
55928
  function privateParent(path) {
55686
55929
  const chain = ancestors(path, true), parent = chain.at(-1);
55687
- pluginNeed(parent && parent.path === dirname13(path), "Plugin storage parent is unavailable");
55930
+ pluginNeed(parent && parent.path === dirname14(path), "Plugin storage parent is unavailable");
55688
55931
  owner(parent.stat);
55689
55932
  pluginNeed((parent.stat.mode & 0o7777n) === 0o700n, "Plugin storage parent must have mode 0700");
55690
55933
  return chain;
@@ -55713,19 +55956,19 @@ function readBytes(path, maximum, kind) {
55713
55956
  fileShape(before2, maximum, kind);
55714
55957
  let fd;
55715
55958
  try {
55716
- fd = openSync9(path, READ_FLAGS);
55717
- const opened = fstatSync9(fd, { bigint: true });
55959
+ fd = openSync10(path, READ_FLAGS);
55960
+ const opened = fstatSync10(fd, { bigint: true });
55718
55961
  same3(before2, opened);
55719
55962
  fileShape(opened, maximum, kind);
55720
55963
  const bytes = new Uint8Array(Number(opened.size));
55721
55964
  let offset = 0;
55722
55965
  while (offset < bytes.byteLength) {
55723
- const count = readSync7(fd, bytes, offset, bytes.byteLength - offset, null);
55966
+ const count = readSync8(fd, bytes, offset, bytes.byteLength - offset, null);
55724
55967
  pluginNeed(count > 0, "Plugin file changed while being read");
55725
55968
  offset += count;
55726
55969
  }
55727
- pluginNeed(readSync7(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
55728
- same3(opened, fstatSync9(fd, { bigint: true }));
55970
+ pluginNeed(readSync8(fd, new Uint8Array(1), 0, 1, null) === 0, "Plugin file grew while being read");
55971
+ same3(opened, fstatSync10(fd, { bigint: true }));
55729
55972
  same3(opened, stat(path));
55730
55973
  verifyAncestors(chain);
55731
55974
  return bytes;
@@ -55735,7 +55978,7 @@ function readBytes(path, maximum, kind) {
55735
55978
  return pluginRefusal("Cannot safely read plugin file");
55736
55979
  } finally {
55737
55980
  if (fd !== undefined)
55738
- closeSync9(fd);
55981
+ closeSync10(fd);
55739
55982
  }
55740
55983
  }
55741
55984
  function exactPaths(entries) {
@@ -55845,16 +56088,16 @@ function verifyPluginTree(root, witnesses) {
55845
56088
  function writeExclusive(path, bytes, mode) {
55846
56089
  let fd, created;
55847
56090
  try {
55848
- fd = openSync9(path, WRITE_FLAGS, mode);
56091
+ fd = openSync10(path, WRITE_FLAGS, mode);
55849
56092
  fchmodSync(fd, mode);
55850
- created = fstatSync9(fd, { bigint: true });
56093
+ created = fstatSync10(fd, { bigint: true });
55851
56094
  let offset = 0;
55852
56095
  while (offset < bytes.byteLength) {
55853
56096
  const count = writeSync2(fd, bytes, offset, bytes.byteLength - offset);
55854
56097
  pluginNeed(count > 0, "Plugin file write did not progress");
55855
56098
  offset += count;
55856
56099
  }
55857
- fsyncSync3(fd);
56100
+ fsyncSync4(fd);
55858
56101
  } catch (error) {
55859
56102
  if (created) {
55860
56103
  same3(created, stat(path), true);
@@ -55863,7 +56106,7 @@ function writeExclusive(path, bytes, mode) {
55863
56106
  throw error;
55864
56107
  } finally {
55865
56108
  if (fd !== undefined)
55866
- closeSync9(fd);
56109
+ closeSync10(fd);
55867
56110
  }
55868
56111
  }
55869
56112
  function materializePluginTree(root, entries) {
@@ -55966,20 +56209,20 @@ function pluginExecutableDigest(path) {
55966
56209
  fileShape(before2, maximum, "executable");
55967
56210
  let fd;
55968
56211
  try {
55969
- fd = openSync9(path, READ_FLAGS);
55970
- const opened = fstatSync9(fd, { bigint: true });
56212
+ fd = openSync10(path, READ_FLAGS);
56213
+ const opened = fstatSync10(fd, { bigint: true });
55971
56214
  same3(before2, opened);
55972
56215
  fileShape(opened, maximum, "executable");
55973
- const hash2 = createHash14("sha256"), buffer = new Uint8Array(64 * 1024);
56216
+ const hash2 = createHash15("sha256"), buffer = new Uint8Array(64 * 1024);
55974
56217
  let remaining = Number(opened.size);
55975
56218
  while (remaining > 0) {
55976
- const count = readSync7(fd, buffer, 0, Math.min(buffer.byteLength, remaining), null);
56219
+ const count = readSync8(fd, buffer, 0, Math.min(buffer.byteLength, remaining), null);
55977
56220
  pluginNeed(count > 0, "Plugin executable changed during hashing");
55978
56221
  hash2.update(buffer.subarray(0, count));
55979
56222
  remaining -= count;
55980
56223
  }
55981
- pluginNeed(readSync7(fd, buffer, 0, 1, null) === 0, "Plugin executable grew during hashing");
55982
- same3(opened, fstatSync9(fd, { bigint: true }));
56224
+ pluginNeed(readSync8(fd, buffer, 0, 1, null) === 0, "Plugin executable grew during hashing");
56225
+ same3(opened, fstatSync10(fd, { bigint: true }));
55983
56226
  same3(opened, stat(path));
55984
56227
  verifyAncestors(chain);
55985
56228
  return `sha256:${hash2.digest("hex")}`;
@@ -55989,14 +56232,14 @@ function pluginExecutableDigest(path) {
55989
56232
  return pluginRefusal("Cannot safely hash plugin executable");
55990
56233
  } finally {
55991
56234
  if (fd !== undefined)
55992
- closeSync9(fd);
56235
+ closeSync10(fd);
55993
56236
  }
55994
56237
  }
55995
56238
  var READ_FLAGS, WRITE_FLAGS, uid = () => typeof process.getuid === "function" ? BigInt(process.getuid()) : undefined, missing = (error) => Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
55996
56239
  var init_plugin_projection_store = __esm(() => {
55997
56240
  init_plugin_projection();
55998
- READ_FLAGS = constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK;
55999
- WRITE_FLAGS = constants9.O_WRONLY | constants9.O_CREAT | constants9.O_EXCL | constants9.O_NOFOLLOW | constants9.O_NONBLOCK;
56241
+ READ_FLAGS = constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK;
56242
+ WRITE_FLAGS = constants10.O_WRONLY | constants10.O_CREAT | constants10.O_EXCL | constants10.O_NOFOLLOW | constants10.O_NONBLOCK;
56000
56243
  });
56001
56244
 
56002
56245
  // src/lib/selected-manifest.ts
@@ -56405,8 +56648,8 @@ var init_plugin_admission = __esm(() => {
56405
56648
  });
56406
56649
 
56407
56650
  // src/lib/plugin-discovery.ts
56408
- import { closeSync as closeSync10, constants as constants10, fstatSync as fstatSync10, lstatSync as lstatSync19, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
56409
- import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join32, resolve as resolve15 } from "path";
56651
+ import { closeSync as closeSync11, constants as constants11, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, opendirSync as opendirSync5, readSync as readSync9 } from "fs";
56652
+ import { basename as basename7, dirname as dirname15, isAbsolute as isAbsolute14, join as join32, resolve as resolve15 } from "path";
56410
56653
  function validateManagedPluginWitnesses(value) {
56411
56654
  pluginNeed(Array.isArray(value) && value.length > 0 && value.length <= PLUGIN_PROJECTION_LIMITS.registrations, "Invalid managed plugin witness collection");
56412
56655
  const seen = new Set;
@@ -56421,29 +56664,29 @@ function validateManagedPluginWitnesses(value) {
56421
56664
  function safe3(path) {
56422
56665
  pluginText(path);
56423
56666
  pluginNeed(isAbsolute14(path) && resolve15(path) === path, "Invalid plugin registry path");
56424
- for (let at2 = path;; at2 = dirname14(at2)) {
56425
- pluginNeed(!lstatSync19(at2, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
56426
- if (at2 === dirname14(at2))
56667
+ for (let at2 = path;; at2 = dirname15(at2)) {
56668
+ pluginNeed(!lstatSync20(at2, { throwIfNoEntry: false })?.isSymbolicLink(), "Plugin registry path traverses a symlink");
56669
+ if (at2 === dirname15(at2))
56427
56670
  break;
56428
56671
  }
56429
56672
  }
56430
56673
  function readRegistry(path) {
56431
56674
  safe3(path);
56432
- const initial2 = lstatSync19(path);
56675
+ const initial2 = lstatSync20(path);
56433
56676
  pluginNeed(initial2.isFile() && initial2.size <= 1024 * 1024, "Plugin registry is not a bounded regular file");
56434
- const fd = openSync10(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
56677
+ const fd = openSync11(path, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
56435
56678
  try {
56436
- const opened = fstatSync10(fd);
56679
+ const opened = fstatSync11(fd);
56437
56680
  pluginNeed(opened.isFile() && opened.ino === initial2.ino && opened.dev === initial2.dev, "Plugin registry changed during open");
56438
56681
  const bytes = Buffer.alloc(1024 * 1024 + 1);
56439
56682
  let length = 0;
56440
56683
  while (length < bytes.length) {
56441
- const count = readSync8(fd, bytes, length, bytes.length - length, null);
56684
+ const count = readSync9(fd, bytes, length, bytes.length - length, null);
56442
56685
  if (!count)
56443
56686
  break;
56444
56687
  length += count;
56445
56688
  }
56446
- const after2 = fstatSync10(fd), current = lstatSync19(path);
56689
+ const after2 = fstatSync11(fd), current = lstatSync20(path);
56447
56690
  safe3(path);
56448
56691
  pluginNeed(length <= 1024 * 1024 && 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, "Plugin registry changed during read");
56449
56692
  let value;
@@ -56455,7 +56698,7 @@ function readRegistry(path) {
56455
56698
  pluginNeed(pluginObject(value) && value.version === 2 && pluginObject(value.plugins), "Unsupported native plugin registry schema");
56456
56699
  return { value, rawDigest: pluginHash(bytes.subarray(0, length)) };
56457
56700
  } finally {
56458
- closeSync10(fd);
56701
+ closeSync11(fd);
56459
56702
  }
56460
56703
  }
56461
56704
  function nativeVersionMatches(version, receipt) {
@@ -56464,7 +56707,7 @@ function nativeVersionMatches(version, receipt) {
56464
56707
  }
56465
56708
  function hashManagedPluginRegistry(path, managed) {
56466
56709
  validateManagedPluginWitnesses(managed);
56467
- const snapshot = readRegistry(path), plugins = snapshot.value.plugins;
56710
+ const snapshot2 = readRegistry(path), plugins = snapshot2.value.plugins;
56468
56711
  const seen = new Set, verified = new Set;
56469
56712
  let verifiedBytes = 0;
56470
56713
  for (const witness of managed) {
@@ -56473,7 +56716,7 @@ function hashManagedPluginRegistry(path, managed) {
56473
56716
  seen.add(target.pluginId);
56474
56717
  const rows = plugins[target.pluginId];
56475
56718
  pluginNeed(Array.isArray(rows) && rows.length === target.registrations.length, "Managed plugin registration membership changed");
56476
- const [name, market] = target.pluginId.split("@"), cacheRoot = join32(dirname14(path), "cache", market, name);
56719
+ const [name, market] = target.pluginId.split("@"), cacheRoot = join32(dirname15(path), "cache", market, name);
56477
56720
  const scopes = new Set, approvals = new Map, currentFiles = new Map;
56478
56721
  plugins[target.pluginId] = rows.map((row) => {
56479
56722
  pluginNeed(pluginObject(row), "Invalid managed plugin registration");
@@ -56516,7 +56759,7 @@ function hashManagedPluginRegistry(path, managed) {
56516
56759
  return Object.fromEntries(Object.entries(row).filter(([key]) => !["version", "installPath", "sourceProducerPath", "previousProducerPaths", "lastUpdated"].includes(key)));
56517
56760
  });
56518
56761
  safe3(cacheRoot);
56519
- const before2 = lstatSync19(cacheRoot);
56762
+ const before2 = lstatSync20(cacheRoot);
56520
56763
  pluginNeed(before2.isDirectory(), "Invalid managed plugin cache root");
56521
56764
  const directory = opendirSync5(cacheRoot), names = [];
56522
56765
  try {
@@ -56539,12 +56782,12 @@ function hashManagedPluginRegistry(path, managed) {
56539
56782
  }
56540
56783
  for (const row of rows)
56541
56784
  pluginNeed(names.includes(row.version), "Managed native plugin installation is missing");
56542
- const after2 = lstatSync19(cacheRoot);
56785
+ const after2 = lstatSync20(cacheRoot);
56543
56786
  safe3(cacheRoot);
56544
56787
  pluginNeed(before2.dev === after2.dev && before2.ino === after2.ino && before2.mtimeMs === after2.mtimeMs && before2.ctimeMs === after2.ctimeMs, "Managed cache version membership changed during verification");
56545
56788
  }
56546
- pluginNeed(readRegistry(path).rawDigest === snapshot.rawDigest, "Native plugin registry changed during content verification");
56547
- return pluginHash(JSON.stringify(snapshot.value));
56789
+ pluginNeed(readRegistry(path).rawDigest === snapshot2.rawDigest, "Native plugin registry changed during content verification");
56790
+ return pluginHash(JSON.stringify(snapshot2.value));
56548
56791
  }
56549
56792
  var init_plugin_discovery = __esm(() => {
56550
56793
  init_plugin_admission();
@@ -56553,50 +56796,50 @@ var init_plugin_discovery = __esm(() => {
56553
56796
  });
56554
56797
 
56555
56798
  // src/lib/claude-marketplace-registry.ts
56556
- import { createHash as createHash15 } from "crypto";
56557
- import { closeSync as closeSync11, constants as constants11, fstatSync as fstatSync11, lstatSync as lstatSync20, openSync as openSync11, readSync as readSync9 } from "fs";
56558
- import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve16 } from "path";
56559
- function need5(value, reason) {
56799
+ import { createHash as createHash16 } from "crypto";
56800
+ import { closeSync as closeSync12, constants as constants12, fstatSync as fstatSync12, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10 } from "fs";
56801
+ import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute15, resolve as resolve16 } from "path";
56802
+ function need6(value, reason) {
56560
56803
  if (!value)
56561
56804
  throw new Error(`Claude marketplace registry ${reason}`);
56562
56805
  }
56563
56806
  function absolutePath3(value) {
56564
- need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve16(value) === value, "requires a normalized absolute path");
56807
+ need6(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve16(value) === value, "requires a normalized absolute path");
56565
56808
  }
56566
56809
  function parents2(path) {
56567
56810
  const result2 = [];
56568
- for (let at2 = dirname15(path);; at2 = dirname15(at2)) {
56569
- const stat2 = lstatSync20(at2, { bigint: true });
56570
- need5(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
56811
+ for (let at2 = dirname16(path);; at2 = dirname16(at2)) {
56812
+ const stat2 = lstatSync21(at2, { bigint: true });
56813
+ need6(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
56571
56814
  result2.push([at2, stat2]);
56572
- if (at2 === dirname15(at2))
56815
+ if (at2 === dirname16(at2))
56573
56816
  return result2;
56574
56817
  }
56575
56818
  }
56576
56819
  function readRegistry2(path, budget) {
56577
56820
  absolutePath3(path);
56578
- need5(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
56579
- need5(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
56580
- const ancestors2 = parents2(path), initial2 = lstatSync20(path, { bigint: true });
56581
- need5(initial2.isFile() && !initial2.isSymbolicLink() && initial2.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
56582
- need5(initial2.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
56583
- const fd = openSync11(path, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
56821
+ need6(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
56822
+ need6(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
56823
+ const ancestors2 = parents2(path), initial2 = lstatSync21(path, { bigint: true });
56824
+ need6(initial2.isFile() && !initial2.isSymbolicLink() && initial2.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
56825
+ need6(initial2.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
56826
+ const fd = openSync12(path, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK);
56584
56827
  try {
56585
- const opened = fstatSync11(fd, { bigint: true });
56586
- need5(opened.isFile() && sameFile2(initial2, opened), "changed during open");
56828
+ const opened = fstatSync12(fd, { bigint: true });
56829
+ need6(opened.isFile() && sameFile2(initial2, opened), "changed during open");
56587
56830
  const bytes = Buffer.alloc(Number(opened.size) + 1);
56588
56831
  let length = 0;
56589
56832
  while (length < bytes.length) {
56590
- const count = readSync9(fd, bytes, length, bytes.length - length, null);
56833
+ const count = readSync10(fd, bytes, length, bytes.length - length, null);
56591
56834
  if (!count)
56592
56835
  break;
56593
56836
  length += count;
56594
56837
  }
56595
- const after2 = fstatSync11(fd, { bigint: true }), current = lstatSync20(path, { bigint: true });
56596
- need5(current.isFile() && sameFile2(opened, after2) && sameFile2(after2, current) && BigInt(length) === opened.size, "changed during read");
56838
+ const after2 = fstatSync12(fd, { bigint: true }), current = lstatSync21(path, { bigint: true });
56839
+ need6(current.isFile() && sameFile2(opened, after2) && sameFile2(after2, current) && BigInt(length) === opened.size, "changed during read");
56597
56840
  for (const [ancestor, before2] of ancestors2) {
56598
- const now3 = lstatSync20(ancestor, { bigint: true });
56599
- need5(now3.isDirectory() && before2.dev === now3.dev && before2.ino === now3.ino && before2.mode === now3.mode, "parent changed during read");
56841
+ const now3 = lstatSync21(ancestor, { bigint: true });
56842
+ need6(now3.isDirectory() && before2.dev === now3.dev && before2.ino === now3.ino && before2.mode === now3.mode, "parent changed during read");
56600
56843
  }
56601
56844
  budget.remaining -= length;
56602
56845
  try {
@@ -56605,7 +56848,7 @@ function readRegistry2(path, budget) {
56605
56848
  throw new Error("Claude marketplace registry is not strict UTF-8");
56606
56849
  }
56607
56850
  } finally {
56608
- closeSync11(fd);
56851
+ closeSync12(fd);
56609
56852
  }
56610
56853
  }
56611
56854
  function parse6(text2) {
@@ -56616,7 +56859,7 @@ function parse6(text2) {
56616
56859
  at2++;
56617
56860
  };
56618
56861
  function string() {
56619
- need5(text2[at2] === '"', "contains invalid JSON");
56862
+ need6(text2[at2] === '"', "contains invalid JSON");
56620
56863
  const start = at2++;
56621
56864
  while (at2 < text2.length) {
56622
56865
  const character = text2[at2++];
@@ -56627,7 +56870,7 @@ function parse6(text2) {
56627
56870
  } catch {
56628
56871
  throw new Error("Claude marketplace registry contains invalid JSON string");
56629
56872
  }
56630
- need5(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
56873
+ need6(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
56631
56874
  return value2;
56632
56875
  }
56633
56876
  if (character === "\\")
@@ -56636,7 +56879,7 @@ function parse6(text2) {
56636
56879
  throw new Error("Claude marketplace registry contains an unterminated JSON string");
56637
56880
  }
56638
56881
  function value(depth) {
56639
- need5(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
56882
+ need6(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
56640
56883
  space();
56641
56884
  const character = text2[at2];
56642
56885
  if (character === '"')
@@ -56650,17 +56893,17 @@ function parse6(text2) {
56650
56893
  if (object5) {
56651
56894
  space();
56652
56895
  const key = string();
56653
- need5(!keys2.has(key), "contains a duplicate JSON key");
56896
+ need6(!keys2.has(key), "contains a duplicate JSON key");
56654
56897
  keys2.add(key);
56655
56898
  space();
56656
- need5(text2[at2++] === ":", "contains invalid JSON");
56899
+ need6(text2[at2++] === ":", "contains invalid JSON");
56657
56900
  entries.push([key, value(depth + 1)]);
56658
56901
  } else
56659
56902
  items.push(value(depth + 1));
56660
56903
  space();
56661
56904
  if (text2[at2] === end)
56662
56905
  break;
56663
- need5(text2[at2++] === ",", "contains invalid JSON");
56906
+ need6(text2[at2++] === ",", "contains invalid JSON");
56664
56907
  }
56665
56908
  at2++;
56666
56909
  return object5 ? { kind: "object", entries } : { kind: "array", items };
@@ -56672,21 +56915,21 @@ function parse6(text2) {
56672
56915
  }
56673
56916
  numberPattern.lastIndex = at2;
56674
56917
  const number = numberPattern.exec(text2);
56675
- need5(number, "contains invalid JSON");
56918
+ need6(number, "contains invalid JSON");
56676
56919
  at2 += number[0].length;
56677
56920
  return { kind: "number", value: number[0] };
56678
56921
  }
56679
56922
  const root2 = value(0);
56680
56923
  space();
56681
- need5(at2 === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
56682
- need5(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
56924
+ need6(at2 === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
56925
+ need6(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
56683
56926
  return root2;
56684
56927
  }
56685
56928
  function exactKeys(value, keys2) {
56686
56929
  return value?.kind === "object" && value.entries.length === keys2.length && value.entries.every(([key]) => keys2.includes(key));
56687
56930
  }
56688
56931
  function textField(value) {
56689
- need5(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
56932
+ need6(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
56690
56933
  return value.value;
56691
56934
  }
56692
56935
  function normalizeRow(row, version = 1) {
@@ -56694,7 +56937,7 @@ function normalizeRow(row, version = 1) {
56694
56937
  if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
56695
56938
  return;
56696
56939
  const autoUpdate = field(row, "autoUpdate");
56697
- need5(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
56940
+ need6(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
56698
56941
  }
56699
56942
  const source = field(row, "source");
56700
56943
  if (source?.kind !== "object")
@@ -56710,23 +56953,23 @@ function normalizeRow(row, version = 1) {
56710
56953
  absolutePath3(sourceLocation);
56711
56954
  absolutePath3(textField(field(row, "installLocation")));
56712
56955
  const updated = textField(field(row, "lastUpdated"));
56713
- need5(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
56956
+ need6(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
56714
56957
  const millis = Date.parse(updated);
56715
- need5(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
56958
+ need6(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
56716
56959
  row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
56717
56960
  }
56718
56961
  function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
56719
56962
  const value = parse6(readRegistry2(path, budget));
56720
56963
  for (const [, row] of value.entries)
56721
56964
  normalizeRow(row);
56722
- const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
56965
+ const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
56723
56966
  return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
56724
56967
  }
56725
56968
  function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
56726
56969
  const value = parse6(readRegistry2(path, budget));
56727
56970
  for (const [, row] of value.entries)
56728
56971
  normalizeRow(row, 2);
56729
- const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
56972
+ const sha2562 = createHash16("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
56730
56973
  return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
56731
56974
  }
56732
56975
  var CLAUDE_MARKETPLACE_REGISTRY_LIMITS, sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
@@ -56734,65 +56977,6 @@ var init_claude_marketplace_registry = __esm(() => {
56734
56977
  CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
56735
56978
  });
56736
56979
 
56737
- // src/lib/codex-hook-trust-files.ts
56738
- import { closeSync as closeSync12, constants as constants12, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync21, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync8 } from "fs";
56739
- import { createHash as createHash16 } from "crypto";
56740
- import { dirname as dirname16 } from "path";
56741
- function need6(value, code) {
56742
- if (!value)
56743
- throw new Error(`CODEX_HOOK_TRUST_${code}`);
56744
- }
56745
- function safeParents(file) {
56746
- for (let p = dirname16(file);; p = dirname16(p)) {
56747
- const s = lstatSync21(p);
56748
- need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
56749
- need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
56750
- if (dirname16(p) === p)
56751
- break;
56752
- }
56753
- }
56754
- function snapshot(file, privateFile = false, options = {}) {
56755
- const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
56756
- safeParents(file);
56757
- const initial2 = lstatSync21(file, { bigint: true });
56758
- need6(initial2.isFile() && initial2.uid === BigInt(process.getuid()) && (initial2.nlink === 1n || readOnlyPackage && initial2.nlink > 1n) && (initial2.mode & 0o022n) === 0n && (!privateFile || (initial2.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
56759
- need6(initial2.size <= 8n * 1024n * 1024n, "FILE_BOUND");
56760
- const fd = openSync12(file, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK);
56761
- try {
56762
- const before2 = fstatSync12(fd, { bigint: true });
56763
- need6(sameStat(initial2, before2), "INPUT_CHANGED");
56764
- const bytes = Buffer.alloc(Number(before2.size) + 1);
56765
- let length = 0;
56766
- while (length < bytes.length) {
56767
- const n2 = readSync10(fd, bytes, length, bytes.length - length, null);
56768
- if (!n2)
56769
- break;
56770
- length += n2;
56771
- }
56772
- need6(length === Number(before2.size) && sameStat(before2, fstatSync12(fd, { bigint: true })) && sameStat(before2, lstatSync21(file, { bigint: true })), "INPUT_CHANGED");
56773
- const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
56774
- return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before2, sha256: createHash16("sha256").update(content).digest("hex") };
56775
- } finally {
56776
- closeSync12(fd);
56777
- }
56778
- }
56779
- function unchanged2(before2) {
56780
- const after2 = snapshot(before2.file, before2.privateFile, before2.readOnlyPackage ? { readOnlyPackage: true } : {});
56781
- need6(sameStat(before2.stat, after2.stat) && after2.sha256 === before2.sha256, "INPUT_CHANGED");
56782
- }
56783
- function save(file, content) {
56784
- safeParents(file);
56785
- const fd = openSync12(file, constants12.O_WRONLY | constants12.O_CREAT | constants12.O_EXCL | constants12.O_NOFOLLOW, 384);
56786
- try {
56787
- writeFileSync8(fd, content);
56788
- fsyncSync4(fd);
56789
- } finally {
56790
- closeSync12(fd);
56791
- }
56792
- }
56793
- var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs", "ctimeNs"].every((key) => a[key] === b[key]);
56794
- var init_codex_hook_trust_files = () => {};
56795
-
56796
56980
  // src/lib/codex-hook-trust-layout.ts
56797
56981
  import { isDeepStrictEqual as isDeepStrictEqual6 } from "util";
56798
56982
  function codexTrustTextWitness(text2, keys2, canonicalize = true) {
@@ -56931,53 +57115,53 @@ var init_codex_hook_trust_layout = () => {};
56931
57115
  import { isAbsolute as isAbsolute16 } from "path";
56932
57116
  import { isDeepStrictEqual as isDeepStrictEqual7 } from "util";
56933
57117
  function exactKeys2(value, keys2) {
56934
- need6(value && typeof value === "object" && !Array.isArray(value) && isDeepStrictEqual7(Object.keys(value).sort(), [...keys2].sort()), "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
57118
+ need5(value && typeof value === "object" && !Array.isArray(value) && isDeepStrictEqual7(Object.keys(value).sort(), [...keys2].sort()), "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
56935
57119
  }
56936
57120
  function readReviewedAdditionTrustStates(proof, before2, current, journalHooks) {
56937
57121
  const captured = proofs.get(proof);
56938
- need6(!!captured && captured.before.file === before2.file && captured.before.sha256 === before2.sha256 && captured.current.file === current.file && captured.current.sha256 === current.sha256 && isDeepStrictEqual7(captured.journalHooks, journalHooks.map(({ key, currentHash }) => ({ key, currentHash }))), "RECONCILE_ADDITIONS_PROOF_CHANGED");
57122
+ need5(!!captured && captured.before.file === before2.file && captured.before.sha256 === before2.sha256 && captured.current.file === current.file && captured.current.sha256 === current.sha256 && isDeepStrictEqual7(captured.journalHooks, journalHooks.map(({ key, currentHash }) => ({ key, currentHash }))), "RECONCILE_ADDITIONS_PROOF_CHANGED");
56939
57123
  captured.recheck();
56940
57124
  unchanged2(before2);
56941
57125
  unchanged2(current);
56942
57126
  return captured.states;
56943
57127
  }
56944
57128
  function reviewNativeHookAdditions(options) {
56945
- need6(isAbsolute16(options.path), "RECONCILE_ADDITIONS_REVIEW_PATH");
57129
+ need5(isAbsolute16(options.path), "RECONCILE_ADDITIONS_REVIEW_PATH");
56946
57130
  const reviewFile = snapshot(options.path, true), review = JSON.parse(reviewFile.text);
56947
57131
  exactKeys2(review, ["version", "kind", "reason", "producerEvidence", "journalIntentSha256", "policySha256", "configCurrentSha256", "hooksCurrentSha256", "producerBefore", "additions"]);
56948
- need6((review.version === 1 || review.version === 2) && review.kind === "native-hook-additions" && text2(review.reason) && text2(review.producerEvidence), "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
57132
+ need5((review.version === 1 || review.version === 2) && review.kind === "native-hook-additions" && text2(review.reason) && text2(review.producerEvidence), "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
56949
57133
  for (const [name, expected] of [["journalIntentSha256", options.intent.sha256], ["policySha256", options.policy.sha256], ["configCurrentSha256", options.configCurrent.sha256], ["hooksCurrentSha256", options.hooksCurrent.sha256]])
56950
- need6(hash2(review[name]) && review[name] === expected, "RECONCILE_ADDITIONS_REVIEW_CHANGED");
57134
+ need5(hash2(review[name]) && review[name] === expected, "RECONCILE_ADDITIONS_REVIEW_CHANGED");
56951
57135
  exactKeys2(review.producerBefore, ["configPath", "configSha256", "hooksPath", "hooksSha256"]);
56952
57136
  const before2 = review.producerBefore;
56953
- need6(isAbsolute16(before2.configPath) && isAbsolute16(before2.hooksPath) && hash2(before2.configSha256) && hash2(before2.hooksSha256), "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
57137
+ need5(isAbsolute16(before2.configPath) && isAbsolute16(before2.hooksPath) && hash2(before2.configSha256) && hash2(before2.hooksSha256), "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
56954
57138
  const producerConfig = snapshot(before2.configPath, true), producerHooks = snapshot(before2.hooksPath, true);
56955
- need6(producerConfig.sha256 === before2.configSha256 && producerHooks.sha256 === before2.hooksSha256 && producerHooks.sha256 === options.hooksBefore.sha256, "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
57139
+ need5(producerConfig.sha256 === before2.configSha256 && producerHooks.sha256 === before2.hooksSha256 && producerHooks.sha256 === options.hooksBefore.sha256, "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
56956
57140
  const original = structure(options.configBefore.text), producer = structure(producerConfig.text), expectedProducer = structuredClone(original), originalKeys = options.journalHooks.map((hook) => hook.key);
56957
57141
  expectedProducer.hooks ??= {};
56958
57142
  expectedProducer.hooks.state ??= {};
56959
57143
  for (const hook of options.journalHooks)
56960
57144
  expectedProducer.hooks.state[hook.key] = { ...expectedProducer.hooks.state[hook.key], enabled: true, trusted_hash: hook.currentHash };
56961
- need6(isDeepStrictEqual7(expectedProducer, producer) && codexTrustReconcileWitness(options.configBefore.text, originalKeys) === codexTrustReconcileWitness(producerConfig.text, originalKeys) && codexTrustUnmanagedSemantic(options.configBefore.text, originalKeys) === codexTrustUnmanagedSemantic(producerConfig.text, originalKeys), "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
56962
- need6(Array.isArray(review.additions) && review.additions.length > 0 && review.additions.length <= 20, "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
57145
+ need5(isDeepStrictEqual7(expectedProducer, producer) && codexTrustReconcileWitness(options.configBefore.text, originalKeys) === codexTrustReconcileWitness(producerConfig.text, originalKeys) && codexTrustUnmanagedSemantic(options.configBefore.text, originalKeys) === codexTrustUnmanagedSemantic(producerConfig.text, originalKeys), "RECONCILE_ADDITIONS_PRODUCER_PREIMAGE");
57146
+ need5(Array.isArray(review.additions) && review.additions.length > 0 && review.additions.length <= 20, "RECONCILE_ADDITIONS_REVIEW_SCHEMA");
56963
57147
  const expectedHooks = JSON.parse(options.hooksBefore.text), currentHooks = JSON.parse(options.hooksCurrent.text), expectedConfig = structuredClone(producer);
56964
- need6(expectedHooks.hooks && typeof expectedHooks.hooks === "object" && !Array.isArray(expectedHooks.hooks), "RECONCILE_ADDITIONS_DECLARATIONS");
57148
+ need5(expectedHooks.hooks && typeof expectedHooks.hooks === "object" && !Array.isArray(expectedHooks.hooks), "RECONCILE_ADDITIONS_DECLARATIONS");
56965
57149
  const added = [], states = [], seen = new Set, commands = new Set(options.recordedNativeHooks.map((hook) => hook.command));
56966
57150
  for (const addition of review.additions) {
56967
57151
  exactKeys2(addition, review.version === 1 ? ["event", "nativeHook"] : ["event", "nativeHook", "trustState"]);
56968
- need6(Object.hasOwn(review.version === 1 ? events : matchedEvents, addition.event), "RECONCILE_ADDITIONS_DECLARATIONS");
57152
+ need5(Object.hasOwn(review.version === 1 ? events : matchedEvents, addition.event), "RECONCILE_ADDITIONS_DECLARATIONS");
56969
57153
  const { event, nativeHook: hook } = addition;
56970
57154
  exactKeys2(hook, nativeKeys);
56971
57155
  const groups = expectedHooks.hooks[event] ?? [];
56972
- need6(Array.isArray(groups), "RECONCILE_ADDITIONS_DECLARATIONS");
57156
+ need5(Array.isArray(groups), "RECONCILE_ADDITIONS_DECLARATIONS");
56973
57157
  const key = `${options.nativeHooksPath}:${matchedEvents[event][1]}:${groups.length}:0`;
56974
- need6(hook.key === key && !seen.has(key) && !options.recordedNativeHooks.some((originalHook) => originalHook.key === key) && !Object.hasOwn(expectedConfig.hooks.state, key), "RECONCILE_ADDITIONS_IDENTITY");
56975
- need6(hook.eventName === matchedEvents[event][0] && hook.handlerType === "command" && (review.version === 2 ? v2Command(hook.command) : text2(hook.command, 16384)) && !commands.has(hook.command) && Number.isInteger(hook.timeoutSec) && hook.timeoutSec > 0 && hook.timeoutSec <= 60 && (event === "PreToolUse" ? text2(hook.matcher) : hook.matcher === null) && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && hook.sourcePath === options.nativeHooksPath && hook.source === "user" && hook.pluginId === null && hook.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash) && hook.enabled === true && hook.trustStatus === "trusted", "RECONCILE_ADDITIONS_IDENTITY");
57158
+ need5(hook.key === key && !seen.has(key) && !options.recordedNativeHooks.some((originalHook) => originalHook.key === key) && !Object.hasOwn(expectedConfig.hooks.state, key), "RECONCILE_ADDITIONS_IDENTITY");
57159
+ need5(hook.eventName === matchedEvents[event][0] && hook.handlerType === "command" && (review.version === 2 ? v2Command(hook.command) : text2(hook.command, 16384)) && !commands.has(hook.command) && Number.isInteger(hook.timeoutSec) && hook.timeoutSec > 0 && hook.timeoutSec <= 60 && (event === "PreToolUse" ? text2(hook.matcher) : hook.matcher === null) && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && hook.sourcePath === options.nativeHooksPath && hook.source === "user" && hook.pluginId === null && hook.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash) && hook.enabled === true && hook.trustStatus === "trusted", "RECONCILE_ADDITIONS_IDENTITY");
56976
57160
  let trustState = { enabled: true, trusted_hash: hook.currentHash };
56977
57161
  if (review.version === 2) {
56978
57162
  const state = addition.trustState;
56979
57163
  exactKeys2(state, state && Object.hasOwn(state, "enabled") ? ["enabled", "trusted_hash"] : ["trusted_hash"]);
56980
- need6(state.trusted_hash === hook.currentHash && (!Object.hasOwn(state, "enabled") || state.enabled === true), "RECONCILE_ADDITIONS_TRUST_STATE");
57164
+ need5(state.trusted_hash === hook.currentHash && (!Object.hasOwn(state, "enabled") || state.enabled === true), "RECONCILE_ADDITIONS_TRUST_STATE");
56981
57165
  trustState = { ...state };
56982
57166
  }
56983
57167
  seen.add(key);
@@ -56987,9 +57171,9 @@ function reviewNativeHookAdditions(options) {
56987
57171
  expectedHooks.hooks[event] = [...groups, { ...event === "PreToolUse" ? { matcher: hook.matcher } : {}, hooks: [{ type: "command", command: hook.command, timeout: hook.timeoutSec }] }];
56988
57172
  expectedConfig.hooks.state[key] = trustState;
56989
57173
  }
56990
- need6(isDeepStrictEqual7(expectedHooks, currentHooks), "RECONCILE_ADDITIONS_DECLARATIONS");
57174
+ need5(isDeepStrictEqual7(expectedHooks, currentHooks), "RECONCILE_ADDITIONS_DECLARATIONS");
56991
57175
  const keys2 = added.map((hook) => hook.key);
56992
- need6(isDeepStrictEqual7(expectedConfig, structure(options.configCurrent.text)) && codexTrustReconcileWitness(producerConfig.text, keys2) === codexTrustReconcileWitness(options.configCurrent.text, keys2) && codexTrustUnmanagedSemantic(producerConfig.text, keys2) === codexTrustUnmanagedSemantic(options.configCurrent.text, keys2), "RECONCILE_ADDITIONS_CONFIG");
57176
+ need5(isDeepStrictEqual7(expectedConfig, structure(options.configCurrent.text)) && codexTrustReconcileWitness(producerConfig.text, keys2) === codexTrustReconcileWitness(options.configCurrent.text, keys2) && codexTrustUnmanagedSemantic(producerConfig.text, keys2) === codexTrustUnmanagedSemantic(options.configCurrent.text, keys2), "RECONCILE_ADDITIONS_CONFIG");
56993
57177
  const inputs = [reviewFile, producerConfig, producerHooks, options.intent, options.policy, options.configBefore, options.configCurrent, options.hooksBefore, options.hooksCurrent];
56994
57178
  const recheck = () => {
56995
57179
  for (const input of inputs)
@@ -57023,16 +57207,16 @@ function createCodexHookDiscoveryRecovery(binding, before2, current, hooks, addi
57023
57207
  const raw = binding.sources.filter((source) => source.path === current.file && source.hashMode === "bytes");
57024
57208
  if (!raw.some((source) => source.sha256 !== current.sha256))
57025
57209
  return;
57026
- need6(binding.agent === "codex" && binding.method === "reviewed" && raw.length === 1 && raw[0].sha256 === before2.sha256 && before2.sha256 !== current.sha256, "RECONCILE_DISCOVERY_CONFIG_WITNESS");
57027
- need6(raw[0].format === undefined && raw[0].fields === undefined && raw[0].managedPlugins === undefined, "RECONCILE_DISCOVERY_CONFIG_WITNESS");
57210
+ need5(binding.agent === "codex" && binding.method === "reviewed" && raw.length === 1 && raw[0].sha256 === before2.sha256 && before2.sha256 !== current.sha256, "RECONCILE_DISCOVERY_CONFIG_WITNESS");
57211
+ need5(raw[0].format === undefined && raw[0].fields === undefined && raw[0].managedPlugins === undefined, "RECONCILE_DISCOVERY_CONFIG_WITNESS");
57028
57212
  const typed = binding.sources.filter((source) => source.path === current.file && source.format === "toml");
57029
- need6(typed.length === 1 && typed[0].hashMode === undefined && isDeepStrictEqual8(typed[0].fields, fields), "RECONCILE_DISCOVERY_TYPED_WITNESS");
57213
+ need5(typed.length === 1 && typed[0].hashMode === undefined && isDeepStrictEqual8(typed[0].fields, fields), "RECONCILE_DISCOVERY_TYPED_WITNESS");
57030
57214
  const original = structure2(before2.text), actual = structure2(current.text);
57031
57215
  const project = (value) => Object.fromEntries(fields.map((field2) => [field2, value[field2] ?? null]));
57032
57216
  const projection = sha4(JSON.stringify(project(original)));
57033
- need6(typed[0].sha256 === projection && sha4(JSON.stringify(project(actual))) === projection && isDeepStrictEqual8(project(original), project(actual)), "RECONCILE_DISCOVERY_TYPED_WITNESS");
57217
+ need5(typed[0].sha256 === projection && sha4(JSON.stringify(project(actual))) === projection && isDeepStrictEqual8(project(original), project(actual)), "RECONCILE_DISCOVERY_TYPED_WITNESS");
57034
57218
  const keys2 = [...journalHooks.map((hook) => hook.key), ...additionStates.map((hook) => hook.key)];
57035
- need6(journalHooks.length > 0 && keys2.length <= 100 && new Set(keys2).size === keys2.length && journalHooks.every((hook) => typeof hook.key === "string" && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash)), "RECONCILE_DISCOVERY_TRUST_WITNESS");
57219
+ need5(journalHooks.length > 0 && keys2.length <= 100 && new Set(keys2).size === keys2.length && journalHooks.every((hook) => typeof hook.key === "string" && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash)), "RECONCILE_DISCOVERY_TRUST_WITNESS");
57036
57220
  const expected = structuredClone(original);
57037
57221
  expected.hooks ??= {};
57038
57222
  expected.hooks.state ??= {};
@@ -57040,7 +57224,7 @@ function createCodexHookDiscoveryRecovery(binding, before2, current, hooks, addi
57040
57224
  expected.hooks.state[hook.key] = { ...expected.hooks.state[hook.key], enabled: true, trusted_hash: hook.currentHash };
57041
57225
  for (const { key, state } of additionStates)
57042
57226
  expected.hooks.state[key] = { ...state };
57043
- need6(isDeepStrictEqual8(expected, actual) && codexTrustReconcileWitness(before2.text, keys2) === codexTrustReconcileWitness(current.text, keys2) && codexTrustUnmanagedSemantic(before2.text, keys2) === codexTrustUnmanagedSemantic(current.text, keys2), "RECONCILE_DISCOVERY_TRUST_WITNESS");
57227
+ need5(isDeepStrictEqual8(expected, actual) && codexTrustReconcileWitness(before2.text, keys2) === codexTrustReconcileWitness(current.text, keys2) && codexTrustUnmanagedSemantic(before2.text, keys2) === codexTrustUnmanagedSemantic(current.text, keys2), "RECONCILE_DISCOVERY_TRUST_WITNESS");
57044
57228
  unchanged2(before2);
57045
57229
  unchanged2(current);
57046
57230
  const witness = Object.freeze({ configPath: current.file, beforeSha256: before2.sha256, currentSha256: current.sha256, discoverySha256: sha4(JSON.stringify(binding)), typedProjectionSha256: projection });
@@ -57049,7 +57233,7 @@ function createCodexHookDiscoveryRecovery(binding, before2, current, hooks, addi
57049
57233
  }
57050
57234
  function assertCodexHookDiscoveryRecovery(binding, witness) {
57051
57235
  const proof = proofs2.get(witness);
57052
- need6(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
57236
+ need5(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
57053
57237
  unchanged2(proof.before);
57054
57238
  unchanged2(proof.current);
57055
57239
  proof.recheckAdditions();
@@ -57058,7 +57242,7 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
57058
57242
  if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
57059
57243
  return false;
57060
57244
  assertCodexHookDiscoveryRecovery(binding, witness);
57061
- need6(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
57245
+ need5(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
57062
57246
  return true;
57063
57247
  }
57064
57248
  var sha4 = (text3) => createHash17("sha256").update(text3).digest("hex"), fields, structure2 = (text3) => JSON.parse(JSON.stringify(Bun.TOML.parse(text3))), proofs2;
@@ -109753,16 +109937,47 @@ function assertConfigUnchanged(configs) {
109753
109937
  }
109754
109938
  }
109755
109939
  }
109756
- async function runNpm(args2, cwd2, home, staging) {
109940
+ function releaseAgePolicy(options) {
109941
+ if (options.minReleaseAgeExclude !== undefined && !Array.isArray(options.minReleaseAgeExclude))
109942
+ throw new Error("MIN_RELEASE_AGE_EXCLUDE_INVALID");
109943
+ if (options.minReleaseAge === undefined) {
109944
+ if (options.minReleaseAgeExclude?.length)
109945
+ throw new Error("MIN_RELEASE_AGE_REQUIRED_FOR_EXCLUSIONS");
109946
+ return;
109947
+ }
109948
+ if (!Number.isSafeInteger(options.minReleaseAge) || options.minReleaseAge < 1)
109949
+ throw new Error("MIN_RELEASE_AGE_INVALID");
109950
+ const exclusions = options.minReleaseAgeExclude ?? [];
109951
+ if (!Array.isArray(exclusions) || exclusions.some((pattern) => typeof pattern !== "string" || pattern.length > 214 || !/^(?:@[a-z0-9*?][a-z0-9._*?-]*\/)?[a-z0-9*?][a-z0-9._*?-]*$/.test(pattern))) {
109952
+ throw new Error("MIN_RELEASE_AGE_EXCLUDE_INVALID");
109953
+ }
109954
+ return { minReleaseAge: options.minReleaseAge, minReleaseAgeExclude: [...new Set(exclusions)] };
109955
+ }
109956
+ async function npmReleaseAgeCapability(cwd2, env3) {
109957
+ try {
109958
+ const versionProbe = Bun.spawn(["npm", "--version"], { cwd: cwd2, env: env3, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
109959
+ const [versionText, versionStatus] = await Promise.all([new Response(versionProbe.stdout).text(), versionProbe.exited]);
109960
+ const version2 = versionText.trim();
109961
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version2);
109962
+ if (versionStatus !== 0 || !match || Number(match[1]) < 11 || Number(match[1]) === 11 && Number(match[2]) < 19)
109963
+ throw new Error("NPM_RELEASE_AGE_UNSUPPORTED");
109964
+ const configProbe = Bun.spawn(["npm", "config", "list", "--json"], { cwd: cwd2, env: env3, stdin: "ignore", stdout: "pipe", stderr: "ignore" });
109965
+ const [configText, configStatus] = await Promise.all([new Response(configProbe.stdout).text(), configProbe.exited]);
109966
+ const config2 = object9(JSON.parse(configText), "NPM_RELEASE_AGE_UNSUPPORTED");
109967
+ if (configStatus !== 0 || !Object.hasOwn(config2, "min-release-age") || !Object.hasOwn(config2, "min-release-age-exclude") || !Array.isArray(config2["min-release-age-exclude"]))
109968
+ throw new Error("NPM_RELEASE_AGE_UNSUPPORTED");
109969
+ return version2;
109970
+ } catch {
109971
+ throw new Error("NPM_RELEASE_AGE_UNSUPPORTED");
109972
+ }
109973
+ }
109974
+ async function runNpm(args2, cwd2, home, staging, policy) {
109757
109975
  const userNpmrc = join56(staging, "npm-user.npmrc"), globalNpmrc = join56(staging, "npm-global.npmrc");
109758
- if (!existsSync34(userNpmrc))
109759
- writeFileSync23(userNpmrc, `registry=${REGISTRY_ORIGIN}/
109976
+ const baseConfig = `registry=${REGISTRY_ORIGIN}/
109760
109977
  ignore-scripts=true
109761
- `, { mode: 384, flag: "wx" });
109978
+ `;
109762
109979
  if (!existsSync34(globalNpmrc))
109763
- writeFileSync23(globalNpmrc, `registry=${REGISTRY_ORIGIN}/
109764
- ignore-scripts=true
109765
- `, { mode: 384, flag: "wx" });
109980
+ writeFileSync23(globalNpmrc, baseConfig, { mode: 384, flag: "wx" });
109766
109981
  const env3 = {
109767
109982
  PATH: process.env.PATH ?? "",
109768
109983
  HOME: home,
@@ -109776,6 +109991,19 @@ ignore-scripts=true
109776
109991
  NPM_CONFIG_REGISTRY: `${REGISTRY_ORIGIN}/`,
109777
109992
  NPM_CONFIG_IGNORE_SCRIPTS: "true"
109778
109993
  };
109994
+ const capabilityNpmrc = join56(staging, "npm-capability.npmrc");
109995
+ if (policy && !existsSync34(capabilityNpmrc))
109996
+ writeFileSync23(capabilityNpmrc, baseConfig, { mode: 384, flag: "wx" });
109997
+ if (policy && readFileSync37(capabilityNpmrc, "utf8") !== baseConfig)
109998
+ throw new Error("NPM_ISOLATED_CONFIG_DRIFT");
109999
+ const npmVersion = policy ? await npmReleaseAgeCapability(cwd2, { ...env3, NPM_CONFIG_USERCONFIG: capabilityNpmrc }) : undefined;
110000
+ const config2 = baseConfig + (policy ? `min-release-age=${policy.minReleaseAge}
110001
+ ${policy.minReleaseAgeExclude.map((pattern) => `min-release-age-exclude[]=${pattern}
110002
+ `).join("")}` : "");
110003
+ if (!existsSync34(userNpmrc))
110004
+ writeFileSync23(userNpmrc, config2, { mode: 384, flag: "wx" });
110005
+ if (readFileSync37(userNpmrc, "utf8") !== config2 || readFileSync37(globalNpmrc, "utf8") !== baseConfig)
110006
+ throw new Error("NPM_ISOLATED_CONFIG_DRIFT");
109779
110007
  const proc = Bun.spawn(["npm", ...args2, "--registry", `${REGISTRY_ORIGIN}/`, "--ignore-scripts", "--no-audit", "--no-fund"], {
109780
110008
  cwd: cwd2,
109781
110009
  env: env3,
@@ -109786,6 +110014,7 @@ ignore-scripts=true
109786
110014
  const exitCode = await proc.exited;
109787
110015
  if (exitCode !== 0)
109788
110016
  throw new Error("DEPENDENCY_GRAPH_INSTALL_FAILED");
110017
+ return npmVersion;
109789
110018
  }
109790
110019
  function comparePackageTrees(expectedRoot, installedRoot) {
109791
110020
  const expected = readTree(expectedRoot, false);
@@ -110164,6 +110393,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
110164
110393
  throw new Error("EXACT_VERSION_REQUIRED");
110165
110394
  if (!STABLE_SEMVER_PATTERN.test(version2))
110166
110395
  throw new Error("EXACT_STABLE_VERSION_REQUIRED");
110396
+ const policy = releaseAgePolicy(options);
110167
110397
  const home = resolve24(options.homeDir ?? process.env.HOME ?? "");
110168
110398
  if (!home || !existsSync34(home))
110169
110399
  throw new Error("HOME_NOT_FOUND");
@@ -110201,8 +110431,10 @@ async function updateCopyfileRuntime(version2, options = {}) {
110201
110431
  mkdirSync24(stagePackage, { mode: 448 });
110202
110432
  copyFileSync(artifact.tarballPath, join56(stagePackage, "verified.tgz"));
110203
110433
  writeFileSync23(join56(stagePackage, "package.json"), JSON.stringify({ private: true, dependencies: { [PACKAGE_NAME]: "file:./verified.tgz" } }, null, 2), { mode: 384, flag: "wx" });
110204
- await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath);
110205
- await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath);
110434
+ const npmVersion = await runNpm(["install", "--package-lock-only", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
110435
+ const ciNpmVersion = await runNpm(["ci", "--prefix", stagePackage], stagePackage, home, stagePath, policy);
110436
+ if (npmVersion !== ciNpmVersion)
110437
+ throw new Error("NPM_VERSION_DRIFT_DURING_UPDATE");
110206
110438
  const lockBytes = readFileSync37(join56(stagePackage, "package-lock.json"));
110207
110439
  const installedPackage = join56(stagePackage, "node_modules", "@hasna", "skills");
110208
110440
  const installed = readPackage(join56(installedPackage, "package.json"));
@@ -110243,6 +110475,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
110243
110475
  targetPackageRoot,
110244
110476
  configs: configPreimages,
110245
110477
  preimageSha256,
110478
+ ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {},
110246
110479
  launchers,
110247
110480
  switchedLaunchers: [],
110248
110481
  rollbackCompletedLaunchers: []
@@ -110281,7 +110514,7 @@ async function updateCopyfileRuntime(version2, options = {}) {
110281
110514
  }
110282
110515
  receipt.state = "switched";
110283
110516
  persistReceipt(join56(finalPath, "rollout-receipt.json"), receipt);
110284
- return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length };
110517
+ return { updated: true, version: version2, currentVersion: layout.currentVersion, receiptId: id2, runtimeRoot: finalPath, tarballSha256: artifact.sha256, tarballIntegrity: artifact.integrity, runtimeTreeSha256: stagedTree.digest, launcherCount: launchers.length, configCount: configPreimages.length, ...policy ? { dependencyPolicy: { ...policy, npmVersion } } : {} };
110285
110518
  } catch (error61) {
110286
110519
  if (switched.length > 0 || moved) {
110287
110520
  let rollbackFailed = false;
@@ -110505,7 +110738,12 @@ function registerRuntime(parent) {
110505
110738
  });
110506
110739
  const setup = parent.command("setup").description("Point this CLI at a Skills API server, or register agent integrations").option("--api-url <url>", "Skills API origin to send remote work to").option("--global", "Accepted for compatibility; the API origin is always per-user", false).option("--json", "Output setup result as JSON", false).action(async (options) => handleSetup(options));
110507
110740
  setup.command("agents").option("--json", "Output registration result as JSON", false).description("Register the Skills MCP server with all supported agents").action(async (options) => handleMcp({ register: "all", json: options.json }));
110508
- parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
110741
+ parent.command("self-update").description("Update @hasna/skills to the latest version").option("--json", "Output result as JSON", false).option("--version <version>", "Install this exact @hasna/skills version as a staged copyfile runtime").option("--min-release-age <days>", "Require a positive integer dependency release age in days for an exact-version update").option("--min-release-age-exclude <pattern>", "Exclude matching package identities from the release-age policy (repeatable)", (pattern, prior) => [...prior, pattern], []).option("--rollback <receipt-id>", "Restore launcher links from an exact copyfile rollout receipt").option("--adopt-aliases", "Switch verified legacy @hasna/skills aliases to the active copyfile runtime").option("--rollback-aliases <receipt-id>", "Restore aliases from an exact adoption receipt").action(async (options) => {
110742
+ if ((options.minReleaseAge !== undefined || options.minReleaseAgeExclude.length > 0) && (!options.version || options.rollback || options.adoptAliases || options.rollbackAliases)) {
110743
+ console.error(JSON.stringify({ error: "RELEASE_AGE_POLICY_REQUIRES_EXACT_VERSION" }));
110744
+ process.exitCode = 1;
110745
+ return;
110746
+ }
110509
110747
  if ([options.version, options.rollback, options.adoptAliases, options.rollbackAliases].filter(Boolean).length > 1) {
110510
110748
  console.error(JSON.stringify({ error: "SELF_UPDATE_OPERATIONS_ARE_MUTUALLY_EXCLUSIVE" }));
110511
110749
  process.exitCode = 1;
@@ -110541,7 +110779,8 @@ function registerRuntime(parent) {
110541
110779
  }
110542
110780
  if (options.version) {
110543
110781
  try {
110544
- const result2 = await updateCopyfileRuntime(options.version);
110782
+ const minReleaseAge = options.minReleaseAge === undefined ? undefined : /^\d+$/.test(options.minReleaseAge) ? Number(options.minReleaseAge) : NaN;
110783
+ const result2 = await updateCopyfileRuntime(options.version, { minReleaseAge, minReleaseAgeExclude: options.minReleaseAgeExclude });
110545
110784
  if (options.json)
110546
110785
  console.log(JSON.stringify(result2));
110547
110786
  else
@@ -112755,40 +112994,40 @@ import { realpathSync as realpathSync17 } from "fs";
112755
112994
  import { dirname as dirname33, isAbsolute as isAbsolute27, join as join60 } from "path";
112756
112995
  import { isDeepStrictEqual as isDeepStrictEqual13 } from "util";
112757
112996
  function inspectRecordedSkillsCli(command, expected) {
112758
- need6(isAbsolute27(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
112997
+ need5(isAbsolute27(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
112759
112998
  const resolved = realpathSync17(command);
112760
112999
  const cli = snapshot(resolved, false, { readOnlyPackage: true });
112761
113000
  const manifest = snapshot(join60(dirname33(dirname33(resolved)), "package.json"), false, { readOnlyPackage: true });
112762
113001
  const pkg = JSON.parse(manifest.text);
112763
- need6(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join60(dirname33(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
112764
- need6((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
113002
+ need5(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join60(dirname33(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
113003
+ need5((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
112765
113004
  const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
112766
- need6(isDeepStrictEqual13(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
113005
+ need5(isDeepStrictEqual13(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
112767
113006
  return { receipt, recheck() {
112768
- need6(realpathSync17(command) === resolved, "SKILLS_COMMAND_CHANGED");
113007
+ need5(realpathSync17(command) === resolved, "SKILLS_COMMAND_CHANGED");
112769
113008
  unchanged2(cli);
112770
113009
  unchanged2(manifest);
112771
113010
  } };
112772
113011
  }
112773
113012
  function bindSkillsCli(command, reviewed) {
112774
113013
  const normal = Bun.which("skills", { PATH: process.env.PATH });
112775
- need6(normal, "SKILLS_COMMAND_UNAVAILABLE");
113014
+ need5(normal, "SKILLS_COMMAND_UNAVAILABLE");
112776
113015
  const resolved = realpathSync17(normal);
112777
113016
  const bound = isAbsolute27(command) ? command : command === "skills" ? normal : undefined;
112778
- need6(bound && realpathSync17(bound) === resolved, "SKILLS_COMMAND_MISMATCH");
113017
+ need5(bound && realpathSync17(bound) === resolved, "SKILLS_COMMAND_MISMATCH");
112779
113018
  const entrypoint = reviewed?.path ?? process.argv[1];
112780
- need6(entrypoint && realpathSync17(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
113019
+ need5(entrypoint && realpathSync17(entrypoint) === resolved, "SKILLS_ENTRYPOINT_MISMATCH");
112781
113020
  const cli = snapshot(resolved, false, { readOnlyPackage: true });
112782
113021
  const manifest = snapshot(join60(dirname33(dirname33(resolved)), "package.json"), false, { readOnlyPackage: true });
112783
113022
  const pkg = JSON.parse(manifest.text);
112784
- need6(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join60(dirname33(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
113023
+ need5(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join60(dirname33(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
112785
113024
  if (reviewed)
112786
- need6(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
112787
- need6((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
113025
+ need5(/^[a-f0-9]{64}$/.test(reviewed.sha256) && reviewed.sha256 === cli.sha256 && reviewed.version === pkg.version, "SKILLS_RELEASE_MISMATCH");
113026
+ need5((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
112788
113027
  return {
112789
113028
  receipt: { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 },
112790
113029
  recheck() {
112791
- need6(Bun.which("skills", { PATH: process.env.PATH }) === normal && realpathSync17(normal) === resolved && realpathSync17(bound) === resolved && realpathSync17(entrypoint) === resolved, "SKILLS_COMMAND_CHANGED");
113030
+ need5(Bun.which("skills", { PATH: process.env.PATH }) === normal && realpathSync17(normal) === resolved && realpathSync17(bound) === resolved && realpathSync17(entrypoint) === resolved, "SKILLS_COMMAND_CHANGED");
112792
113031
  unchanged2(cli);
112793
113032
  unchanged2(manifest);
112794
113033
  }
@@ -112801,17 +113040,17 @@ var init_codex_hook_trust_identity = __esm(() => {
112801
113040
  // src/lib/codex-hook-policy-recovery.ts
112802
113041
  import { isDeepStrictEqual as isDeepStrictEqual14 } from "util";
112803
113042
  function reviewedClaudeDiscoveryChange(beforeText, currentText, options) {
112804
- need6(typeof options.reason === "string" && options.reason.trim().length > 0 && options.reason.length <= 512 && !/[\x00-\x1f\x7f]/.test(options.reason), "RECONCILE_RECOVERY_REASON_REQUIRED");
112805
- need6(options.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(options.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
113043
+ need5(typeof options.reason === "string" && options.reason.trim().length > 0 && options.reason.length <= 512 && !/[\x00-\x1f\x7f]/.test(options.reason), "RECONCILE_RECOVERY_REASON_REQUIRED");
113044
+ need5(options.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(options.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
112806
113045
  const before2 = parseManagedSkillPolicy(beforeText), current = parseManagedSkillPolicy(currentText);
112807
113046
  const oldDiscovery = before2.bridge?.discovery?.claude, newDiscovery = current.bridge?.discovery?.claude;
112808
- need6(oldDiscovery?.agent === "claude" && newDiscovery?.agent === "claude" && oldDiscovery.method === "reviewed" && newDiscovery.method === "reviewed", "RECONCILE_POLICY_CHANGED");
113047
+ need5(oldDiscovery?.agent === "claude" && newDiscovery?.agent === "claude" && oldDiscovery.method === "reviewed" && newDiscovery.method === "reviewed", "RECONCILE_POLICY_CHANGED");
112809
113048
  const changes = ["roots", "sources"].filter((key) => !isDeepStrictEqual14(oldDiscovery[key], newDiscovery[key])).map((key) => ({ path: `bridge.discovery.claude.${key}`, before: oldDiscovery[key], after: newDiscovery[key] }));
112810
- need6(changes.length > 0, "RECONCILE_POLICY_CHANGED");
113049
+ need5(changes.length > 0, "RECONCILE_POLICY_CHANGED");
112811
113050
  const compared = structuredClone(current);
112812
113051
  compared.bridge.discovery.claude.roots = oldDiscovery.roots;
112813
113052
  compared.bridge.discovery.claude.sources = oldDiscovery.sources;
112814
- need6(isDeepStrictEqual14(before2, compared), "RECONCILE_POLICY_CHANGED");
113053
+ need5(isDeepStrictEqual14(before2, compared), "RECONCILE_POLICY_CHANGED");
112815
113054
  verifyAgentDiscovery(newDiscovery);
112816
113055
  return { reason: options.reason, changes };
112817
113056
  }
@@ -112829,15 +113068,15 @@ import { dirname as dirname34, isAbsolute as isAbsolute28, join as join61, resol
112829
113068
  import { homedir as homedir12 } from "os";
112830
113069
  import { isDeepStrictEqual as isDeepStrictEqual15 } from "util";
112831
113070
  function verifyReleaseArtifact(proof, skillsCli) {
112832
- need6(isAbsolute28(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
113071
+ need5(isAbsolute28(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
112833
113072
  const tarPath = realpathSync18(proof.packageTarPath);
112834
- need6(statSync21(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
113073
+ need5(statSync21(tarPath).isFile(), "RECONCILE_RELEASE_PROVENANCE_PATH");
112835
113074
  const tarBytes = readFileSync40(tarPath);
112836
- need6(tarBytes.byteLength === proof.packageTarBytes && createHash31("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
113075
+ need5(tarBytes.byteLength === proof.packageTarBytes && createHash31("sha256").update(tarBytes).digest("hex") === proof.packageTarSha256, "RECONCILE_RELEASE_ARTIFACT_CHANGED");
112837
113076
  const packageRoot = dirname34(dirname34(realpathSync18(skillsCli.receipt.path)));
112838
113077
  const manifest = readFileSync40(join61(packageRoot, "package.json"));
112839
- need6(createHash31("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
112840
- need6(createHash31("sha256").update(readFileSync40(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
113078
+ need5(createHash31("sha256").update(manifest).digest("hex") === skillsCli.receipt.manifestSha256 && skillsCli.receipt.manifestSha256 === proof.manifestSha256, "RECONCILE_RELEASE_MANIFEST_CHANGED");
113079
+ need5(createHash31("sha256").update(readFileSync40(skillsCli.receipt.path)).digest("hex") === proof.executableSha256 && skillsCli.receipt.sha256 === proof.executableSha256, "RECONCILE_RELEASE_EXECUTABLE_CHANGED");
112841
113080
  const members2 = [["package/package.json", manifest], ["package/bin/index.js", readFileSync40(skillsCli.receipt.path)]];
112842
113081
  for (const [member, expected] of members2) {
112843
113082
  let actual;
@@ -112846,7 +113085,7 @@ function verifyReleaseArtifact(proof, skillsCli) {
112846
113085
  } catch {
112847
113086
  throw new Error("RECONCILE_RELEASE_ARTIFACT_MEMBER_MISSING");
112848
113087
  }
112849
- need6(Buffer.compare(actual, expected) === 0, "RECONCILE_RELEASE_ARTIFACT_MEMBER_CHANGED");
113088
+ need5(Buffer.compare(actual, expected) === 0, "RECONCILE_RELEASE_ARTIFACT_MEMBER_CHANGED");
112850
113089
  }
112851
113090
  }
112852
113091
  async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc) {
@@ -112854,48 +113093,48 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
112854
113093
  try {
112855
113094
  const home = resolve26(options.home ?? homedir12()), dataDir3 = resolve26(options.dataDir ?? getDataDirReadOnly());
112856
113095
  const parent = resolve26(join61(dataDir3, "native-hook-trust")), journal = resolve26(options.journal);
112857
- need6(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
113096
+ need5(journal.startsWith(parent + "/") && /^[a-f0-9-]{36}$/.test(journal.slice(parent.length + 1)), "RECONCILE_JOURNAL_PATH");
112858
113097
  const dir = lstatSync32(journal, { throwIfNoEntry: false });
112859
- need6(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
112860
- need6(!lstatSync32(join61(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
112861
- need6(lstatSync32(join61(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
113098
+ need5(!!dir && dir.isDirectory() && !dir.isSymbolicLink() && dir.uid === process.getuid() && (dir.mode & 511) === 448, "UNSAFE_JOURNAL");
113099
+ need5(!lstatSync32(join61(journal, "receipt.json"), { throwIfNoEntry: false }), "RECONCILE_ALREADY_COMPLETE");
113100
+ need5(lstatSync32(join61(journal, "stopped.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_JOURNAL_INCOMPLETE");
112862
113101
  const stoppedFile = snapshot(join61(journal, "stopped.json"), true);
112863
113102
  const intentFile = snapshot(join61(journal, "intent.json"), true), intent = JSON.parse(intentFile.text);
112864
113103
  const configBefore = snapshot(join61(journal, "config.before.toml"), true), hooksBefore = snapshot(join61(journal, "hooks.before.json"), true), policyBefore = snapshot(join61(journal, "policy.before.json"), true);
112865
113104
  const legacyIntent = intent.home === undefined && intent.configSha256 === undefined && intent.configVersion === undefined && intent.declarations === undefined && intent.admitted === undefined && intent.nativeHooks === undefined;
112866
- need6(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");
113105
+ need5(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");
112867
113106
  const declarations = intent.declarations ?? intent.hooks.map((hook) => ({ event: hook.event, key: `${join61(home, ".codex/hooks.json")}${hook.key.slice(hook.key.indexOf(".codex/hooks.json") + ".codex/hooks.json".length)}`, command: hook.command }));
112868
113107
  const admitted = intent.admitted ?? intent.hooks;
112869
113108
  const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
112870
113109
  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 };
112871
- need6(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
112872
- need6(isDeepStrictEqual15(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
112873
- need6(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
113110
+ need5(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
113111
+ need5(isDeepStrictEqual15(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
113112
+ need5(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
112874
113113
  const policyCurrent = snapshot(join61(dataDir3, "agent-policy.json"), true);
112875
113114
  const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
112876
113115
  const recovery = claudeRecovery ?? additionsRecovery;
112877
- need6(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
112878
- need6(!claudeRecovery || !additionsRecovery || claudeRecovery.reviewedPlanDigest === additionsRecovery.reviewedPlanDigest, "RECONCILE_RECOVERY_MODE_CONFLICT");
112879
- need6(recovery?.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(recovery.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
113116
+ need5(!recovery || !options.supersedeBinding, "RECONCILE_RECOVERY_MODE_CONFLICT");
113117
+ need5(!claudeRecovery || !additionsRecovery || claudeRecovery.reviewedPlanDigest === additionsRecovery.reviewedPlanDigest, "RECONCILE_RECOVERY_MODE_CONFLICT");
113118
+ need5(recovery?.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(recovery.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
112880
113119
  const policyChange = claudeRecovery ? reviewedClaudeDiscoveryChange(policyBefore.text, policyCurrent.text, claudeRecovery) : undefined;
112881
113120
  let supersededBinding = false;
112882
113121
  if (policyCurrent.sha256 !== policyBefore.sha256 && !claudeRecovery) {
112883
- need6(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
113122
+ need5(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
112884
113123
  const oldPolicy = JSON.parse(policyBefore.text), currentPolicy = JSON.parse(policyCurrent.text);
112885
- need6(isDeepStrictEqual15(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
113124
+ need5(isDeepStrictEqual15(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
112886
113125
  supersededBinding = true;
112887
113126
  }
112888
113127
  if (supersededBinding || recovery) {
112889
- need6(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
113128
+ need5(Array.isArray(intent.nativeHooks) && intent.nativeHooks.length > 0, "RECONCILE_NATIVE_INVENTORY_REQUIRED");
112890
113129
  const proof = options.releaseProof;
112891
- need6(!!proof && isAbsolute28(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");
113130
+ need5(!!proof && isAbsolute28(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");
112892
113131
  }
112893
113132
  const currentConfig = snapshot(intent.configPath, true);
112894
113133
  const policy = parseManagedSkillPolicy(policyCurrent.text), binding = policy.bridge;
112895
113134
  const alias = binding.rootAliases?.find((item) => item.agent === "codex"), physicalRoot = alias?.target ?? join61(home, ".codex"), hooksPath = join61(physicalRoot, "hooks.json");
112896
- need6(resolve26(intent.configPath) === resolve26(join61(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
113135
+ need5(resolve26(intent.configPath) === resolve26(join61(physicalRoot, "config.toml")), "RECONCILE_CONFIG_PATH_CHANGED");
112897
113136
  const nativeConfigPath = intent.nativeConfigPath ?? intent.configPath;
112898
- need6([join61(home, ".codex/config.toml"), join61(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
113137
+ need5([join61(home, ".codex/config.toml"), join61(physicalRoot, "config.toml")].includes(nativeConfigPath), "RECONCILE_NATIVE_CONFIG_PATH_CHANGED");
112899
113138
  const codexHome = nativeConfigPath !== intent.configPath ? undefined : dirname34(nativeConfigPath);
112900
113139
  const hooksCurrent = snapshot(hooksPath);
112901
113140
  const additions = additionsRecovery ? reviewNativeHookAdditions({ path: additionsRecovery.path, intent: intentFile, policy: policyCurrent, configBefore, configCurrent: currentConfig, hooksBefore, hooksCurrent, nativeHooksPath: join61(dirname34(nativeConfigPath), "hooks.json"), journalHooks: intent.hooks, recordedNativeHooks: intent.nativeHooks }) : undefined;
@@ -112903,7 +113142,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
112903
113142
  const managedKeys = managedHooks.map((h) => h.key);
112904
113143
  const witnessSame = codexTrustReconcileWitness(configBefore.text, managedKeys) === codexTrustReconcileWitness(currentConfig.text, managedKeys);
112905
113144
  const unmanagedSame = codexTrustUnmanagedSemantic(configBefore.text, managedKeys) === codexTrustUnmanagedSemantic(currentConfig.text, managedKeys);
112906
- need6(unmanagedSame && (witnessSame || supersededBinding), "RECONCILE_UNRELATED_CONFIG_CHANGED");
113145
+ need5(unmanagedSame && (witnessSame || supersededBinding), "RECONCILE_UNRELATED_CONFIG_CHANGED");
112907
113146
  const before2 = structure3(configBefore.text), current = structure3(currentConfig.text), expectedConfig = structuredClone(before2);
112908
113147
  expectedConfig.hooks ??= {};
112909
113148
  expectedConfig.hooks.state ??= {};
@@ -112915,10 +113154,10 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
112915
113154
  if (supersededBinding) {
112916
113155
  for (const hook of intent.hooks) {
112917
113156
  const state = current.hooks?.state?.[hook.key];
112918
- need6(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
113157
+ need5(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
112919
113158
  }
112920
113159
  } else
112921
- need6(isDeepStrictEqual15(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
113160
+ need5(isDeepStrictEqual15(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
112922
113161
  const configDiscoveryRecovery = recovery && binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, configBefore, currentConfig, intent.hooks, additions?.trustProof) : undefined;
112923
113162
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: configDiscoveryRecovery });
112924
113163
  const recoveryCli = recovery ? bindSkillsCli("skills", options.reviewedSkillsCli) : undefined;
@@ -112928,28 +113167,28 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
112928
113167
  if (supersededBinding)
112929
113168
  verifyReleaseArtifact(options.releaseProof, skillsCli);
112930
113169
  const skillsBindingChanged = !isDeepStrictEqual15(skillsCli.receipt, intent.skillsCli);
112931
- need6(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
113170
+ need5(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
112932
113171
  if (!additions && hooksCurrent.sha256 !== intent.hooksSha256 && hooksCurrent.sha256 !== hooksBefore.sha256) {
112933
- need6(supersededBinding, "RECONCILE_HOOKS_CHANGED");
113172
+ need5(supersededBinding, "RECONCILE_HOOKS_CHANGED");
112934
113173
  const currentDeclarations = JSON.parse(hooksCurrent.text).hooks;
112935
113174
  for (const [event, [nativeEvent]] of Object.entries(eventNames)) {
112936
113175
  const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
112937
- need6(isDeepStrictEqual15(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
113176
+ need5(isDeepStrictEqual15(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
112938
113177
  }
112939
113178
  }
112940
113179
  rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
112941
- need6(rpc.version === intent.nativeVersion, "RECONCILE_NATIVE_VERSION_CHANGED");
113180
+ need5(rpc.version === intent.nativeVersion, "RECONCILE_NATIVE_VERSION_CHANGED");
112942
113181
  const discovered = await rpc.request("hooks/list", { cwds: [home] });
112943
- need6(Array.isArray(discovered?.data) && discovered.data.length === 1 && discovered.data[0].cwd === home && !discovered.data[0].errors?.length && !discovered.data[0].warnings?.length, "RECONCILE_NATIVE_DISCOVERY_REFUSED");
113182
+ need5(Array.isArray(discovered?.data) && discovered.data.length === 1 && discovered.data[0].cwd === home && !discovered.data[0].errors?.length && !discovered.data[0].warnings?.length, "RECONCILE_NATIVE_DISCOVERY_REFUSED");
112944
113183
  const nativeHooks = discovered.data[0].hooks;
112945
113184
  const recordedNativeHooks = intent.nativeHooks;
112946
113185
  if (recordedNativeHooks)
112947
- need6(nativeHooks.length === recordedNativeHooks.length + (additions?.hooks.length ?? 0), "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
113186
+ need5(nativeHooks.length === recordedNativeHooks.length + (additions?.hooks.length ?? 0), "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
112948
113187
  const plannedKeys = new Set(intent.hooks.map((hook) => hook.key));
112949
113188
  let nativeTrustPending = false;
112950
113189
  for (const expected of [...recordedNativeHooks ?? [], ...additions?.hooks ?? []]) {
112951
113190
  const matches2 = nativeHooks.filter((hook) => hook.key === expected.key);
112952
- need6(matches2.length === 1, "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
113191
+ need5(matches2.length === 1, "RECONCILE_NATIVE_HOOK_LIST_CHANGED");
112953
113192
  const actual = safeNativeHook(matches2[0]);
112954
113193
  const stable = (hook) => {
112955
113194
  const { enabled, trustStatus, ...identity5 } = hook;
@@ -112957,35 +113196,35 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
112957
113196
  delete identity5.command;
112958
113197
  return identity5;
112959
113198
  };
112960
- need6(isDeepStrictEqual15(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
113199
+ need5(isDeepStrictEqual15(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
112961
113200
  if (!plannedKeys.has(expected.key))
112962
- need6(isDeepStrictEqual15(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
113201
+ need5(isDeepStrictEqual15(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
112963
113202
  }
112964
113203
  for (const wanted of intent.hooks) {
112965
113204
  const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join61(home, ".codex/hooks.json"), hooksPath));
112966
- need6(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
113205
+ need5(matches2.length === 1, "RECONCILE_NATIVE_IDENTITY_CHANGED");
112967
113206
  const h = matches2[0];
112968
113207
  const trusted = h.trustStatus === "trusted";
112969
- need6(h.enabled === true && (trusted || supersededBinding && h.trustStatus === "modified") && (h.currentHash === wanted.currentHash || supersededBinding && /^sha256:[a-f0-9]{64}$/.test(h.currentHash)), "RECONCILE_NATIVE_STATE_INCOMPLETE");
113208
+ need5(h.enabled === true && (trusted || supersededBinding && h.trustStatus === "modified") && (h.currentHash === wanted.currentHash || supersededBinding && /^sha256:[a-f0-9]{64}$/.test(h.currentHash)), "RECONCILE_NATIVE_STATE_INCOMPLETE");
112970
113209
  if (!trusted)
112971
113210
  nativeTrustPending = true;
112972
113211
  if (supersededBinding) {
112973
113212
  const nativeToCliEvent = { userPromptSubmit: "UserPromptSubmit", sessionStart: "SessionStart", subagentStart: "SubagentStart" };
112974
- need6(h.command === renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, nativeToCliEvent[wanted.event] ?? ""), "RECONCILE_BINDING_HOOK_CHANGED");
113213
+ need5(h.command === renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, nativeToCliEvent[wanted.event] ?? ""), "RECONCILE_BINDING_HOOK_CHANGED");
112975
113214
  }
112976
113215
  }
112977
113216
  const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
112978
113217
  const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
112979
- need6(layers?.length === 1 && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, current), "RECONCILE_NATIVE_CONFIG_MISMATCH");
113218
+ need5(layers?.length === 1 && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, current), "RECONCILE_NATIVE_CONFIG_MISMATCH");
112980
113219
  const finalDiscovery = await rpc.request("hooks/list", { cwds: [home] });
112981
- need6(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks || isDeepStrictEqual15(finalDiscovery.data[0].hooks.map(safeNativeHook), nativeHooks.map(safeNativeHook))), "RECONCILE_NATIVE_STATE_CHANGED");
113220
+ need5(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks || isDeepStrictEqual15(finalDiscovery.data[0].hooks.map(safeNativeHook), nativeHooks.map(safeNativeHook))), "RECONCILE_NATIVE_STATE_CHANGED");
112982
113221
  const finalConfig = await rpc.request("config/read", { includeLayers: true, cwd: home });
112983
113222
  const finalLayers = finalConfig?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
112984
- need6(finalLayers?.length === 1 && !finalLayers[0].disabledReason && codexNativeConfigEqual(finalLayers[0].config, current), "RECONCILE_NATIVE_CONFIG_CHANGED");
113223
+ need5(finalLayers?.length === 1 && !finalLayers[0].disabledReason && codexNativeConfigEqual(finalLayers[0].config, current), "RECONCILE_NATIVE_CONFIG_CHANGED");
112985
113224
  if (recovery) {
112986
113225
  for (const response of [config2, finalConfig]) {
112987
113226
  const layerIndex = response.layers.findIndex((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
112988
- need6(response.config && response.config.features?.hooks !== false && response.layers.slice(0, layerIndex).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "RECONCILE_NATIVE_CONFIG_OVERRIDDEN");
113227
+ need5(response.config && response.config.features?.hooks !== false && response.layers.slice(0, layerIndex).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "RECONCILE_NATIVE_CONFIG_OVERRIDDEN");
112989
113228
  }
112990
113229
  recoveryCli.recheck();
112991
113230
  verifyReleaseArtifact(options.releaseProof, recoveryCli);
@@ -113006,7 +113245,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
113006
113245
  const recoveryPlan = recovery ? { version: 1, kind: additions ? claudeRecovery ? "claude-discovery-and-native-hook-additions" : "native-hook-additions" : "claude-discovery-only", journal, journalIntentSha256: intentFile.sha256, stoppedSha256: stoppedFile.sha256, originalPlanDigest: intent.planDigest, policyBeforeSha256: policyBefore.sha256, policyCurrentSha256: policyCurrent.sha256, configBeforeSha256: configBefore.sha256, configCurrentSha256: currentConfig.sha256, ...configDiscoveryRecovery ? { configDiscoveryRecovery } : {}, ...additions ? { nativeHookAdditions: additions.receipt } : {}, hooksSha256: hooksCurrent.sha256, nativeConfigPath, nativeVersion: rpc.version, nativeInventorySha256: makePlanDigest(nativeHooks.map(safeNativeHook)), recordedNativeInventorySha256: makePlanDigest(recordedNativeHooks), boundSkillsCli: skillsCli.receipt, recoveryCli: recoveryCli.receipt, releaseProof: options.releaseProof, ...policyChange } : undefined;
113007
113246
  const recoveryPlanDigest = recoveryPlan ? makePlanDigest(recoveryPlan) : undefined;
113008
113247
  if (recovery?.reviewedPlanDigest !== undefined)
113009
- need6(recovery.reviewedPlanDigest === recoveryPlanDigest, "RECONCILE_RECOVERY_PLAN_CHANGED");
113248
+ need5(recovery.reviewedPlanDigest === recoveryPlanDigest, "RECONCILE_RECOVERY_PLAN_CHANGED");
113010
113249
  const preview2 = !!recovery && recovery.reviewedPlanDigest === undefined;
113011
113250
  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 } : {} };
113012
113251
  if (preview2)
@@ -113029,7 +113268,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
113029
113268
  const binding = policy.bridge, alias = binding.rootAliases?.find((item) => item.agent === "codex");
113030
113269
  const skillsCli = bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
113031
113270
  const physicalRoot = alias?.target ?? join61(home, ".codex"), hooksPath = join61(physicalRoot, "hooks.json"), configPath = join61(physicalRoot, "config.toml");
113032
- need6(options.codexHome === undefined || isAbsolute28(options.codexHome) && [join61(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
113271
+ need5(options.codexHome === undefined || isAbsolute28(options.codexHome) && [join61(home, ".codex"), physicalRoot].includes(options.codexHome), "NATIVE_HOME_NOT_ADMITTED");
113033
113272
  const codexHome = options.codexHome === undefined ? undefined : physicalRoot;
113034
113273
  const nativeConfigPath = join61(codexHome ?? join61(home, ".codex"), "config.toml");
113035
113274
  const hooksFile = snapshot(hooksPath), configFile = snapshot(configPath, true), original = structure3(configFile.text);
@@ -113037,38 +113276,38 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
113037
113276
  for (const [event, [nativeEvent, label]] of Object.entries(eventNames)) {
113038
113277
  const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
113039
113278
  const groups = declarations[event];
113040
- need6(Array.isArray(groups), "DECLARATION_CHANGED");
113279
+ need5(Array.isArray(groups), "DECLARATION_CHANGED");
113041
113280
  const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
113042
- need6(matches2.length === 1 && isDeepStrictEqual15(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
113281
+ need5(matches2.length === 1 && isDeepStrictEqual15(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
113043
113282
  expected.push({ event: nativeEvent, key: `${join61(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
113044
113283
  }
113045
113284
  codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
113046
113285
  rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
113047
- need6(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
113286
+ need5(SUPPORTED_CODEX_HOOK_VERSIONS.includes(rpc.version), "NATIVE_UNSUPPORTED_VERSION");
113048
113287
  const list2 = async () => {
113049
113288
  const response = await rpc.request("hooks/list", { cwds: [home] });
113050
113289
  const nativeRoots = [...new Set([physicalRoot, join61(home, ".codex")])];
113051
113290
  const coexistenceWarnings = nativeRoots.flatMap((hooksRoot) => nativeRoots.map((configRoot) => `loading hooks from both ${join61(hooksRoot, "hooks.json")} and ${join61(configRoot, "config.toml")}; prefer a single representation for this layer`));
113052
113291
  const entry = response?.data?.[0];
113053
- need6(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");
113292
+ need5(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");
113054
113293
  return response.data[0].hooks;
113055
113294
  };
113056
113295
  const admit = (hooks) => expected.map((wanted) => {
113057
113296
  const canonicalKey = wanted.key.replace(join61(home, ".codex/hooks.json"), hooksPath);
113058
113297
  const matches2 = hooks.filter((h2) => h2.key === wanted.key || h2.key === canonicalKey);
113059
- need6(matches2.length === 1, "AMBIGUOUS_IDENTITY");
113298
+ need5(matches2.length === 1, "AMBIGUOUS_IDENTITY");
113060
113299
  const h = matches2[0];
113061
- need6(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, join61(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");
113062
- need6(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
113063
- need6(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
113300
+ need5(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, join61(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");
113301
+ need5(hooks.filter((other) => other.command === wanted.command).length === 1, "AMBIGUOUS_IDENTITY");
113302
+ need5(["trusted", "untrusted", "modified"].includes(h.trustStatus), "UNKNOWN_TRUST_STATUS");
113064
113303
  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 };
113065
113304
  });
113066
113305
  const discovered = await list2(), admitted = admit(discovered);
113067
113306
  const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
113068
113307
  const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
113069
- need6(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, original), "NATIVE_CONFIG_MISMATCH");
113070
- need6(config2.config && config2.config.features?.hooks !== false, "NATIVE_HOOKS_DISABLED");
113071
- need6(config2.layers.slice(0, config2.layers.indexOf(layers[0])).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "NATIVE_CONFIG_OVERRIDDEN");
113308
+ need5(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason && codexNativeConfigEqual(layers[0].config, original), "NATIVE_CONFIG_MISMATCH");
113309
+ need5(config2.config && config2.config.features?.hooks !== false, "NATIVE_HOOKS_DISABLED");
113310
+ need5(config2.layers.slice(0, config2.layers.indexOf(layers[0])).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "NATIVE_CONFIG_OVERRIDDEN");
113072
113311
  const keys3 = admitted.map((h) => h.key), planned = admitted.filter((h) => !h.enabled || h.trustStatus !== "trusted");
113073
113312
  const preservationText = codexTrustTextWitness(configFile.text, keys3);
113074
113313
  const checkInputs = () => {
@@ -113083,16 +113322,16 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
113083
113322
  const planDigest = makePlanDigest(digestInput);
113084
113323
  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 };
113085
113324
  if (options.apply)
113086
- need6(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
113325
+ need5(options.reviewedPlanDigest === planDigest, "PLAN_CHANGED: run the dry-run again and pass its reviewed --plan-digest with --apply");
113087
113326
  const parent = join61(dataDir3, "native-hook-trust");
113088
113327
  const st = lstatSync32(parent, { throwIfNoEntry: false });
113089
113328
  if (st) {
113090
- need6(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
113329
+ need5(st.isDirectory() && !st.isSymbolicLink() && st.uid === process.getuid() && (st.mode & 511) === 448, "UNSAFE_JOURNAL");
113091
113330
  const priorJournals = readdirSync23(parent);
113092
- need6(priorJournals.length < 1000, "JOURNAL_BOUND");
113331
+ need5(priorJournals.length < 1000, "JOURNAL_BOUND");
113093
113332
  for (const name of priorJournals) {
113094
- need6(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
113095
- need6(lstatSync32(join61(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
113333
+ need5(/^[a-f0-9-]{36}$/.test(name), "UNSAFE_JOURNAL");
113334
+ need5(lstatSync32(join61(parent, name, "receipt.json"), { throwIfNoEntry: false })?.isFile(), "RECONCILE_REQUIRED: inspect the incomplete private journal before applying again");
113096
113335
  }
113097
113336
  }
113098
113337
  if (!options.apply || !planned.length)
@@ -113105,7 +113344,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
113105
113344
  save(join61(journal, "hooks.before.json"), hooksFile.bytes);
113106
113345
  save(join61(journal, "policy.before.json"), policyFile.bytes);
113107
113346
  save(join61(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
113108
- need6(isDeepStrictEqual15(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
113347
+ need5(isDeepStrictEqual15(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
113109
113348
  checkInputs();
113110
113349
  let result2;
113111
113350
  try {
@@ -113113,22 +113352,22 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
113113
113352
  } catch {
113114
113353
  throw new Error("CODEX_HOOK_TRUST_NATIVE_WRITE_FAILED: reconcile the private journal before retrying");
113115
113354
  }
113116
- need6(result2?.status === "ok" && result2.filePath === configPath && /^sha256:[a-f0-9]{64}$/.test(result2.version), "NATIVE_WRITE_OVERRIDDEN");
113355
+ need5(result2?.status === "ok" && result2.filePath === configPath && /^sha256:[a-f0-9]{64}$/.test(result2.version), "NATIVE_WRITE_OVERRIDDEN");
113117
113356
  const after2 = snapshot(configPath, true), expectedConfig = structuredClone(original);
113118
113357
  expectedConfig.hooks ??= {};
113119
113358
  expectedConfig.hooks.state ??= {};
113120
113359
  for (const h of planned)
113121
113360
  expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
113122
- need6(isDeepStrictEqual15(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
113361
+ need5(isDeepStrictEqual15(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
113123
113362
  const savedConfig = snapshot(join61(journal, "config.before.toml"), true);
113124
- need6(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
113363
+ need5(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
113125
113364
  const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
113126
113365
  unchanged2(policyFile);
113127
113366
  unchanged2(hooksFile);
113128
113367
  assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
113129
113368
  const current = await list2(), confirmed = admit(current);
113130
- need6(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") && isDeepStrictEqual15(confirmed.map((h) => [h.key, h.currentHash]), admitted.map((h) => [h.key, h.currentHash])), "NATIVE_NOT_ELIGIBLE");
113131
- need6(isDeepStrictEqual15(current.filter((h) => !keys3.includes(h.key)), discovered.filter((h) => !keys3.includes(h.key))), "UNRELATED_HOOK_CHANGED");
113369
+ need5(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") && isDeepStrictEqual15(confirmed.map((h) => [h.key, h.currentHash]), admitted.map((h) => [h.key, h.currentHash])), "NATIVE_NOT_ELIGIBLE");
113370
+ need5(isDeepStrictEqual15(current.filter((h) => !keys3.includes(h.key)), discovered.filter((h) => !keys3.includes(h.key))), "UNRELATED_HOOK_CHANGED");
113132
113371
  unchanged2(after2);
113133
113372
  unchanged2(savedConfig);
113134
113373
  unchanged2(hooksFile);
@@ -113291,7 +113530,7 @@ var exports_agent_integration = {};
113291
113530
  __export(exports_agent_integration, {
113292
113531
  registerAgentIntegration: () => registerAgentIntegration
113293
113532
  });
113294
- import { existsSync as existsSync36, lstatSync as lstatSync33, readFileSync as readFileSync41, writeFileSync as writeFileSync26 } from "fs";
113533
+ import { existsSync as existsSync36, lstatSync as lstatSync33, readFileSync as readFileSync41, readSync as readSync17, writeFileSync as writeFileSync26 } from "fs";
113295
113534
  import { createHash as createHash32 } from "crypto";
113296
113535
  import { homedir as homedir13 } from "os";
113297
113536
  import { isAbsolute as isAbsolute29 } from "path";
@@ -113361,6 +113600,32 @@ function agents(value) {
113361
113600
  }
113362
113601
  function registerAgentIntegration(parent) {
113363
113602
  const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
113603
+ hook.command("project-claude-settings").requiredOption("--expected-target-sha256 <sha256>", "SHA-256 of the exact target settings bytes supplied on stdin").option("--expected-source-sha256 <sha256>", "Source witness from the preceding projection, for revalidation").option("--json", "Return only command-leaf replacements and content witnesses", false).description("Read-only projection of verified Skills hooks into copied Claude settings supplied on stdin").action(async (options) => {
113604
+ try {
113605
+ const bytes = Buffer.alloc(1024 * 1024 + 1);
113606
+ let length = 0;
113607
+ while (length < bytes.length) {
113608
+ const count = readSync17(0, bytes, length, bytes.length - length, null);
113609
+ if (!count)
113610
+ break;
113611
+ length += count;
113612
+ }
113613
+ if (length > 1024 * 1024)
113614
+ throw new Error("CLAUDE_MANAGED_HOOK_TARGET_INVALID");
113615
+ const targetSettings = new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, length));
113616
+ const plan = planClaudeManagedHookProjection({
113617
+ targetSettings,
113618
+ expectedTargetSha256: options.expectedTargetSha256,
113619
+ expectedSourceSha256: options.expectedSourceSha256,
113620
+ projectDir: process.cwd()
113621
+ });
113622
+ await writeCliOutput(JSON.stringify(plan));
113623
+ } catch (error61) {
113624
+ const code2 = error61 instanceof Error && /^CLAUDE_MANAGED_HOOK_[A-Z_]+$/.test(error61.message) ? error61.message : "CLAUDE_MANAGED_HOOK_PROJECTION_FAILED";
113625
+ console.error(code2);
113626
+ process.exitCode = 1;
113627
+ }
113628
+ });
113364
113629
  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) => {
113365
113630
  try {
113366
113631
  if (!isAbsolute29(options.cwd) || !isAbsolute29(options.output))
@@ -113772,7 +114037,7 @@ var exports_profiles = {};
113772
114037
  __export(exports_profiles, {
113773
114038
  registerProfiles: () => registerProfiles
113774
114039
  });
113775
- import { closeSync as closeSync21, constants as constants20, fstatSync as fstatSync21, openSync as openSync21, readSync as readSync17, writeFileSync as writeFileSync27 } from "fs";
114040
+ import { closeSync as closeSync21, constants as constants20, fstatSync as fstatSync21, openSync as openSync21, readSync as readSync18, writeFileSync as writeFileSync27 } from "fs";
113776
114041
  function readProfileInput(path) {
113777
114042
  const fd = openSync21(path, constants20.O_RDONLY | constants20.O_NONBLOCK);
113778
114043
  try {
@@ -113782,7 +114047,7 @@ function readProfileInput(path) {
113782
114047
  const bytes = Buffer.alloc(MAX_PROFILE_DOCUMENT_BYTES + 1);
113783
114048
  let size2 = 0;
113784
114049
  while (size2 < bytes.length) {
113785
- const read3 = readSync17(fd, bytes, size2, bytes.length - size2, null);
114050
+ const read3 = readSync18(fd, bytes, size2, bytes.length - size2, null);
113786
114051
  if (!read3)
113787
114052
  break;
113788
114053
  size2 += read3;
@@ -113846,7 +114111,7 @@ import {
113846
114111
  closeSync as closeSync23,
113847
114112
  fstatSync as fstatSync23,
113848
114113
  openSync as openSync23,
113849
- readSync as readSync18,
114114
+ readSync as readSync19,
113850
114115
  writeFileSync as writeFileSync28
113851
114116
  } from "fs";
113852
114117
  function readInput(path) {
@@ -113858,7 +114123,7 @@ function readInput(path) {
113858
114123
  const bytes = Buffer.alloc(MAX_EXECUTION_GRANT_BYTES + 1);
113859
114124
  let size2 = 0;
113860
114125
  while (size2 < bytes.length) {
113861
- const count = readSync18(fd, bytes, size2, bytes.length - size2, null);
114126
+ const count = readSync19(fd, bytes, size2, bytes.length - size2, null);
113862
114127
  if (!count)
113863
114128
  break;
113864
114129
  size2 += count;