@hasna/skills 0.10.19 → 0.10.22

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/dist/index.js CHANGED
@@ -7262,7 +7262,7 @@ import {
7262
7262
  statSync as statSync10,
7263
7263
  writeFileSync as writeFileSync8
7264
7264
  } from "fs";
7265
- import { basename as basename10, dirname as dirname19, isAbsolute as isAbsolute17, join as join29, normalize as normalize3, relative as relative5, resolve as resolve17, sep as sep8 } from "path";
7265
+ import { basename as basename11, dirname as dirname19, isAbsolute as isAbsolute17, join as join29, normalize as normalize4, relative as relative6, resolve as resolve17, sep as sep8 } from "path";
7266
7266
 
7267
7267
  // src/lib/registry-data/index.ts
7268
7268
  var SKILLS = [];
@@ -8920,7 +8920,7 @@ var NATIVE_SKILL_ROOTS = [
8920
8920
  // src/lib/authoring-path.ts
8921
8921
  import { lstatSync as lstatSync22, realpathSync as realpathSync8 } from "fs";
8922
8922
  import { homedir as homedir6 } from "os";
8923
- import { basename as basename9, dirname as dirname18, join as join28, resolve as resolve16, sep as sep7 } from "path";
8923
+ import { basename as basename10, dirname as dirname18, join as join28, resolve as resolve16, sep as sep7 } from "path";
8924
8924
 
8925
8925
  // src/lib/agent-adapters.ts
8926
8926
  var INTEGRATION_AGENTS = ["claude", "codex", "gemini", "opencode", "cursor", "hermes"];
@@ -9038,12 +9038,14 @@ function assertAgentPolicyCollections(policy) {
9038
9038
  }
9039
9039
  if (bridge.codexPluginSkills !== undefined)
9040
9040
  for (const control of array(bridge.codexPluginSkills, 4096)) {
9041
- requireBound(object(control) && Object.keys(control).every((key) => ["name", "pluginId", "namespace", "pluginParent", "manifestSha256", "appSha256"].includes(key)));
9041
+ requireBound(object(control) && Object.keys(control).every((key) => ["name", "pluginId", "namespace", "pluginParent", "manifestSha256", "appSha256", "remotePluginId"].includes(key)));
9042
9042
  text(control.name, 129);
9043
9043
  text(control.pluginId, 1024);
9044
9044
  text(control.namespace, 64);
9045
9045
  text(control.pluginParent);
9046
9046
  requireBound(isAbsolute2(control.pluginParent) && resolve2(control.pluginParent) === control.pluginParent && /^[a-f0-9]{64}$/.test(control.manifestSha256));
9047
+ if (control.remotePluginId !== undefined)
9048
+ requireBound(typeof control.remotePluginId === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(control.remotePluginId));
9047
9049
  if (control.appSha256 !== undefined)
9048
9050
  requireBound(typeof control.appSha256 === "string" && /^[a-f0-9]{64}$/.test(control.appSha256));
9049
9051
  }
@@ -9066,8 +9068,8 @@ function assertAgentPolicyCollections(policy) {
9066
9068
  text(source.path);
9067
9069
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
9068
9070
  if (source.hashMode !== undefined)
9069
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2", "codex-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9070
- if (source.hashMode === "codex-settings-v1")
9071
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9072
+ if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2")
9071
9073
  requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve2(source.path) === source.path && basename2(source.path) === "config.toml");
9072
9074
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
9073
9075
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve2(source.path) === source.path && basename2(source.path) === "settings.json");
@@ -9103,10 +9105,10 @@ function assertAgentPolicyCollections(policy) {
9103
9105
  }
9104
9106
 
9105
9107
  // src/lib/agent-discovery.ts
9106
- import { createHash as createHash17 } from "crypto";
9108
+ import { createHash as createHash18 } from "crypto";
9107
9109
  import { lstatSync as lstatSync21, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync7 } from "fs";
9108
- import { basename as basename8, dirname as dirname17, isAbsolute as isAbsolute16, join as join27, resolve as resolve15, sep as sep6 } from "path";
9109
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
9110
+ import { basename as basename9, dirname as dirname17, isAbsolute as isAbsolute16, join as join27, resolve as resolve15, sep as sep6 } from "path";
9111
+ import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
9110
9112
 
9111
9113
  // node_modules/yaml/dist/index.js
9112
9114
  var composer = require_composer();
@@ -9616,7 +9618,7 @@ function hashDiscoveryPathFile(path, budget, changes) {
9616
9618
 
9617
9619
  // src/lib/plugin-discovery.ts
9618
9620
  import { closeSync as closeSync10, constants as constants9, fstatSync as fstatSync10, lstatSync as lstatSync18, openSync as openSync10, opendirSync as opendirSync5, readSync as readSync8 } from "fs";
9619
- import { basename as basename6, dirname as dirname14, isAbsolute as isAbsolute14, join as join26, resolve as resolve13 } from "path";
9621
+ import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute14, join as join26, resolve as resolve13 } from "path";
9620
9622
 
9621
9623
  // src/lib/plugin-admission.ts
9622
9624
  import { homedir as homedir5 } from "os";
@@ -10877,7 +10879,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
10877
10879
  // package.json
10878
10880
  var package_default = {
10879
10881
  name: "@hasna/skills",
10880
- version: "0.10.19",
10882
+ version: "0.10.22",
10881
10883
  description: "Skills library for AI coding agents",
10882
10884
  type: "module",
10883
10885
  bin: {
@@ -12710,7 +12712,7 @@ import {
12710
12712
  writeFileSync as writeFileSync5
12711
12713
  } from "fs";
12712
12714
  import { homedir as homedir3 } from "os";
12713
- import { basename as basename5, dirname as dirname12, join as join18 } from "path";
12715
+ import { basename as basename6, dirname as dirname12, join as join18 } from "path";
12714
12716
 
12715
12717
  // src/lib/claude-settings-witness.ts
12716
12718
  import { createHash as createHash8 } from "crypto";
@@ -13024,9 +13026,28 @@ function hashNativeJsonControls(text2, excludedMetadata) {
13024
13026
  }
13025
13027
 
13026
13028
  // src/lib/codex-plugin-skill-controls.ts
13027
- import { dirname as dirname10, join as join14, resolve as resolve9, sep as sep4 } from "path";
13029
+ import { basename as basename5, dirname as dirname10, join as join14, relative as relative4, resolve as resolve9, sep as sep4 } from "path";
13028
13030
  import { lstatSync as lstatSync12, readdirSync as readdirSync6, readlinkSync as readlinkSync3, realpathSync as realpathSync5 } from "fs";
13029
13031
 
13032
+ // src/lib/codex-native-compatibility.ts
13033
+ var MEASURED_CODEX_CAPABILITIES = {
13034
+ "codex-cli 0.153.0": ["hooks"],
13035
+ "codex-cli 0.154.0": ["hooks"],
13036
+ "codex-cli 0.155.0": ["hooks"],
13037
+ "codex-cli 0.155.1": ["hooks"],
13038
+ "codex-cli 0.156.1": ["hooks"],
13039
+ "codex-cli 0.157.0": ["hooks"],
13040
+ "codex-cli 0.157.1": ["hooks"],
13041
+ "codex-cli 0.158.0": ["hooks"],
13042
+ "codex-cli 0.159.0": ["hooks"],
13043
+ "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
13044
+ "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
13045
+ };
13046
+ function supportsCodexNativeCapability(version, capability) {
13047
+ return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
13048
+ }
13049
+ var SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
13050
+
13030
13051
  // src/lib/codex-native-skill-catalog.ts
13031
13052
  import { realpathSync as realpathSync4 } from "fs";
13032
13053
  import { isAbsolute as isAbsolute10, normalize as normalize2 } from "path";
@@ -13036,7 +13057,6 @@ import { execFile, spawn } from "child_process";
13036
13057
  import { realpathSync as realpathSync3, statSync as statSync7 } from "fs";
13037
13058
  import { isAbsolute as isAbsolute9 } from "path";
13038
13059
  import { promisify } from "util";
13039
- var SUPPORTED_CODEX_HOOK_VERSIONS = ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1", "codex-cli 0.157.0", "codex-cli 0.157.1", "codex-cli 0.158.0", "codex-cli 0.159.0", "codex-cli 0.159.2"];
13040
13060
  async function connectCodexHookRpc(options) {
13041
13061
  const timeoutMs = options.timeoutMs ?? 20000;
13042
13062
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 20000)
@@ -13054,7 +13074,7 @@ async function connectCodexHookRpc(options) {
13054
13074
  } catch {
13055
13075
  throw new Error("CODEX_HOOK_TRUST_NATIVE_UNAVAILABLE");
13056
13076
  }
13057
- if (!SUPPORTED_CODEX_HOOK_VERSIONS.includes(version))
13077
+ if (!supportsCodexNativeCapability(version, "hooks"))
13058
13078
  throw new Error("CODEX_HOOK_TRUST_NATIVE_UNSUPPORTED_VERSION");
13059
13079
  const current = statSync7(binary);
13060
13080
  if (realpathSync3(found) !== binary || ["dev", "ino", "size", "mtimeMs", "ctimeMs"].some((k) => stat[k] !== current[k]))
@@ -13178,12 +13198,13 @@ async function connectCodexHookRpc(options) {
13178
13198
  var MAX_SKILLS = 4096;
13179
13199
  var MAX_NAME_BYTES = 1024;
13180
13200
  var MAX_PATH_BYTES = 16384;
13181
- var SUPPORTED_NAME_CONTROL_VERSIONS = new Set(["codex-cli 0.159.2"]);
13201
+ var MAX_PLUGIN_ID_BYTES = 1024;
13182
13202
  function refuse() {
13183
13203
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_INVALID");
13184
13204
  }
13185
13205
  var object4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
13186
13206
  var scalar = (value, max) => typeof value === "string" && value.length > 0 && value.trim() === value && Buffer.byteLength(value, "utf8") <= max && !/[\u0000-\u001f\u007f]/u.test(value);
13207
+ var remotePluginIdentifier = (value) => typeof value === "string" && /^[A-Za-z0-9_~-]{1,1024}$/.test(value);
13187
13208
  var absolutePath2 = (value) => scalar(value, MAX_PATH_BYTES) && isAbsolute10(value);
13188
13209
  function projectCodexNativeSkillCatalog(response, cwd) {
13189
13210
  if (!absolutePath2(cwd) || !object4(response) || !Array.isArray(response.data) || response.data.length !== 1)
@@ -13201,6 +13222,47 @@ function projectCodexNativeSkillCatalog(response, cwd) {
13201
13222
  return { name: value.name, path: value.path, enabled: value.enabled, pluginId: value.pluginId ?? null };
13202
13223
  });
13203
13224
  }
13225
+ function projectCodexInstalledPlugins(response) {
13226
+ if (!object4(response) || !Array.isArray(response.marketplaces) || !Array.isArray(response.marketplaceLoadErrors) || response.marketplaceLoadErrors.length !== 0 || response.marketplaces.length > MAX_SKILLS)
13227
+ refuse();
13228
+ const plugins = [];
13229
+ for (const marketplace of response.marketplaces) {
13230
+ if (!object4(marketplace) || !scalar(marketplace.name, MAX_NAME_BYTES) || !Array.isArray(marketplace.plugins) || marketplace.plugins.length > MAX_SKILLS)
13231
+ refuse();
13232
+ for (const value of marketplace.plugins) {
13233
+ if (!object4(value) || !scalar(value.id, MAX_PLUGIN_ID_BYTES) || !scalar(value.name, MAX_NAME_BYTES) || value.id !== `${value.name}@${marketplace.name}`)
13234
+ refuse();
13235
+ plugins.push({
13236
+ ...value,
13237
+ marketplace: marketplace.name,
13238
+ sourceType: object4(value.source) && value.source.type === "remote" ? "remote" : undefined
13239
+ });
13240
+ }
13241
+ }
13242
+ return projectCodexInstalledPluginEntries(plugins);
13243
+ }
13244
+ function projectCodexInstalledPluginEntries(value) {
13245
+ if (!Array.isArray(value) || value.length > MAX_SKILLS)
13246
+ refuse();
13247
+ const ids = new Set;
13248
+ return value.map((item) => {
13249
+ if (!object4(item) || !scalar(item.id, MAX_PLUGIN_ID_BYTES) || !scalar(item.name, MAX_NAME_BYTES) || typeof item.installed !== "boolean" || typeof item.enabled !== "boolean" || item.localVersion !== null && !scalar(item.localVersion, MAX_NAME_BYTES) || item.remotePluginId !== undefined && item.remotePluginId !== null && !remotePluginIdentifier(item.remotePluginId) || item.sourceType !== undefined && item.sourceType !== "remote")
13250
+ refuse();
13251
+ const at = item.id.lastIndexOf("@");
13252
+ if (at < 1 || item.id.slice(0, at) !== item.name || at === item.id.length - 1 || ids.has(item.id))
13253
+ refuse();
13254
+ ids.add(item.id);
13255
+ return {
13256
+ id: item.id,
13257
+ name: item.name,
13258
+ installed: item.installed,
13259
+ enabled: item.enabled,
13260
+ localVersion: item.localVersion ?? null,
13261
+ ...item.sourceType === "remote" ? { sourceType: "remote" } : {},
13262
+ ...item.remotePluginId == null ? {} : { remotePluginId: item.remotePluginId }
13263
+ };
13264
+ });
13265
+ }
13204
13266
  async function captureCodexNativeSkillCatalog(options, connect = connectCodexHookRpc) {
13205
13267
  if (!absolutePath2(options.cwd) || !absolutePath2(options.home))
13206
13268
  refuse();
@@ -13211,10 +13273,16 @@ async function captureCodexNativeSkillCatalog(options, connect = connectCodexHoo
13211
13273
  ...options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }
13212
13274
  });
13213
13275
  try {
13214
- if (!SUPPORTED_NAME_CONTROL_VERSIONS.has(rpc.version))
13276
+ if (!supportsCodexNativeCapability(rpc.version, "qualified-skill-catalog"))
13215
13277
  throw new Error("CODEX_NATIVE_SKILL_CATALOG_UNSUPPORTED_VERSION");
13216
13278
  const response = await rpc.request("skills/list", { cwds: [options.cwd], forceReload: true });
13217
- return { version: rpc.version, cwd: options.cwd, skills: projectCodexNativeSkillCatalog(response, options.cwd) };
13279
+ const installed = await rpc.request("plugin/installed", { cwds: [options.cwd] });
13280
+ return {
13281
+ version: rpc.version,
13282
+ cwd: options.cwd,
13283
+ skills: projectCodexNativeSkillCatalog(response, options.cwd),
13284
+ plugins: projectCodexInstalledPlugins(installed)
13285
+ };
13218
13286
  } finally {
13219
13287
  await rpc.close();
13220
13288
  }
@@ -13259,9 +13327,25 @@ function isCodexNativeSkillDisabled(skill, rules) {
13259
13327
 
13260
13328
  // src/lib/codex-plugin-skill-controls.ts
13261
13329
  var identifier2 = (v) => typeof v === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(v);
13330
+ var CODEX_DEFAULT_AGENT_PLUGIN_VERSION = "1.0.0";
13262
13331
  function refuse2() {
13263
13332
  throw new Error("CODEX_NATIVE_SKILL_IDENTITY_UNSUPPORTED: review native plugin names and hook controls");
13264
13333
  }
13334
+ function remoteInstallationMatches(parent, remotePluginId, read2) {
13335
+ try {
13336
+ if (!remotePluginIdentifier(remotePluginId) || !lstatSync12(parent).isDirectory() || realpathSync5(parent) !== parent)
13337
+ return false;
13338
+ const path = join14(parent, ".codex-remote-plugin-install.json"), stat = lstatSync12(path, { throwIfNoEntry: false });
13339
+ if (!stat?.isFile() || stat.isSymbolicLink() || stat.size > 16384)
13340
+ return false;
13341
+ const text2 = read2(path);
13342
+ hashNativeJsonControls(text2);
13343
+ const value = JSON.parse(text2);
13344
+ return Boolean(value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 2 && value.schema_version === 1 && value.remote_plugin_id === remotePluginId);
13345
+ } catch {
13346
+ return false;
13347
+ }
13348
+ }
13265
13349
  function appControls(path, read2) {
13266
13350
  const text2 = read2(path);
13267
13351
  let value, sha2562;
@@ -13297,6 +13381,9 @@ function rootControls(root, read2) {
13297
13381
  }
13298
13382
  if (!manifest || typeof manifest !== "object" || Array.isArray(manifest) || !identifier2(manifest.name))
13299
13383
  refuse2();
13384
+ const cacheVersion = basename5(root), declaredVersion = manifest.version === undefined ? "" : typeof manifest.version === "string" ? manifest.version.trim() : null;
13385
+ if (declaredVersion === null || (declaredVersion ? declaredVersion !== cacheVersion : cacheVersion !== CODEX_DEFAULT_AGENT_PLUGIN_VERSION))
13386
+ refuse2();
13300
13387
  if (manifest.hooks !== undefined || manifest.mcpServers !== undefined || manifest.commands !== undefined || manifest.apps !== undefined && manifest.apps !== "./.app.json" || ["hooks", ".mcp.json"].some((path) => lstatSync12(join14(root, path), { throwIfNoEntry: false })))
13301
13388
  refuse2();
13302
13389
  const app = join14(root, ".app.json"), present = lstatSync12(app, { throwIfNoEntry: false });
@@ -13348,6 +13435,10 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
13348
13435
  for (const parent of new Set(controls.map((control) => control.pluginParent))) {
13349
13436
  if (!parent.startsWith(cache + sep4) || !lstatSync12(parent, { throwIfNoEntry: false })?.isDirectory())
13350
13437
  return false;
13438
+ for (const control of controls.filter((control2) => control2.pluginParent === parent)) {
13439
+ if (control.remotePluginId !== undefined && !remoteInstallationMatches(parent, control.remotePluginId, read2))
13440
+ return false;
13441
+ }
13351
13442
  const names = readdirSync6(parent);
13352
13443
  entries += names.length;
13353
13444
  if (entries > 4096)
@@ -13373,22 +13464,56 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2) {
13373
13464
  return false;
13374
13465
  }
13375
13466
  }
13376
- function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2) {
13377
- if (catalog?.version !== "codex-cli 0.159.2" || catalog.cwd !== cwd)
13467
+ function reviewCodexPluginSkillControls(catalog, documents, cache, cwd, read2, rules) {
13468
+ if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
13378
13469
  refuse2();
13379
13470
  const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
13380
13471
  const allowed = new Set(documents), result = [];
13381
- for (const skill of skills) {
13382
- if (!allowed.has(skill.path) || !skill.path.startsWith(cache + sep4))
13472
+ let installedPlugins;
13473
+ try {
13474
+ installedPlugins = projectCodexInstalledPluginEntries(catalog.plugins ?? []);
13475
+ } catch {
13476
+ refuse2();
13477
+ }
13478
+ const candidates = catalog.plugins === undefined ? skills.map((skill) => skill.path).filter((path) => allowed.has(path)) : allowed;
13479
+ for (const document2 of candidates) {
13480
+ if (!document2.startsWith(cache + sep4))
13383
13481
  continue;
13384
- if (skills.filter((other) => other.name === skill.name).length !== 1)
13482
+ const parsed = identity3(document2, cache, read2);
13483
+ if (parsed.name === "skills-cli" || parsed.name.endsWith(":skills-cli"))
13484
+ refuse2();
13485
+ const cacheParts = relative4(cache, parsed.pluginParent).split(sep4);
13486
+ if (cacheParts.length !== 2 || cacheParts.some((part) => !identifier2(part)))
13385
13487
  refuse2();
13386
- if (!skill.pluginId || skill.name === "skills-cli" || skill.name.endsWith(":skills-cli"))
13488
+ const [marketplace, pluginName] = cacheParts;
13489
+ if (pluginName !== parsed.namespace)
13387
13490
  refuse2();
13388
- const parsed = identity3(skill.path, cache, read2);
13389
- if (parsed.name !== skill.name)
13491
+ const expectedPluginId = `${pluginName}@${marketplace}`;
13492
+ const listed = skills.filter((skill) => skill.path === document2);
13493
+ if (listed.length > 1)
13390
13494
  refuse2();
13391
- result.push({ ...parsed, pluginId: skill.pluginId });
13495
+ let remotePluginId;
13496
+ if (listed.length === 1) {
13497
+ if (listed[0].name !== parsed.name || listed[0].pluginId !== expectedPluginId)
13498
+ refuse2();
13499
+ } else {
13500
+ const installed = installedPlugins.filter((plugin2) => plugin2.id === expectedPluginId && plugin2.name === pluginName);
13501
+ if (installed.length !== 1 || !installed[0].installed || !installed[0].enabled)
13502
+ refuse2();
13503
+ const plugin = installed[0];
13504
+ if (plugin.localVersion !== null) {
13505
+ if (plugin.localVersion !== basename5(dirname10(dirname10(dirname10(document2)))))
13506
+ continue;
13507
+ } else {
13508
+ if (plugin.sourceType !== "remote" || !plugin.remotePluginId || !remoteInstallationMatches(parsed.pluginParent, plugin.remotePluginId, read2) || !lstatSync12(document2).isFile() || realpathSync5(document2) !== document2)
13509
+ refuse2();
13510
+ remotePluginId = plugin.remotePluginId;
13511
+ }
13512
+ const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document2 && rule?.enabled === false);
13513
+ if (!exactPathDeny || !isCodexNativeSkillDisabled({ name: parsed.name, path: document2, pluginId: expectedPluginId, enabled: true }, rules))
13514
+ refuse2();
13515
+ }
13516
+ result.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
13392
13517
  }
13393
13518
  if (!result.length || new Set(result.map((item) => item.name)).size !== result.length)
13394
13519
  refuse2();
@@ -13404,7 +13529,7 @@ function isReviewedCodexPluginSkillDisabled(document2, cache, controls, rules, r
13404
13529
  return false;
13405
13530
  }
13406
13531
  const reviewed = controls.find((item) => item.name === parsed.name && item.namespace === parsed.namespace && item.pluginParent === parsed.pluginParent && item.manifestSha256 === parsed.manifestSha256 && item.appSha256 === parsed.appSha256);
13407
- if (!reviewed)
13532
+ if (!reviewed || reviewed.remotePluginId !== undefined && !remoteInstallationMatches(reviewed.pluginParent, reviewed.remotePluginId, read2))
13408
13533
  return false;
13409
13534
  return isCodexNativeSkillDisabled({ name: parsed.name, path: document2, pluginId: reviewed.pluginId, enabled: true }, rules);
13410
13535
  }
@@ -13423,7 +13548,7 @@ enabled = false
13423
13548
  return text2;
13424
13549
  }
13425
13550
 
13426
- // src/lib/codex-settings-witness.ts
13551
+ // src/lib/codex-settings-witness-v1.ts
13427
13552
  import { createHash as createHash9 } from "crypto";
13428
13553
  var sha = (text2) => createHash9("sha256").update(text2).digest("hex");
13429
13554
  var EFFORT = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
@@ -13517,21 +13642,169 @@ function captureCodexSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
13517
13642
  const text2 = readNativeSettingsWitnessFile(path, budget, "config.toml");
13518
13643
  return { path, hashMode: "codex-settings-v1", sha256: hashCodexSettingsReplacement(text2) };
13519
13644
  }
13645
+
13646
+ // src/lib/codex-settings-witness.ts
13647
+ import { createHash as createHash10 } from "crypto";
13648
+ var sha2 = (text2) => createHash10("sha256").update(text2).digest("hex");
13649
+ var EFFORT2 = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
13650
+ var VERBOSITY2 = new Set(["low", "medium", "high"]);
13651
+ var MODEL = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
13652
+ var CODEX_DISCOVERY_PROJECTION_FIELDS = ["plugins", "marketplaces", "skills"];
13653
+ var INFERENCE = ["model", "model_reasoning_effort", "model_verbosity"];
13654
+ function need4(value) {
13655
+ if (!value)
13656
+ throw new Error("Invalid Codex settings witness");
13657
+ }
13658
+ function isRecord3(value) {
13659
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
13660
+ }
13661
+ function isDate(value) {
13662
+ return value instanceof Date;
13663
+ }
13664
+ function numericTokens2(text2) {
13665
+ const result = [];
13666
+ for (let at = 0;at < text2.length; ) {
13667
+ const char = text2[at];
13668
+ if (char === "#") {
13669
+ while (at < text2.length && text2[at] !== `
13670
+ `)
13671
+ at++;
13672
+ continue;
13673
+ }
13674
+ if (char === '"' || char === "'") {
13675
+ const quote = char, triple = text2.slice(at, at + 3) === quote.repeat(3);
13676
+ at += triple ? 3 : 1;
13677
+ while (at < text2.length) {
13678
+ if (quote === '"' && text2[at] === "\\") {
13679
+ at += 2;
13680
+ continue;
13681
+ }
13682
+ if (text2.slice(at, at + (triple ? 3 : 1)) === quote.repeat(triple ? 3 : 1)) {
13683
+ at += triple ? 3 : 1;
13684
+ break;
13685
+ }
13686
+ at++;
13687
+ }
13688
+ continue;
13689
+ }
13690
+ if (/[0-9+-]/.test(char)) {
13691
+ const start = at;
13692
+ while (at < text2.length && !/[\s,\]{}#=]/.test(text2[at]))
13693
+ at++;
13694
+ if (at > start) {
13695
+ result.push(text2.slice(start, at));
13696
+ continue;
13697
+ }
13698
+ }
13699
+ at++;
13700
+ }
13701
+ return result.sort();
13702
+ }
13703
+ function canonical4(value, depth = 0, budget = { remaining: 65536 }) {
13704
+ need4(depth <= 32 && --budget.remaining >= 0);
13705
+ if (value === null)
13706
+ return ["null"];
13707
+ if (typeof value === "string")
13708
+ return ["string", value];
13709
+ if (typeof value === "boolean")
13710
+ return ["boolean", value];
13711
+ if (typeof value === "number") {
13712
+ need4(Number.isFinite(value));
13713
+ return ["number", String(value)];
13714
+ }
13715
+ if (isDate(value)) {
13716
+ need4(Number.isFinite(value.getTime()));
13717
+ return ["date", value.toISOString()];
13718
+ }
13719
+ if (Array.isArray(value))
13720
+ return ["array", value.map((child) => canonical4(child, depth + 1, budget))];
13721
+ need4(isRecord3(value));
13722
+ return ["object", Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, child]) => [key, canonical4(child, depth + 1, budget)])];
13723
+ }
13724
+ function normalize3(config) {
13725
+ if (isRecord3(config.hooks)) {
13726
+ delete config.hooks.state;
13727
+ if (!Object.keys(config.hooks).length)
13728
+ delete config.hooks;
13729
+ }
13730
+ if (isRecord3(config.skills) && Array.isArray(config.skills.config)) {
13731
+ const skills = config.skills;
13732
+ const retained = skills.config.filter((entry) => !disablesOneSkill(entry));
13733
+ if (retained.length !== skills.config.length) {
13734
+ if (retained.length)
13735
+ skills.config = retained;
13736
+ else
13737
+ delete skills.config;
13738
+ }
13739
+ if (!Object.keys(skills).length)
13740
+ delete config.skills;
13741
+ }
13742
+ inference(config);
13743
+ if (isRecord3(config.profiles)) {
13744
+ for (const profile of Object.values(config.profiles))
13745
+ if (isRecord3(profile))
13746
+ inference(profile);
13747
+ }
13748
+ }
13749
+ function disablesOneSkill(entry) {
13750
+ if (!isRecord3(entry) || entry.enabled !== false)
13751
+ return false;
13752
+ const keys = Object.keys(entry);
13753
+ if (!keys.every((key) => key === "path" || key === "name" || key === "enabled"))
13754
+ return false;
13755
+ const selector = ["path", "name"].filter((key) => Object.hasOwn(entry, key));
13756
+ return selector.length === 1 && typeof entry[selector[0]] === "string" && entry[selector[0]].length > 0;
13757
+ }
13758
+ function inference(target) {
13759
+ for (const key of INFERENCE)
13760
+ if (Object.hasOwn(target, key)) {
13761
+ const value = target[key];
13762
+ if (key === "model")
13763
+ need4(typeof value === "string" && MODEL.test(value));
13764
+ else
13765
+ need4(typeof value === "string" && (key === "model_reasoning_effort" ? EFFORT2.has(value) : VERBOSITY2.has(value)));
13766
+ delete target[key];
13767
+ }
13768
+ }
13769
+ function settingsDigest2(text2) {
13770
+ let config;
13771
+ try {
13772
+ config = Bun.TOML.parse(text2);
13773
+ } catch {
13774
+ throw new Error("Invalid Codex settings witness");
13775
+ }
13776
+ need4(isRecord3(config));
13777
+ normalize3(config);
13778
+ return sha2(`hasna.skills.codex-settings.v2\x00${JSON.stringify([canonical4(config), numericTokens2(text2)])}`);
13779
+ }
13780
+ function hashCodexSettingsReplacementV2(text2, budget = { remaining: 256 * 1024 * 1024 }) {
13781
+ need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
13782
+ budget.remaining -= Buffer.byteLength(text2);
13783
+ return settingsDigest2(text2);
13784
+ }
13785
+ function captureCodexSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
13786
+ const text2 = readNativeSettingsWitnessFile(path, budget, "config.toml");
13787
+ return { path, hashMode: "codex-settings-v2", sha256: settingsDigest2(text2) };
13788
+ }
13789
+ function readCodexSettingsPreimage(path, budget = { remaining: 2 * 1024 * 1024 }) {
13790
+ return readNativeSettingsWitnessFile(path, budget, "config.toml");
13791
+ }
13520
13792
  function upgradeCodexSettingsWitness(previous, reviewedSettingsPath) {
13521
- need3(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes") && /^[a-f0-9]{64}$/.test(previous.sha256));
13793
+ need4(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "codex-settings-v1") && /^[a-f0-9]{64}$/.test(previous.sha256));
13522
13794
  const budget = { remaining: 2 * 1024 * 1024 };
13523
13795
  const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget, "config.toml"), current = readNativeSettingsWitnessFile(previous.path, budget, "config.toml");
13524
- need3(sha(before) === previous.sha256);
13525
- const sha2562 = hashCodexSettingsReplacement(current);
13526
- if (hashCodexSettingsReplacement(before) !== sha2562)
13796
+ need4((previous.hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(before) : sha2(before)) === previous.sha256);
13797
+ const sha2562 = settingsDigest2(current);
13798
+ if (settingsDigest2(before) !== sha2562)
13527
13799
  throw new Error("Codex non-preference settings changed; explicit discovery review required");
13528
- return { path: previous.path, hashMode: "codex-settings-v1", sha256: sha2562 };
13800
+ return { path: previous.path, hashMode: "codex-settings-v2", sha256: sha2562 };
13529
13801
  }
13530
13802
 
13531
13803
  // src/lib/agent-integration.ts
13532
- import { createHash as createHash11, randomUUID as randomUUID2 } from "crypto";
13804
+ import { createHash as createHash12, randomUUID as randomUUID2 } from "crypto";
13805
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
13533
13806
  import { existsSync as existsSync8, lstatSync as lstatSync15, statSync as statSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync12, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync6, 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 constants7, linkSync } from "fs";
13534
- import { dirname as dirname11, join as join17, resolve as resolve10, relative as relative4, isAbsolute as isAbsolute11, sep as sep5 } from "path";
13807
+ import { dirname as dirname11, join as join17, resolve as resolve10, relative as relative5, isAbsolute as isAbsolute11, sep as sep5 } from "path";
13535
13808
  import { homedir as homedir2 } from "os";
13536
13809
 
13537
13810
  // src/lib/managed-policy.ts
@@ -13615,7 +13888,7 @@ function requiresCliSkillLoading(dataDir2 = getDataDirReadOnly()) {
13615
13888
  }
13616
13889
 
13617
13890
  // src/lib/agent-bridge.ts
13618
- import { createHash as createHash10 } from "crypto";
13891
+ import { createHash as createHash11 } from "crypto";
13619
13892
  import { lstatSync as lstatSync14, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "fs";
13620
13893
  import { join as join16 } from "path";
13621
13894
  var CLI_BRIDGE_NAME = "skills-cli";
@@ -13634,7 +13907,7 @@ Use the Skills CLI as the skill authority for this station.
13634
13907
 
13635
13908
  This bridge contains no skill payloads. Do not copy skill content into agent-native skill directories or load other native skill copies. If a Skills command refuses authority, selection, integrity, or native drift, report that refusal and use its repair guidance; do not substitute bundled or stale local content. Explicit local draft authoring remains available.
13636
13909
  `;
13637
- var CLI_BRIDGE_DIGEST = createHash10("sha256").update(markdown).digest("hex");
13910
+ var CLI_BRIDGE_DIGEST = createHash11("sha256").update(markdown).digest("hex");
13638
13911
  var CLI_BRIDGE_FILES = Object.freeze({
13639
13912
  "SKILL.md": markdown,
13640
13913
  ".hasna-skills.json": `${JSON.stringify({ managedBy: "@hasna/skills", kind: "cli-bridge", version: CLI_BRIDGE_VERSION, protocol: "selection-v1", contentSha256: CLI_BRIDGE_DIGEST }, null, 2)}
@@ -13926,7 +14199,7 @@ function disableCodexBundledSkills(text2) {
13926
14199
  }
13927
14200
 
13928
14201
  // src/lib/agent-integration.ts
13929
- var sha2 = (data) => createHash11("sha256").update(data).digest("hex");
14202
+ var sha3 = (data) => createHash12("sha256").update(data).digest("hex");
13930
14203
  var ROOTS = NATIVE_SKILL_ROOTS;
13931
14204
  function assertSafePath(path) {
13932
14205
  let cursor = resolve10(path);
@@ -13950,7 +14223,7 @@ function readRootAlias(home, agent) {
13950
14223
  const targetStat = lstatSync15(targetPath, { throwIfNoEntry: false });
13951
14224
  if (!targetStat?.isDirectory())
13952
14225
  throw new Error(`Agent root alias must target an existing directory: ${alias}`);
13953
- const target = realpathSync6(targetPath), inside = relative4(realHome, target);
14226
+ const target = realpathSync6(targetPath), inside = relative5(realHome, target);
13954
14227
  if (!inside || inside === ".." || inside.startsWith(`..${sep5}`) || isAbsolute11(inside))
13955
14228
  throw new Error(`Agent root alias target must be inside the real home: ${alias}`);
13956
14229
  return { agent, home: realHome, alias, target, link, aliasIdentity: `${stat.dev}:${stat.ino}:${stat.ctimeNs}`, targetIdentity: `${targetStat.dev}:${targetStat.ino}` };
@@ -13968,7 +14241,7 @@ function rootAliases(home, allowed = false) {
13968
14241
  }
13969
14242
  function canonicalAgentPath(path, aliases) {
13970
14243
  const absolute = resolve10(path), binding = aliases.find((item) => absolute === item.alias || absolute.startsWith(item.alias + sep5));
13971
- return binding ? join17(binding.target, relative4(binding.alias, absolute)) : absolute;
14244
+ return binding ? join17(binding.target, relative5(binding.alias, absolute)) : absolute;
13972
14245
  }
13973
14246
  function disabledCodexSkillPaths(config, aliases) {
13974
14247
  const skills = config.skills;
@@ -14033,13 +14306,13 @@ function readNativeBytes(path, maximum = 64 * 1024 * 1024) {
14033
14306
  }
14034
14307
  }
14035
14308
  function treeHash(root) {
14036
- const hash2 = createHash11("sha256");
14309
+ const hash2 = createHash12("sha256");
14037
14310
  let bytes = 0, files = 0;
14038
14311
  function visit2(path) {
14039
14312
  const stat = lstatSync15(path);
14040
14313
  if (stat.isSymbolicLink())
14041
14314
  throw new Error(`Refusing symlink in skill: ${path}`);
14042
- const rel = relative4(root, path);
14315
+ const rel = relative5(root, path);
14043
14316
  if (stat.isDirectory()) {
14044
14317
  hash2.update(`d\x00${rel}\x00`);
14045
14318
  for (const name of readdirSync8(path).sort())
@@ -14065,7 +14338,7 @@ function selectNativeMigrationTargets(inventory, manifest) {
14065
14338
  const identities = new Set;
14066
14339
  const selected = [];
14067
14340
  for (const target of manifest.targets) {
14068
- const projectRoot = resolve10(target.projectRoot), normalized = relative4(projectRoot, resolve10(projectRoot, target.path));
14341
+ const projectRoot = resolve10(target.projectRoot), normalized = relative5(projectRoot, resolve10(projectRoot, target.path));
14069
14342
  if (!isAbsolute11(target.projectRoot) || projectRoot !== target.projectRoot || !existsSync8(projectRoot) || !lstatSync15(projectRoot).isDirectory())
14070
14343
  throw new Error(`Native migration target project root must be an existing absolute directory: ${target.projectRoot}`);
14071
14344
  assertSafePath(projectRoot);
@@ -14418,9 +14691,9 @@ function planAgentIntegration(options) {
14418
14691
  const observedNativeSources = [];
14419
14692
  const codexPluginSkills = options.codexNativeCatalog ? reviewCodexPluginSkillControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join17(entry.path, "SKILL.md")), canonicalAgentPath(join17(home, ".codex/plugins/cache"), aliases), resolve10(options.projectDir ?? home), (path) => {
14420
14693
  const bytes = readNativeBytes(path, 1024 * 1024);
14421
- observedNativeSources.push({ path, sha256: sha2(bytes) });
14694
+ observedNativeSources.push({ path, sha256: sha3(bytes) });
14422
14695
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
14423
- }) : policy.bridge?.codexPluginSkills ?? [];
14696
+ }, codexConfig.skills?.config ?? []) : policy.bridge?.codexPluginSkills ?? [];
14424
14697
  const changes = [];
14425
14698
  for (const agent of [...new Set(options.agents)]) {
14426
14699
  if (!INTEGRATION_AGENTS.includes(agent))
@@ -14446,7 +14719,7 @@ function planAgentIntegration(options) {
14446
14719
  throw new Error("Refusing to overwrite an unrecognized Hermes supervisor");
14447
14720
  if (supervisorBefore !== supervisorAfter)
14448
14721
  changes.push({ path: supervisorPath, before: supervisorBefore, after: supervisorAfter });
14449
- const supervisor = { path: supervisorPath, runtime: process.execPath, sha256: sha2(supervisorAfter) };
14722
+ const supervisor = { path: supervisorPath, runtime: process.execPath, sha256: sha3(supervisorAfter) };
14450
14723
  const after2 = configureHermesHooks(before, supervisor, previous);
14451
14724
  if (before !== after2)
14452
14725
  changes.push({ path, before, after: after2 });
@@ -14493,12 +14766,12 @@ function planAgentIntegration(options) {
14493
14766
  agents: [...new Set([...priorAgents, ...options.agents])].sort(),
14494
14767
  home: resolve10(home),
14495
14768
  includeVendor: true,
14496
- ...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join17(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha2(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
14769
+ ...options.agents.includes("hermes") ? { supervisors: { ...policy.bridge?.supervisors, hermes: { path: join17(dataDir2, "agent-hooks", "hermes.js"), runtime: process.execPath, sha256: sha3(renderHermesSupervisor(bindings.get("hermes").command, bindings.get("hermes").profileId)) } } } : {},
14497
14770
  commands: { ...policy.bridge?.commands, ...Object.fromEntries([...bindings].map(([agent, binding]) => [agent, binding.command])) },
14498
14771
  profiles: Object.fromEntries([...new Set([...priorAgents, ...options.agents])].sort().map((agent) => [agent, bindings.get(agent)?.profileId ?? policy.bridge?.profiles?.[agent] ?? policy.profileId])),
14499
14772
  disabledBuiltins: options.agents.includes("codex") ? nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor && entry.path.startsWith(canonicalAgentPath(join17(home, ".codex", "skills", ".system"), aliases) + sep5)).map((entry) => ({ path: entry.path, hash: entry.hash })) : policy.bridge?.disabledBuiltins ?? [],
14500
14773
  rootAliases: aliases,
14501
- ...codexPluginSkills.length ? { codexPluginSkills, codexPluginSkillReview: options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha2(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd) })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join17(home, ".codex/config.toml"), aliases))?.before === null ? null : sha2(readOptional(canonicalAgentPath(join17(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview } : {},
14774
+ ...codexPluginSkills.length ? { codexPluginSkills, codexPluginSkillReview: options.codexNativeCatalog ? { version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, catalogSha256: sha3(JSON.stringify({ version: options.codexNativeCatalog.version, cwd: options.codexNativeCatalog.cwd, skills: projectCodexNativeSkillCatalog({ data: [{ cwd: options.codexNativeCatalog.cwd, errors: [], skills: options.codexNativeCatalog.skills }] }, options.codexNativeCatalog.cwd), ...options.codexNativeCatalog.plugins === undefined ? {} : { plugins: projectCodexInstalledPluginEntries(options.codexNativeCatalog.plugins) } })), configSha256: changes.find((change) => change.path === canonicalAgentPath(join17(home, ".codex/config.toml"), aliases))?.before === null ? null : sha3(readOptional(canonicalAgentPath(join17(home, ".codex/config.toml"), aliases)) ?? "") } : policy.bridge?.codexPluginSkillReview } : {},
14502
14775
  discovery: { ...policy.bridge?.discovery, ...Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) }
14503
14776
  } };
14504
14777
  const serializedPolicy = serializeManagedSkillPolicy(nextPolicy);
@@ -14514,12 +14787,12 @@ function planClaudePreToolUseHookUpdate(options) {
14514
14787
  return planClaudeHookEventsUpdate({ ...options, events: ["PreToolUse"] });
14515
14788
  }
14516
14789
  function claudeSettingsConsumers(home, path, discoveries, aliases) {
14517
- const canonical4 = (value) => canonicalAgentPath(value, aliases);
14790
+ const canonical5 = (value) => canonicalAgentPath(value, aliases);
14518
14791
  const containsSettings = (root) => {
14519
- const distance = relative4(canonical4(root), path);
14792
+ const distance = relative5(canonical5(root), path);
14520
14793
  return distance === "" || distance !== ".." && !distance.startsWith(`..${sep5}`) && !isAbsolute11(distance);
14521
14794
  };
14522
- return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical4(join17(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical4(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
14795
+ return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical5(join17(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical5(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
14523
14796
  }
14524
14797
  function verifyCoordinatedDiscovery(binding, home, aliases) {
14525
14798
  try {
@@ -14551,7 +14824,7 @@ function planClaudeHookEventsUpdate(options) {
14551
14824
  const path = canonicalAgentPath(join17(home, ".claude", "settings.json"), aliases);
14552
14825
  assertSafePath(path);
14553
14826
  const before = lstatSync15(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
14554
- if ((before === null ? "absent" : sha2(before)) !== options.expectedSettingsSha256)
14827
+ if ((before === null ? "absent" : sha3(before)) !== options.expectedSettingsSha256)
14555
14828
  throw new Error("Claude settings preimage changed");
14556
14829
  assertClaudeHookEventsReplacement(before, options.replacement, options.events);
14557
14830
  const agents = bridge.agents;
@@ -14617,7 +14890,7 @@ function atomicWrite2(path, content) {
14617
14890
  function planAgentSettingsWitnessUpgrade(options) {
14618
14891
  const home = resolve10(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
14619
14892
  const snapshot = readManagedSkillPolicySnapshot(dataDir2);
14620
- if (!snapshot || sha2(snapshot.text) !== options.expectedPolicySha256)
14893
+ if (!snapshot || sha3(snapshot.text) !== options.expectedPolicySha256)
14621
14894
  throw new Error("Managed policy preimage changed");
14622
14895
  const policy = snapshot.value, aliases = policy.bridge?.rootAliases ?? [];
14623
14896
  recheckRootAliases(aliases);
@@ -14626,7 +14899,7 @@ function planAgentSettingsWitnessUpgrade(options) {
14626
14899
  throw new Error("Settings witness upgrade requires an existing reviewed native binding");
14627
14900
  const configPath = canonicalAgentPath(join17(home, options.agent === "claude" ? ".claude/settings.json" : ".codex/config.toml"), aliases);
14628
14901
  const settings = readOptional(configPath);
14629
- if (settings === null || sha2(settings) !== options.expectedSettingsSha256)
14902
+ if (settings === null || sha3(settings) !== options.expectedSettingsSha256)
14630
14903
  throw new Error("Native settings preimage changed");
14631
14904
  const sources = binding.sources.filter((source) => source.path === configPath && source.format === undefined && source.fields === undefined);
14632
14905
  if (sources.length !== 1)
@@ -14634,15 +14907,40 @@ function planAgentSettingsWitnessUpgrade(options) {
14634
14907
  const previous = sources[0];
14635
14908
  if (typeof previous.sha256 !== "string")
14636
14909
  throw new Error("Settings witness upgrade requires a preserved settings preimage");
14910
+ const replaced = [previous];
14911
+ if (options.agent === "codex") {
14912
+ if (previous.hashMode !== undefined && previous.hashMode !== "bytes" && previous.hashMode !== "codex-settings-v1")
14913
+ throw new Error("Codex settings witness upgrade requires a preserved raw or codex-settings-v1 configuration witness");
14914
+ const preimage = readCodexSettingsPreimage(options.reviewedPreimage);
14915
+ for (const source of binding.sources) {
14916
+ if (source === previous || source.path !== configPath)
14917
+ continue;
14918
+ const fields = source.fields;
14919
+ if (source.hashMode !== undefined || source.format !== "toml" || !Array.isArray(fields) || !isDeepStrictEqual3(fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]) || typeof source.sha256 !== "string")
14920
+ throw new Error("Codex settings witness upgrade found an unrecognized configuration witness");
14921
+ if (projectNativeDiscoveryFields(preimage, "toml", fields, configPath) !== source.sha256)
14922
+ throw new Error("Reviewed preimage does not explain the Codex discovery projection witness");
14923
+ replaced.push(source);
14924
+ }
14925
+ }
14637
14926
  const next = options.agent === "claude" ? upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
14638
- const replacement = { ...binding, sources: binding.sources.map((source) => source === previous ? next : source) };
14927
+ const rebound = [];
14928
+ for (const source of binding.sources) {
14929
+ if (!replaced.includes(source)) {
14930
+ rebound.push(source);
14931
+ continue;
14932
+ }
14933
+ if (source === replaced[0])
14934
+ rebound.push(next);
14935
+ }
14936
+ const replacement = { ...binding, sources: rebound };
14639
14937
  verifyAgentDiscovery(replacement);
14640
14938
  const after = serializeManagedSkillPolicy({ ...policy, bridge: { ...policy.bridge, discovery: { ...policy.bridge.discovery, [options.agent]: replacement } } });
14641
- return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join17(dataDir2, "agent-policy.json"), before: snapshot.text, after }], nativeSkills: [], observedPolicy: { path: join17(dataDir2, "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 } };
14939
+ return { dataDir: dataDir2, profileId: policy.profileId, changes: [{ path: join17(dataDir2, "agent-policy.json"), before: snapshot.text, after }], nativeSkills: [], observedPolicy: { path: join17(dataDir2, "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 })) } };
14642
14940
  }
14643
14941
  function applyAgentIntegration(plan) {
14644
14942
  for (const source of plan.observedNativeSources ?? [])
14645
- if (sha2(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
14943
+ if (sha3(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
14646
14944
  throw new Error("Native identity source changed after planning");
14647
14945
  if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
14648
14946
  throw new Error("Native settings changed after witness planning");
@@ -14662,7 +14960,7 @@ function applyAgentIntegration(plan) {
14662
14960
  const upgrade = plan.settingsWitnessUpgrade;
14663
14961
  if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
14664
14962
  throw new Error("Invalid settings witness upgrade plan");
14665
- const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha2(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
14963
+ const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha3(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
14666
14964
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
14667
14965
  throw new Error("Settings witness upgrade plan changed");
14668
14966
  provenDiscovery = verified.discoveryBefore[0];
@@ -14690,7 +14988,7 @@ function applyAgentIntegration(plan) {
14690
14988
  chmodSync(backupRoot, 448);
14691
14989
  for (const [index, change] of plan.changes.entries())
14692
14990
  if (change.before !== null) {
14693
- const backup = join17(backupRoot, `${index}-${sha2(change.path).slice(0, 12)}.backup`);
14991
+ const backup = join17(backupRoot, `${index}-${sha3(change.path).slice(0, 12)}.backup`);
14694
14992
  writeFileSync4(backup, change.before, { mode: 384, flag: "wx" });
14695
14993
  if (readFileSync12(backup, "utf8") !== change.before)
14696
14994
  throw new Error("Native configuration preservation readback failed");
@@ -14709,7 +15007,7 @@ function applyAgentIntegration(plan) {
14709
15007
  }
14710
15008
  recheckRootAliases(aliases);
14711
15009
  for (const source of plan.observedNativeSources ?? [])
14712
- if (sha2(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
15010
+ if (sha3(readNativeBytes(source.path, 1024 * 1024)) !== source.sha256)
14713
15011
  throw new Error("Native identity source changed during application");
14714
15012
  if (plan.observedSettings && readOptional(plan.observedSettings.path) !== plan.observedSettings.before)
14715
15013
  throw new Error("Native settings changed during witness application");
@@ -14721,7 +15019,7 @@ function applyAgentIntegration(plan) {
14721
15019
  }
14722
15020
  for (const agent of plan.managedAgentChecks?.agents ?? [])
14723
15021
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
14724
- atomicWrite2(join17(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha2(change.before), afterHash: sha2(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
15022
+ atomicWrite2(join17(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha3(change.before), afterHash: sha3(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
14725
15023
  `);
14726
15024
  } catch (error) {
14727
15025
  for (const change of written.reverse()) {
@@ -14800,8 +15098,8 @@ function archiveNativeSkills(inventory, options) {
14800
15098
  const operationId = randomUUID2(), archiveRoot = join17(options.dataDir ?? getDataDir(), "migration", operationId, "native"), receiptPath = join17(archiveRoot, "receipt.json");
14801
15099
  const entries = selected.map((entry, index) => ({
14802
15100
  source: entry.vendor ? join17(entry.path, "SKILL.md") : entry.path,
14803
- archive: join17(archiveRoot, `${index}-${sha2(entry.path).slice(0, 12)}`),
14804
- hash: entry.vendor ? sha2(readNativeBytes(join17(entry.path, "SKILL.md"))) : entry.hash,
15101
+ archive: join17(archiveRoot, `${index}-${sha3(entry.path).slice(0, 12)}`),
15102
+ hash: entry.vendor ? sha3(readNativeBytes(join17(entry.path, "SKILL.md"))) : entry.hash,
14805
15103
  ...entry.vendor ? { discoveryOnly: true } : {},
14806
15104
  status: "planned"
14807
15105
  }));
@@ -14812,7 +15110,7 @@ function archiveNativeSkills(inventory, options) {
14812
15110
  const stat = lstatSync15(entry.archive);
14813
15111
  if (entry.discoveryOnly ? !stat.isFile() : !stat.isDirectory())
14814
15112
  throw new Error("Archive type changed");
14815
- if ((entry.discoveryOnly ? sha2(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
15113
+ if ((entry.discoveryOnly ? sha3(readNativeBytes(entry.archive)) : treeHash(entry.archive)) !== entry.hash)
14816
15114
  throw new Error("Archive bytes changed");
14817
15115
  };
14818
15116
  assertSafePath(archiveRoot);
@@ -14932,7 +15230,7 @@ function assertManagedAgentBridgeWithDiscovery(agent, options = {}, provenDiscov
14932
15230
  throw new Error("NATIVE_SKILL_DRIFT: the hook selection profile differs from its managed binding; restart the native client after reviewing skills hook install");
14933
15231
  if (agent === "hermes") {
14934
15232
  const supervisor = binding.supervisors?.hermes;
14935
- if (supervisor?.path !== join17(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha2(renderHermesSupervisor(command, profile)))
15233
+ if (supervisor?.path !== join17(dataDir2, "agent-hooks", "hermes.js") || supervisor?.sha256 !== sha3(renderHermesSupervisor(command, profile)))
14936
15234
  throw new Error("NATIVE_SKILL_DRIFT: Hermes supervisor binding changed; run skills hook install");
14937
15235
  assertHermesProtection(home, config, command, profile, supervisor);
14938
15236
  } else if (agent === "opencode") {
@@ -15261,7 +15559,7 @@ function writeManagedSkillDir(dir, skillMd, options) {
15261
15559
  return { action, path: skillMdPath };
15262
15560
  const parentDir = dirname12(dir);
15263
15561
  mkdirSync6(parentDir, { recursive: true });
15264
- const transactionDir = mkdtempSync(join18(parentDir, `.hasna-skills-write-${basename5(dir)}-`));
15562
+ const transactionDir = mkdtempSync(join18(parentDir, `.hasna-skills-write-${basename6(dir)}-`));
15265
15563
  const candidateDir = join18(transactionDir, "candidate");
15266
15564
  const backupDir = join18(transactionDir, "backup");
15267
15565
  const candidateSkillMdPath = join18(candidateDir, "SKILL.md");
@@ -16230,7 +16528,7 @@ function exactProfileSelection(spec, profile) {
16230
16528
  }
16231
16529
 
16232
16530
  // src/lib/plugin-projection.ts
16233
- import { createHash as createHash12 } from "crypto";
16531
+ import { createHash as createHash13 } from "crypto";
16234
16532
  import { posix } from "path";
16235
16533
  var PLUGIN_PROJECTION_MANIFEST = "plugin-projection.json";
16236
16534
  var PLUGIN_PROJECTION_LIMITS = Object.freeze({ files: 1024, bytes: 64 * 1024 * 1024, fileBytes: 16 * 1024 * 1024, metadataBytes: 512 * 1024, registrations: 64, history: 128 });
@@ -16241,7 +16539,7 @@ function pluginNeed(value, message) {
16241
16539
  if (!value)
16242
16540
  pluginRefusal(message);
16243
16541
  }
16244
- var pluginHash = (bytes) => createHash12("sha256").update(bytes).digest("hex");
16542
+ var pluginHash = (bytes) => createHash13("sha256").update(bytes).digest("hex");
16245
16543
  function pluginObject(value) {
16246
16544
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
16247
16545
  }
@@ -16429,7 +16727,7 @@ function buildPluginProjection(entries) {
16429
16727
  }
16430
16728
 
16431
16729
  // src/lib/plugin-projection-store.ts
16432
- import { createHash as createHash13, randomUUID as randomUUID3 } from "crypto";
16730
+ import { createHash as createHash14, randomUUID as randomUUID3 } from "crypto";
16433
16731
  import { constants as constants8, closeSync as closeSync9, fchmodSync, fstatSync as fstatSync9, fsyncSync as fsyncSync3, linkSync as linkSync2, lstatSync as lstatSync17, mkdirSync as mkdirSync8, openSync as openSync9, opendirSync as opendirSync4, readSync as readSync7, renameSync as renameSync4, rmdirSync as rmdirSync3, rmSync as rmSync3, unlinkSync as unlinkSync4, writeSync as writeSync2 } from "fs";
16434
16732
  import { dirname as dirname13, isAbsolute as isAbsolute12, join as join23, parse as parse5, resolve as resolve11 } from "path";
16435
16733
  var READ_FLAGS = constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK;
@@ -16445,7 +16743,7 @@ function stat(path) {
16445
16743
  return pluginRefusal("Cannot inspect plugin filesystem entry");
16446
16744
  }
16447
16745
  }
16448
- function canonical4(path) {
16746
+ function canonical5(path) {
16449
16747
  pluginNeed(typeof path === "string" && path.length <= 8192 && isAbsolute12(path) && resolve11(path) === path && !/[\x00-\x1f\x7f]/.test(path), "Plugin storage paths must be canonical absolute paths");
16450
16748
  }
16451
16749
  function owner(stats, system = false) {
@@ -16456,7 +16754,7 @@ function same3(left, right, identityOnly = false) {
16456
16754
  pluginNeed(right && left.dev === right.dev && left.ino === right.ino && left.mode === right.mode && left.uid === right.uid && left.gid === right.gid && (identityOnly || left.size === right.size && left.nlink === right.nlink && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs), "Plugin filesystem entry changed during access");
16457
16755
  }
16458
16756
  function ancestors(path, create = false) {
16459
- canonical4(path);
16757
+ canonical5(path);
16460
16758
  const result = [], parent = dirname13(path), filesystemRoot = parse5(parent).root;
16461
16759
  let current = filesystemRoot;
16462
16760
  for (const segment of ["", ...parent.slice(filesystemRoot.length).split("/").filter(Boolean)]) {
@@ -16566,7 +16864,7 @@ function directoryNames(path) {
16566
16864
  function snapshotPluginTree(root, options = {}) {
16567
16865
  const chain = ancestors(root), entries = [], files = [], directories = [];
16568
16866
  let bytes = 0, visited = 0;
16569
- const visit2 = (directory, relative5) => {
16867
+ const visit2 = (directory, relative6) => {
16570
16868
  const before = stat(directory);
16571
16869
  pluginNeed(before && before.isDirectory() && !before.isSymbolicLink(), "Plugin tree must contain real directories");
16572
16870
  const names = directoryNames(directory);
@@ -16574,18 +16872,18 @@ function snapshotPluginTree(root, options = {}) {
16574
16872
  directories.push({ path: directory, stat: before, names });
16575
16873
  for (const name of names) {
16576
16874
  pluginNeed(++visited <= PLUGIN_PROJECTION_LIMITS.files * 50, "Plugin tree membership exceeds its limit");
16577
- const path = relative5 ? `${relative5}/${name}` : name;
16875
+ const path = relative6 ? `${relative6}/${name}` : name;
16578
16876
  pluginNeed(new TextEncoder().encode(path).byteLength <= 100 && !/[\\:\x00-\x1f\x7f]/u.test(path), "Plugin entry path exceeds its limit or is unsafe");
16579
16877
  const absolute = join23(directory, name), entryStat = stat(absolute);
16580
16878
  pluginNeed(entryStat, "Plugin tree member disappeared");
16581
- if (relative5 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16879
+ if (relative6 === "" && name === ".orphaned_at" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16582
16880
  const bytes2 = readBytes(absolute, 32, "native-pin");
16583
16881
  pluginNeed(/^[1-9][0-9]{12}$/.test(new TextDecoder().decode(bytes2)), "Malformed native orphan timestamp");
16584
16882
  same3(entryStat, stat(absolute));
16585
16883
  files.push({ path: absolute, stat: entryStat });
16586
16884
  continue;
16587
16885
  }
16588
- if (relative5 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16886
+ if (relative6 === "" && name === ".in_use" && (options.nativeRuntime === "claude-2.1.274" || options.nativeRuntime === "claude-2.1.276")) {
16589
16887
  pluginNeed(entryStat.isDirectory() && !entryStat.isSymbolicLink(), "Native plugin pins require a real directory");
16590
16888
  owner(entryStat);
16591
16889
  const pins = directoryNames(absolute);
@@ -16771,7 +17069,7 @@ function pluginExecutableDigest(path) {
16771
17069
  const opened = fstatSync9(fd, { bigint: true });
16772
17070
  same3(before, opened);
16773
17071
  fileShape(opened, maximum, "executable");
16774
- const hash2 = createHash13("sha256"), buffer = new Uint8Array(64 * 1024);
17072
+ const hash2 = createHash14("sha256"), buffer = new Uint8Array(64 * 1024);
16775
17073
  let remaining = Number(opened.size);
16776
17074
  while (remaining > 0) {
16777
17075
  const count = readSync7(fd, buffer, 0, Math.min(buffer.byteLength, remaining), null);
@@ -17268,7 +17566,7 @@ function hashManagedPluginRegistry(path, managed) {
17268
17566
  pluginNeed(/^(?:[A-Za-z0-9][A-Za-z0-9._+-]*-)?[a-f0-9]{12}$/.test(row.version) && row.installPath === join26(cacheRoot, row.version), "Managed plugin cache path or version is outside its registration");
17269
17567
  const approval = (producer) => {
17270
17568
  pluginText(producer);
17271
- const digest = `sha256:${basename6(producer)}`;
17569
+ const digest = `sha256:${basename7(producer)}`;
17272
17570
  const receipt2 = readPluginAdmissionReceipt(witness.storeRoot, witness.bindingId, digest);
17273
17571
  pluginNeed(receipt2.materializedPath === producer, "Managed producer path is not an approved immutable projection");
17274
17572
  if (!verified.has(producer)) {
@@ -17330,23 +17628,23 @@ function captureManagedPluginRegistry(path, managedPlugins) {
17330
17628
  }
17331
17629
 
17332
17630
  // src/lib/claude-marketplace-registry.ts
17333
- import { createHash as createHash14 } from "crypto";
17631
+ import { createHash as createHash15 } from "crypto";
17334
17632
  import { closeSync as closeSync11, constants as constants10, fstatSync as fstatSync11, lstatSync as lstatSync19, openSync as openSync11, readSync as readSync9 } from "fs";
17335
- import { basename as basename7, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve14 } from "path";
17633
+ import { basename as basename8, dirname as dirname15, isAbsolute as isAbsolute15, resolve as resolve14 } from "path";
17336
17634
  var CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
17337
- function need4(value, reason) {
17635
+ function need5(value, reason) {
17338
17636
  if (!value)
17339
17637
  throw new Error(`Claude marketplace registry ${reason}`);
17340
17638
  }
17341
17639
  function absolutePath3(value) {
17342
- need4(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve14(value) === value, "requires a normalized absolute path");
17640
+ need5(typeof value === "string" && value.length > 0 && value.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.pathCharacters && !/[\x00-\x1f\x7f]/.test(value) && isAbsolute15(value) && resolve14(value) === value, "requires a normalized absolute path");
17343
17641
  }
17344
17642
  var 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;
17345
17643
  function parents2(path) {
17346
17644
  const result = [];
17347
17645
  for (let at = dirname15(path);; at = dirname15(at)) {
17348
17646
  const stat2 = lstatSync19(at, { bigint: true });
17349
- need4(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
17647
+ need5(stat2.isDirectory() && !stat2.isSymbolicLink(), "path traverses a symlink or non-directory");
17350
17648
  result.push([at, stat2]);
17351
17649
  if (at === dirname15(at))
17352
17650
  return result;
@@ -17354,15 +17652,15 @@ function parents2(path) {
17354
17652
  }
17355
17653
  function readRegistry2(path, budget) {
17356
17654
  absolutePath3(path);
17357
- need4(basename7(path) === "known_marketplaces.json", "requires known_marketplaces.json");
17358
- need4(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
17655
+ need5(basename8(path) === "known_marketplaces.json", "requires known_marketplaces.json");
17656
+ need5(Number.isSafeInteger(budget.remaining) && budget.remaining >= 0, "has an invalid byte budget");
17359
17657
  const ancestors2 = parents2(path), initial = lstatSync19(path, { bigint: true });
17360
- need4(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
17361
- need4(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
17658
+ need5(initial.isFile() && !initial.isSymbolicLink() && initial.size <= BigInt(CLAUDE_MARKETPLACE_REGISTRY_LIMITS.bytes), "is not a bounded regular file");
17659
+ need5(initial.size <= BigInt(budget.remaining), "exceeds its aggregate byte limit");
17362
17660
  const fd = openSync11(path, constants10.O_RDONLY | constants10.O_NOFOLLOW | constants10.O_NONBLOCK);
17363
17661
  try {
17364
17662
  const opened = fstatSync11(fd, { bigint: true });
17365
- need4(opened.isFile() && sameFile2(initial, opened), "changed during open");
17663
+ need5(opened.isFile() && sameFile2(initial, opened), "changed during open");
17366
17664
  const bytes = Buffer.alloc(Number(opened.size) + 1);
17367
17665
  let length = 0;
17368
17666
  while (length < bytes.length) {
@@ -17372,10 +17670,10 @@ function readRegistry2(path, budget) {
17372
17670
  length += count;
17373
17671
  }
17374
17672
  const after = fstatSync11(fd, { bigint: true }), current = lstatSync19(path, { bigint: true });
17375
- need4(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
17673
+ need5(current.isFile() && sameFile2(opened, after) && sameFile2(after, current) && BigInt(length) === opened.size, "changed during read");
17376
17674
  for (const [ancestor, before] of ancestors2) {
17377
17675
  const now = lstatSync19(ancestor, { bigint: true });
17378
- need4(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
17676
+ need5(now.isDirectory() && before.dev === now.dev && before.ino === now.ino && before.mode === now.mode, "parent changed during read");
17379
17677
  }
17380
17678
  budget.remaining -= length;
17381
17679
  try {
@@ -17395,7 +17693,7 @@ function parse6(text2) {
17395
17693
  at++;
17396
17694
  };
17397
17695
  function string() {
17398
- need4(text2[at] === '"', "contains invalid JSON");
17696
+ need5(text2[at] === '"', "contains invalid JSON");
17399
17697
  const start = at++;
17400
17698
  while (at < text2.length) {
17401
17699
  const character = text2[at++];
@@ -17406,7 +17704,7 @@ function parse6(text2) {
17406
17704
  } catch {
17407
17705
  throw new Error("Claude marketplace registry contains invalid JSON string");
17408
17706
  }
17409
- need4(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
17707
+ need5(value2.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.stringCharacters, "exceeds its string limit");
17410
17708
  return value2;
17411
17709
  }
17412
17710
  if (character === "\\")
@@ -17415,7 +17713,7 @@ function parse6(text2) {
17415
17713
  throw new Error("Claude marketplace registry contains an unterminated JSON string");
17416
17714
  }
17417
17715
  function value(depth) {
17418
- need4(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
17716
+ need5(depth <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.depth && ++nodes <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.nodes, "exceeds its nesting or node limit");
17419
17717
  space();
17420
17718
  const character = text2[at];
17421
17719
  if (character === '"')
@@ -17429,17 +17727,17 @@ function parse6(text2) {
17429
17727
  if (object5) {
17430
17728
  space();
17431
17729
  const key = string();
17432
- need4(!keys.has(key), "contains a duplicate JSON key");
17730
+ need5(!keys.has(key), "contains a duplicate JSON key");
17433
17731
  keys.add(key);
17434
17732
  space();
17435
- need4(text2[at++] === ":", "contains invalid JSON");
17733
+ need5(text2[at++] === ":", "contains invalid JSON");
17436
17734
  entries.push([key, value(depth + 1)]);
17437
17735
  } else
17438
17736
  items.push(value(depth + 1));
17439
17737
  space();
17440
17738
  if (text2[at] === end)
17441
17739
  break;
17442
- need4(text2[at++] === ",", "contains invalid JSON");
17740
+ need5(text2[at++] === ",", "contains invalid JSON");
17443
17741
  }
17444
17742
  at++;
17445
17743
  return object5 ? { kind: "object", entries } : { kind: "array", items };
@@ -17451,14 +17749,14 @@ function parse6(text2) {
17451
17749
  }
17452
17750
  numberPattern.lastIndex = at;
17453
17751
  const number = numberPattern.exec(text2);
17454
- need4(number, "contains invalid JSON");
17752
+ need5(number, "contains invalid JSON");
17455
17753
  at += number[0].length;
17456
17754
  return { kind: "number", value: number[0] };
17457
17755
  }
17458
17756
  const root2 = value(0);
17459
17757
  space();
17460
- need4(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
17461
- need4(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
17758
+ need5(at === text2.length && root2.kind === "object", "requires a JSON object without trailing content");
17759
+ need5(root2.entries.length <= CLAUDE_MARKETPLACE_REGISTRY_LIMITS.rows, "exceeds its marketplace row limit");
17462
17760
  return root2;
17463
17761
  }
17464
17762
  function exactKeys(value, keys) {
@@ -17466,7 +17764,7 @@ function exactKeys(value, keys) {
17466
17764
  }
17467
17765
  var field = (object5, key) => object5.entries.find(([name]) => name === key)?.[1];
17468
17766
  function textField(value) {
17469
- need4(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
17767
+ need5(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
17470
17768
  return value.value;
17471
17769
  }
17472
17770
  function normalizeRow(row, version = 1) {
@@ -17474,7 +17772,7 @@ function normalizeRow(row, version = 1) {
17474
17772
  if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
17475
17773
  return;
17476
17774
  const autoUpdate = field(row, "autoUpdate");
17477
- need4(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
17775
+ need5(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
17478
17776
  }
17479
17777
  const source = field(row, "source");
17480
17778
  if (source?.kind !== "object")
@@ -17490,34 +17788,34 @@ function normalizeRow(row, version = 1) {
17490
17788
  absolutePath3(sourceLocation);
17491
17789
  absolutePath3(textField(field(row, "installLocation")));
17492
17790
  const updated = textField(field(row, "lastUpdated"));
17493
- need4(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(updated), "contains an invalid recognized update timestamp");
17791
+ 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");
17494
17792
  const millis = Date.parse(updated);
17495
- need4(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
17793
+ need5(Number.isFinite(millis) && new Date(millis).toISOString() === updated, "contains an invalid recognized update timestamp");
17496
17794
  row.entries = row.entries.map(([key, value]) => [key, key === "lastUpdated" ? { kind: "validated-update-time" } : value]);
17497
17795
  }
17498
17796
  function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024 * 1024 }) {
17499
17797
  const value = parse6(readRegistry2(path, budget));
17500
17798
  for (const [, row] of value.entries)
17501
17799
  normalizeRow(row);
17502
- const sha2562 = createHash14("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
17800
+ const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
17503
17801
  return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
17504
17802
  }
17505
17803
  function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
17506
17804
  const value = parse6(readRegistry2(path, budget));
17507
17805
  for (const [, row] of value.entries)
17508
17806
  normalizeRow(row, 2);
17509
- const sha2562 = createHash14("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
17807
+ const sha2562 = createHash15("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
17510
17808
  return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
17511
17809
  }
17512
17810
 
17513
17811
  // src/lib/codex-hook-discovery-recovery.ts
17514
- import { createHash as createHash16 } from "crypto";
17812
+ import { createHash as createHash17 } from "crypto";
17515
17813
 
17516
17814
  // src/lib/codex-hook-trust-files.ts
17517
17815
  import { closeSync as closeSync12, constants as constants11, fstatSync as fstatSync12, fsyncSync as fsyncSync4, lstatSync as lstatSync20, openSync as openSync12, readSync as readSync10, writeFileSync as writeFileSync7 } from "fs";
17518
- import { createHash as createHash15 } from "crypto";
17816
+ import { createHash as createHash16 } from "crypto";
17519
17817
  import { dirname as dirname16 } from "path";
17520
- function need5(value, code) {
17818
+ function need6(value, code) {
17521
17819
  if (!value)
17522
17820
  throw new Error(`CODEX_HOOK_TRUST_${code}`);
17523
17821
  }
@@ -17525,8 +17823,8 @@ var sameStat = (a, b) => ["dev", "ino", "uid", "nlink", "mode", "size", "mtimeNs
17525
17823
  function safeParents(file) {
17526
17824
  for (let p = dirname16(file);; p = dirname16(p)) {
17527
17825
  const s = lstatSync20(p);
17528
- need5(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
17529
- need5((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
17826
+ need6(s.isDirectory() && !s.isSymbolicLink(), "UNSAFE_PARENT");
17827
+ need6((s.mode & 18) === 0 || s.uid === 0 && (s.mode & 512) !== 0, "UNSAFE_PARENT");
17530
17828
  if (dirname16(p) === p)
17531
17829
  break;
17532
17830
  }
@@ -17535,12 +17833,12 @@ function snapshot(file, privateFile = false, options = {}) {
17535
17833
  const readOnlyPackage = options.readOnlyPackage === true && !privateFile;
17536
17834
  safeParents(file);
17537
17835
  const initial = lstatSync20(file, { bigint: true });
17538
- need5(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
17539
- need5(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
17836
+ need6(initial.isFile() && initial.uid === BigInt(process.getuid()) && (initial.nlink === 1n || readOnlyPackage && initial.nlink > 1n) && (initial.mode & 0o022n) === 0n && (!privateFile || (initial.mode & 0o777n) === 0o600n), "UNSAFE_FILE");
17837
+ need6(initial.size <= 8n * 1024n * 1024n, "FILE_BOUND");
17540
17838
  const fd = openSync12(file, constants11.O_RDONLY | constants11.O_NOFOLLOW | constants11.O_NONBLOCK);
17541
17839
  try {
17542
17840
  const before = fstatSync12(fd, { bigint: true });
17543
- need5(sameStat(initial, before), "INPUT_CHANGED");
17841
+ need6(sameStat(initial, before), "INPUT_CHANGED");
17544
17842
  const bytes = Buffer.alloc(Number(before.size) + 1);
17545
17843
  let length = 0;
17546
17844
  while (length < bytes.length) {
@@ -17549,16 +17847,16 @@ function snapshot(file, privateFile = false, options = {}) {
17549
17847
  break;
17550
17848
  length += n2;
17551
17849
  }
17552
- need5(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync20(file, { bigint: true })), "INPUT_CHANGED");
17850
+ need6(length === Number(before.size) && sameStat(before, fstatSync12(fd, { bigint: true })) && sameStat(before, lstatSync20(file, { bigint: true })), "INPUT_CHANGED");
17553
17851
  const content = bytes.subarray(0, length), text2 = new TextDecoder("utf-8", { fatal: true }).decode(content);
17554
- return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash15("sha256").update(content).digest("hex") };
17852
+ return { file, privateFile, readOnlyPackage, text: text2, bytes: content, stat: before, sha256: createHash16("sha256").update(content).digest("hex") };
17555
17853
  } finally {
17556
17854
  closeSync12(fd);
17557
17855
  }
17558
17856
  }
17559
17857
  function unchanged2(before) {
17560
17858
  const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
17561
- need5(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
17859
+ need6(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
17562
17860
  }
17563
17861
 
17564
17862
  // src/lib/codex-hook-additions-review.ts
@@ -17567,11 +17865,12 @@ var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
17567
17865
  var proofs = new WeakMap;
17568
17866
 
17569
17867
  // src/lib/codex-hook-discovery-recovery.ts
17570
- var sha3 = (text2) => createHash16("sha256").update(text2).digest("hex");
17868
+ var sha4 = (text2) => createHash17("sha256").update(text2).digest("hex");
17869
+ var fields = [...CODEX_DISCOVERY_PROJECTION_FIELDS];
17571
17870
  var proofs2 = new WeakMap;
17572
17871
  function assertCodexHookDiscoveryRecovery(binding, witness) {
17573
17872
  const proof = proofs2.get(witness);
17574
- need5(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha3(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
17873
+ need6(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha4(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
17575
17874
  unchanged2(proof.before);
17576
17875
  unchanged2(proof.current);
17577
17876
  proof.recheckAdditions();
@@ -17580,12 +17879,12 @@ function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witnes
17580
17879
  if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
17581
17880
  return false;
17582
17881
  assertCodexHookDiscoveryRecovery(binding, witness);
17583
- need5(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
17882
+ need6(source.sha256 === witness.beforeSha256 && currentDigest === witness.currentSha256, "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
17584
17883
  return true;
17585
17884
  }
17586
17885
 
17587
17886
  // src/lib/agent-discovery.ts
17588
- var digest = (text2) => createHash17("sha256").update(text2).digest("hex");
17887
+ var digest = (text2) => createHash18("sha256").update(text2).digest("hex");
17589
17888
  function parseConfig(text2, path, toml = false) {
17590
17889
  try {
17591
17890
  const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
@@ -17617,14 +17916,26 @@ function read2(path, changes) {
17617
17916
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
17618
17917
  return readFileSync17(path, "utf8");
17619
17918
  }
17919
+ function projectNativeDiscoveryFields(text2, format, fields2, path) {
17920
+ const object5 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
17921
+ if (!object5 || typeof object5 !== "object" || Array.isArray(object5))
17922
+ throw new Error("Expected native discovery configuration object");
17923
+ return digest(JSON.stringify(Object.fromEntries(fields2.map((field2) => [field2, object5[field2] ?? null]))));
17924
+ }
17925
+ function isSupersededCodexProjection(agent, configPath, source) {
17926
+ return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual4(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
17927
+ }
17928
+ function supersedesCodexProjection(sources, configPath) {
17929
+ return sources.some((source) => source.path === configPath && source.hashMode === "codex-settings-v2");
17930
+ }
17620
17931
  function projected(source, changes, budget = discoveryByteBudget()) {
17621
- if (source.hashMode === "codex-settings-v1") {
17932
+ if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2") {
17622
17933
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
17623
17934
  throw new Error("Codex settings witnesses require exact typed metadata");
17624
- const current = captureCodexSettings(source.path, budget).sha256;
17935
+ const current = (source.hashMode === "codex-settings-v2" ? captureCodexSettingsV2 : captureCodexSettings)(source.path, budget).sha256;
17625
17936
  if (current !== source.sha256)
17626
17937
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
17627
- return changes?.has(source.path) ? hashCodexSettingsReplacement(changes.get(source.path), budget) : current;
17938
+ return changes?.has(source.path) ? (source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget) : current;
17628
17939
  }
17629
17940
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
17630
17941
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
@@ -17663,10 +17974,7 @@ function projected(source, changes, budget = discoveryByteBudget()) {
17663
17974
  return null;
17664
17975
  if (!source.format)
17665
17976
  return digest(text2);
17666
- const object5 = source.format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, source.path, source.format === "toml");
17667
- if (!object5 || typeof object5 !== "object" || Array.isArray(object5))
17668
- throw new Error("Expected native discovery configuration object");
17669
- return digest(JSON.stringify(Object.fromEntries((source.fields ?? []).map((field2) => [field2, object5[field2] ?? null]))));
17977
+ return projectNativeDiscoveryFields(text2, source.format, source.fields ?? [], source.path);
17670
17978
  }
17671
17979
  function verifyAgentDiscovery(binding, codexRecovery) {
17672
17980
  if (codexRecovery)
@@ -17694,9 +18002,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
17694
18002
  safe4(root2);
17695
18003
  }
17696
18004
  function assertMarketplaceBinding(binding, source) {
17697
- if (source.hashMode === "codex-settings-v1" && (binding.agent !== "codex" || binding.method !== "reviewed" || basename8(source.path) !== "config.toml"))
18005
+ if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename9(source.path) !== "config.toml"))
17698
18006
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
17699
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
18007
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename9(source.path) !== "settings.json"))
17700
18008
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
17701
18009
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
17702
18010
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -17723,8 +18031,8 @@ function captureDiscoveryPathSources(paths) {
17723
18031
  const budget = discoveryByteBudget();
17724
18032
  return paths.map((path) => ({ path, hashMode: "path-bytes", sha256: hashDiscoveryPathFile(path, budget) }));
17725
18033
  }
17726
- function assertProjectDiscovery(agent, directories, home, canonical5 = resolve15, reviewed) {
17727
- const homeConfigPath = canonical5(agentDiscoveryConfigPath(home, agent));
18034
+ function assertProjectDiscovery(agent, directories, home, canonical6 = resolve15, reviewed) {
18035
+ const homeConfigPath = canonical6(agentDiscoveryConfigPath(home, agent));
17728
18036
  for (const directory of directories) {
17729
18037
  const isHome = resolve15(directory) === resolve15(home);
17730
18038
  if (agent === "hermes") {
@@ -17735,7 +18043,7 @@ function assertProjectDiscovery(agent, directories, home, canonical5 = resolve15
17735
18043
  continue;
17736
18044
  const paths = agent === "claude" ? [...isHome ? [] : [".claude/settings.json"], ".claude/settings.local.json"] : agent === "codex" ? [".codex/config.toml"] : agent === "gemini" ? [".gemini/settings.json"] : agent === "opencode" ? ["opencode.json", "opencode.jsonc", ".opencode/opencode.json", ".opencode/opencode.jsonc"] : [".cursor/hooks.json"];
17737
18045
  for (const suffix of paths) {
17738
- const path = canonical5(join27(directory, suffix)), raw = read2(path);
18046
+ const path = canonical6(join27(directory, suffix)), raw = read2(path);
17739
18047
  if (raw === null)
17740
18048
  continue;
17741
18049
  const isRootAliasedHomeConfig = agent === "claude" && suffix === ".claude/settings.json" || agent === "codex" && suffix === ".codex/config.toml";
@@ -17751,14 +18059,14 @@ function assertProjectDiscovery(agent, directories, home, canonical5 = resolve15
17751
18059
  const managed = reviewed.sources.flatMap((source) => source.hashMode === "claude-plugin-registry" ? source.managedPlugins ?? [] : []);
17752
18060
  if (exact && projected(exact) === exact.sha256 && managed.length) {
17753
18061
  const bindings = managed.map((item) => readPluginBinding(item.storeRoot, item.bindingId));
17754
- admittedPlugins = Object.entries(config.enabledPlugins).every(([id, enabled]) => typeof enabled === "boolean" && bindings.some((binding) => binding.target.pluginId === id && binding.target.registrations.some((scope) => scope.scope === "project" && scope.projectPath === canonical5(directory))));
18062
+ admittedPlugins = Object.entries(config.enabledPlugins).every(([id, enabled]) => typeof enabled === "boolean" && bindings.some((binding) => binding.target.pluginId === id && binding.target.registrations.some((scope) => scope.scope === "project" && scope.projectPath === canonical6(directory))));
17755
18063
  }
17756
18064
  }
17757
18065
  if (keys.some((key) => config[key] !== undefined && !(key === "enabledPlugins" && admittedPlugins)) || config.disableAllHooks === true || config.disableBundledSkills === false || config.hooksConfig?.enabled === false || config.permission?.skill !== undefined || config.permissions?.deny?.some((rule) => typeof rule === "string" && /^Skill(?:\(|$)/.test(rule)))
17758
18066
  throw new Error(`NATIVE_SKILL_DRIFT: higher-precedence project skill or hook configuration requires review: ${path}`);
17759
18067
  }
17760
18068
  if (agent === "claude") {
17761
- const commands = canonical5(join27(directory, ".claude/commands"));
18069
+ const commands = canonical6(join27(directory, ".claude/commands"));
17762
18070
  safe4(commands);
17763
18071
  if (lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
17764
18072
  throw new Error("NATIVE_SKILL_DRIFT: legacy project command discovery requires a dedicated format adapter");
@@ -17769,10 +18077,10 @@ function agentDiscoveryConfigPath(home, agent) {
17769
18077
  return join27(home, agent === "hermes" ? ".hermes/config.yaml" : agent === "opencode" ? ".config/opencode/opencode.json" : `.${agent}/${agent === "codex" ? "config.toml" : agent === "cursor" ? "hooks.json" : "settings.json"}`);
17770
18078
  }
17771
18079
  function resolveAgentDiscovery(options) {
17772
- const canonical5 = options.canonical ?? resolve15, home = resolve15(options.home), agent = options.agent;
18080
+ const canonical6 = options.canonical ?? resolve15, home = resolve15(options.home), agent = options.agent;
17773
18081
  const sources = [], roots = new Set, builtinNames = [];
17774
- const witness = (path, format, fields) => {
17775
- const source = { path: canonical5(path), sha256: null, ...format ? { format, fields } : {} };
18082
+ const witness = (path, format, fields2) => {
18083
+ const source = { path: canonical6(path), sha256: null, ...format ? { format, fields: fields2 } : {} };
17776
18084
  source.sha256 = projected(source);
17777
18085
  sources.push(source);
17778
18086
  return read2(source.path);
@@ -17780,7 +18088,7 @@ function resolveAgentDiscovery(options) {
17780
18088
  if (agent === "hermes")
17781
18089
  assertHermesEnvironment(home);
17782
18090
  const configPath = agentDiscoveryConfigPath(home, agent);
17783
- const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? ["plugins", "marketplaces", "skills"] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : ["version"]);
18091
+ const configText = witness(configPath, agent === "hermes" ? "yaml" : agent === "codex" ? "toml" : "json", agent === "hermes" ? ["skills", "plugins", "hooks"] : agent === "claude" ? ["enabledPlugins", "extraKnownMarketplaces"] : agent === "codex" ? [...CODEX_DISCOVERY_PROJECTION_FIELDS] : agent === "gemini" ? ["skills", "extensions", "security"] : agent === "opencode" ? ["plugin", "skills"] : ["version"]);
17784
18092
  const config = configText === null ? {} : agent === "hermes" ? parseHermesConfig(configText) : parseConfig(configText, configPath, agent === "codex");
17785
18093
  const unresolved = (detail) => {
17786
18094
  throw new Error(`Native discovery is unresolved (${agent}: ${detail}); provide a reviewed --discovery-inputs file`);
@@ -17839,11 +18147,12 @@ function resolveAgentDiscovery(options) {
17839
18147
  if (retained.agent !== agent || retained.method !== "reviewed")
17840
18148
  unresolved("invalid retained review");
17841
18149
  verifyAgentDiscovery(retained);
17842
- if (!retained.sources.some((source) => source.path === canonical5(configPath) && source.format === undefined && source.fields === undefined))
18150
+ if (!retained.sources.some((source) => source.path === canonical6(configPath) && source.format === undefined && source.fields === undefined))
17843
18151
  unresolved("retained review is missing the full agent configuration source");
17844
- if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "codex-settings-v1")) && source.path !== canonical5(configPath)))
18152
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2"))) && source.path !== canonical6(configPath)))
17845
18153
  unresolved("retained settings witness names another configuration source");
17846
- if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual3(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
18154
+ const covered = supersedesCodexProjection(retained.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
18155
+ if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual4(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
17847
18156
  unresolved("retained review is missing current runtime discovery coverage");
17848
18157
  return retained;
17849
18158
  }
@@ -17857,14 +18166,15 @@ function resolveAgentDiscovery(options) {
17857
18166
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
17858
18167
  throw new Error("Explicit discovery reviews require full source-file hashes");
17859
18168
  verifyAgentDiscovery(supplied);
17860
- if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "codex-settings-v1")) && source.path !== canonical5(configPath)))
18169
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2"))) && source.path !== canonical6(configPath)))
17861
18170
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
17862
- if (!review.sources.some((source) => source.path === canonical5(configPath)))
18171
+ if (!review.sources.some((source) => source.path === canonical6(configPath)))
17863
18172
  throw new Error("Discovery review must include the agent configuration source");
17864
- return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: [...sources, ...review.sources], ...agent === "gemini" ? { builtinNames } : {} };
18173
+ const reviewedSources = supersedesCodexProjection(review.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
18174
+ return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: [...reviewedSources, ...review.sources], ...agent === "gemini" ? { builtinNames } : {} };
17865
18175
  }
17866
18176
  function plugin(root2) {
17867
- root2 = canonical5(root2);
18177
+ root2 = canonical6(root2);
17868
18178
  safe4(root2);
17869
18179
  const manifestPath = join27(root2, agent === "claude" ? ".claude-plugin/plugin.json" : ".codex-plugin/plugin.json");
17870
18180
  const raw = witness(manifestPath);
@@ -17896,7 +18206,7 @@ function resolveAgentDiscovery(options) {
17896
18206
  if (agent === "claude") {
17897
18207
  const registrations = witness(join27(home, ".claude/plugins/installed_plugins.json"));
17898
18208
  const installed = registrations === null ? {} : parseConfig(registrations, join27(home, ".claude/plugins/installed_plugins.json")).plugins;
17899
- const commands = canonical5(join27(home, ".claude/commands"));
18209
+ const commands = canonical6(join27(home, ".claude/commands"));
17900
18210
  safe4(commands);
17901
18211
  if (lstatSync21(commands, { throwIfNoEntry: false })?.isDirectory() && readdirSync10(commands).length)
17902
18212
  unresolved("legacy home command discovery requires a dedicated format adapter");
@@ -17970,7 +18280,7 @@ function resolveAgentDiscovery(options) {
17970
18280
  roots.add(join27(root2, "skills"));
17971
18281
  }
17972
18282
  }
17973
- return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join27(home, ".hermes/plugins"), join27(home, ".hermes/hermes-agent")].map((path) => canonical5(path))) } : {} };
18283
+ return { agent, roots: [...roots].sort(), sources, method: "automatic", ...agent === "gemini" ? { builtinNames } : {}, ...agent === "hermes" ? { directories: captureDiscoveryDirectories([join27(home, ".hermes/plugins"), join27(home, ".hermes/hermes-agent")].map((path) => canonical6(path))) } : {} };
17974
18284
  }
17975
18285
 
17976
18286
  // src/lib/authoring-path.ts
@@ -17993,7 +18303,7 @@ function physicalPath(path) {
17993
18303
  let cursor = resolve16(path);
17994
18304
  const suffix = [];
17995
18305
  while (!lstatSync22(cursor, { throwIfNoEntry: false })) {
17996
- suffix.unshift(basename9(cursor));
18306
+ suffix.unshift(basename10(cursor));
17997
18307
  const parent = dirname18(cursor);
17998
18308
  if (parent === cursor)
17999
18309
  return resolve16(path);
@@ -18162,7 +18472,7 @@ function scaffoldPortableSkill(name, options = {}) {
18162
18472
  return { name: skillName, path: skillPath, manifest, created: true };
18163
18473
  }
18164
18474
  function portPortableSkillDirectory(sourceDir, options = {}) {
18165
- const absoluteSource = normalize3(sourceDir);
18475
+ const absoluteSource = normalize4(sourceDir);
18166
18476
  if (!existsSync13(absoluteSource) || !statSync10(absoluteSource).isDirectory()) {
18167
18477
  throw new Error(`Import directory not found: ${sourceDir}`);
18168
18478
  }
@@ -18212,7 +18522,7 @@ function physicalImportPath(path) {
18212
18522
  let ancestor = resolve17(path);
18213
18523
  const suffix = [];
18214
18524
  while (!existsSync13(ancestor)) {
18215
- suffix.unshift(basename10(ancestor));
18525
+ suffix.unshift(basename11(ancestor));
18216
18526
  ancestor = dirname19(ancestor);
18217
18527
  }
18218
18528
  return join29(realpathSync9(ancestor), ...suffix);
@@ -18221,7 +18531,7 @@ function assertDisjointImportPaths(source, destination) {
18221
18531
  const sourcePath = realpathSync9(source);
18222
18532
  const destinationPath = physicalImportPath(destination);
18223
18533
  const contains2 = (parent, child) => {
18224
- const path = relative5(parent, child);
18534
+ const path = relative6(parent, child);
18225
18535
  return path === "" || !isAbsolute17(path) && path !== ".." && !path.startsWith(`..${sep8}`);
18226
18536
  };
18227
18537
  if (contains2(sourcePath, destinationPath) || contains2(destinationPath, sourcePath)) {
@@ -18229,7 +18539,7 @@ function assertDisjointImportPaths(source, destination) {
18229
18539
  }
18230
18540
  }
18231
18541
  function portPortableSkill(sourcePath, options = {}) {
18232
- const absoluteSource = normalize3(sourcePath);
18542
+ const absoluteSource = normalize4(sourcePath);
18233
18543
  if (!existsSync13(absoluteSource) || !statSync10(absoluteSource).isDirectory()) {
18234
18544
  throw new Error(`Skill source directory not found: ${sourcePath}`);
18235
18545
  }
@@ -18237,8 +18547,8 @@ function portPortableSkill(sourcePath, options = {}) {
18237
18547
  const explicitName = options.name != null;
18238
18548
  const skillName = normalizeNewPortableSkillName(options.name ?? inferred.name);
18239
18549
  if (isOfficialSkillName(skillName) && !options.allowShadow) {
18240
- const sourceSlug = safeNormalizeName(basename10(absoluteSource));
18241
- const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename10(absoluteSource)}')` : ""} matches a bundled official skill.`;
18550
+ const sourceSlug = safeNormalizeName(basename11(absoluteSource));
18551
+ const via = explicitName ? `Name '${skillName}' matches a bundled official skill.` : `Inferred name '${skillName}'${sourceSlug && sourceSlug !== skillName ? ` (from source folder '${basename11(absoluteSource)}')` : ""} matches a bundled official skill.`;
18242
18552
  throw new Error(`${via} Importing it would shadow the official '${skillName}'. ` + `Pass --name to choose a different name, or --allow-shadow to override deliberately.`);
18243
18553
  }
18244
18554
  const readOnlyDestination = join29(resolvePortableSkillsRoot(options, false), skillName);
@@ -18505,7 +18815,7 @@ function safeIsDirectory(path) {
18505
18815
  function isSafeRelativePath2(value) {
18506
18816
  if (!value.trim() || isAbsolute17(value))
18507
18817
  return false;
18508
- const normalized = normalize3(value).replace(/\\/g, "/");
18818
+ const normalized = normalize4(value).replace(/\\/g, "/");
18509
18819
  return normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../");
18510
18820
  }
18511
18821
  function add3(target, code, message) {
@@ -18819,9 +19129,9 @@ function getAllTags() {
18819
19129
  return Array.from(tagSet).sort();
18820
19130
  }
18821
19131
  // src/lib/run-state.ts
18822
- import { createHash as createHash18, randomBytes } from "crypto";
19132
+ import { createHash as createHash19, randomBytes } from "crypto";
18823
19133
  import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync19, readdirSync as readdirSync13, statSync as statSync11, writeFileSync as writeFileSync9 } from "fs";
18824
- import { extname, join as join31, relative as relative6 } from "path";
19134
+ import { extname, join as join31, relative as relative7 } from "path";
18825
19135
  function createSkillRun(params, targetDir = process.cwd()) {
18826
19136
  const now = new Date;
18827
19137
  const id = createRunId(now);
@@ -18946,7 +19256,7 @@ function collectRunArtifacts(context) {
18946
19256
  artifacts.push({
18947
19257
  path: toProjectRelative(context.targetDir, path),
18948
19258
  mime: mimeForPath(path),
18949
- sha256: createHash18("sha256").update(bytes).digest("hex"),
19259
+ sha256: createHash19("sha256").update(bytes).digest("hex"),
18950
19260
  sizeBytes: stat2.size
18951
19261
  });
18952
19262
  }
@@ -18977,7 +19287,7 @@ function createRunId(now) {
18977
19287
  return `run_${now.getTime().toString(36)}_${randomBytes(4).toString("hex")}`;
18978
19288
  }
18979
19289
  function toProjectRelative(targetDir, path) {
18980
- const rel = relative6(targetDir, path).split(/[\\/]/).join("/");
19290
+ const rel = relative7(targetDir, path).split(/[\\/]/).join("/");
18981
19291
  return rel.startsWith(".") ? rel : `./${rel}`;
18982
19292
  }
18983
19293
  function mimeForPath(path) {
@@ -22304,15 +22614,15 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
22304
22614
  } catch (_err) {}
22305
22615
  }
22306
22616
  const input = payload.value;
22307
- const isDate = input instanceof Date;
22308
- const isValidDate = isDate && !Number.isNaN(input.getTime());
22617
+ const isDate2 = input instanceof Date;
22618
+ const isValidDate = isDate2 && !Number.isNaN(input.getTime());
22309
22619
  if (isValidDate)
22310
22620
  return payload;
22311
22621
  payload.issues.push({
22312
22622
  expected: "date",
22313
22623
  code: "invalid_type",
22314
22624
  input,
22315
- ...isDate ? { received: "Invalid Date" } : {},
22625
+ ...isDate2 ? { received: "Invalid Date" } : {},
22316
22626
  inst
22317
22627
  });
22318
22628
  return payload;
@@ -38365,7 +38675,7 @@ function clone2(value) {
38365
38675
  return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
38366
38676
  }
38367
38677
  // src/lib/canonical-json.ts
38368
- import { createHash as createHash19 } from "crypto";
38678
+ import { createHash as createHash20 } from "crypto";
38369
38679
  function canonicalJson2(value, maximumBytes = 2 * 1024 * 1024) {
38370
38680
  return canonicalJsonAtDepth(value, maximumBytes, 64);
38371
38681
  }
@@ -38409,7 +38719,7 @@ function canonicalJsonAtDepth(value, maximumBytes, maximumDepth) {
38409
38719
  return visit3(value, 0);
38410
38720
  }
38411
38721
  function canonicalJsonSha256(value, maximumBytes) {
38412
- return createHash19("sha256").update(canonicalJson2(value, maximumBytes)).digest("hex");
38722
+ return createHash20("sha256").update(canonicalJson2(value, maximumBytes)).digest("hex");
38413
38723
  }
38414
38724
 
38415
38725
  // src/lib/remote-recurring-contract.ts
@@ -39165,7 +39475,7 @@ function getApiUrl(action, env = process.env, options = {}) {
39165
39475
  // src/lib/remote-run-contract.ts
39166
39476
  var REMOTE_SKILL_RUN_CONTRACT_VERSION = 1;
39167
39477
  function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
39168
- const record6 = isRecord3(payload) ? payload : {};
39478
+ const record6 = isRecord4(payload) ? payload : {};
39169
39479
  return {
39170
39480
  contractVersion: REMOTE_SKILL_RUN_CONTRACT_VERSION,
39171
39481
  ...pickString(record6, "id"),
@@ -39187,7 +39497,7 @@ function normalizeRemoteSkillRunContract(payload, fallbackSkill) {
39187
39497
  ...hasOwn(record6, "details") ? { details: record6.details } : {}
39188
39498
  };
39189
39499
  }
39190
- function isRecord3(value) {
39500
+ function isRecord4(value) {
39191
39501
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
39192
39502
  }
39193
39503
  function hasOwn(record6, key) {
@@ -39319,7 +39629,7 @@ function creditCheckoutFailure(value, status, key) {
39319
39629
 
39320
39630
  // src/lib/remote-profile.ts
39321
39631
  function customerNamePatch(input2, field2) {
39322
- if (!isRecord4(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field2))
39632
+ if (!isRecord5(input2) || Object.keys(input2).length !== 1 || !Object.hasOwn(input2, field2))
39323
39633
  throw new Error("Provide only the requested name field.");
39324
39634
  const value = input2[field2];
39325
39635
  if (typeof value !== "string" || /[\p{Cc}\p{Cs}\u2028\u2029]/u.test(value) || !value.trim() || [...value.trim()].length > 100) {
@@ -39327,22 +39637,22 @@ function customerNamePatch(input2, field2) {
39327
39637
  }
39328
39638
  return { [field2]: value.trim() };
39329
39639
  }
39330
- function isRecord4(value) {
39640
+ function isRecord5(value) {
39331
39641
  return !!value && typeof value === "object" && !Array.isArray(value);
39332
39642
  }
39333
39643
  function string4(value) {
39334
39644
  return typeof value === "string" && value.length > 0;
39335
39645
  }
39336
39646
  function parseUpdatedProfile(value) {
39337
- const user = isRecord4(value) && value.user;
39338
- if (!isRecord4(user) || !string4(user.id) || !string4(user.email) || !(user.displayName === null || typeof user.displayName === "string") || typeof user.role !== "string" || !["owner", "admin", "member", "viewer"].includes(user.role)) {
39647
+ const user = isRecord5(value) && value.user;
39648
+ if (!isRecord5(user) || !string4(user.id) || !string4(user.email) || !(user.displayName === null || typeof user.displayName === "string") || typeof user.role !== "string" || !["owner", "admin", "member", "viewer"].includes(user.role)) {
39339
39649
  throw new Error("The server returned an invalid account profile.");
39340
39650
  }
39341
39651
  return { user: { id: user.id, email: user.email, displayName: user.displayName, role: user.role } };
39342
39652
  }
39343
39653
  function parseUpdatedWorkspace(value) {
39344
- const organization2 = isRecord4(value) && value.organization;
39345
- if (!isRecord4(organization2) || !string4(organization2.id) || !string4(organization2.slug) || !string4(organization2.name)) {
39654
+ const organization2 = isRecord5(value) && value.organization;
39655
+ if (!isRecord5(organization2) || !string4(organization2.id) || !string4(organization2.slug) || !string4(organization2.name)) {
39346
39656
  throw new Error("The server returned an invalid workspace.");
39347
39657
  }
39348
39658
  return { organization: { id: organization2.id, slug: organization2.slug, name: organization2.name } };
@@ -40531,11 +40841,11 @@ function validateCronField(expr, min, max, label) {
40531
40841
  return { valid: true };
40532
40842
  }
40533
40843
  function validateCron(expr) {
40534
- const fields = expr.trim().split(/\s+/);
40535
- if (fields.length !== 5) {
40536
- return { valid: false, error: `Expected 5 fields, got ${fields.length}. Format: "minute hour day-of-month month day-of-week"` };
40844
+ const fields2 = expr.trim().split(/\s+/);
40845
+ if (fields2.length !== 5) {
40846
+ return { valid: false, error: `Expected 5 fields, got ${fields2.length}. Format: "minute hour day-of-month month day-of-week"` };
40537
40847
  }
40538
- const [minuteF, hourF, domF, monthF, dowF] = fields;
40848
+ const [minuteF, hourF, domF, monthF, dowF] = fields2;
40539
40849
  const checks3 = [
40540
40850
  { expr: minuteF, min: 0, max: 59, label: "minute" },
40541
40851
  { expr: hourF, min: 0, max: 23, label: "hour" },
@@ -40676,7 +40986,7 @@ function recordScheduleRun(id, status, targetDir) {
40676
40986
  saveSchedules(data, targetDir);
40677
40987
  }
40678
40988
  // src/lib/revision.ts
40679
- import { createHash as createHash20 } from "crypto";
40989
+ import { createHash as createHash21 } from "crypto";
40680
40990
  var REVISION_ID_PATTERN = /^[0-9a-f]{64}$/;
40681
40991
  function revisionIdOf(content) {
40682
40992
  const canonicalContent = {
@@ -40697,8 +41007,8 @@ function revisionIdOf(content) {
40697
41007
  canonicalContent.archiveReason = content.archiveReason ?? null;
40698
41008
  canonicalContent.replacementSlug = content.replacementSlug ?? null;
40699
41009
  }
40700
- const canonical5 = JSON.stringify(canonicalContent);
40701
- return createHash20("sha256").update(canonical5).digest("hex");
41010
+ const canonical6 = JSON.stringify(canonicalContent);
41011
+ return createHash21("sha256").update(canonical6).digest("hex");
40702
41012
  }
40703
41013
  function revisionIdOfRecord(record7) {
40704
41014
  return revisionIdOf(record7);
@@ -41239,7 +41549,7 @@ async function requestInvitationEmail(origin, action, input2) {
41239
41549
  }
41240
41550
 
41241
41551
  // src/lib/remote-private-publications.ts
41242
- import { createHash as createHash21 } from "crypto";
41552
+ import { createHash as createHash22 } from "crypto";
41243
41553
  var PRIVATE_PUBLICATION_MAX_BYTES = 16 * 1024 * 1024;
41244
41554
  var failures2 = {
41245
41555
  INVALID_REQUEST: [400, "The publication request is invalid."],
@@ -41286,7 +41596,7 @@ var hash3 = (v) => typeof v === "string" && /^[a-f0-9]{64}$/.test(v);
41286
41596
  var record8 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
41287
41597
  var exact = (v, keys) => record8(v) && Object.keys(v).sort().join(",") === keys.sort().join(",");
41288
41598
  var date5 = (v) => typeof v === "string" && /^\d{4}-\d\d-\d\dT[0-9:.]+(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(v) && Number.isFinite(Date.parse(v));
41289
- var publicationSha256 = (bytes) => createHash21("sha256").update(bytes).digest("hex");
41599
+ var publicationSha256 = (bytes) => createHash22("sha256").update(bytes).digest("hex");
41290
41600
  function checkedPublicationDeclaration(value) {
41291
41601
  if (!exact(value, ["idempotencyKey", "version", "expectedCurrentVersionId", "manifestText", "archiveSha256", "archiveByteSize"]) || !publicationUuid(value.idempotencyKey) || !(value.expectedCurrentVersionId === null || publicationUuid(value.expectedCurrentVersionId)) || typeof value.version !== "string" || !value.version || value.version.length > 128 || /[\p{Cc}\p{Cs}]/u.test(value.version) || typeof value.manifestText !== "string" || Buffer.byteLength(value.manifestText) > 16384 || !hash3(value.archiveSha256) || !Number.isSafeInteger(value.archiveByteSize) || Number(value.archiveByteSize) < 1 || Number(value.archiveByteSize) > PRIVATE_PUBLICATION_MAX_BYTES)
41292
41602
  return bad();
@@ -41585,7 +41895,7 @@ async function requestAuthApi(instance, path, options) {
41585
41895
  const text3 = await res.text();
41586
41896
  const body = text3 ? parseJsonBody(text3) : {};
41587
41897
  if (!res.ok) {
41588
- const record9 = isRecord5(body) ? body : {};
41898
+ const record9 = isRecord6(body) ? body : {};
41589
41899
  const detail = typeof record9.detail === "string" ? record9.detail : undefined;
41590
41900
  const error61 = typeof record9.error === "string" ? record9.error : undefined;
41591
41901
  const code = typeof record9.code === "string" ? record9.code : undefined;
@@ -41613,7 +41923,7 @@ function condenseErrorBody(text3) {
41613
41923
  return collapsed;
41614
41924
  return `${collapsed.slice(0, MAX_ERROR_DETAIL_LENGTH - 1).trimEnd()}\u2026`;
41615
41925
  }
41616
- function isRecord5(value) {
41926
+ function isRecord6(value) {
41617
41927
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
41618
41928
  }
41619
41929
 
@@ -41770,7 +42080,7 @@ class RemoteSkillsAuthClient {
41770
42080
 
41771
42081
  // src/lib/recurring-recovery.ts
41772
42082
  var uuid12 = exports_external.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
41773
- var sha4 = exports_external.string().regex(/^[a-f0-9]{64}$/);
42083
+ var sha5 = exports_external.string().regex(/^[a-f0-9]{64}$/);
41774
42084
  var common = exports_external.object({
41775
42085
  contractVersion: exports_external.literal(1),
41776
42086
  apiOrigin: exports_external.string().min(1).max(4096),
@@ -41783,7 +42093,7 @@ var intentSchema = exports_external.discriminatedUnion("operation", [
41783
42093
  common.extend({
41784
42094
  operation: exports_external.literal("activate"),
41785
42095
  draftId: uuid12,
41786
- termsSha256: sha4,
42096
+ termsSha256: sha5,
41787
42097
  approvalDeadline: exports_external.string().datetime(),
41788
42098
  approval: recurringActivationSchema
41789
42099
  }).strict(),
@@ -41791,7 +42101,7 @@ var intentSchema = exports_external.discriminatedUnion("operation", [
41791
42101
  ]);
41792
42102
  var stateSchema = exports_external.object({
41793
42103
  contractVersion: exports_external.literal(1),
41794
- intentSha256: sha4,
42104
+ intentSha256: sha5,
41795
42105
  phase: exports_external.enum(["prepared", "pending", "observed"]),
41796
42106
  consentId: uuid12.nullable()
41797
42107
  }).strict();
@@ -41951,7 +42261,7 @@ function findSkillsParityForMcpTool(tool) {
41951
42261
  }
41952
42262
  // src/lib/registry-sync.ts
41953
42263
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync12 } from "fs";
41954
- import { dirname as dirname21, relative as relative7 } from "path";
42264
+ import { dirname as dirname21, relative as relative8 } from "path";
41955
42265
  function createRegistrySyncArtifact(options = {}) {
41956
42266
  const profile = options.profile ?? "all";
41957
42267
  const includeDocs = options.includeDocs ?? true;
@@ -41963,7 +42273,7 @@ function createRegistrySyncArtifact(options = {}) {
41963
42273
  const registry2 = [...loadRegistryProfile(profile)].sort((a, b) => a.name.localeCompare(b.name));
41964
42274
  const skills = registry2.map((skill) => {
41965
42275
  const skillPath = getSkillPath(skill.name);
41966
- const directory2 = relative7(process.cwd(), skillPath) || skillPath;
42276
+ const directory2 = relative8(process.cwd(), skillPath) || skillPath;
41967
42277
  const validation = includeValidation ? validateSkillDirectory(skill.name, skillPath, skill) : undefined;
41968
42278
  const docs = includeDocs ? buildDocs(skill.name) : undefined;
41969
42279
  return {
@@ -43152,7 +43462,7 @@ function isApiMode(env = process.env) {
43152
43462
  }
43153
43463
  }
43154
43464
  // src/lib/native-storage.ts
43155
- import { createHash as createHash22, createHmac as createHmac2 } from "crypto";
43465
+ import { createHash as createHash23, createHmac as createHmac2 } from "crypto";
43156
43466
  import {
43157
43467
  existsSync as existsSync19,
43158
43468
  mkdirSync as mkdirSync15,
@@ -43161,7 +43471,7 @@ import {
43161
43471
  statSync as statSync12,
43162
43472
  writeFileSync as writeFileSync13
43163
43473
  } from "fs";
43164
- import { dirname as dirname23, join as join35, normalize as normalize4, relative as relative8, sep as sep9 } from "path";
43474
+ import { dirname as dirname23, join as join35, normalize as normalize5, relative as relative9, sep as sep9 } from "path";
43165
43475
  var SKILLS_STORAGE_TABLES = [
43166
43476
  "skills_sync_records",
43167
43477
  "skills_sync_cursors"
@@ -43324,11 +43634,11 @@ function exportSkillsLocalSnapshot(targetDir = process.cwd(), options = {}) {
43324
43634
  if (existsSync19(projectStateDir)) {
43325
43635
  for (const filePath of walkFiles2(projectStateDir)) {
43326
43636
  const bytes = readFileSync23(filePath);
43327
- const relativePath = toPosix(relative8(targetDir, filePath));
43637
+ const relativePath = toPosix(relative9(targetDir, filePath));
43328
43638
  files.push({
43329
43639
  path: relativePath,
43330
43640
  sizeBytes: bytes.byteLength,
43331
- sha256: createHash22("sha256").update(bytes).digest("hex"),
43641
+ sha256: createHash23("sha256").update(bytes).digest("hex"),
43332
43642
  ...options.includeFileContents ? { contentBase64: Buffer.from(bytes).toString("base64") } : {}
43333
43643
  });
43334
43644
  }
@@ -43353,7 +43663,7 @@ function importSkillsLocalSnapshot(snapshot2, targetDir = process.cwd(), options
43353
43663
  continue;
43354
43664
  }
43355
43665
  const bytes = Buffer.from(file2.contentBase64, "base64");
43356
- const hash4 = createHash22("sha256").update(bytes).digest("hex");
43666
+ const hash4 = createHash23("sha256").update(bytes).digest("hex");
43357
43667
  if (hash4 !== file2.sha256) {
43358
43668
  throw new Error(`Snapshot file checksum mismatch: ${file2.path}`);
43359
43669
  }
@@ -43665,7 +43975,7 @@ function walkFiles2(dir) {
43665
43975
  return files;
43666
43976
  }
43667
43977
  function resolveSnapshotPath(targetDir, snapshotPath) {
43668
- const normalizedPath = normalize4(snapshotPath);
43978
+ const normalizedPath = normalize5(snapshotPath);
43669
43979
  if (normalizedPath.startsWith("..") || normalizedPath.includes(`${sep9}..${sep9}`) || normalizedPath.startsWith(sep9)) {
43670
43980
  throw new Error(`Unsafe snapshot path: ${snapshotPath}`);
43671
43981
  }
@@ -43685,7 +43995,7 @@ function toIsoString(value) {
43685
43995
  return Number.isNaN(date6.getTime()) ? value : date6.toISOString();
43686
43996
  }
43687
43997
  function sha256Hex2(value) {
43688
- return createHash22("sha256").update(value).digest("hex");
43998
+ return createHash23("sha256").update(value).digest("hex");
43689
43999
  }
43690
44000
  function normalizeHeaders(headers) {
43691
44001
  const result = {};
@@ -43737,7 +44047,7 @@ function toArrayBuffer(bytes) {
43737
44047
  return buffer;
43738
44048
  }
43739
44049
  // src/lib/station-snapshot.ts
43740
- import { createHash as createHash23 } from "crypto";
44050
+ import { createHash as createHash24 } from "crypto";
43741
44051
  import {
43742
44052
  copyFileSync,
43743
44053
  mkdirSync as mkdirSync16,
@@ -43745,7 +44055,7 @@ import {
43745
44055
  statSync as statSync14,
43746
44056
  writeFileSync as writeFileSync14
43747
44057
  } from "fs";
43748
- import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative9, resolve as resolve18, sep as sep11 } from "path";
44058
+ import { dirname as dirname24, isAbsolute as isAbsolute19, relative as relative10, resolve as resolve18, sep as sep11 } from "path";
43749
44059
 
43750
44060
  // src/lib/portable-snapshot-filter.ts
43751
44061
  import { readdirSync as readdirSync15, statSync as statSync13 } from "fs";
@@ -43912,7 +44222,7 @@ function validateStationId(stationId) {
43912
44222
  }
43913
44223
  }
43914
44224
  function sha256File(filePath) {
43915
- return createHash23("sha256").update(readFileSync25(filePath)).digest("hex");
44225
+ return createHash24("sha256").update(readFileSync25(filePath)).digest("hex");
43916
44226
  }
43917
44227
  function scanHome(definition, homesRoot) {
43918
44228
  const homePath = homePathFor(definition, homesRoot);
@@ -44010,7 +44320,7 @@ function writeStationSnapshot(options) {
44010
44320
  const untouched = [];
44011
44321
  for (const plan of plans) {
44012
44322
  const destination = resolve18(repoRoot, plan.destination);
44013
- const destinationRelative = relative9(repoRoot, destination);
44323
+ const destinationRelative = relative10(repoRoot, destination);
44014
44324
  if (destinationRelative.startsWith("..") || destinationRelative.startsWith(sep11) || isAbsolute19(destinationRelative)) {
44015
44325
  throw new StationSnapshotError("DESTINATION_ESCAPE", `destination escapes repo root: ${plan.destination}`);
44016
44326
  }
@@ -44063,7 +44373,7 @@ function writeStationSnapshot(options) {
44063
44373
  };
44064
44374
  }
44065
44375
  // src/lib/station-hydrate.ts
44066
- import { createHash as createHash24 } from "crypto";
44376
+ import { createHash as createHash25 } from "crypto";
44067
44377
  import {
44068
44378
  copyFileSync as copyFileSync2,
44069
44379
  mkdirSync as mkdirSync17,
@@ -44269,7 +44579,7 @@ function skillSha256(skill) {
44269
44579
  return sha256File(skill.files[0].winner.fullPath);
44270
44580
  }
44271
44581
  const joined = skill.files.map((file2) => sha256File(file2.winner.fullPath));
44272
- return createHash24("sha256").update(joined.sort().join(`
44582
+ return createHash25("sha256").update(joined.sort().join(`
44273
44583
  `)).digest("hex");
44274
44584
  }
44275
44585
  function writeStationHydration(options) {
@@ -44597,7 +44907,7 @@ async function inspectLocked(client2, directory2, cancel) {
44597
44907
  return privatePublicationResult(directory2, receipt);
44598
44908
  }
44599
44909
  // src/lib/skill-context.ts
44600
- import { createHash as createHash25 } from "crypto";
44910
+ import { createHash as createHash26 } from "crypto";
44601
44911
  import { resolve as resolve21 } from "path";
44602
44912
  function words(value) {
44603
44913
  return new Set(value.toLowerCase().match(/[a-z0-9]+/g) ?? []);
@@ -44613,9 +44923,9 @@ async function buildSkillContext(input2, options = {}) {
44613
44923
  if (prompt.length > 128 * 1024 || (input2.paths?.length ?? 0) > 100 || (input2.skills?.length ?? 0) > 100) {
44614
44924
  throw new SkillSelectionError("CONTEXT_INPUT_TOO_LARGE", "The Skills context input exceeds its size limit.");
44615
44925
  }
44616
- const maxChars = options.maxChars ?? 8000;
44926
+ const maxChars = options.maxChars;
44617
44927
  const maxSkills = options.maxSkills ?? 3;
44618
- if (!Number.isInteger(maxChars) || maxChars < 512 || maxChars > 64000 || !Number.isInteger(maxSkills) || maxSkills < 1 || maxSkills > 20) {
44928
+ if (maxChars !== undefined && (!Number.isInteger(maxChars) || maxChars < 512 || maxChars > 64000) || !Number.isInteger(maxSkills) || maxSkills < 1 || maxSkills > 20) {
44619
44929
  throw new SkillSelectionError("INVALID_CONTEXT_BUDGET", "Context limits must be 512\u201364000 characters and 1\u201320 skills.");
44620
44930
  }
44621
44931
  const profileId = input2.profileId ?? "default";
@@ -44672,7 +44982,7 @@ async function buildSkillContext(input2, options = {}) {
44672
44982
  const { content } = readSelectedDocument(entries);
44673
44983
  const section = `Skill ${selection.slug}@${selection.version} (${selection.bundleDigest}; ${reason})
44674
44984
  ${content}`;
44675
- if (chars + section.length + (sections.length ? 2 : 0) > maxChars) {
44985
+ if (maxChars !== undefined && chars + section.length + (sections.length ? 2 : 0) > maxChars) {
44676
44986
  omitted.push({ slug: selection.slug, version: selection.version, reason: "context-budget", loadCommand });
44677
44987
  continue;
44678
44988
  }
@@ -44686,7 +44996,7 @@ ${content}`;
44686
44996
  `);
44687
44997
  const receipt = {
44688
44998
  schemaVersion: 1,
44689
- id: createHash25("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
44999
+ id: createHash26("sha256").update(JSON.stringify({ profile: profile.profileRevision, sessionId, selections, omitted })).digest("hex"),
44690
45000
  profileId: profile.profileId,
44691
45001
  profileRevision: profile.profileRevision,
44692
45002
  authority: profile.authority,
@@ -44702,9 +45012,9 @@ ${content}`;
44702
45012
  return { context: context2, selections, omitted, receipt };
44703
45013
  }
44704
45014
  // src/lib/session-reconciliation.ts
44705
- import { createHash as createHash26 } from "crypto";
45015
+ import { createHash as createHash27 } from "crypto";
44706
45016
  var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
44707
- var digest3 = (value) => createHash26("sha256").update(JSON.stringify(value)).digest("hex");
45017
+ var digest3 = (value) => createHash27("sha256").update(JSON.stringify(value)).digest("hex");
44708
45018
  function exactTimestamp(value) {
44709
45019
  const parsed = value === undefined ? Number.NaN : Date.parse(value);
44710
45020
  if (!Number.isFinite(parsed) || new Date(parsed).toISOString() !== value) {
@@ -44871,6 +45181,8 @@ export {
44871
45181
  publicDiscoveryDocumentation,
44872
45182
  publicDiscoveryDependencies,
44873
45183
  projectCodexNativeSkillCatalog,
45184
+ projectCodexInstalledPlugins,
45185
+ projectCodexInstalledPluginEntries,
44874
45186
  preparePrivatePublication,
44875
45187
  portPortableSkillDirectory,
44876
45188
  portPortableSkill,
@@ -45001,6 +45313,7 @@ export {
45001
45313
  captureDiscoveryPathSources,
45002
45314
  captureDiscoveryDirectories,
45003
45315
  captureDiscoveryByteSources,
45316
+ captureCodexSettingsV2,
45004
45317
  captureCodexSettings,
45005
45318
  captureCodexNativeSkillCatalog,
45006
45319
  captureClaudeSettingsV3,