@hasna/skills 0.10.49 → 0.10.51

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
@@ -9177,6 +9177,8 @@ var BUILTIN_MODEL_SELECTIONS = new Set([
9177
9177
  "claude-opus-4-6[1m]",
9178
9178
  "claude-sonnet-4-6[1m]"
9179
9179
  ]);
9180
+ var CLAUDE_BUILTIN_THEMES = Object.freeze(["auto", "dark", "light", "dark-daltonized", "light-daltonized", "dark-ansi", "light-ansi"]);
9181
+ var BUILTIN_THEMES = new Set(CLAUDE_BUILTIN_THEMES);
9180
9182
  function canonical(value) {
9181
9183
  if (value.kind === "object")
9182
9184
  return { kind: "object", entries: value.entries.map(([key, child]) => [key, canonical(child)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0) };
@@ -9235,7 +9237,9 @@ function inferencePreference(key, child, version) {
9235
9237
  function settingsDigest(text, version = 1) {
9236
9238
  const value = parse(text);
9237
9239
  value.entries = value.entries.filter(([key, child]) => {
9238
- if (version !== 1 && inferencePreference(key, child, version))
9240
+ if (version === 4 && key === "theme" && child.kind === "string" && BUILTIN_THEMES.has(child.value))
9241
+ return false;
9242
+ if (version !== 1 && inferencePreference(key, child, version === 4 ? 3 : version))
9239
9243
  return false;
9240
9244
  if (key === "model") {
9241
9245
  need(child.kind === "string", "contains an invalid model selection");
@@ -9293,6 +9297,26 @@ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
9293
9297
  need(settingsDigest(before, 3) === sha256, "non-preference settings changed; explicit discovery review required");
9294
9298
  return { path: previous.path, hashMode: "claude-settings-v3", sha256 };
9295
9299
  }
9300
+ function captureClaudeSettingsV4(path, budget = { remaining: 256 * 1024 * 1024 }) {
9301
+ return { path, hashMode: "claude-settings-v4", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 4) };
9302
+ }
9303
+ function hashClaudeSettingsReplacementV4(text, budget) {
9304
+ need(typeof text === "string", "requires settings text");
9305
+ const bytes = Buffer.byteLength(text);
9306
+ need(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
9307
+ budget.remaining -= bytes;
9308
+ return settingsDigest(text, 4);
9309
+ }
9310
+ function upgradeClaudeSettingsWitnessV4(previous, reviewedSettingsPath) {
9311
+ need(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === undefined || previous.hashMode === "bytes" || previous.hashMode === "claude-settings-v1" || previous.hashMode === "claude-settings-v2" || previous.hashMode === "claude-settings-v3") && typeof previous.sha256 === "string" && /^[a-f0-9]{64}$/.test(previous.sha256), "requires an exact prior settings witness");
9312
+ const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
9313
+ const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget), current = readNativeSettingsWitnessFile(previous.path, budget);
9314
+ const beforeDigest = previous.hashMode === undefined || previous.hashMode === "bytes" ? createHash2("sha256").update(before).digest("hex") : settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : previous.hashMode === "claude-settings-v2" ? 2 : 3);
9315
+ need(beforeDigest === previous.sha256, "reviewed preimage does not match the prior witness");
9316
+ const sha256 = settingsDigest(current, 4);
9317
+ need(settingsDigest(before, 4) === sha256, "non-preference settings changed; explicit discovery review required");
9318
+ return { path: previous.path, hashMode: "claude-settings-v4", sha256 };
9319
+ }
9296
9320
  function hashNativeJsonControls(text, excludedMetadata) {
9297
9321
  need(Buffer.byteLength(text) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, "exceeds its byte limit");
9298
9322
  const value = parse(text);
@@ -9356,6 +9380,28 @@ function upgradeSumiSettingsWitness(previous, reviewedSettingsPath) {
9356
9380
 
9357
9381
  // src/lib/agent-policy-limits.ts
9358
9382
  import { basename as basename3, isAbsolute as isAbsolute3, join as join7, resolve as resolve3, sep as sep2 } from "path";
9383
+
9384
+ // src/lib/codex-native-compatibility.ts
9385
+ var MEASURED_CODEX_CAPABILITIES = {
9386
+ "codex-cli 0.153.0": ["hooks"],
9387
+ "codex-cli 0.154.0": ["hooks"],
9388
+ "codex-cli 0.155.0": ["hooks"],
9389
+ "codex-cli 0.155.1": ["hooks"],
9390
+ "codex-cli 0.156.1": ["hooks"],
9391
+ "codex-cli 0.157.0": ["hooks"],
9392
+ "codex-cli 0.157.1": ["hooks"],
9393
+ "codex-cli 0.158.0": ["hooks"],
9394
+ "codex-cli 0.159.0": ["hooks"],
9395
+ "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
9396
+ "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9397
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
9398
+ };
9399
+ function supportsCodexNativeCapability(version, capability) {
9400
+ return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
9401
+ }
9402
+ var SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
9403
+
9404
+ // src/lib/agent-policy-limits.ts
9359
9405
  var AGENT_POLICY_LIMITS = Object.freeze({ bytes: 1024 * 1024, agents: 16, discoverySources: 2048, discoveryRawSourceBytes: 64 * 1024 * 1024, discoveryRawTotalBytes: 256 * 1024 * 1024, discoveryPathLinks: 40, discoveryPathSteps: 256, discoveryPathSourceMetadataBytes: 64 * 1024, discoveryPathTotalMetadataBytes: 8 * 1024 * 1024, discoveryRoots: 512, discoveryDirectories: 64, discoveryDirectoryEntries: 20000, discoveryDirectoryBytes: 8 * 1024 * 1024, builtinNames: 2048, rootAliases: 2, fields: 64, pathCharacters: 4096 });
9360
9406
  function object(value) {
9361
9407
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
@@ -9421,7 +9467,7 @@ function assertAgentPolicyCollections(policy) {
9421
9467
  }
9422
9468
  if (bridge.codexInactivePlugins !== undefined) {
9423
9469
  const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
9424
- requireBound(controls.length === 0 || object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === "codex-cli 0.160.0" && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
9470
+ requireBound(controls.length === 0 || object(bridge.codexPluginSkillReview) && supportsCodexNativeCapability(bridge.codexPluginSkillReview.version, "installed-plugin-review") && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
9425
9471
  for (const control of controls) {
9426
9472
  requireBound(object(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
9427
9473
  text(control.pluginId, 1024);
@@ -9473,7 +9519,7 @@ function assertAgentPolicyCollections(policy) {
9473
9519
  }
9474
9520
  if (value.codexInstallationInputs !== undefined) {
9475
9521
  const proof = record(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
9476
- requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && proof.version === "codex-cli 0.160.0" && typeof proof.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(proof.catalogSha256) && inputs.length > 0 && object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
9522
+ requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && supportsCodexNativeCapability(proof.version, "installed-plugin-review") && typeof proof.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(proof.catalogSha256) && inputs.length > 0 && object(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
9477
9523
  for (const input of inputs) {
9478
9524
  requireBound(object(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
9479
9525
  text(input.pluginId, 1024);
@@ -9497,14 +9543,14 @@ function assertAgentPolicyCollections(policy) {
9497
9543
  text(source.path);
9498
9544
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
9499
9545
  if (source.hashMode !== undefined)
9500
- requireBound(["bytes", "path-bytes", "claude-plugin-manifest-v1", "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", "sumi-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9546
+ requireBound(["bytes", "path-bytes", "claude-plugin-manifest-v1", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-settings-v4", "claude-marketplace-registry-v2", "codex-settings-v1", "codex-settings-v2", "codex-settings-v3", "codex-settings-v4", "sumi-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9501
9547
  if (source.hashMode === "claude-plugin-manifest-v1")
9502
9548
  requireBound(agent === "claude" && value.agent === "claude" && ["automatic", "reviewed"].includes(value.method) && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && source.path.endsWith(join7(sep2, ".claude-plugin", "plugin.json")) && source.managedPlugins === undefined);
9503
9549
  if (source.hashMode === "sumi-settings-v1")
9504
9550
  requireBound(agent === "sumi" && value.agent === "sumi" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename3(source.path) === "sumi.json");
9505
9551
  if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4"))
9506
9552
  requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename3(source.path) === "config.toml");
9507
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
9553
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4"))
9508
9554
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename3(source.path) === "settings.json");
9509
9555
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
9510
9556
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename3(source.path) === "known_marketplaces.json");
@@ -11572,7 +11618,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
11572
11618
  // package.json
11573
11619
  var package_default = {
11574
11620
  name: "@hasna/skills",
11575
- version: "0.10.49",
11621
+ version: "0.10.51",
11576
11622
  description: "Skills library for AI coding agents",
11577
11623
  type: "module",
11578
11624
  bin: {
@@ -13660,25 +13706,6 @@ function isCodexLocalStdioMcpServer(name, server) {
13660
13706
  import { basename as basename6, dirname as dirname11, join as join17, relative as relative5, resolve as resolve10, sep as sep6 } from "path";
13661
13707
  import { lstatSync as lstatSync13, readdirSync as readdirSync6, readlinkSync as readlinkSync3, realpathSync as realpathSync6 } from "fs";
13662
13708
 
13663
- // src/lib/codex-native-compatibility.ts
13664
- var MEASURED_CODEX_CAPABILITIES = {
13665
- "codex-cli 0.153.0": ["hooks"],
13666
- "codex-cli 0.154.0": ["hooks"],
13667
- "codex-cli 0.155.0": ["hooks"],
13668
- "codex-cli 0.155.1": ["hooks"],
13669
- "codex-cli 0.156.1": ["hooks"],
13670
- "codex-cli 0.157.0": ["hooks"],
13671
- "codex-cli 0.157.1": ["hooks"],
13672
- "codex-cli 0.158.0": ["hooks"],
13673
- "codex-cli 0.159.0": ["hooks"],
13674
- "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
13675
- "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
13676
- };
13677
- function supportsCodexNativeCapability(version, capability) {
13678
- return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
13679
- }
13680
- var SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
13681
-
13682
13709
  // src/lib/codex-native-skill-catalog.ts
13683
13710
  import { realpathSync as realpathSync5 } from "fs";
13684
13711
  import { isAbsolute as isAbsolute11, normalize as normalize2 } from "path";
@@ -14273,6 +14300,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read2, rules)
14273
14300
  function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules, pluginSettings, plannedRules, denials = []) {
14274
14301
  if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
14275
14302
  refuse2();
14303
+ const installedPluginReview = supportsCodexNativeCapability(catalog.version, "installed-plugin-review");
14276
14304
  if (!Array.isArray(denials) || denials.length > 4096 || new Set(denials.map((item) => item?.path)).size !== denials.length || denials.some((item) => !item || Object.keys(item).sort().join(",") !== "name,path,sha256" || typeof item.name !== "string" || typeof item.path !== "string" || !/^[a-f0-9]{64}$/.test(item.sha256)))
14277
14305
  refuse2();
14278
14306
  const usedDenials = new Set;
@@ -14304,7 +14332,7 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules,
14304
14332
  if (listed.length > 1)
14305
14333
  refuse2();
14306
14334
  const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
14307
- if (catalog.version === "codex-cli 0.160.0" && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename6(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
14335
+ if (installedPluginReview && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename6(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
14308
14336
  const plugin = installed[0], input = { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceRoot: plugin.sourcePath, sourceSha256: plugin.sourceSha256 };
14309
14337
  if (input.sourceRoot !== located.root && !input.sourceRoot.startsWith(cache + sep6) && (lstatSync13(input.sourceRoot, { throwIfNoEntry: false }) || documents.some((path) => path.startsWith(input.sourceRoot + sep6)))) {
14310
14338
  if (skills.some((skill) => skill.path === input.sourceRoot || skill.path.startsWith(input.sourceRoot + sep6)) || !sourceInputUnchanged(input, cache, read2))
@@ -14313,7 +14341,7 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules,
14313
14341
  }
14314
14342
  }
14315
14343
  const settings = pluginSettings && typeof pluginSettings === "object" && !Array.isArray(pluginSettings) ? pluginSettings : undefined;
14316
- if (catalog.version === "codex-cli 0.160.0" && !listed.length && !skills.some((skill) => skill.pluginId === expectedPluginId) && installed.length === 1 && installed[0].installed && !installed[0].enabled && codexPluginSourceIsConfigControlled(installed[0]) && settings && Object.hasOwn(settings, expectedPluginId) && settings[expectedPluginId]?.enabled === false) {
14344
+ if (installedPluginReview && !listed.length && !skills.some((skill) => skill.pluginId === expectedPluginId) && installed.length === 1 && installed[0].installed && !installed[0].enabled && codexPluginSourceIsConfigControlled(installed[0]) && settings && Object.hasOwn(settings, expectedPluginId) && settings[expectedPluginId]?.enabled === false) {
14317
14345
  if (!Array.isArray(rules))
14318
14346
  refuse2();
14319
14347
  const exactPathDeny = rules.some((rule) => rule?.path === document2 && rule?.enabled === false);
@@ -14346,12 +14374,12 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read2, rules,
14346
14374
  const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document2 && rule?.enabled === false);
14347
14375
  const nativeSkill = { name: parsed.name, path: document2, pluginId: expectedPluginId, enabled: true };
14348
14376
  const denial = denials.find((item) => item.path === document2);
14349
- const explicitDeny = denial !== undefined && remotePluginId !== undefined && catalog.version === "codex-cli 0.160.0" && denial.name === parsed.name && denial.sha256 === createHash12("sha256").update(read2(document2)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document2 && rule.enabled === false);
14377
+ const explicitDeny = denial !== undefined && remotePluginId !== undefined && installedPluginReview && denial.name === parsed.name && denial.sha256 === createHash12("sha256").update(read2(document2)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document2 && rule.enabled === false);
14350
14378
  if (denial && !explicitDeny)
14351
14379
  refuse2();
14352
14380
  if (explicitDeny)
14353
14381
  usedDenials.add(document2);
14354
- const plannedPathDeny = catalog.version === "codex-cli 0.160.0" && remotePluginId !== undefined && Array.isArray(rules) && rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === parsed.name && rule.enabled === false) && !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document2 && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
14382
+ const plannedPathDeny = installedPluginReview && remotePluginId !== undefined && Array.isArray(rules) && rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === parsed.name && rule.enabled === false) && !rules.some((rule) => (rule?.path === document2 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document2 && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
14355
14383
  if (!explicitDeny && (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules)))
14356
14384
  refuse2();
14357
14385
  }
@@ -16939,7 +16967,7 @@ function planAgentIntegration(options) {
16939
16967
  const { codexInstallationInputs: previousInputs, ...current } = rebound;
16940
16968
  const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
16941
16969
  const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory) => inputs.some((root) => root.startsWith(directory.path + sep7))).map((directory) => directory.path), inputs) : previousInputs.directories;
16942
- const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: "codex-cli 0.160.0", catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
16970
+ const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: codexPluginSkillReview.version, catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
16943
16971
  return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(join23(home, ".codex/config.toml"), aliases))?.after ?? codexConfigBefore ?? "").skills?.config ?? []);
16944
16972
  });
16945
16973
  const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
@@ -17254,6 +17282,8 @@ function atomicWrite2(path, content) {
17254
17282
  function planAgentSettingsWitnessUpgrade(options) {
17255
17283
  if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3, 4].includes(options.targetCodexVersion)))
17256
17284
  throw new Error("Invalid Codex witness target version");
17285
+ if (options.targetClaudeVersion !== undefined && (options.agent !== "claude" || ![3, 4].includes(options.targetClaudeVersion)))
17286
+ throw new Error("Invalid Claude witness target version");
17257
17287
  const home = resolve13(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
17258
17288
  const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
17259
17289
  if (!snapshot2 || sha3(snapshot2.text) !== options.expectedPolicySha256)
@@ -17301,7 +17331,7 @@ function planAgentSettingsWitnessUpgrade(options) {
17301
17331
  replaced.push(source);
17302
17332
  }
17303
17333
  }
17304
- const next = options.agent === "sumi" ? upgradeSumiSettingsWitness(previous, options.reviewedPreimage) : 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);
17334
+ const next = options.agent === "sumi" ? upgradeSumiSettingsWitness(previous, options.reviewedPreimage) : options.agent === "claude" ? options.targetClaudeVersion === 4 ? upgradeClaudeSettingsWitnessV4(previous, options.reviewedPreimage) : upgradeClaudeSettingsWitness(previous, options.reviewedPreimage) : options.targetCodexVersion === 4 ? upgradeCodexSettingsWitnessV4(previous, options.reviewedPreimage) : options.targetCodexVersion === 3 ? upgradeCodexSettingsWitnessV3(previous, options.reviewedPreimage) : upgradeCodexSettingsWitness(previous, options.reviewedPreimage);
17305
17335
  const rebound = [];
17306
17336
  for (const source of binding.sources) {
17307
17337
  if (!replaced.includes(source)) {
@@ -17347,7 +17377,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
17347
17377
  const upgrade = plan.settingsWitnessUpgrade;
17348
17378
  if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
17349
17379
  throw new Error("Invalid settings witness upgrade plan");
17350
- 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 });
17380
+ const verified = planAgentSettingsWitnessUpgrade({ agent: upgrade.agent, ...upgrade.agent === "codex" ? { targetCodexVersion: upgrade.toHashMode === "codex-settings-v4" ? 4 : upgrade.toHashMode === "codex-settings-v3" ? 3 : 2 } : {}, ...upgrade.agent === "claude" && upgrade.toHashMode === "claude-settings-v4" ? { targetClaudeVersion: 4 } : {}, reviewedPreimage: upgrade.reviewedPreimage, home: plan.managedAgentChecks.home, dataDir: plan.dataDir, expectedPolicySha256: sha3(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
17351
17381
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
17352
17382
  throw new Error("Settings witness upgrade plan changed");
17353
17383
  provenDiscovery = verified.discoveryBefore[0];
@@ -20394,13 +20424,13 @@ function projected(source, changes, budget2 = discoveryByteBudget()) {
20394
20424
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
20395
20425
  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), budget2) : current;
20396
20426
  }
20397
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
20427
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) {
20398
20428
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
20399
20429
  throw new Error("Claude settings witnesses require exact typed metadata");
20400
- const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget2).sha256;
20430
+ const current = (source.hashMode === "claude-settings-v4" ? captureClaudeSettingsV4 : source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget2).sha256;
20401
20431
  if (current !== source.sha256)
20402
20432
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
20403
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget2) : current;
20433
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v4" ? hashClaudeSettingsReplacementV4 : source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget2) : current;
20404
20434
  }
20405
20435
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
20406
20436
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
@@ -20611,7 +20641,7 @@ function codexInstallationRoots(binding) {
20611
20641
  const proof = binding.codexInstallationInputs;
20612
20642
  if (proof === undefined)
20613
20643
  return [];
20614
- if (binding.agent !== "codex" || binding.method !== "reviewed" || proof.version !== "codex-cli 0.160.0" || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
20644
+ if (binding.agent !== "codex" || binding.method !== "reviewed" || !supportsCodexNativeCapability(proof.version, "installed-plugin-review") || !/^[a-f0-9]{64}$/.test(proof.catalogSha256) || !Array.isArray(proof.plugins) || !proof.plugins.length)
20615
20645
  throw new Error("Invalid native installation input proof");
20616
20646
  const cache = dirname19(dirname19(proof.plugins[0].pluginParent));
20617
20647
  const config = join32(dirname19(dirname19(cache)), "config.toml"), home = dirname19(dirname19(dirname19(cache)));
@@ -20635,7 +20665,7 @@ function assertMarketplaceBinding(binding, source) {
20635
20665
  throw new Error("Sumi settings witnesses require explicit reviewed Sumi configuration");
20636
20666
  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"))
20637
20667
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
20638
- 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"))
20668
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) && (binding.agent !== "claude" || binding.method !== "reviewed" || basename10(source.path) !== "settings.json"))
20639
20669
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
20640
20670
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
20641
20671
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -20837,7 +20867,7 @@ function resolveAgentDiscovery(options) {
20837
20867
  verifyAgentDiscovery(retained);
20838
20868
  if (!retained.sources.some((source) => source.path === canonical7(configPath) && source.format === undefined && source.fields === undefined))
20839
20869
  unresolved("retained review is missing the full agent configuration source");
20840
- if (retained.sources.some((source) => (source.hashMode === "sumi-settings-v1" || 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 !== canonical7(configPath)))
20870
+ if (retained.sources.some((source) => (source.hashMode === "sumi-settings-v1" || source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical7(configPath)))
20841
20871
  unresolved("retained settings witness names another configuration source");
20842
20872
  const covered = supersedesSettingsProjection(agent, retained.sources, canonical7(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical7(configPath), source)) : sources;
20843
20873
  if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual7(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
@@ -20851,7 +20881,7 @@ function resolveAgentDiscovery(options) {
20851
20881
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
20852
20882
  throw new Error("Explicit discovery reviews require full source-file hashes");
20853
20883
  verifyAgentDiscovery(supplied);
20854
- if (review.sources.some((source) => (source.hashMode === "sumi-settings-v1" || 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 !== canonical7(configPath)))
20884
+ if (review.sources.some((source) => (source.hashMode === "sumi-settings-v1" || source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4" || (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4")))) && source.path !== canonical7(configPath)))
20855
20885
  throw new Error("Semantic settings witnesses must name the configured agent configuration source");
20856
20886
  if (!review.sources.some((source) => source.path === canonical7(configPath)))
20857
20887
  throw new Error("Discovery review must include the agent configuration source");
@@ -47854,6 +47884,7 @@ export {
47854
47884
  upgradeCodexSettingsWitnessV4,
47855
47885
  upgradeCodexSettingsWitnessV3,
47856
47886
  upgradeCodexSettingsWitness,
47887
+ upgradeClaudeSettingsWitnessV4,
47857
47888
  upgradeClaudeSettingsWitness,
47858
47889
  updateSkillRun,
47859
47890
  unsetConfig,
@@ -48045,6 +48076,7 @@ export {
48045
48076
  captureCodexSettingsV2,
48046
48077
  captureCodexSettings,
48047
48078
  captureCodexNativeSkillCatalog,
48079
+ captureClaudeSettingsV4,
48048
48080
  captureClaudeSettingsV3,
48049
48081
  captureClaudeSettingsV2,
48050
48082
  captureClaudeSettings,
@@ -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-manifest-v1" | "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" | "sumi-settings-v1";
10
+ hashMode?: "bytes" | "path-bytes" | "claude-plugin-manifest-v1" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3" | "claude-settings-v4" | "claude-marketplace-registry-v2" | "codex-settings-v1" | "codex-settings-v2" | "codex-settings-v3" | "codex-settings-v4" | "sumi-settings-v1";
11
11
  managedPlugins?: ManagedPluginRegistrationWitness[];
12
12
  format?: "json" | "toml" | "yaml";
13
13
  fields?: string[];
@@ -28,7 +28,7 @@ export interface AgentDiscoveryBinding {
28
28
  }>;
29
29
  };
30
30
  codexInstallationInputs?: {
31
- version: "codex-cli 0.160.0";
31
+ version: string;
32
32
  catalogSha256: string;
33
33
  plugins: CodexPluginSourceInput[];
34
34
  directories?: DiscoveryDirectory[];
@@ -190,6 +190,7 @@ export declare function planClaudeHookEventsUpdate(options: ClaudeHookUpdateOpti
190
190
  export declare function planAgentSettingsWitnessUpgrade(options: {
191
191
  agent: "claude" | "codex" | "sumi";
192
192
  targetCodexVersion?: 2 | 3 | 4;
193
+ targetClaudeVersion?: 3 | 4;
193
194
  reviewedPreimage: string;
194
195
  home?: string;
195
196
  dataDir?: string;
@@ -10,6 +10,7 @@ export interface ClaudeSettingsWitnessBudget {
10
10
  remaining: number;
11
11
  }
12
12
  export declare function readNativeSettingsWitnessFile(path: string, budget: ClaudeSettingsWitnessBudget, filename?: "settings.json" | "config.toml" | "sumi.json"): string;
13
+ export declare const CLAUDE_BUILTIN_THEMES: readonly ["auto", "dark", "light", "dark-daltonized", "light-daltonized", "dark-ansi", "light-ansi"];
13
14
  /** Cooperating installers may replace only their explicitly selected event. Compare the strict syntax
14
15
  * trees, not JSON.parse values or the preference-tolerant witness digest: all
15
16
  * other hooks, discovery settings, unknown values and number spellings remain
@@ -51,6 +52,24 @@ export declare function upgradeClaudeSettingsWitness(previous: {
51
52
  hashMode?: "bytes" | "claude-settings-v1" | "claude-settings-v2";
52
53
  sha256: string;
53
54
  }, reviewedSettingsPath: string): ReturnType<typeof captureClaudeSettingsV3>;
55
+ /** Explicit review that additionally permits a built-in top-level theme.
56
+ * v1/v2/v3 witnesses keep their meaning; nothing selects v4 implicitly. */
57
+ export declare function captureClaudeSettingsV4(path: string, budget?: ClaudeSettingsWitnessBudget): {
58
+ path: string;
59
+ hashMode: "claude-settings-v4";
60
+ sha256: string;
61
+ };
62
+ export declare function hashClaudeSettingsReplacementV4(text: string, budget: ClaudeSettingsWitnessBudget): string;
63
+ /** Explicit opt-in upgrade to v4 with an exact preserved preimage of the prior
64
+ * review (raw bytes, v1, v2 or v3). It proves that every setting v4 binds is
65
+ * unchanged; any other drift, including skipDangerousModePermissionPrompt,
66
+ * refuses. A stored witness is never rewritten without this call.
67
+ */
68
+ export declare function upgradeClaudeSettingsWitnessV4(previous: {
69
+ path: string;
70
+ hashMode?: "bytes" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3";
71
+ sha256: string;
72
+ }, reviewedSettingsPath: string): ReturnType<typeof captureClaudeSettingsV4>;
54
73
  /** Strict bounded JSON control witness; metadata exclusion must be explicit. */
55
74
  export declare function hashNativeJsonControls(text: string, excludedMetadata?: "version"): string;
56
75
  /** Sumi's versioned review uses the same strict JSON parser and bounded syntax
@@ -1,3 +1,6 @@
1
- export type CodexNativeCapability = "hooks" | "qualified-skill-catalog";
1
+ /** `installed-plugin-review`: the 0.160 `plugin/installed` source identities and
2
+ * plugin loader semantics behind local installation inputs, inert disabled
3
+ * nonremote plugins and remote refresh denials. */
4
+ export type CodexNativeCapability = "hooks" | "qualified-skill-catalog" | "installed-plugin-review";
2
5
  export declare function supportsCodexNativeCapability(version: unknown, capability: CodexNativeCapability): boolean;
3
6
  export declare const SUPPORTED_CODEX_HOOK_VERSIONS: readonly string[];