@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 +33 -18
- package/bin/mcp.js +22 -12
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +33 -12
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/claude-settings-witness.d.ts +16 -0
- package/dist/lib/codex-hook-rpc.d.ts +1 -1
- package/dist/sdk/index.js +31 -12
- package/docs/plugin-admission.md +18 -0
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
80160
|
-
const
|
|
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.
|
|
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
|
|
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
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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");
|
package/docs/plugin-admission.md
CHANGED
|
@@ -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
|