@hasna/skills 0.10.41 → 0.10.42

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
@@ -9126,8 +9126,8 @@ function assertAgentPolicyCollections(policy) {
9126
9126
  text(source.path);
9127
9127
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
9128
9128
  if (source.hashMode !== undefined)
9129
- 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", "codex-settings-v3"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9130
- if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3")
9129
+ 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", "codex-settings-v3", "codex-settings-v4"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9130
+ if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4"))
9131
9131
  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");
9132
9132
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
9133
9133
  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");
@@ -9166,7 +9166,7 @@ function assertAgentPolicyCollections(policy) {
9166
9166
  import { createHash as createHash20 } from "crypto";
9167
9167
  import { lstatSync as lstatSync23, readFileSync as readFileSync18, readdirSync as readdirSync10, realpathSync as realpathSync9 } from "fs";
9168
9168
  import { basename as basename10, dirname as dirname18, isAbsolute as isAbsolute17, join as join30, resolve as resolve17, sep as sep7 } from "path";
9169
- import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
9169
+ import { isDeepStrictEqual as isDeepStrictEqual6 } from "util";
9170
9170
 
9171
9171
  // node_modules/yaml/dist/index.js
9172
9172
  var composer = require_composer();
@@ -11040,7 +11040,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
11040
11040
  // package.json
11041
11041
  var package_default = {
11042
11042
  name: "@hasna/skills",
11043
- version: "0.10.41",
11043
+ version: "0.10.42",
11044
11044
  description: "Skills library for AI coding agents",
11045
11045
  type: "module",
11046
11046
  bin: {
@@ -14340,6 +14340,7 @@ function projectRegularCodexMcp(config, source) {
14340
14340
 
14341
14341
  // src/lib/codex-settings-witness.ts
14342
14342
  import { createHash as createHash12 } from "crypto";
14343
+ import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
14343
14344
  var sha2 = (text2) => createHash12("sha256").update(text2).digest("hex");
14344
14345
  var EFFORT2 = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
14345
14346
  var VERBOSITY2 = new Set(["low", "medium", "high"]);
@@ -14451,7 +14452,7 @@ function disablesOneSkill(entry) {
14451
14452
  return selector.length === 1 && typeof entry[selector[0]] === "string" && entry[selector[0]].length > 0;
14452
14453
  }
14453
14454
  function inference(target, version) {
14454
- const keys = version === 3 ? [...INFERENCE, "service_tier", "plan_mode_reasoning_effort", "personality"] : INFERENCE;
14455
+ const keys = version >= 3 ? [...INFERENCE, "service_tier", "plan_mode_reasoning_effort", "personality"] : INFERENCE;
14455
14456
  for (const key of keys)
14456
14457
  if (Object.hasOwn(target, key)) {
14457
14458
  const value = target[key];
@@ -14459,7 +14460,7 @@ function inference(target, version) {
14459
14460
  need4(typeof value === "string" && MODEL.test(value));
14460
14461
  else if (key === "personality")
14461
14462
  need4(typeof value === "string" && ["none", "friendly", "pragmatic"].includes(value));
14462
- else if (version === 3 && key !== "model_verbosity") {
14463
+ else if (version >= 3 && key !== "model_verbosity") {
14463
14464
  need4(typeof value === "string" && value.length <= 128 && !/[\x00-\x1f\x7f]/.test(value));
14464
14465
  if (key !== "service_tier")
14465
14466
  need4(value.length > 0);
@@ -14468,6 +14469,115 @@ function inference(target, version) {
14468
14469
  delete target[key];
14469
14470
  }
14470
14471
  }
14472
+ function projectModelAvailabilityNux(config, text2) {
14473
+ const tui = config.tui, counts = isRecord3(tui) ? tui.model_availability_nux : undefined;
14474
+ if (isRecord3(tui) && !Object.keys(tui).length) {
14475
+ delete config.tui;
14476
+ return { config, numericSource: text2 };
14477
+ }
14478
+ if (!isRecord3(counts) || Object.keys(counts).length > 256 || !Object.values(counts).every((value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4294967295))
14479
+ return { config, numericSource: text2 };
14480
+ const expected = structuredClone(config);
14481
+ delete expected.tui.model_availability_nux;
14482
+ const stripEmptyTui = (value) => {
14483
+ if (isRecord3(value.tui) && !Object.keys(value.tui).length)
14484
+ delete value.tui;
14485
+ };
14486
+ stripEmptyTui(expected);
14487
+ const headers = [];
14488
+ const spans = [];
14489
+ const integer = /^(?:\+?(?:0|[1-9](?:_?\d)*)|0x[0-9a-fA-F](?:_?[0-9a-fA-F])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*)$/;
14490
+ const counterIntegers = (span) => {
14491
+ let values = 0;
14492
+ for (let at = 0;at < span.length; ) {
14493
+ const char = span[at];
14494
+ if (char === "#") {
14495
+ while (at < span.length && span[at] !== `
14496
+ `)
14497
+ at++;
14498
+ continue;
14499
+ }
14500
+ if (char === '"' || char === "'") {
14501
+ const quote = char;
14502
+ at++;
14503
+ while (at < span.length) {
14504
+ if (quote === '"' && span[at] === "\\") {
14505
+ at += 2;
14506
+ continue;
14507
+ }
14508
+ if (span[at++] === quote)
14509
+ break;
14510
+ }
14511
+ continue;
14512
+ }
14513
+ if (char !== "=") {
14514
+ at++;
14515
+ continue;
14516
+ }
14517
+ at++;
14518
+ while (at < span.length && /\s/.test(span[at]))
14519
+ at++;
14520
+ if (span[at] === "{") {
14521
+ at++;
14522
+ continue;
14523
+ }
14524
+ const start = at;
14525
+ while (at < span.length && !/[\s,\]{}#]/.test(span[at]))
14526
+ at++;
14527
+ if (!integer.test(span.slice(start, at)))
14528
+ return false;
14529
+ values++;
14530
+ }
14531
+ return values === Object.keys(counts).length;
14532
+ };
14533
+ let attempts = 0;
14534
+ const realPrefix = (end) => {
14535
+ if (++attempts > 256)
14536
+ return false;
14537
+ try {
14538
+ Bun.TOML.parse(text2.slice(0, end));
14539
+ return true;
14540
+ } catch {
14541
+ return false;
14542
+ }
14543
+ };
14544
+ for (const match of text2.matchAll(/^[ \t]*\[[^\r\n]+\][ \t]*(?:#[^\r\n]*)?(?:\r?\n|$)/gm)) {
14545
+ const start = match.index, end = start + match[0].length;
14546
+ if (realPrefix(end))
14547
+ headers.push({ start, end });
14548
+ }
14549
+ for (let at = 0;at < headers.length; at++) {
14550
+ const header = headers[at];
14551
+ let parsed;
14552
+ try {
14553
+ parsed = Bun.TOML.parse(text2.slice(header.start, header.end));
14554
+ } catch {
14555
+ continue;
14556
+ }
14557
+ if (isRecord3(parsed.tui) && isRecord3(parsed.tui.model_availability_nux) && !Object.keys(parsed.tui.model_availability_nux).length && Object.keys(parsed).length === 1 && Object.keys(parsed.tui).length === 1)
14558
+ spans.push({ start: header.start, end: headers[at + 1]?.start ?? text2.length });
14559
+ }
14560
+ for (const match of text2.matchAll(/^[ \t]*(?:(?:tui|"tui"|'tui')[ \t]*\.[ \t]*)?(?:model_availability_nux|"model_availability_nux"|'model_availability_nux')[ \t]*=[ \t]*\{[^\r\n]*(?:\r?\n|$)/gm)) {
14561
+ const start = match.index, end = start + match[0].length;
14562
+ if (realPrefix(end))
14563
+ spans.push({ start, end });
14564
+ }
14565
+ for (const { start, end } of spans) {
14566
+ if (!counterIntegers(text2.slice(start, end)))
14567
+ continue;
14568
+ const remaining = text2.slice(0, start) + text2.slice(end);
14569
+ let parsed;
14570
+ try {
14571
+ parsed = Bun.TOML.parse(remaining);
14572
+ } catch {
14573
+ continue;
14574
+ }
14575
+ stripEmptyTui(parsed);
14576
+ if (isDeepStrictEqual3(parsed, expected))
14577
+ return { config: expected, numericSource: remaining };
14578
+ }
14579
+ return { config, numericSource: text2 };
14580
+ }
14471
14581
  function settingsDigest2(text2, version = 2) {
14472
14582
  let config;
14473
14583
  try {
@@ -14476,7 +14586,8 @@ function settingsDigest2(text2, version = 2) {
14476
14586
  throw new Error("Invalid Codex settings witness");
14477
14587
  }
14478
14588
  need4(isRecord3(config));
14479
- const projected = version === 3 ? projectRegularCodexMcp(config, text2) : { config, numericSource: text2 };
14589
+ const mcp = version >= 3 ? projectRegularCodexMcp(config, text2) : { config, numericSource: text2 };
14590
+ const projected = version === 4 ? projectModelAvailabilityNux(mcp.config, mcp.numericSource) : mcp;
14480
14591
  normalize3(projected.config, version);
14481
14592
  return sha2(`hasna.skills.codex-settings.v${version}\x00${JSON.stringify([canonical4(projected.config), numericTokens2(projected.numericSource)])}`);
14482
14593
  }
@@ -14521,10 +14632,29 @@ function upgradeCodexSettingsWitnessV3(previous, reviewedSettingsPath) {
14521
14632
  throw new Error("Codex settings outside the reviewed V3 contract changed; explicit discovery review required");
14522
14633
  return { path: previous.path, hashMode: "codex-settings-v3", sha256: sha2562 };
14523
14634
  }
14635
+ function hashCodexSettingsReplacementV4(text2, budget = { remaining: 256 * 1024 * 1024 }) {
14636
+ need4(Buffer.byteLength(text2) <= 1024 * 1024 && budget.remaining >= Buffer.byteLength(text2));
14637
+ budget.remaining -= Buffer.byteLength(text2);
14638
+ return settingsDigest2(text2, 4);
14639
+ }
14640
+ function captureCodexSettingsV4(path, budget = { remaining: 256 * 1024 * 1024 }) {
14641
+ return { path, hashMode: "codex-settings-v4", sha256: settingsDigest2(readNativeSettingsWitnessFile(path, budget, "config.toml"), 4) };
14642
+ }
14643
+ function upgradeCodexSettingsWitnessV4(previous, reviewedSettingsPath) {
14644
+ need4(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || ["bytes", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3"].includes(previous.hashMode)) && /^[a-f0-9]{64}$/.test(previous.sha256));
14645
+ const budget = { remaining: 2 * 1024 * 1024 };
14646
+ const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget, "config.toml"), current = readNativeSettingsWitnessFile(previous.path, budget, "config.toml");
14647
+ const original = previous.hashMode === "codex-settings-v3" ? settingsDigest2(before, 3) : previous.hashMode === "codex-settings-v2" ? settingsDigest2(before, 2) : previous.hashMode === "codex-settings-v1" ? hashCodexSettingsReplacement(before) : sha2(before);
14648
+ need4(original === previous.sha256);
14649
+ const sha2562 = settingsDigest2(current, 4);
14650
+ if (settingsDigest2(before, 4) !== sha2562)
14651
+ throw new Error("Codex settings outside the reviewed V4 contract changed; explicit discovery review required");
14652
+ return { path: previous.path, hashMode: "codex-settings-v4", sha256: sha2562 };
14653
+ }
14524
14654
 
14525
14655
  // src/lib/agent-integration.ts
14526
14656
  import { createHash as createHash15, randomUUID as randomUUID2 } from "crypto";
14527
- import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
14657
+ import { isDeepStrictEqual as isDeepStrictEqual5 } from "util";
14528
14658
  import { existsSync as existsSync9, lstatSync as lstatSync18, statSync as statSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync13, readdirSync as readdirSync8, opendirSync as opendirSync3, readlinkSync as readlinkSync4, realpathSync as realpathSync8, renameSync as renameSync2, rmdirSync as rmdirSync2, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, chmodSync, openSync as openSync9, closeSync as closeSync9, fsyncSync as fsyncSync3, fstatSync as fstatSync9, readSync as readSync7, constants as constants9, linkSync as linkSync2 } from "fs";
14529
14659
  import { dirname as dirname13, join as join20, resolve as resolve12, relative as relative6, isAbsolute as isAbsolute12, sep as sep6 } from "path";
14530
14660
  import { homedir as homedir2 } from "os";
@@ -14653,7 +14783,7 @@ function isOwnedCliBridge(path, expectedPaths) {
14653
14783
  }
14654
14784
 
14655
14785
  // src/lib/agent-codex.ts
14656
- import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
14786
+ import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
14657
14787
  var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
14658
14788
  var codexPathConfigRefusal = () => new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables or a native single-line inline array; preserve and review unsupported inline, dotted, or malformed definitions before running skills hook install");
14659
14789
  function splitTopLevel(value, delimiter2) {
@@ -14784,7 +14914,7 @@ function normalizeCodexInlinePathConfig(text2) {
14784
14914
  const expectedWithoutConfig = structuredClone(before);
14785
14915
  delete expectedWithoutConfig.skills.config;
14786
14916
  try {
14787
- if (isDeepStrictEqual3(Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, "")), expectedWithoutConfig))
14917
+ if (isDeepStrictEqual4(Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, "")), expectedWithoutConfig))
14788
14918
  return text2;
14789
14919
  } catch {}
14790
14920
  const matches = [];
@@ -14837,7 +14967,7 @@ function normalizeCodexInlinePathConfig(text2) {
14837
14967
  try {
14838
14968
  const after = Bun.TOML.parse(candidate);
14839
14969
  const expected = structuredClone(before);
14840
- if (isDeepStrictEqual3(after, expected))
14970
+ if (isDeepStrictEqual4(after, expected))
14841
14971
  matches.push(candidate);
14842
14972
  } catch {}
14843
14973
  }
@@ -14853,11 +14983,11 @@ function assertCodexPathConfigEditable(text2) {
14853
14983
  delete expected.skills.config;
14854
14984
  try {
14855
14985
  const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
14856
- if (isDeepStrictEqual3(withoutSections, expected))
14986
+ if (isDeepStrictEqual4(withoutSections, expected))
14857
14987
  return;
14858
14988
  const normalized = normalizeCodexInlinePathConfig(text2);
14859
14989
  const withoutNormalizedSections = Bun.TOML.parse(normalized.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
14860
- if (isDeepStrictEqual3(withoutNormalizedSections, expected))
14990
+ if (isDeepStrictEqual4(withoutNormalizedSections, expected))
14861
14991
  return;
14862
14992
  } catch {}
14863
14993
  throw codexPathConfigRefusal();
@@ -14895,7 +15025,7 @@ function disableCodexBundledSkills(text2) {
14895
15025
  const candidates = new Set;
14896
15026
  const consider = (candidate) => {
14897
15027
  try {
14898
- if (isDeepStrictEqual3(Bun.TOML.parse(candidate), expected))
15028
+ if (isDeepStrictEqual4(Bun.TOML.parse(candidate), expected))
14899
15029
  candidates.add(candidate);
14900
15030
  } catch {}
14901
15031
  };
@@ -15882,7 +16012,7 @@ function projectedHookExecutable(command, expectedResolved) {
15882
16012
  hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
15883
16013
  const recheck = () => {
15884
16014
  try {
15885
- if (realpathSync8(command) !== resolved || linkIdentity(lstatSync18(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual4(parents2(), parentIdentity) || !isDeepStrictEqual4(parents2(resolved), resolvedParentIdentity))
16015
+ if (realpathSync8(command) !== resolved || linkIdentity(lstatSync18(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual5(parents2(), parentIdentity) || !isDeepStrictEqual5(parents2(resolved), resolvedParentIdentity))
15886
16016
  hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
15887
16017
  projectedHookAcl(cli.file, cli.stat);
15888
16018
  projectedHookAcl(manifest.file, manifest.stat);
@@ -16103,7 +16233,7 @@ function atomicWrite2(path, content) {
16103
16233
  }
16104
16234
  }
16105
16235
  function planAgentSettingsWitnessUpgrade(options) {
16106
- if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3].includes(options.targetCodexVersion)))
16236
+ if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3, 4].includes(options.targetCodexVersion)))
16107
16237
  throw new Error("Invalid Codex witness target version");
16108
16238
  const home = resolve12(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
16109
16239
  const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
@@ -16126,21 +16256,21 @@ function planAgentSettingsWitnessUpgrade(options) {
16126
16256
  throw new Error("Settings witness upgrade requires a preserved settings preimage");
16127
16257
  const replaced = [previous];
16128
16258
  if (options.agent === "codex") {
16129
- if (previous.hashMode !== undefined && previous.hashMode !== "bytes" && previous.hashMode !== "codex-settings-v1" && !(options.targetCodexVersion === 3 && previous.hashMode === "codex-settings-v2"))
16259
+ if (previous.hashMode !== undefined && previous.hashMode !== "bytes" && previous.hashMode !== "codex-settings-v1" && !((options.targetCodexVersion === 3 || options.targetCodexVersion === 4) && previous.hashMode === "codex-settings-v2") && !(options.targetCodexVersion === 4 && previous.hashMode === "codex-settings-v3"))
16130
16260
  throw new Error("Codex settings witness upgrade requires an eligible preserved configuration witness");
16131
16261
  const preimage = readCodexSettingsPreimage(options.reviewedPreimage);
16132
16262
  for (const source of binding.sources) {
16133
16263
  if (source === previous || source.path !== configPath)
16134
16264
  continue;
16135
16265
  const fields = source.fields;
16136
- if (source.hashMode !== undefined || source.format !== "toml" || !Array.isArray(fields) || !isDeepStrictEqual4(fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]) || typeof source.sha256 !== "string")
16266
+ if (source.hashMode !== undefined || source.format !== "toml" || !Array.isArray(fields) || !isDeepStrictEqual5(fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]) || typeof source.sha256 !== "string")
16137
16267
  throw new Error("Codex settings witness upgrade found an unrecognized configuration witness");
16138
16268
  if (projectNativeDiscoveryFields(preimage, "toml", fields, configPath) !== source.sha256)
16139
16269
  throw new Error("Reviewed preimage does not explain the Codex discovery projection witness");
16140
16270
  replaced.push(source);
16141
16271
  }
16142
16272
  }
16143
- const next = options.agent === "claude" ? upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
16273
+ const next = options.agent === "claude" ? upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 4 ? upgradeCodexSettingsWitnessV4(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
16144
16274
  const rebound = [];
16145
16275
  for (const source of binding.sources) {
16146
16276
  if (!replaced.includes(source)) {
@@ -16186,7 +16316,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
16186
16316
  const upgrade = plan.settingsWitnessUpgrade;
16187
16317
  if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
16188
16318
  throw new Error("Invalid settings witness upgrade plan");
16189
- const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha3(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
16319
+ const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v4" ? 4 : upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha3(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
16190
16320
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
16191
16321
  throw new Error("Settings witness upgrade plan changed");
16192
16322
  provenDiscovery = verified.discoveryBefore[0];
@@ -19169,19 +19299,19 @@ function projectNativeDiscoveryFields(text2, format, fields2, path) {
19169
19299
  return digest(JSON.stringify(Object.fromEntries(fields2.map((field2) => [field2, object5[field2] ?? null]))));
19170
19300
  }
19171
19301
  function isSupersededCodexProjection(agent, configPath, source) {
19172
- return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual5(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
19302
+ return agent === "codex" && source.path === configPath && source.format === "toml" && isDeepStrictEqual6(source.fields, [...CODEX_DISCOVERY_PROJECTION_FIELDS]);
19173
19303
  }
19174
19304
  function supersedesCodexProjection(sources, configPath) {
19175
- return sources.some((source) => source.path === configPath && (source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3"));
19305
+ return sources.some((source) => source.path === configPath && (source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")));
19176
19306
  }
19177
19307
  function projected(source, changes, budget = discoveryByteBudget()) {
19178
- if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3") {
19308
+ if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")) {
19179
19309
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
19180
19310
  throw new Error("Codex settings witnesses require exact typed metadata");
19181
- const current = (source.hashMode === "codex-settings-v3" ? captureCodexSettingsV3 : source.hashMode === "codex-settings-v2" ? captureCodexSettingsV2 : captureCodexSettings)(source.path, budget).sha256;
19311
+ const current = (source.hashMode === "codex-settings-v4" ? captureCodexSettingsV4 : source.hashMode === "codex-settings-v3" ? captureCodexSettingsV3 : source.hashMode === "codex-settings-v2" ? captureCodexSettingsV2 : captureCodexSettings)(source.path, budget).sha256;
19182
19312
  if (current !== source.sha256)
19183
19313
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
19184
- return changes?.has(source.path) ? (source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget) : current;
19314
+ return changes?.has(source.path) ? (source.hashMode === "codex-settings-v4" ? hashCodexSettingsReplacementV4 : source.hashMode === "codex-settings-v3" ? hashCodexSettingsReplacementV3 : source.hashMode === "codex-settings-v2" ? hashCodexSettingsReplacementV2 : hashCodexSettingsReplacement)(changes.get(source.path), budget) : current;
19185
19315
  }
19186
19316
  if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
19187
19317
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
@@ -19277,7 +19407,7 @@ function captureRetiredCodexDiscovery(binding, controls, rules) {
19277
19407
  return current;
19278
19408
  const installation = codexInstallationRoots(binding);
19279
19409
  const directories = (binding.directories ?? []).filter((directory) => !installation.some((root2) => directory.path === root2 || directory.path.startsWith(root2 + sep7) || root2.startsWith(directory.path + sep7)));
19280
- if (previous && isDeepStrictEqual5(previous.roots, roots) && isDeepStrictEqual5(previous.parents, parents3) && isDeepStrictEqual5(previous.directories.map((item) => item.path), directories.map((item) => item.path)))
19410
+ if (previous && isDeepStrictEqual6(previous.roots, roots) && isDeepStrictEqual6(previous.parents, parents3) && isDeepStrictEqual6(previous.directories.map((item) => item.path), directories.map((item) => item.path)))
19281
19411
  return { ...current, codexRetiredMaterializations: previous };
19282
19412
  return { ...current, codexRetiredMaterializations: { roots, parents: parents3, directories: captureDiscoveryDirectoryProjection(directories.map((item) => item.path), roots) } };
19283
19413
  }
@@ -19353,7 +19483,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
19353
19483
  if (omitted.some((root2) => !retiredRoots.includes(root2)))
19354
19484
  throw new Error("Missing reviewed directory membership inventory; provide a fresh discovery review");
19355
19485
  const current = captureDiscoveryDirectoryProjection(ordinary.map((item) => item.path), retiredRoots).map(({ entries, ...item }) => item);
19356
- if (!isDeepStrictEqual5(current, projection))
19486
+ if (!isDeepStrictEqual6(current, projection))
19357
19487
  throw new Error("Native discovery directory membership changed outside retired Codex roots");
19358
19488
  }
19359
19489
  } else
@@ -19412,7 +19542,7 @@ function codexInstallationRoots(binding) {
19412
19542
  });
19413
19543
  }
19414
19544
  function assertMarketplaceBinding(binding, source) {
19415
- if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || source.hashMode === "codex-settings-v3") && (binding.agent !== "codex" || binding.method !== "reviewed" || basename10(source.path) !== "config.toml"))
19545
+ if ((source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")) && (binding.agent !== "codex" || binding.method !== "reviewed" || basename10(source.path) !== "config.toml"))
19416
19546
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
19417
19547
  if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename10(source.path) !== "settings.json"))
19418
19548
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
@@ -19613,10 +19743,10 @@ function resolveAgentDiscovery(options) {
19613
19743
  verifyAgentDiscovery(retained);
19614
19744
  if (!retained.sources.some((source) => source.path === canonical6(configPath) && source.format === undefined && source.fields === undefined))
19615
19745
  unresolved("retained review is missing the full agent configuration source");
19616
- 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.hashMode === "codex-settings-v3"))) && source.path !== canonical6(configPath)))
19746
+ 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.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical6(configPath)))
19617
19747
  unresolved("retained settings witness names another configuration source");
19618
19748
  const covered = supersedesCodexProjection(retained.sources, canonical6(configPath)) ? sources.filter((source) => !isSupersededCodexProjection(agent, canonical6(configPath), source)) : sources;
19619
- if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual5(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
19749
+ if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual6(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
19620
19750
  unresolved("retained review is missing current runtime discovery coverage");
19621
19751
  return retained;
19622
19752
  }
@@ -19630,7 +19760,7 @@ function resolveAgentDiscovery(options) {
19630
19760
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
19631
19761
  throw new Error("Explicit discovery reviews require full source-file hashes");
19632
19762
  verifyAgentDiscovery(supplied);
19633
- 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.hashMode === "codex-settings-v3"))) && source.path !== canonical6(configPath)))
19763
+ 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.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical6(configPath)))
19634
19764
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
19635
19765
  if (!review.sources.some((source) => source.path === canonical6(configPath)))
19636
19766
  throw new Error("Discovery review must include the agent configuration source");
@@ -46599,6 +46729,7 @@ export {
46599
46729
  validateCron,
46600
46730
  validateBlogArticleRunOptions,
46601
46731
  uploadSkillsSnapshotFilesToS3,
46732
+ upgradeCodexSettingsWitnessV4,
46602
46733
  upgradeCodexSettingsWitnessV3,
46603
46734
  upgradeCodexSettingsWitness,
46604
46735
  upgradeClaudeSettingsWitness,
@@ -46786,6 +46917,7 @@ export {
46786
46917
  captureDiscoveryPathSources,
46787
46918
  captureDiscoveryDirectories,
46788
46919
  captureDiscoveryByteSources,
46920
+ captureCodexSettingsV4,
46789
46921
  captureCodexSettingsV3,
46790
46922
  captureCodexSettingsV2,
46791
46923
  captureCodexSettings,
@@ -7,7 +7,7 @@ export { captureDiscoveryDirectories, type DiscoveryDirectory } from "./agent-di
7
7
  export interface DiscoverySource {
8
8
  path: string;
9
9
  sha256: string | null;
10
- hashMode?: "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" | "codex-settings-v3";
10
+ hashMode?: "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" | "codex-settings-v3" | "codex-settings-v4";
11
11
  managedPlugins?: ManagedPluginRegistrationWitness[];
12
12
  format?: "json" | "toml" | "yaml";
13
13
  fields?: string[];
@@ -188,7 +188,7 @@ export declare function planClaudeHookEventsUpdate(options: ClaudeHookUpdateOpti
188
188
  */
189
189
  export declare function planAgentSettingsWitnessUpgrade(options: {
190
190
  agent: "claude" | "codex";
191
- targetCodexVersion?: 2 | 3;
191
+ targetCodexVersion?: 2 | 3 | 4;
192
192
  reviewedPreimage: string;
193
193
  home?: string;
194
194
  dataDir?: string;
@@ -33,3 +33,16 @@ export declare function upgradeCodexSettingsWitnessV3(previous: {
33
33
  hashMode?: "bytes" | "codex-settings-v1" | "codex-settings-v2";
34
34
  sha256: string;
35
35
  }, reviewedSettingsPath: string): ReturnType<typeof captureCodexSettingsV3>;
36
+ /** Explicit V4 adds only schema-valid native model-availability UI counts. */
37
+ export declare function hashCodexSettingsReplacementV4(text: string, budget?: ClaudeSettingsWitnessBudget): string;
38
+ export declare function captureCodexSettingsV4(path: string, budget?: ClaudeSettingsWitnessBudget): {
39
+ path: string;
40
+ hashMode: "codex-settings-v4";
41
+ sha256: string;
42
+ };
43
+ /** Prove the original V1/V2/V3/byte digest first; a V4 hash never verifies an old pin. */
44
+ export declare function upgradeCodexSettingsWitnessV4(previous: {
45
+ path: string;
46
+ hashMode?: "bytes" | "codex-settings-v1" | "codex-settings-v2" | "codex-settings-v3";
47
+ sha256: string;
48
+ }, reviewedSettingsPath: string): ReturnType<typeof captureCodexSettingsV4>;