@hasna/skills 0.9.16 → 0.9.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.9.16",
36871
+ version: "0.9.17",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -39947,8 +39947,8 @@ function assertAgentPolicyCollections(policy) {
39947
39947
  text(source.path);
39948
39948
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
39949
39949
  if (source.hashMode !== undefined)
39950
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
39951
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
39950
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
39951
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
39952
39952
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "settings.json");
39953
39953
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
39954
39954
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
@@ -50545,7 +50545,7 @@ function canonical2(value) {
50545
50545
  return { kind: "array", items: value.items.map(canonical2) };
50546
50546
  return value;
50547
50547
  }
50548
- function inferencePreference(key, child) {
50548
+ function inferencePreference(key, child, version) {
50549
50549
  if (key === "skipDangerousModePermissionPrompt") {
50550
50550
  need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
50551
50551
  return false;
@@ -50558,7 +50558,7 @@ function inferencePreference(key, child) {
50558
50558
  return false;
50559
50559
  need2(child.kind === "object", "requires an object of model preferences");
50560
50560
  child.entries = child.entries.filter(([model, preferences]) => {
50561
- if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
50561
+ if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
50562
50562
  return true;
50563
50563
  const effort = preferences.entries[0][1];
50564
50564
  need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
@@ -50569,7 +50569,7 @@ function inferencePreference(key, child) {
50569
50569
  function settingsDigest(text2, version = 1) {
50570
50570
  const value = parse3(text2);
50571
50571
  value.entries = value.entries.filter(([key, child]) => {
50572
- if (version === 2 && inferencePreference(key, child))
50572
+ if (version !== 1 && inferencePreference(key, child, version))
50573
50573
  return false;
50574
50574
  if (key === "model") {
50575
50575
  need2(child.kind === "string", "contains an invalid model selection");
@@ -50607,6 +50607,16 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
50607
50607
  budget.remaining -= bytes;
50608
50608
  return settingsDigest(text2, 2);
50609
50609
  }
50610
+ function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
50611
+ return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
50612
+ }
50613
+ function hashClaudeSettingsReplacementV3(text2, budget) {
50614
+ need2(typeof text2 === "string", "requires settings text");
50615
+ const bytes = Buffer.byteLength(text2);
50616
+ need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
50617
+ budget.remaining -= bytes;
50618
+ return settingsDigest(text2, 3);
50619
+ }
50610
50620
  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;
50611
50621
  var init_claude_settings_witness = __esm(() => {
50612
50622
  CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
@@ -54886,13 +54896,13 @@ function read2(path, changes) {
54886
54896
  return readFileSync17(path, "utf8");
54887
54897
  }
54888
54898
  function projected(source, changes, budget = discoveryByteBudget()) {
54889
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
54899
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
54890
54900
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
54891
54901
  throw new Error("Claude settings witnesses require exact typed metadata");
54892
- const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
54902
+ const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
54893
54903
  if (current !== source.sha256)
54894
54904
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
54895
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
54905
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
54896
54906
  }
54897
54907
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
54898
54908
  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))
@@ -54954,7 +54964,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
54954
54964
  safe4(root2);
54955
54965
  }
54956
54966
  function assertMarketplaceBinding(binding, source) {
54957
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
54967
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
54958
54968
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
54959
54969
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
54960
54970
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -55085,7 +55095,7 @@ function resolveAgentDiscovery(options) {
55085
55095
  verifyAgentDiscovery(retained);
55086
55096
  if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
55087
55097
  unresolved("retained review is missing the full agent configuration source");
55088
- if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
55098
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
55089
55099
  unresolved("retained settings witness names another configuration source");
55090
55100
  if (sources.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))
55091
55101
  unresolved("retained review is missing current runtime discovery coverage");
@@ -55101,7 +55111,7 @@ function resolveAgentDiscovery(options) {
55101
55111
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
55102
55112
  throw new Error("Explicit discovery reviews require full source-file hashes");
55103
55113
  verifyAgentDiscovery(supplied);
55104
- if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
55114
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
55105
55115
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
55106
55116
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
55107
55117
  throw new Error("Discovery review must include the agent configuration source");
@@ -80156,8 +80166,9 @@ Preserved original receipt: ${result2.archivePath}` : `Planned one session recon
80156
80166
  });
80157
80167
  }
80158
80168
  function reportContextError(error61, json2 = false) {
80159
- const code = error61 instanceof SkillSelectionError ? error61.code : "SKILLS_CONTEXT_FAILED";
80160
- const message = error61 instanceof SkillSelectionError ? error61.message : "Skills context could not be loaded. Check authentication and the selected profile.";
80169
+ const storageFull = error61 instanceof Error && ["ENOSPC", "EDQUOT"].includes(error61.code ?? "");
80170
+ const code = error61 instanceof SkillSelectionError ? error61.code : storageFull ? "SKILLS_STORAGE_FULL" : "SKILLS_CONTEXT_FAILED";
80171
+ const message = error61 instanceof SkillSelectionError ? error61.message : storageFull ? "Skills could not save verified local state because the filesystem is full or its quota is exhausted. Free space through your approved recoverable cleanup process, then retry the same session." : "Skills context could not be loaded. Check authentication and the selected profile.";
80161
80172
  if (json2)
80162
80173
  console.log(JSON.stringify({ error: { code, message } }));
80163
80174
  else
@@ -108616,7 +108627,7 @@ async function connectCodexHookRpc(options) {
108616
108627
  }
108617
108628
  var SUPPORTED_CODEX_HOOK_VERSIONS;
108618
108629
  var init_codex_hook_rpc = __esm(() => {
108619
- SUPPORTED_CODEX_HOOK_VERSIONS = ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1"];
108630
+ SUPPORTED_CODEX_HOOK_VERSIONS = ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1"];
108620
108631
  });
108621
108632
 
108622
108633
  // src/lib/codex-hook-trust-identity.ts
@@ -109054,6 +109065,9 @@ function hookFailureReason(error61, profileId) {
109054
109065
  const profile = typeof profileId === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) && !profileId.includes("..") ? profileId : undefined;
109055
109066
  const identity4 = [profile ? `profile=${profile}` : undefined, owned ? `stage=${error61.stage}` : undefined].filter(Boolean).join(", ");
109056
109067
  const prefix = `Skills context is unavailable [${code2}]${identity4 ? ` (${identity4})` : ""}.`;
109068
+ if (code2 === "SKILLS_STORAGE_FULL") {
109069
+ return `${prefix} The filesystem is full or its quota is exhausted. Free space through your approved recoverable cleanup process, then retry the same session. Do not delete session receipts or change credentials to bypass this failure.`;
109070
+ }
109057
109071
  if (code2 === "PROFILE_LOCK_MISMATCH") {
109058
109072
  return `${prefix} This session or project is pinned to a different profile. Inspect the session with skills sessions show <session-id> --json; review skills sessions reconcile for an intentional session change, or explicit project sync for a project change. Sync alone does not change that pin.`;
109059
109073
  }
@@ -109070,6 +109084,7 @@ var CONTEXT_ERROR_CODES, HookDiagnosticError;
109070
109084
  var init_hook_diagnostics = __esm(() => {
109071
109085
  CONTEXT_ERROR_CODES = new Set([
109072
109086
  "SKILLS_CONTEXT_FAILED",
109087
+ "SKILLS_STORAGE_FULL",
109073
109088
  "PROFILE_LOCK_MISMATCH",
109074
109089
  "PROFILE_IDENTITY_MISMATCH",
109075
109090
  "PROFILE_APPLIED_REPORT_FAILED",
@@ -109185,10 +109200,10 @@ function agents(value) {
109185
109200
  }
109186
109201
  function registerAgentIntegration(parent) {
109187
109202
  const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
109188
- hook.command("witness").requiredOption("--kind <kind>", "claude-settings-v2 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) => {
109189
- const capture2 = options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
109203
+ hook.command("witness").requiredOption("--kind <kind>", "claude-settings-v3, claude-settings-v2 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) => {
109204
+ const capture2 = options.kind === "claude-settings-v3" ? captureClaudeSettingsV3 : options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
109190
109205
  if (!capture2)
109191
- throw new Error("Unsupported witness kind; select claude-settings-v2 or claude-marketplace-registry-v2");
109206
+ throw new Error("Unsupported witness kind; select claude-settings-v3, claude-settings-v2 or claude-marketplace-registry-v2");
109192
109207
  await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
109193
109208
  });
109194
109209
  hook.command("agents").option("--json", "Output the adapter capability inventory", false).description("Show maintained native adapters and explicit coverage limits").action(async () => {
package/bin/mcp.js CHANGED
@@ -20019,7 +20019,7 @@ var package_default;
20019
20019
  var init_package = __esm(() => {
20020
20020
  package_default = {
20021
20021
  name: "@hasna/skills",
20022
- version: "0.9.16",
20022
+ version: "0.9.17",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -38126,8 +38126,8 @@ function assertAgentPolicyCollections(policy) {
38126
38126
  text(source.path);
38127
38127
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
38128
38128
  if (source.hashMode !== undefined)
38129
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
38130
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
38129
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
38130
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
38131
38131
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve2(source.path) === source.path && basename3(source.path) === "settings.json");
38132
38132
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
38133
38133
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve2(source.path) === source.path && basename3(source.path) === "known_marketplaces.json");
@@ -47080,7 +47080,7 @@ function canonical2(value) {
47080
47080
  return { kind: "array", items: value.items.map(canonical2) };
47081
47081
  return value;
47082
47082
  }
47083
- function inferencePreference(key, child) {
47083
+ function inferencePreference(key, child, version2) {
47084
47084
  if (key === "skipDangerousModePermissionPrompt") {
47085
47085
  need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
47086
47086
  return false;
@@ -47093,7 +47093,7 @@ function inferencePreference(key, child) {
47093
47093
  return false;
47094
47094
  need2(child.kind === "object", "requires an object of model preferences");
47095
47095
  child.entries = child.entries.filter(([model, preferences]) => {
47096
- if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
47096
+ if (version2 === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
47097
47097
  return true;
47098
47098
  const effort = preferences.entries[0][1];
47099
47099
  need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
@@ -47104,7 +47104,7 @@ function inferencePreference(key, child) {
47104
47104
  function settingsDigest(text2, version2 = 1) {
47105
47105
  const value = parse8(text2);
47106
47106
  value.entries = value.entries.filter(([key, child]) => {
47107
- if (version2 === 2 && inferencePreference(key, child))
47107
+ if (version2 !== 1 && inferencePreference(key, child, version2))
47108
47108
  return false;
47109
47109
  if (key === "model") {
47110
47110
  need2(child.kind === "string", "contains an invalid model selection");
@@ -47142,6 +47142,16 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
47142
47142
  budget.remaining -= bytes;
47143
47143
  return settingsDigest(text2, 2);
47144
47144
  }
47145
+ function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
47146
+ return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
47147
+ }
47148
+ function hashClaudeSettingsReplacementV3(text2, budget) {
47149
+ need2(typeof text2 === "string", "requires settings text");
47150
+ const bytes = Buffer.byteLength(text2);
47151
+ need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
47152
+ budget.remaining -= bytes;
47153
+ return settingsDigest(text2, 3);
47154
+ }
47145
47155
  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;
47146
47156
  var init_claude_settings_witness = __esm(() => {
47147
47157
  CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
@@ -48867,13 +48877,13 @@ function read(path, changes) {
48867
48877
  return readFileSync14(path, "utf8");
48868
48878
  }
48869
48879
  function projected(source, changes, budget = discoveryByteBudget()) {
48870
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
48880
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
48871
48881
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
48872
48882
  throw new Error("Claude settings witnesses require exact typed metadata");
48873
- const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
48883
+ const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
48874
48884
  if (current !== source.sha256)
48875
48885
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
48876
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
48886
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
48877
48887
  }
48878
48888
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
48879
48889
  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))
@@ -48935,7 +48945,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
48935
48945
  safe4(root);
48936
48946
  }
48937
48947
  function assertMarketplaceBinding(binding, source) {
48938
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename7(source.path) !== "settings.json"))
48948
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename7(source.path) !== "settings.json"))
48939
48949
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
48940
48950
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
48941
48951
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -49016,7 +49026,7 @@ function resolveAgentDiscovery(options) {
49016
49026
  verifyAgentDiscovery(retained);
49017
49027
  if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
49018
49028
  unresolved("retained review is missing the full agent configuration source");
49019
- if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
49029
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
49020
49030
  unresolved("retained settings witness names another configuration source");
49021
49031
  if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual2(saved, source))) || [...roots].some((root) => !retained.roots.includes(root)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
49022
49032
  unresolved("retained review is missing current runtime discovery coverage");
@@ -49032,7 +49042,7 @@ function resolveAgentDiscovery(options) {
49032
49042
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
49033
49043
  throw new Error("Explicit discovery reviews require full source-file hashes");
49034
49044
  verifyAgentDiscovery(supplied);
49035
- if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
49045
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
49036
49046
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
49037
49047
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
49038
49048
  throw new Error("Discovery review must include the agent configuration source");
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.9.16",
10
+ version: "0.9.17",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
package/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.9.16",
24125
+ version: "0.9.17",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.9.16",
24128
+ version: "0.9.17",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
package/dist/index.d.ts CHANGED
@@ -63,7 +63,7 @@ export { inspectSkillSession, reconcileSkillSession, type SessionReconciliationI
63
63
  export { planAgentIntegration, planClaudeStopHookUpdate, planClaudePreToolUseHookUpdate, planClaudeHookEventsUpdate, applyAgentIntegration, inventoryNativeSkills, archiveNativeSkills } from "./lib/agent-integration.js";
64
64
  export { captureDiscoveryDirectories, captureDiscoveryByteSources, captureDiscoveryPathSources, type DiscoveryDirectory, type DiscoverySource } from "./lib/agent-discovery.js";
65
65
  export { captureClaudeMarketplaceRegistry, captureClaudeMarketplaceRegistryV2 } from "./lib/claude-marketplace-registry.js";
66
- export { captureClaudeSettings, captureClaudeSettingsV2 } from "./lib/claude-settings-witness.js";
66
+ export { captureClaudeSettings, captureClaudeSettingsV2, captureClaudeSettingsV3, upgradeClaudeSettingsWitness } from "./lib/claude-settings-witness.js";
67
67
  export { buildPluginProjection, pluginTreeDigest, type PluginProjectionManifest, type PluginPayloadMapping, type PluginFileWitness } from "./lib/plugin-projection.js";
68
68
  export { planPluginAdmission, admitPlugin, resolveAdmittedPlugin, pluginResolverCommand, type PluginAdmissionTarget, type PluginAdmissionBinding, type PluginAdmissionPrincipal, type PluginAdmissionPlan, type PluginAdmissionIdentity, type PluginBundleIdentity, type PluginRoutingIdentity, type PluginAdmissionReceipt } from "./lib/plugin-admission.js";
69
69
  export { captureManagedPluginRegistry, type ManagedPluginRegistrationWitness } from "./lib/plugin-discovery.js";
package/dist/index.js CHANGED
@@ -9055,8 +9055,8 @@ function assertAgentPolicyCollections(policy) {
9055
9055
  text(source.path);
9056
9056
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
9057
9057
  if (source.hashMode !== undefined)
9058
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9059
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
9058
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9059
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
9060
9060
  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");
9061
9061
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
9062
9062
  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) === "known_marketplaces.json");
@@ -9611,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
9611
9611
  // package.json
9612
9612
  var package_default = {
9613
9613
  name: "@hasna/skills",
9614
- version: "0.9.16",
9614
+ version: "0.9.17",
9615
9615
  description: "Skills library for AI coding agents",
9616
9616
  type: "module",
9617
9617
  bin: {
@@ -12874,7 +12874,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
12874
12874
  need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
12875
12875
  }
12876
12876
  var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
12877
- function inferencePreference(key, child) {
12877
+ function inferencePreference(key, child, version) {
12878
12878
  if (key === "skipDangerousModePermissionPrompt") {
12879
12879
  need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
12880
12880
  return false;
@@ -12887,7 +12887,7 @@ function inferencePreference(key, child) {
12887
12887
  return false;
12888
12888
  need2(child.kind === "object", "requires an object of model preferences");
12889
12889
  child.entries = child.entries.filter(([model, preferences]) => {
12890
- if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
12890
+ if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
12891
12891
  return true;
12892
12892
  const effort = preferences.entries[0][1];
12893
12893
  need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
@@ -12898,7 +12898,7 @@ function inferencePreference(key, child) {
12898
12898
  function settingsDigest(text2, version = 1) {
12899
12899
  const value = parse3(text2);
12900
12900
  value.entries = value.entries.filter(([key, child]) => {
12901
- if (version === 2 && inferencePreference(key, child))
12901
+ if (version !== 1 && inferencePreference(key, child, version))
12902
12902
  return false;
12903
12903
  if (key === "model") {
12904
12904
  need2(child.kind === "string", "contains an invalid model selection");
@@ -12936,6 +12936,25 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
12936
12936
  budget.remaining -= bytes;
12937
12937
  return settingsDigest(text2, 2);
12938
12938
  }
12939
+ function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
12940
+ return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
12941
+ }
12942
+ function hashClaudeSettingsReplacementV3(text2, budget) {
12943
+ need2(typeof text2 === "string", "requires settings text");
12944
+ const bytes = Buffer.byteLength(text2);
12945
+ need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
12946
+ budget.remaining -= bytes;
12947
+ return settingsDigest(text2, 3);
12948
+ }
12949
+ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
12950
+ need2(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === "claude-settings-v1" || previous.hashMode === "claude-settings-v2") && typeof previous.sha256 === "string" && /^[a-f0-9]{64}$/.test(previous.sha256), "requires an exact legacy settings witness");
12951
+ const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
12952
+ const before = readSettings(reviewedSettingsPath, budget), current = readSettings(previous.path, budget);
12953
+ need2(settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : 2) === previous.sha256, "reviewed preimage does not match the legacy witness");
12954
+ const sha2562 = settingsDigest(current, 3);
12955
+ need2(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
12956
+ return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
12957
+ }
12939
12958
 
12940
12959
  // src/lib/agent-integration.ts
12941
12960
  var sha = (data) => createHash10("sha256").update(data).digest("hex");
@@ -16507,13 +16526,13 @@ function read2(path, changes) {
16507
16526
  return readFileSync16(path, "utf8");
16508
16527
  }
16509
16528
  function projected(source, changes, budget = discoveryByteBudget()) {
16510
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
16529
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
16511
16530
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
16512
16531
  throw new Error("Claude settings witnesses require exact typed metadata");
16513
- const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
16532
+ const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
16514
16533
  if (current !== source.sha256)
16515
16534
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
16516
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
16535
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
16517
16536
  }
16518
16537
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
16519
16538
  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))
@@ -16575,7 +16594,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
16575
16594
  safe4(root2);
16576
16595
  }
16577
16596
  function assertMarketplaceBinding(binding, source) {
16578
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
16597
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
16579
16598
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
16580
16599
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
16581
16600
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -16718,7 +16737,7 @@ function resolveAgentDiscovery(options) {
16718
16737
  verifyAgentDiscovery(retained);
16719
16738
  if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
16720
16739
  unresolved("retained review is missing the full agent configuration source");
16721
- if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
16740
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
16722
16741
  unresolved("retained settings witness names another configuration source");
16723
16742
  if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual3(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
16724
16743
  unresolved("retained review is missing current runtime discovery coverage");
@@ -16734,7 +16753,7 @@ function resolveAgentDiscovery(options) {
16734
16753
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
16735
16754
  throw new Error("Explicit discovery reviews require full source-file hashes");
16736
16755
  verifyAgentDiscovery(supplied);
16737
- if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
16756
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
16738
16757
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
16739
16758
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
16740
16759
  throw new Error("Discovery review must include the agent configuration source");
@@ -43682,6 +43701,7 @@ export {
43682
43701
  validateCron,
43683
43702
  validateBlogArticleRunOptions,
43684
43703
  uploadSkillsSnapshotFilesToS3,
43704
+ upgradeClaudeSettingsWitness,
43685
43705
  updateSkillRun,
43686
43706
  unsetConfig,
43687
43707
  unpinSkill,
@@ -43860,6 +43880,7 @@ export {
43860
43880
  captureDiscoveryPathSources,
43861
43881
  captureDiscoveryDirectories,
43862
43882
  captureDiscoveryByteSources,
43883
+ captureClaudeSettingsV3,
43863
43884
  captureClaudeSettingsV2,
43864
43885
  captureClaudeSettings,
43865
43886
  captureClaudeMarketplaceRegistryV2,
@@ -6,7 +6,7 @@ export { captureDiscoveryDirectories, type DiscoveryDirectory } from "./agent-di
6
6
  export interface DiscoverySource {
7
7
  path: string;
8
8
  sha256: string | null;
9
- hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-marketplace-registry-v2";
9
+ hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-settings-v3" | "claude-marketplace-registry-v2";
10
10
  managedPlugins?: ManagedPluginRegistrationWitness[];
11
11
  format?: "json" | "toml" | "yaml";
12
12
  fields?: string[];
@@ -34,3 +34,19 @@ export declare function captureClaudeSettingsV2(path: string, budget?: ClaudeSet
34
34
  sha256: string;
35
35
  };
36
36
  export declare function hashClaudeSettingsReplacementV2(text: string, budget: ClaudeSettingsWitnessBudget): string;
37
+ /** Explicit review of model-independent effort preferences; v1/v2 stay exact. */
38
+ export declare function captureClaudeSettingsV3(path: string, budget?: ClaudeSettingsWitnessBudget): {
39
+ path: string;
40
+ hashMode: "claude-settings-v3";
41
+ sha256: string;
42
+ };
43
+ export declare function hashClaudeSettingsReplacementV3(text: string, budget: ClaudeSettingsWitnessBudget): string;
44
+ /** Upgrade only with an exact semantic preimage of the prior review. This
45
+ * proves every non-preference setting is unchanged; it never refreshes drift.
46
+ * Both files are bounded regular settings.json files, read without symlinks.
47
+ */
48
+ export declare function upgradeClaudeSettingsWitness(previous: {
49
+ path: string;
50
+ hashMode: "claude-settings-v1" | "claude-settings-v2";
51
+ sha256: string;
52
+ }, reviewedSettingsPath: string): ReturnType<typeof captureClaudeSettingsV3>;
@@ -1,4 +1,4 @@
1
- export declare const SUPPORTED_CODEX_HOOK_VERSIONS: readonly ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1"];
1
+ export declare const SUPPORTED_CODEX_HOOK_VERSIONS: readonly ["codex-cli 0.153.0", "codex-cli 0.154.0", "codex-cli 0.155.0", "codex-cli 0.155.1", "codex-cli 0.156.1"];
2
2
  export interface CodexHookRpc {
3
3
  version: string;
4
4
  processId?: number;
package/dist/sdk/index.js CHANGED
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.9.16",
32376
+ version: "0.9.17",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -37545,8 +37545,8 @@ function assertAgentPolicyCollections(policy) {
37545
37545
  text(source.path);
37546
37546
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
37547
37547
  if (source.hashMode !== undefined)
37548
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
37549
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
37548
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-settings-v3", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
37549
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
37550
37550
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "settings.json");
37551
37551
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
37552
37552
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
@@ -40154,7 +40154,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
40154
40154
  need3(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
40155
40155
  }
40156
40156
  var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
40157
- function inferencePreference(key, child) {
40157
+ function inferencePreference(key, child, version) {
40158
40158
  if (key === "skipDangerousModePermissionPrompt") {
40159
40159
  need3(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
40160
40160
  return false;
@@ -40167,7 +40167,7 @@ function inferencePreference(key, child) {
40167
40167
  return false;
40168
40168
  need3(child.kind === "object", "requires an object of model preferences");
40169
40169
  child.entries = child.entries.filter(([model, preferences]) => {
40170
- if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
40170
+ if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
40171
40171
  return true;
40172
40172
  const effort = preferences.entries[0][1];
40173
40173
  need3(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
@@ -40178,7 +40178,7 @@ function inferencePreference(key, child) {
40178
40178
  function settingsDigest(text2, version = 1) {
40179
40179
  const value = parse6(text2);
40180
40180
  value.entries = value.entries.filter(([key, child]) => {
40181
- if (version === 2 && inferencePreference(key, child))
40181
+ if (version !== 1 && inferencePreference(key, child, version))
40182
40182
  return false;
40183
40183
  if (key === "model") {
40184
40184
  need3(child.kind === "string", "contains an invalid model selection");
@@ -40216,6 +40216,25 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
40216
40216
  budget.remaining -= bytes;
40217
40217
  return settingsDigest(text2, 2);
40218
40218
  }
40219
+ function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
40220
+ return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
40221
+ }
40222
+ function hashClaudeSettingsReplacementV3(text2, budget) {
40223
+ need3(typeof text2 === "string", "requires settings text");
40224
+ const bytes = Buffer.byteLength(text2);
40225
+ need3(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
40226
+ budget.remaining -= bytes;
40227
+ return settingsDigest(text2, 3);
40228
+ }
40229
+ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
40230
+ need3(previous && Object.keys(previous).every((key) => ["path", "hashMode", "sha256"].includes(key)) && (previous.hashMode === "claude-settings-v1" || previous.hashMode === "claude-settings-v2") && typeof previous.sha256 === "string" && /^[a-f0-9]{64}$/.test(previous.sha256), "requires an exact legacy settings witness");
40231
+ const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
40232
+ const before = readSettings(reviewedSettingsPath, budget), current = readSettings(previous.path, budget);
40233
+ need3(settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : 2) === previous.sha256, "reviewed preimage does not match the legacy witness");
40234
+ const sha2562 = settingsDigest(current, 3);
40235
+ need3(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
40236
+ return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
40237
+ }
40219
40238
 
40220
40239
  // src/lib/codex-hook-discovery-recovery.ts
40221
40240
  import { createHash as createHash14 } from "crypto";
@@ -40325,13 +40344,13 @@ function read2(path, changes) {
40325
40344
  return readFileSync12(path, "utf8");
40326
40345
  }
40327
40346
  function projected(source, changes, budget = discoveryByteBudget()) {
40328
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
40347
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
40329
40348
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
40330
40349
  throw new Error("Claude settings witnesses require exact typed metadata");
40331
- const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
40350
+ const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
40332
40351
  if (current !== source.sha256)
40333
40352
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
40334
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
40353
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
40335
40354
  }
40336
40355
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
40337
40356
  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))
@@ -40393,7 +40412,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
40393
40412
  safe4(root2);
40394
40413
  }
40395
40414
  function assertMarketplaceBinding(binding, source) {
40396
- if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename6(source.path) !== "settings.json"))
40415
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename6(source.path) !== "settings.json"))
40397
40416
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
40398
40417
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
40399
40418
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -40536,7 +40555,7 @@ function resolveAgentDiscovery(options) {
40536
40555
  verifyAgentDiscovery(retained);
40537
40556
  if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
40538
40557
  unresolved("retained review is missing the full agent configuration source");
40539
- if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
40558
+ if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
40540
40559
  unresolved("retained settings witness names another configuration source");
40541
40560
  if (sources.some((source) => !retained.sources.some((saved) => isDeepStrictEqual3(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
40542
40561
  unresolved("retained review is missing current runtime discovery coverage");
@@ -40552,7 +40571,7 @@ function resolveAgentDiscovery(options) {
40552
40571
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
40553
40572
  throw new Error("Explicit discovery reviews require full source-file hashes");
40554
40573
  verifyAgentDiscovery(supplied);
40555
- if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
40574
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
40556
40575
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
40557
40576
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
40558
40577
  throw new Error("Discovery review must include the agent configuration source");
@@ -289,6 +289,24 @@ Primary runtime references: [command sources](https://code.claude.com/docs/en/pl
289
289
 
290
290
  ### Explicit v2 preference reviews
291
291
 
292
+ For new reviews, use `captureClaudeSettingsV3(path)` or
293
+ `skills hook witness --kind claude-settings-v3 --path <settings.json> --json`.
294
+ The v3 witness recognizes an entry containing only a valid `effortLevel`
295
+ independently of its model name. Claude can persist a preference for a newly
296
+ released model without blocking the next prompt. Entries with additional
297
+ properties, including `maxEffortLevel`, remain fully bound, as do provider
298
+ mappings, hooks, permissions, plugins, environment and unknown settings.
299
+ The top-level model selection rules remain unchanged.
300
+
301
+ Legacy v1 and v2 digests retain their meaning. `upgradeClaudeSettingsWitness`
302
+ accepts a legacy witness and a saved `settings.json` preimage. It verifies that
303
+ the preimage matches the old digest and that current settings differ only in
304
+ recognized preferences before returning a v3 witness. It refuses all other
305
+ drift. Use that witness in reviewed discovery inputs and apply the normal
306
+ guarded integration plan. Install compatible runtimes before activating v3;
307
+ when updating agents, verify their actual hook commands and native trust, not
308
+ only the executable found on `PATH`.
309
+
292
310
  `captureClaudeSettingsV2(path)` creates a `claude-settings-v2` witness. In
293
311
  addition to the v1 display/model selections, it tolerates the documented
294
312
  persisted effort values `low`, `medium`, `high`, and `xhigh`, both at the top
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.9.16",
3
+ "version": "0.9.17",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {