@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/README.md CHANGED
@@ -559,7 +559,8 @@ The plan proves the preserved original matches every witness being replaced,
559
559
  permits only the documented non-discovery differences, and labels the replacement
560
560
  witness (the JSON receipt lists them under `replacedWitnesses`). `--apply`
561
561
  preserves and reads back the original policy before writing, and never writes the
562
- native configuration. Claude upgrades to `claude-settings-v3`; Codex uses
562
+ native configuration. Claude upgrades to `claude-settings-v3`, or to
563
+ `claude-settings-v4` with `--claude-witness-version 4`; Codex uses
563
564
  `codex-settings-v2`, which also replaces the narrower automatic
564
565
  `plugins`/`marketplaces`/`skills` projection of the same file once the preserved
565
566
  original is proved to explain it. That projection is order-sensitive, so it keeps
@@ -591,10 +592,13 @@ skills hook native-catalog --cwd /absolute/project --output /private/catalog.jso
591
592
  skills hook install --agent codex --codex-native-catalog /private/catalog.json --json
592
593
  ```
593
594
 
594
- Codex 0.159.2 and 0.160.0 have measured qualified-name catalog support. Native
595
- transport, capture and review use one capability registry. Unmeasured releases
596
- refuse until the native catalog/name-denial and hook-dispatch acceptance tests
597
- pass; an RPC handshake alone does not establish those semantics.
595
+ Codex 0.159.2, 0.160.0 and 0.160.1 have measured qualified-name catalog support.
596
+ Native transport, capture and review use one capability registry of exact
597
+ releases. Unmeasured releases, including 0.160.2 and 0.161.0, refuse until the
598
+ native catalog/name-denial and hook-dispatch acceptance tests pass; an RPC
599
+ handshake alone does not establish those semantics. Admitting a release does not
600
+ admit its corpus: the transport still requires the existing native corpus
601
+ admission (see `docs/codex-corpus-writers.md`).
598
602
 
599
603
  The capture refuses an existing output file, writes mode `0600`, verifies the
600
604
  exact bytes, and reports only version, directory, count, file path and SHA-256.
@@ -642,7 +646,7 @@ different extant, witnessed user version. Individual missing files, current
642
646
  registered roots, aliases and registration changes still refuse; historical
643
647
  source hashes and typed settings witnesses remain intact.
644
648
 
645
- For a Codex 0.160.0 remote refresh omitted by an existing qualified-name disable,
649
+ For a Codex 0.160.0 or 0.160.1 remote refresh omitted by an existing qualified-name disable,
646
650
  a fresh native catalog and discovery review can plan the new exact path disable
647
651
  in the same transaction. The remote installation receipt, namespace and capability
648
652
  controls must match; conflicting enable rules, unknown names and ambiguous
@@ -1304,6 +1308,21 @@ the backup keeps the exact previous launcher (symlink text or pinned bytes), and
1304
1308
  projection and `skills self-update` read a managed pinned launcher as the exact
1305
1309
  entry it runs and bind the launcher's own bytes as well.
1306
1310
 
1311
+ An updater from before pinned launchers (0.10.48 and earlier) writes bare symlinks,
1312
+ including when it installs a version that has them, and an exact-version update to
1313
+ the version already installed is refused. `skills self-update --adopt-aliases` pins
1314
+ those launchers in place: in `~/.local/bin`, `~/.bun/bin`, `/opt/homebrew/bin` and
1315
+ `/usr/local/bin`, a bare symlink that already reaches the current runtime's entry is
1316
+ adopted like an alias still pointing at an older install. The same refusals apply
1317
+ (an unsafe or unwritable directory, a launcher owned by another account, a foreign
1318
+ file, an unsupported link chain), the exact symlink is kept as
1319
+ `<launcher>.skills-alias-prev-<receipt id>`, the receipt records it as the old
1320
+ `symlink` shape, and `--rollback-aliases <receipt id>` restores it exactly. A
1321
+ launcher already pinned to the current runtime is left unchanged. Undo in reverse
1322
+ order: roll back the adoption before rolling back the runtime update that wrote
1323
+ those symlinks, because that update's `--rollback` refuses with
1324
+ `LAUNCHER_DRIFT_ROLLBACK_REFUSED` while the adopted pinned launchers are in place.
1325
+
1307
1326
  To require a minimum dependency age, pass a positive integer number of days and
1308
1327
  repeat the exclusion option for package names or package globs:
1309
1328
 
@@ -2568,3 +2587,18 @@ Upgrade an existing review with `skills hook rebind-settings --agent codex
2568
2587
  Review the plan before adding `--apply`; the original review preimage must match
2569
2588
  its original mode, and unrelated changes refuse. This preserves the native
2570
2589
  configuration, root aliases, other witnesses and active session pins.
2590
+
2591
+ `claude-settings-v4` is an explicit successor to `claude-settings-v3`. It
2592
+ additionally ignores only a top-level `theme` whose value is exactly one of the
2593
+ documented built-in values (`auto`, `dark`, `light`, `dark-daltonized`,
2594
+ `light-daltonized`, `dark-ansi`, `light-ansi`). Custom and plugin themes, other
2595
+ values or types, nested `theme` keys, `skipDangerousModePermissionPrompt` and
2596
+ every permission, hook, environment, helper and unknown setting stay bound.
2597
+ Existing raw/V1/V2/V3 reviews retain their original meaning.
2598
+
2599
+ Capture with `skills hook witness --kind claude-settings-v4 --path <settings.json>`.
2600
+ Upgrade an existing review with `skills hook rebind-settings --agent claude
2601
+ --claude-witness-version 4 --reviewed-preimage <preserved-settings.json>
2602
+ --expected-policy-sha256 <policy-hash> --expected-settings-sha256 <settings-hash>`.
2603
+ Review the plan before adding `--apply`. See
2604
+ [built-in theme reviews](docs/plugin-admission.md#explicit-v4-built-in-theme-reviews).
package/bin/index.js CHANGED
@@ -36888,7 +36888,7 @@ var package_default;
36888
36888
  var init_package = __esm(() => {
36889
36889
  package_default = {
36890
36890
  name: "@hasna/skills",
36891
- version: "0.10.49",
36891
+ version: "0.10.51",
36892
36892
  description: "Skills library for AI coding agents",
36893
36893
  type: "module",
36894
36894
  bin: {
@@ -40066,7 +40066,9 @@ function inferencePreference(key, child, version) {
40066
40066
  function settingsDigest(text, version = 1) {
40067
40067
  const value = parse(text);
40068
40068
  value.entries = value.entries.filter(([key, child]) => {
40069
- if (version !== 1 && inferencePreference(key, child, version))
40069
+ if (version === 4 && key === "theme" && child.kind === "string" && BUILTIN_THEMES.has(child.value))
40070
+ return false;
40071
+ if (version !== 1 && inferencePreference(key, child, version === 4 ? 3 : version))
40070
40072
  return false;
40071
40073
  if (key === "model") {
40072
40074
  need(child.kind === "string", "contains an invalid model selection");
@@ -40124,6 +40126,26 @@ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
40124
40126
  need(settingsDigest(before2, 3) === sha256, "non-preference settings changed; explicit discovery review required");
40125
40127
  return { path: previous.path, hashMode: "claude-settings-v3", sha256 };
40126
40128
  }
40129
+ function captureClaudeSettingsV4(path, budget = { remaining: 256 * 1024 * 1024 }) {
40130
+ return { path, hashMode: "claude-settings-v4", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 4) };
40131
+ }
40132
+ function hashClaudeSettingsReplacementV4(text, budget) {
40133
+ need(typeof text === "string", "requires settings text");
40134
+ const bytes = Buffer.byteLength(text);
40135
+ need(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
40136
+ budget.remaining -= bytes;
40137
+ return settingsDigest(text, 4);
40138
+ }
40139
+ function upgradeClaudeSettingsWitnessV4(previous, reviewedSettingsPath) {
40140
+ 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");
40141
+ const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
40142
+ const before2 = readNativeSettingsWitnessFile(reviewedSettingsPath, budget), current = readNativeSettingsWitnessFile(previous.path, budget);
40143
+ const beforeDigest = previous.hashMode === undefined || previous.hashMode === "bytes" ? createHash2("sha256").update(before2).digest("hex") : settingsDigest(before2, previous.hashMode === "claude-settings-v1" ? 1 : previous.hashMode === "claude-settings-v2" ? 2 : 3);
40144
+ need(beforeDigest === previous.sha256, "reviewed preimage does not match the prior witness");
40145
+ const sha256 = settingsDigest(current, 4);
40146
+ need(settingsDigest(before2, 4) === sha256, "non-preference settings changed; explicit discovery review required");
40147
+ return { path: previous.path, hashMode: "claude-settings-v4", sha256 };
40148
+ }
40127
40149
  function hashNativeJsonControls(text, excludedMetadata) {
40128
40150
  need(Buffer.byteLength(text) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, "exceeds its byte limit");
40129
40151
  const value = parse(text);
@@ -40156,7 +40178,7 @@ function hashSumiNativeJsonControls(text) {
40156
40178
  });
40157
40179
  return createHash2("sha256").update("hasna.skills.sumi-settings.v1\x00").update(JSON.stringify(value)).digest("hex");
40158
40180
  }
40159
- var CLAUDE_SETTINGS_WITNESS_LIMITS, sameFile = (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, BOOLEAN_PREFERENCES, ENUM_PREFERENCES, BUILTIN_MODEL_SELECTIONS, PERSISTED_EFFORT_LEVELS;
40181
+ var CLAUDE_SETTINGS_WITNESS_LIMITS, sameFile = (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, BOOLEAN_PREFERENCES, ENUM_PREFERENCES, BUILTIN_MODEL_SELECTIONS, CLAUDE_BUILTIN_THEMES, BUILTIN_THEMES, PERSISTED_EFFORT_LEVELS;
40160
40182
  var init_claude_settings_witness = __esm(() => {
40161
40183
  CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
40162
40184
  BOOLEAN_PREFERENCES = new Set([
@@ -40210,6 +40232,8 @@ var init_claude_settings_witness = __esm(() => {
40210
40232
  "claude-opus-4-6[1m]",
40211
40233
  "claude-sonnet-4-6[1m]"
40212
40234
  ]);
40235
+ CLAUDE_BUILTIN_THEMES = Object.freeze(["auto", "dark", "light", "dark-daltonized", "light-daltonized", "dark-ansi", "light-ansi"]);
40236
+ BUILTIN_THEMES = new Set(CLAUDE_BUILTIN_THEMES);
40213
40237
  PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
40214
40238
  });
40215
40239
 
@@ -40245,6 +40269,29 @@ var init_sumi_settings_witness = __esm(() => {
40245
40269
  SUMI_DISCOVERY_PROJECTION_FIELDS = ["skills", "plugin", "plugins", "permissions"];
40246
40270
  });
40247
40271
 
40272
+ // src/lib/codex-native-compatibility.ts
40273
+ function supportsCodexNativeCapability(version, capability) {
40274
+ return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
40275
+ }
40276
+ var MEASURED_CODEX_CAPABILITIES, SUPPORTED_CODEX_HOOK_VERSIONS;
40277
+ var init_codex_native_compatibility = __esm(() => {
40278
+ MEASURED_CODEX_CAPABILITIES = {
40279
+ "codex-cli 0.153.0": ["hooks"],
40280
+ "codex-cli 0.154.0": ["hooks"],
40281
+ "codex-cli 0.155.0": ["hooks"],
40282
+ "codex-cli 0.155.1": ["hooks"],
40283
+ "codex-cli 0.156.1": ["hooks"],
40284
+ "codex-cli 0.157.0": ["hooks"],
40285
+ "codex-cli 0.157.1": ["hooks"],
40286
+ "codex-cli 0.158.0": ["hooks"],
40287
+ "codex-cli 0.159.0": ["hooks"],
40288
+ "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
40289
+ "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
40290
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
40291
+ };
40292
+ SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
40293
+ });
40294
+
40248
40295
  // src/lib/agent-policy-limits.ts
40249
40296
  import { basename as basename3, isAbsolute as isAbsolute3, join as join11, resolve as resolve4, sep as sep2 } from "path";
40250
40297
  function object(value) {
@@ -40311,7 +40358,7 @@ function assertAgentPolicyCollections(policy) {
40311
40358
  }
40312
40359
  if (bridge.codexInactivePlugins !== undefined) {
40313
40360
  const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
40314
- 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));
40361
+ 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));
40315
40362
  for (const control of controls) {
40316
40363
  requireBound(object(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
40317
40364
  text(control.pluginId, 1024);
@@ -40363,7 +40410,7 @@ function assertAgentPolicyCollections(policy) {
40363
40410
  }
40364
40411
  if (value.codexInstallationInputs !== undefined) {
40365
40412
  const proof = record(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
40366
- 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);
40413
+ 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);
40367
40414
  for (const input of inputs) {
40368
40415
  requireBound(object(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
40369
40416
  text(input.pluginId, 1024);
@@ -40387,14 +40434,14 @@ function assertAgentPolicyCollections(policy) {
40387
40434
  text(source.path);
40388
40435
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
40389
40436
  if (source.hashMode !== undefined)
40390
- 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));
40437
+ 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));
40391
40438
  if (source.hashMode === "claude-plugin-manifest-v1")
40392
40439
  requireBound(agent === "claude" && value.agent === "claude" && ["automatic", "reviewed"].includes(value.method) && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && source.path.endsWith(join11(sep2, ".claude-plugin", "plugin.json")) && source.managedPlugins === undefined);
40393
40440
  if (source.hashMode === "sumi-settings-v1")
40394
40441
  requireBound(agent === "sumi" && value.agent === "sumi" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && basename3(source.path) === "sumi.json");
40395
40442
  if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4"))
40396
40443
  requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && basename3(source.path) === "config.toml");
40397
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
40444
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4"))
40398
40445
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && basename3(source.path) === "settings.json");
40399
40446
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
40400
40447
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve4(source.path) === source.path && basename3(source.path) === "known_marketplaces.json");
@@ -40428,6 +40475,7 @@ function assertAgentPolicyCollections(policy) {
40428
40475
  }
40429
40476
  var AGENT_POLICY_LIMITS;
40430
40477
  var init_agent_policy_limits = __esm(() => {
40478
+ init_codex_native_compatibility();
40431
40479
  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 });
40432
40480
  });
40433
40481
 
@@ -51551,28 +51599,6 @@ function isCodexLocalStdioMcpServer(name, server) {
51551
51599
  }
51552
51600
  }
51553
51601
 
51554
- // src/lib/codex-native-compatibility.ts
51555
- function supportsCodexNativeCapability(version, capability) {
51556
- return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
51557
- }
51558
- var MEASURED_CODEX_CAPABILITIES, SUPPORTED_CODEX_HOOK_VERSIONS;
51559
- var init_codex_native_compatibility = __esm(() => {
51560
- MEASURED_CODEX_CAPABILITIES = {
51561
- "codex-cli 0.153.0": ["hooks"],
51562
- "codex-cli 0.154.0": ["hooks"],
51563
- "codex-cli 0.155.0": ["hooks"],
51564
- "codex-cli 0.155.1": ["hooks"],
51565
- "codex-cli 0.156.1": ["hooks"],
51566
- "codex-cli 0.157.0": ["hooks"],
51567
- "codex-cli 0.157.1": ["hooks"],
51568
- "codex-cli 0.158.0": ["hooks"],
51569
- "codex-cli 0.159.0": ["hooks"],
51570
- "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
51571
- "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
51572
- };
51573
- SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
51574
- });
51575
-
51576
51602
  // src/lib/codex-hook-rpc.ts
51577
51603
  import { join as join20 } from "path";
51578
51604
  import { execFile, spawn as spawn2 } from "child_process";
@@ -52169,6 +52195,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache3, controls, read2, rules
52169
52195
  function reviewCodexPluginControls(catalog, documents, cache3, cwd2, read2, rules, pluginSettings, plannedRules, denials = []) {
52170
52196
  if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd2)
52171
52197
  refuse2();
52198
+ const installedPluginReview = supportsCodexNativeCapability(catalog.version, "installed-plugin-review");
52172
52199
  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)))
52173
52200
  refuse2();
52174
52201
  const usedDenials = new Set;
@@ -52200,7 +52227,7 @@ function reviewCodexPluginControls(catalog, documents, cache3, cwd2, read2, rule
52200
52227
  if (listed.length > 1)
52201
52228
  refuse2();
52202
52229
  const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
52203
- 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])) {
52230
+ if (installedPluginReview && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename6(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
52204
52231
  const plugin = installed[0], input = { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceRoot: plugin.sourcePath, sourceSha256: plugin.sourceSha256 };
52205
52232
  if (input.sourceRoot !== located.root && !input.sourceRoot.startsWith(cache3 + sep6) && (lstatSync13(input.sourceRoot, { throwIfNoEntry: false }) || documents.some((path) => path.startsWith(input.sourceRoot + sep6)))) {
52206
52233
  if (skills.some((skill) => skill.path === input.sourceRoot || skill.path.startsWith(input.sourceRoot + sep6)) || !sourceInputUnchanged(input, cache3, read2))
@@ -52209,7 +52236,7 @@ function reviewCodexPluginControls(catalog, documents, cache3, cwd2, read2, rule
52209
52236
  }
52210
52237
  }
52211
52238
  const settings = pluginSettings && typeof pluginSettings === "object" && !Array.isArray(pluginSettings) ? pluginSettings : undefined;
52212
- 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) {
52239
+ 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) {
52213
52240
  if (!Array.isArray(rules))
52214
52241
  refuse2();
52215
52242
  const exactPathDeny = rules.some((rule) => rule?.path === document3 && rule?.enabled === false);
@@ -52242,12 +52269,12 @@ function reviewCodexPluginControls(catalog, documents, cache3, cwd2, read2, rule
52242
52269
  const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document3 && rule?.enabled === false);
52243
52270
  const nativeSkill = { name: parsed.name, path: document3, pluginId: expectedPluginId, enabled: true };
52244
52271
  const denial = denials.find((item) => item.path === document3);
52245
- const explicitDeny = denial !== undefined && remotePluginId !== undefined && catalog.version === "codex-cli 0.160.0" && denial.name === parsed.name && denial.sha256 === createHash12("sha256").update(read2(document3)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document3 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document3 && rule.enabled === false);
52272
+ const explicitDeny = denial !== undefined && remotePluginId !== undefined && installedPluginReview && denial.name === parsed.name && denial.sha256 === createHash12("sha256").update(read2(document3)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document3 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document3 && rule.enabled === false);
52246
52273
  if (denial && !explicitDeny)
52247
52274
  refuse2();
52248
52275
  if (explicitDeny)
52249
52276
  usedDenials.add(document3);
52250
- 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 === document3 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document3 && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
52277
+ 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 === document3 || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document3 && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
52251
52278
  if (!explicitDeny && (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules)))
52252
52279
  refuse2();
52253
52280
  }
@@ -55028,7 +55055,7 @@ function planAgentIntegration(options) {
55028
55055
  const { codexInstallationInputs: previousInputs, ...current } = rebound;
55029
55056
  const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
55030
55057
  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;
55031
- const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: "codex-cli 0.160.0", catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
55058
+ const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: codexPluginSkillReview.version, catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
55032
55059
  return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(join28(home, ".codex/config.toml"), aliases))?.after ?? codexConfigBefore ?? "").skills?.config ?? []);
55033
55060
  });
55034
55061
  const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
@@ -55262,6 +55289,8 @@ function atomicWrite2(path, content) {
55262
55289
  function planAgentSettingsWitnessUpgrade(options) {
55263
55290
  if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3, 4].includes(options.targetCodexVersion)))
55264
55291
  throw new Error("Invalid Codex witness target version");
55292
+ if (options.targetClaudeVersion !== undefined && (options.agent !== "claude" || ![3, 4].includes(options.targetClaudeVersion)))
55293
+ throw new Error("Invalid Claude witness target version");
55265
55294
  const home = resolve14(options.home ?? homedir3()), dataDir3 = options.dataDir ?? getDataDirReadOnly();
55266
55295
  const snapshot2 = readManagedSkillPolicySnapshot(dataDir3);
55267
55296
  if (!snapshot2 || sha3(snapshot2.text) !== options.expectedPolicySha256)
@@ -55309,7 +55338,7 @@ function planAgentSettingsWitnessUpgrade(options) {
55309
55338
  replaced.push(source);
55310
55339
  }
55311
55340
  }
55312
- 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);
55341
+ 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);
55313
55342
  const rebound = [];
55314
55343
  for (const source of binding.sources) {
55315
55344
  if (!replaced.includes(source)) {
@@ -55355,7 +55384,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
55355
55384
  const upgrade = plan.settingsWitnessUpgrade;
55356
55385
  if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
55357
55386
  throw new Error("Invalid settings witness upgrade plan");
55358
- 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 });
55387
+ 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 });
55359
55388
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
55360
55389
  throw new Error("Settings witness upgrade plan changed");
55361
55390
  provenDiscovery = verified.discoveryBefore[0];
@@ -59082,13 +59111,13 @@ function projected(source, changes, budget2 = discoveryByteBudget()) {
59082
59111
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
59083
59112
  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;
59084
59113
  }
59085
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
59114
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) {
59086
59115
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
59087
59116
  throw new Error("Claude settings witnesses require exact typed metadata");
59088
- const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget2).sha256;
59117
+ 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;
59089
59118
  if (current !== source.sha256)
59090
59119
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
59091
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget2) : current;
59120
+ 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;
59092
59121
  }
59093
59122
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
59094
59123
  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))
@@ -59299,7 +59328,7 @@ function codexInstallationRoots(binding) {
59299
59328
  const proof = binding.codexInstallationInputs;
59300
59329
  if (proof === undefined)
59301
59330
  return [];
59302
- 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)
59331
+ 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)
59303
59332
  throw new Error("Invalid native installation input proof");
59304
59333
  const cache3 = dirname19(dirname19(proof.plugins[0].pluginParent));
59305
59334
  const config = join37(dirname19(dirname19(cache3)), "config.toml"), home = dirname19(dirname19(dirname19(cache3)));
@@ -59323,7 +59352,7 @@ function assertMarketplaceBinding(binding, source) {
59323
59352
  throw new Error("Sumi settings witnesses require explicit reviewed Sumi configuration");
59324
59353
  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"))
59325
59354
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
59326
- 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"))
59355
+ 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"))
59327
59356
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
59328
59357
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
59329
59358
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -59513,7 +59542,7 @@ function resolveAgentDiscovery(options) {
59513
59542
  verifyAgentDiscovery(retained);
59514
59543
  if (!retained.sources.some((source) => source.path === canonical7(configPath) && source.format === undefined && source.fields === undefined))
59515
59544
  unresolved("retained review is missing the full agent configuration source");
59516
- 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)))
59545
+ 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)))
59517
59546
  unresolved("retained settings witness names another configuration source");
59518
59547
  const covered = supersedesSettingsProjection(agent, retained.sources, canonical7(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical7(configPath), source)) : sources;
59519
59548
  if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual11(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
@@ -59527,7 +59556,7 @@ function resolveAgentDiscovery(options) {
59527
59556
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
59528
59557
  throw new Error("Explicit discovery reviews require full source-file hashes");
59529
59558
  verifyAgentDiscovery(supplied);
59530
- 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)))
59559
+ 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)))
59531
59560
  throw new Error("Semantic settings witnesses must name the configured agent configuration source");
59532
59561
  if (!review.sources.some((source) => source.path === canonical7(configPath)))
59533
59562
  throw new Error("Discovery review must include the agent configuration source");
@@ -59694,6 +59723,7 @@ var init_agent_discovery = __esm(() => {
59694
59723
  init_codex_plugin_skill_controls();
59695
59724
  init_agent_sumi();
59696
59725
  init_native_discovery_roots();
59726
+ init_codex_native_compatibility();
59697
59727
  init_agent_discovery_directories();
59698
59728
  });
59699
59729
 
@@ -112470,7 +112500,7 @@ function adoptCopyfileAliases(options = {}) {
112470
112500
  const actual = launcher.target;
112471
112501
  const oldLinkTarget = launcher.kind === "symlink" ? launcher.linkTarget : actual;
112472
112502
  const newTarget = join60(layout.currentPackageRoot, target);
112473
- if (actual === newTarget)
112503
+ if (actual === newTarget && launcher.kind === "pinned")
112474
112504
  continue;
112475
112505
  const chain = inspectAliasPackageChain(home, path, oldLinkTarget, actual, target);
112476
112506
  const source = validateAliasSource(home, name, actual);
@@ -112627,7 +112657,7 @@ function rollbackCopyfileAliases(receiptId, options = {}) {
112627
112657
  if (entryExists(item.backupPath)) {
112628
112658
  if (!launcherIs(item.backupPath, oldLauncherState(item)))
112629
112659
  throw new Error("ALIAS_PREIMAGE_DRIFT");
112630
- } else if (current !== item.oldTarget) {
112660
+ } else if (current !== item.oldTarget || !launcherIs(item.path, oldLauncherState(item))) {
112631
112661
  throw new Error("ALIAS_BACKUP_MISSING_FOR_SWITCH");
112632
112662
  }
112633
112663
  }
@@ -116159,19 +116189,21 @@ function registerAgentIntegration(parent) {
116159
116189
  process.exitCode = 1;
116160
116190
  }
116161
116191
  });
116162
- hook.command("witness").requiredOption("--kind <kind>", "sumi-settings-v1, claude-settings-v3, claude-settings-v2, codex-settings-v4, codex-settings-v3, codex-settings-v2, codex-settings-v1 or claude-marketplace-registry-v2").requiredOption("--path <path>", "Canonical absolute path to the reviewed settings or registry file").option("--json", "Output the discovery witness as JSON", false).description("Capture an explicit versioned review witness without installing it").action(async (options) => {
116163
- const capture2 = options.kind === "sumi-settings-v1" ? captureSumiSettings : options.kind === "codex-settings-v4" ? captureCodexSettingsV4 : options.kind === "codex-settings-v3" ? captureCodexSettingsV3 : options.kind === "codex-settings-v2" ? captureCodexSettingsV2 : options.kind === "codex-settings-v1" ? captureCodexSettings : options.kind === "claude-settings-v3" ? captureClaudeSettingsV3 : options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
116192
+ hook.command("witness").requiredOption("--kind <kind>", "sumi-settings-v1, claude-settings-v4, claude-settings-v3, claude-settings-v2, codex-settings-v4, codex-settings-v3, codex-settings-v2, codex-settings-v1 or claude-marketplace-registry-v2").requiredOption("--path <path>", "Canonical absolute path to the reviewed settings or registry file").option("--json", "Output the discovery witness as JSON", false).description("Capture an explicit versioned review witness without installing it").action(async (options) => {
116193
+ const capture2 = options.kind === "sumi-settings-v1" ? captureSumiSettings : options.kind === "codex-settings-v4" ? captureCodexSettingsV4 : options.kind === "codex-settings-v3" ? captureCodexSettingsV3 : options.kind === "codex-settings-v2" ? captureCodexSettingsV2 : options.kind === "codex-settings-v1" ? captureCodexSettings : options.kind === "claude-settings-v4" ? captureClaudeSettingsV4 : options.kind === "claude-settings-v3" ? captureClaudeSettingsV3 : options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
116164
116194
  if (!capture2)
116165
- throw new Error("Unsupported witness kind; select sumi-settings-v1, claude-settings-v3, claude-settings-v2, codex-settings-v4, codex-settings-v3, codex-settings-v2, codex-settings-v1 or claude-marketplace-registry-v2");
116195
+ throw new Error("Unsupported witness kind; select sumi-settings-v1, claude-settings-v4, claude-settings-v3, claude-settings-v2, codex-settings-v4, codex-settings-v3, codex-settings-v2, codex-settings-v1 or claude-marketplace-registry-v2");
116166
116196
  await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
116167
116197
  });
116168
- hook.command("rebind-settings").requiredOption("--agent <agent>", "claude, codex or sumi").requiredOption("--reviewed-preimage <path>", "Exact preserved legacy settings.json, config.toml or sumi.json").requiredOption("--expected-policy-sha256 <sha256>", "Exact current managed policy bytes").requiredOption("--expected-settings-sha256 <sha256>", "Exact current native settings bytes").option("--codex-witness-version <version>", "Explicit target: 2 (legacy default) 3 (service tier and model-advertised effort), or 4 (native availability UI counts)").option("--apply", "Apply the explicit semantic witness upgrade with preservation and readback", false).option("--json", "Return the metadata-only plan or receipt", false).description("Explicitly migrate a legacy native settings witness after proving only known preferences changed").action(async (options) => {
116198
+ hook.command("rebind-settings").requiredOption("--agent <agent>", "claude, codex or sumi").requiredOption("--reviewed-preimage <path>", "Exact preserved legacy settings.json, config.toml or sumi.json").requiredOption("--expected-policy-sha256 <sha256>", "Exact current managed policy bytes").requiredOption("--expected-settings-sha256 <sha256>", "Exact current native settings bytes").option("--codex-witness-version <version>", "Explicit target: 2 (legacy default) 3 (service tier and model-advertised effort), or 4 (native availability UI counts)").option("--claude-witness-version <version>", "Explicit target: 3 (default) or 4 (also permits a built-in top-level theme)").option("--apply", "Apply the explicit semantic witness upgrade with preservation and readback", false).option("--json", "Return the metadata-only plan or receipt", false).description("Explicitly migrate a legacy native settings witness after proving only known preferences changed").action(async (options) => {
116169
116199
  try {
116170
116200
  if (!["claude", "codex", "sumi"].includes(options.agent))
116171
116201
  throw new Error("Settings witness rebind accepts claude, codex or sumi");
116172
116202
  if (options.codexWitnessVersion !== undefined && !["2", "3", "4"].includes(options.codexWitnessVersion))
116173
116203
  throw new Error("Codex witness version accepts 2, 3 or 4");
116174
- const plan = planAgentSettingsWitnessUpgrade({ ...options.codexWitnessVersion !== undefined ? { targetCodexVersion: Number(options.codexWitnessVersion) } : {}, agent: options.agent, reviewedPreimage: options.reviewedPreimage, expectedPolicySha256: options.expectedPolicySha256, expectedSettingsSha256: options.expectedSettingsSha256 });
116204
+ if (options.claudeWitnessVersion !== undefined && !["3", "4"].includes(options.claudeWitnessVersion))
116205
+ throw new Error("Claude witness version accepts 3 or 4");
116206
+ const plan = planAgentSettingsWitnessUpgrade({ ...options.codexWitnessVersion !== undefined ? { targetCodexVersion: Number(options.codexWitnessVersion) } : {}, ...options.claudeWitnessVersion !== undefined ? { targetClaudeVersion: Number(options.claudeWitnessVersion) } : {}, agent: options.agent, reviewedPreimage: options.reviewedPreimage, expectedPolicySha256: options.expectedPolicySha256, expectedSettingsSha256: options.expectedSettingsSha256 });
116175
116207
  const result2 = options.apply ? applyAgentIntegration(plan) : { changed: [], backups: [] };
116176
116208
  const receipt = { applied: options.apply, settingsWitnessUpgrade: plan.settingsWitnessUpgrade, ...result2 };
116177
116209
  await writeCliOutput(options.json ? JSON.stringify(receipt) : `Settings witness ${options.apply ? "upgraded" : "planned"} for ${options.agent}. Native configuration was not changed.`);