@hasna/skills 0.10.49 → 0.10.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.10.49",
32376
+ version: "0.10.51",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -38140,6 +38140,8 @@ var BUILTIN_MODEL_SELECTIONS = new Set([
38140
38140
  "claude-opus-4-6[1m]",
38141
38141
  "claude-sonnet-4-6[1m]"
38142
38142
  ]);
38143
+ var CLAUDE_BUILTIN_THEMES = Object.freeze(["auto", "dark", "light", "dark-daltonized", "light-daltonized", "dark-ansi", "light-ansi"]);
38144
+ var BUILTIN_THEMES = new Set(CLAUDE_BUILTIN_THEMES);
38143
38145
  function canonical(value) {
38144
38146
  if (value.kind === "object")
38145
38147
  return { kind: "object", entries: value.entries.map(([key, child]) => [key, canonical(child)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0) };
@@ -38198,7 +38200,9 @@ function inferencePreference(key, child, version) {
38198
38200
  function settingsDigest(text, version = 1) {
38199
38201
  const value = parse2(text);
38200
38202
  value.entries = value.entries.filter(([key, child]) => {
38201
- if (version !== 1 && inferencePreference(key, child, version))
38203
+ if (version === 4 && key === "theme" && child.kind === "string" && BUILTIN_THEMES.has(child.value))
38204
+ return false;
38205
+ if (version !== 1 && inferencePreference(key, child, version === 4 ? 3 : version))
38202
38206
  return false;
38203
38207
  if (key === "model") {
38204
38208
  need(child.kind === "string", "contains an invalid model selection");
@@ -38256,6 +38260,26 @@ function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
38256
38260
  need(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
38257
38261
  return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
38258
38262
  }
38263
+ function captureClaudeSettingsV4(path, budget = { remaining: 256 * 1024 * 1024 }) {
38264
+ return { path, hashMode: "claude-settings-v4", sha256: settingsDigest(readNativeSettingsWitnessFile(path, budget), 4) };
38265
+ }
38266
+ function hashClaudeSettingsReplacementV4(text, budget) {
38267
+ need(typeof text === "string", "requires settings text");
38268
+ const bytes = Buffer.byteLength(text);
38269
+ need(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
38270
+ budget.remaining -= bytes;
38271
+ return settingsDigest(text, 4);
38272
+ }
38273
+ function upgradeClaudeSettingsWitnessV4(previous, reviewedSettingsPath) {
38274
+ 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");
38275
+ const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
38276
+ const before = readNativeSettingsWitnessFile(reviewedSettingsPath, budget), current = readNativeSettingsWitnessFile(previous.path, budget);
38277
+ const beforeDigest = previous.hashMode === undefined || previous.hashMode === "bytes" ? createHash6("sha256").update(before).digest("hex") : settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : previous.hashMode === "claude-settings-v2" ? 2 : 3);
38278
+ need(beforeDigest === previous.sha256, "reviewed preimage does not match the prior witness");
38279
+ const sha2562 = settingsDigest(current, 4);
38280
+ need(settingsDigest(before, 4) === sha2562, "non-preference settings changed; explicit discovery review required");
38281
+ return { path: previous.path, hashMode: "claude-settings-v4", sha256: sha2562 };
38282
+ }
38259
38283
  function hashNativeJsonControls(text, excludedMetadata) {
38260
38284
  need(Buffer.byteLength(text) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, "exceeds its byte limit");
38261
38285
  const value = parse2(text);
@@ -38402,7 +38426,8 @@ var MEASURED_CODEX_CAPABILITIES = {
38402
38426
  "codex-cli 0.158.0": ["hooks"],
38403
38427
  "codex-cli 0.159.0": ["hooks"],
38404
38428
  "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
38405
- "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog"]
38429
+ "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
38430
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
38406
38431
  };
38407
38432
  function supportsCodexNativeCapability(version, capability) {
38408
38433
  return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
@@ -39003,6 +39028,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read, rules)
39003
39028
  function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules, pluginSettings, plannedRules, denials = []) {
39004
39029
  if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
39005
39030
  refuse3();
39031
+ const installedPluginReview = supportsCodexNativeCapability(catalog.version, "installed-plugin-review");
39006
39032
  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)))
39007
39033
  refuse3();
39008
39034
  const usedDenials = new Set;
@@ -39034,7 +39060,7 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
39034
39060
  if (listed.length > 1)
39035
39061
  refuse3();
39036
39062
  const installed = installedPlugins.filter((plugin) => plugin.id === expectedPluginId && plugin.name === pluginName);
39037
- if (catalog.version === "codex-cli 0.160.0" && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename4(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
39063
+ if (installedPluginReview && installed.length === 1 && installed[0].installed && installed[0].localVersion === basename4(located.root) && installed[0].sourceType === "local" && codexPluginSourceIsConfigControlled(installed[0])) {
39038
39064
  const plugin = installed[0], input = { pluginId: expectedPluginId, namespace: located.namespace, pluginParent: located.pluginParent, sourceRoot: plugin.sourcePath, sourceSha256: plugin.sourceSha256 };
39039
39065
  if (input.sourceRoot !== located.root && !input.sourceRoot.startsWith(cache + sep3) && (lstatSync7(input.sourceRoot, { throwIfNoEntry: false }) || documents.some((path) => path.startsWith(input.sourceRoot + sep3)))) {
39040
39066
  if (skills.some((skill) => skill.path === input.sourceRoot || skill.path.startsWith(input.sourceRoot + sep3)) || !sourceInputUnchanged(input, cache, read))
@@ -39043,7 +39069,7 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
39043
39069
  }
39044
39070
  }
39045
39071
  const settings = pluginSettings && typeof pluginSettings === "object" && !Array.isArray(pluginSettings) ? pluginSettings : undefined;
39046
- 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) {
39072
+ 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) {
39047
39073
  if (!Array.isArray(rules))
39048
39074
  refuse3();
39049
39075
  const exactPathDeny = rules.some((rule) => rule?.path === document && rule?.enabled === false);
@@ -39076,12 +39102,12 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
39076
39102
  const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document && rule?.enabled === false);
39077
39103
  const nativeSkill = { name: parsed.name, path: document, pluginId: expectedPluginId, enabled: true };
39078
39104
  const denial = denials.find((item) => item.path === document);
39079
- const explicitDeny = denial !== undefined && remotePluginId !== undefined && catalog.version === "codex-cli 0.160.0" && denial.name === parsed.name && denial.sha256 === createHash8("sha256").update(read(document)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false);
39105
+ const explicitDeny = denial !== undefined && remotePluginId !== undefined && installedPluginReview && denial.name === parsed.name && denial.sha256 === createHash8("sha256").update(read(document)).digest("hex") && Array.isArray(rules) && !rules.some((rule) => (rule?.path === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false);
39080
39106
  if (denial && !explicitDeny)
39081
39107
  refuse3();
39082
39108
  if (explicitDeny)
39083
39109
  usedDenials.add(document);
39084
- 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 === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
39110
+ 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 === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
39085
39111
  if (!explicitDeny && (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules)))
39086
39112
  refuse3();
39087
39113
  }
@@ -40329,7 +40355,7 @@ function assertAgentPolicyCollections(policy) {
40329
40355
  }
40330
40356
  if (bridge.codexInactivePlugins !== undefined) {
40331
40357
  const controls = array(bridge.codexInactivePlugins, 4096), ids = new Set;
40332
- requireBound(controls.length === 0 || object4(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === "codex-cli 0.160.0" && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
40358
+ requireBound(controls.length === 0 || object4(bridge.codexPluginSkillReview) && supportsCodexNativeCapability(bridge.codexPluginSkillReview.version, "installed-plugin-review") && typeof bridge.codexPluginSkillReview.catalogSha256 === "string" && /^[a-f0-9]{64}$/.test(bridge.codexPluginSkillReview.catalogSha256));
40333
40359
  for (const control of controls) {
40334
40360
  requireBound(object4(control) && Object.keys(control).every((key) => ["pluginId", "namespace", "pluginParent", "sourceType", "sourceSha256"].includes(key)));
40335
40361
  text(control.pluginId, 1024);
@@ -40381,7 +40407,7 @@ function assertAgentPolicyCollections(policy) {
40381
40407
  }
40382
40408
  if (value.codexInstallationInputs !== undefined) {
40383
40409
  const proof = record3(value.codexInstallationInputs, 4), inputs = array(proof.plugins, 4096), ids = new Set;
40384
- 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 && object4(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
40410
+ 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 && object4(bridge.codexPluginSkillReview) && bridge.codexPluginSkillReview.version === proof.version && bridge.codexPluginSkillReview.catalogSha256 === proof.catalogSha256);
40385
40411
  for (const input of inputs) {
40386
40412
  requireBound(object4(input) && Object.keys(input).every((key) => ["pluginId", "namespace", "pluginParent", "sourceRoot", "sourceSha256"].includes(key)));
40387
40413
  text(input.pluginId, 1024);
@@ -40405,14 +40431,14 @@ function assertAgentPolicyCollections(policy) {
40405
40431
  text(source.path);
40406
40432
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
40407
40433
  if (source.hashMode !== undefined)
40408
- 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));
40434
+ 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));
40409
40435
  if (source.hashMode === "claude-plugin-manifest-v1")
40410
40436
  requireBound(agent === "claude" && value.agent === "claude" && ["automatic", "reviewed"].includes(value.method) && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && source.path.endsWith(join14(sep4, ".claude-plugin", "plugin.json")) && source.managedPlugins === undefined);
40411
40437
  if (source.hashMode === "sumi-settings-v1")
40412
40438
  requireBound(agent === "sumi" && value.agent === "sumi" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "sumi.json");
40413
40439
  if (source.hashMode === "codex-settings-v1" || source.hashMode === "codex-settings-v2" || (source.hashMode === "codex-settings-v3" || source.hashMode === "codex-settings-v4"))
40414
40440
  requireBound(agent === "codex" && value.agent === "codex" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "config.toml");
40415
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
40441
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4"))
40416
40442
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "settings.json");
40417
40443
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
40418
40444
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute8(source.path) && resolve7(source.path) === source.path && basename5(source.path) === "known_marketplaces.json");
@@ -43246,13 +43272,13 @@ function projected(source, changes, budget2 = discoveryByteBudget()) {
43246
43272
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
43247
43273
  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;
43248
43274
  }
43249
- if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
43275
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || (source.hashMode === "claude-settings-v3" || source.hashMode === "claude-settings-v4")) {
43250
43276
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
43251
43277
  throw new Error("Claude settings witnesses require exact typed metadata");
43252
- const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget2).sha256;
43278
+ 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;
43253
43279
  if (current !== source.sha256)
43254
43280
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
43255
- return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget2) : current;
43281
+ 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;
43256
43282
  }
43257
43283
  if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
43258
43284
  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))
@@ -43463,7 +43489,7 @@ function codexInstallationRoots(binding) {
43463
43489
  const proof = binding.codexInstallationInputs;
43464
43490
  if (proof === undefined)
43465
43491
  return [];
43466
- 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)
43492
+ 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)
43467
43493
  throw new Error("Invalid native installation input proof");
43468
43494
  const cache = dirname16(dirname16(proof.plugins[0].pluginParent));
43469
43495
  const config = join26(dirname16(dirname16(cache)), "config.toml"), home = dirname16(dirname16(dirname16(cache)));
@@ -43487,7 +43513,7 @@ function assertMarketplaceBinding(binding, source) {
43487
43513
  throw new Error("Sumi settings witnesses require explicit reviewed Sumi configuration");
43488
43514
  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" || basename8(source.path) !== "config.toml"))
43489
43515
  throw new Error("Codex settings witnesses require explicit reviewed Codex configuration");
43490
- 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"))
43516
+ 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" || basename8(source.path) !== "settings.json"))
43491
43517
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
43492
43518
  if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
43493
43519
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
@@ -43689,7 +43715,7 @@ function resolveAgentDiscovery(options) {
43689
43715
  verifyAgentDiscovery(retained);
43690
43716
  if (!retained.sources.some((source) => source.path === canonical7(configPath) && source.format === undefined && source.fields === undefined))
43691
43717
  unresolved("retained review is missing the full agent configuration source");
43692
- 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)))
43718
+ 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)))
43693
43719
  unresolved("retained settings witness names another configuration source");
43694
43720
  const covered = supersedesSettingsProjection(agent, retained.sources, canonical7(configPath)) ? sources.filter((source) => !isSupersededSettingsProjection(agent, canonical7(configPath), source)) : sources;
43695
43721
  if (covered.some((source) => !retained.sources.some((saved) => isDeepStrictEqual6(saved, source))) || [...roots].some((root2) => !retained.roots.includes(root2)) || agent === "gemini" && JSON.stringify(retained.builtinNames) !== JSON.stringify(builtinNames))
@@ -43703,7 +43729,7 @@ function resolveAgentDiscovery(options) {
43703
43729
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
43704
43730
  throw new Error("Explicit discovery reviews require full source-file hashes");
43705
43731
  verifyAgentDiscovery(supplied);
43706
- 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)))
43732
+ 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)))
43707
43733
  throw new Error("Semantic settings witnesses must name the configured agent configuration source");
43708
43734
  if (!review.sources.some((source) => source.path === canonical7(configPath)))
43709
43735
  throw new Error("Discovery review must include the agent configuration source");
@@ -45059,7 +45085,7 @@ function planAgentIntegration(options) {
45059
45085
  const { codexInstallationInputs: previousInputs, ...current } = rebound;
45060
45086
  const inputs = codexPluginSourceInputs.map((input) => input.sourceRoot);
45061
45087
  const directories = options.codexNativeCatalog || !previousInputs ? captureDiscoveryDirectories((binding.directories ?? []).filter((directory) => inputs.some((root2) => root2.startsWith(directory.path + sep7))).map((directory) => directory.path), inputs) : previousInputs.directories;
45062
- const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: "codex-cli 0.160.0", catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
45088
+ const installed = codexPluginSourceInputs.length ? { ...current, codexInstallationInputs: { version: codexPluginSkillReview.version, catalogSha256: codexPluginSkillReview.catalogSha256, plugins: codexPluginSourceInputs, ...directories?.length ? { directories } : {} } } : current;
45063
45089
  return captureRetiredCodexDiscovery(installed, codexPluginSkills, Bun.TOML.parse(changes.find((change) => change.path === canonicalAgentPath(join27(home, ".codex/config.toml"), aliases))?.after ?? codexConfigBefore ?? "").skills?.config ?? []);
45064
45090
  });
45065
45091
  const nextPolicy = { ...policy, version: 1, loading: "cli", profileId, bridge: {
@@ -45374,6 +45400,8 @@ function atomicWrite2(path, content) {
45374
45400
  function planAgentSettingsWitnessUpgrade(options) {
45375
45401
  if (options.targetCodexVersion !== undefined && (options.agent !== "codex" || ![2, 3, 4].includes(options.targetCodexVersion)))
45376
45402
  throw new Error("Invalid Codex witness target version");
45403
+ if (options.targetClaudeVersion !== undefined && (options.agent !== "claude" || ![3, 4].includes(options.targetClaudeVersion)))
45404
+ throw new Error("Invalid Claude witness target version");
45377
45405
  const home = resolve18(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
45378
45406
  const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
45379
45407
  if (!snapshot2 || sha4(snapshot2.text) !== options.expectedPolicySha256)
@@ -45421,7 +45449,7 @@ function planAgentSettingsWitnessUpgrade(options) {
45421
45449
  replaced.push(source);
45422
45450
  }
45423
45451
  }
45424
- 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);
45452
+ 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);
45425
45453
  const rebound = [];
45426
45454
  for (const source of binding.sources) {
45427
45455
  if (!replaced.includes(source)) {
@@ -45467,7 +45495,7 @@ function applyAgentIntegrationUnlocked(plan, options = {}, assertCurrent) {
45467
45495
  const upgrade = plan.settingsWitnessUpgrade;
45468
45496
  if (!plan.managedAgentChecks || !plan.observedPolicy?.before)
45469
45497
  throw new Error("Invalid settings witness upgrade plan");
45470
- 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: sha4(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
45498
+ 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: sha4(plan.observedPolicy.before), expectedSettingsSha256: upgrade.currentSettingsSha256 });
45471
45499
  if (JSON.stringify(verified.changes) !== JSON.stringify(plan.changes) || JSON.stringify(verified.settingsWitnessUpgrade) !== JSON.stringify(upgrade))
45472
45500
  throw new Error("Settings witness upgrade plan changed");
45473
45501
  provenDiscovery = verified.discoveryBefore[0];
@@ -9,7 +9,21 @@ type InspectionManifest = EnrollmentInspectionInput & {
9
9
  expiresAt: string;
10
10
  manifestDigest: string;
11
11
  };
12
+ /** The schema census has its own authorization; no enrollment or inspection manifest can authorize it. */
13
+ type SchemaCensusManifest = {
14
+ app: "skills";
15
+ operation: "inspect-schema";
16
+ manifestVersion: 1;
17
+ operationId: string;
18
+ stationId: string;
19
+ keyId: string;
20
+ orgId: string;
21
+ expiresAt: string;
22
+ manifestDigest: string;
23
+ };
12
24
  /** Inspection has its own authorization; a failed enrollment manifest cannot authorize it. */
13
25
  export declare function readInspectionManifest(path: string, receiptPath: string): InspectionManifest;
26
+ /** The census has its own authorization; a failed enrollment or inspection manifest cannot authorize it. */
27
+ export declare function readSchemaCensusManifest(path: string, receiptPath: string): SchemaCensusManifest;
14
28
  export declare function registerMaintenance(parent: Command): void;
15
29
  export {};
@@ -0,0 +1,77 @@
1
+ /**
2
+ * One fixed, closed census of the Skills database maintenance surface.
3
+ *
4
+ * Why it exists (I6-00024): a production migration is held until the exact
5
+ * current registry, version ledger and migration ledger are read from the live
6
+ * database. The enrollment inspection answers a different question (one
7
+ * operator key), and no existing reader returns the fixed aggregate + ledger
8
+ * census. This module is that reader, and it is deliberately shaped so it
9
+ * cannot become a general SQL console:
10
+ *
11
+ * - No target, query, schema or table input exists. The only argument is the
12
+ * database URL, and the operation refuses to run unless the connection
13
+ * resolves to the fixed public schema the Skills store uses.
14
+ * - Every owned-table read is schema-qualified to `public`. A connection whose
15
+ * search path falls through to a later schema cannot substitute another
16
+ * schema's tables: a missing public table refuses (CENSUS_SCHEMA_UNAVAILABLE)
17
+ * instead of counting rows that do not belong to the fixed target.
18
+ * - The transaction is READ ONLY, REPEATABLE READ, with a five-second
19
+ * statement timeout and a one-second lock timeout, asserted from inside the
20
+ * read path rather than assumed.
21
+ * - The output is a closed shape with per-field size bounds, a bounded
22
+ * migration ledger and a total serialized-size gate. Nothing is truncated
23
+ * silently: a census that would exceed a bound refuses instead.
24
+ * - Nothing imports the application store: no initialization, migrations,
25
+ * backfills or writes happen on this path.
26
+ */
27
+ export interface SchemaCensusResult {
28
+ status: "inspected";
29
+ database: string;
30
+ role: string;
31
+ serverVersion: string;
32
+ schema: "public";
33
+ transaction: {
34
+ readOnly: true;
35
+ isolation: "repeatable read";
36
+ statementTimeoutMs: number;
37
+ lockTimeoutMs: number;
38
+ };
39
+ migrations: {
40
+ count: number;
41
+ versions: string[];
42
+ };
43
+ registry: {
44
+ rows: number;
45
+ };
46
+ versions: {
47
+ rows: number;
48
+ };
49
+ lifecycle: {
50
+ active: {
51
+ live: number;
52
+ tombstoned: number;
53
+ };
54
+ archived: {
55
+ live: number;
56
+ tombstoned: number;
57
+ };
58
+ };
59
+ authorizationEpoch: {
60
+ exists: false;
61
+ } | {
62
+ exists: true;
63
+ nullCount: number;
64
+ };
65
+ }
66
+ type CensusCode = "CENSUS_POSTGRES_REQUIRED" | "CENSUS_SCHEMA_UNAVAILABLE" | "CENSUS_ACCESS_DENIED" | "CENSUS_WRITE_REFUSED" | "CENSUS_TIMEOUT" | "CENSUS_CONNECTION_FAILED" | "CENSUS_TARGET_MISMATCH" | "CENSUS_INVALID_RESULT" | "CENSUS_OUTPUT_UNBOUNDED" | "CENSUS_FAILED";
67
+ export declare class SchemaCensusError extends Error {
68
+ readonly code: CensusCode;
69
+ constructor(code: CensusCode);
70
+ }
71
+ /**
72
+ * Census metadata only. Deliberately does not import or initialize the application store.
73
+ *
74
+ * @param databaseUrl explicit PostgreSQL URL; there is no implicit localhost fallback.
75
+ */
76
+ export declare function inspectSchemaCensus(databaseUrl: string): Promise<SchemaCensusResult>;
77
+ export {};
@@ -239,8 +239,8 @@ Invalid values for the display preferences refuse capture and verification. Ever
239
239
  other field remains bound, including unknown future fields, hooks and their
240
240
  exact commands, permissions, native skill suppression and synchronization,
241
241
  plugin roots, marketplaces, environment and configuration precedence. Language,
242
- output style, theme, and command-bearing status or file suggestion settings are
243
- intentionally retained. These are narrow preference and inference-selection
242
+ output style, theme (v4 alone permits the built-in theme values), and
243
+ command-bearing status or file suggestion settings are intentionally retained. These are narrow preference and inference-selection
244
244
  exceptions, not general permission to change Claude configuration. The native
245
245
  bridge guard continues to check its exact commands and native Skill policy.
246
246
 
@@ -263,6 +263,43 @@ or guarantee when a running native client adopts a settings edit.
263
263
  Those complementary checks retain their existing exact registration and hook
264
264
  entry comparisons; this mode does not relax them for property-order changes.
265
265
 
266
+ ### Explicit v4 built-in theme reviews
267
+
268
+ `claude-settings-v4` is an explicit successor to v3. It binds everything v3
269
+ binds, with one more omission: the top-level `theme` key, and only when its
270
+ value is exactly one of the built-in values `auto`, `dark`, `light`,
271
+ `dark-daltonized`, `light-daltonized`, `dark-ansi` or `light-ansi`. The list is
272
+ pinned in code (`CLAUDE_BUILTIN_THEMES`) from the
273
+ [`theme` setting reference](https://code.claude.com/docs/en/settings-reference#theme)
274
+ and the [built-in presets](https://code.claude.com/docs/en/terminal-config#match-the-color-theme),
275
+ read on 2026-10-07. Custom and plugin themes (`custom:<slug>`,
276
+ `custom:<plugin-name>:<slug>`) load theme files, so they stay bound, as does any
277
+ other value, type, spelling or letter case, and any `theme` key nested inside
278
+ another setting. `skipDangerousModePermissionPrompt`, permissions, hooks,
279
+ environment, `apiKeyHelper` and other helpers, plugins, marketplaces, custom
280
+ model values and unknown settings stay bound exactly as in v3. The digest is
281
+ domain-separated from v3.
282
+
283
+ Existing v1, v2 and v3 witnesses keep their meaning; nothing selects v4
284
+ implicitly, and a stored witness is never rewritten. A policy opts in through
285
+ either explicit path:
286
+
287
+ - a fresh review: `skills hook witness --kind claude-settings-v4 --path
288
+ <settings.json> --json` (or `captureClaudeSettingsV4(path)`), placed in the
289
+ reviewed discovery inputs and applied with the normal `skills hook install
290
+ --discovery-inputs <file>` plan/apply flow;
291
+ - a preserved-preimage upgrade: `skills hook rebind-settings --agent claude
292
+ --claude-witness-version 4 --reviewed-preimage <preserved-settings.json>
293
+ --expected-policy-sha256 <policy-hash> --expected-settings-sha256
294
+ <settings-hash>` (or `upgradeClaudeSettingsWitnessV4`). It accepts a raw, v1,
295
+ v2 or v3 witness, proves the preimage matches it in its original mode, and
296
+ refuses unless the current settings equal the preimage under v4. Without
297
+ `--claude-witness-version` the rebind target remains v3.
298
+
299
+ Install a runtime that recognizes `claude-settings-v4`, including any bundled
300
+ copy of the verifier, before a policy carries it; an older runtime refuses the
301
+ unknown hash mode.
302
+
266
303
  ## Verification and limits
267
304
 
268
305
  Unit tests cover provenance and mapping failures, offline/revoked authorities,
@@ -367,8 +404,8 @@ witness intersecting the write still refuses instead of adopting a new inode.
367
404
 
368
405
  ## Codex bundled cleanup controls
369
406
 
370
- Qualified native skill review supports a narrow subset of Codex 0.159.2 and
371
- 0.160.0's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
407
+ Qualified native skill review supports a narrow subset of Codex 0.159.2,
408
+ 0.160.0 and 0.160.1's bundled cleanup hooks. The identities `browser`, `chrome`, `chrome-dev`,
372
409
  `chrome-internal` and `computer-use` in `openai-bundled` may call
373
410
  `node_repl.turn_ended`; `unified-computer-use` may call `cua_repl.turn_ended`.
374
411
  Only `Interrupt`, `SubagentStop` and `Stop` are accepted, with one group and one
@@ -384,4 +421,6 @@ apply and subsequent discovery checks. This review preserves native cleanup;
384
421
  it neither enables plugins nor grants hook trust, browser authority or signing.
385
422
  The upstream contracts are
386
423
  [0.159.2 bundled hooks](https://github.com/openai/codex/blob/ff6aec96948b70d94983af2641a6b67c94faeff5/codex-rs/plugin/src/bundled_hooks.rs)
387
- and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs).
424
+ and [0.160.0 bundled hooks](https://github.com/openai/codex/blob/a956835d020762cb2b570053af06f643a11c0ecc/codex-rs/plugin/src/bundled_hooks.rs);
425
+ 0.160.1 carries the identical file
426
+ ([0.160.1 bundled hooks](https://github.com/openai/codex/blob/d27764b82f7118f674371e6d6e76271d9d606edb/codex-rs/plugin/src/bundled_hooks.rs)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.49",
3
+ "version": "0.10.51",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {