@hasna/skills 0.9.16 → 0.9.18
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 +49 -20
- package/bin/mcp.js +38 -14
- 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 +49 -14
- 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 +47 -14
- 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.18",
|
|
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");
|
|
@@ -48622,7 +48622,8 @@ async function resolveSkillsConnection(env3 = process.env, options = {}) {
|
|
|
48622
48622
|
}
|
|
48623
48623
|
let completed;
|
|
48624
48624
|
try {
|
|
48625
|
-
|
|
48625
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
48626
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env3);
|
|
48626
48627
|
} catch (error) {
|
|
48627
48628
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
48628
48629
|
if (translated)
|
|
@@ -50545,7 +50546,7 @@ function canonical2(value) {
|
|
|
50545
50546
|
return { kind: "array", items: value.items.map(canonical2) };
|
|
50546
50547
|
return value;
|
|
50547
50548
|
}
|
|
50548
|
-
function inferencePreference(key, child) {
|
|
50549
|
+
function inferencePreference(key, child, version) {
|
|
50549
50550
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
50550
50551
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
50551
50552
|
return false;
|
|
@@ -50558,7 +50559,7 @@ function inferencePreference(key, child) {
|
|
|
50558
50559
|
return false;
|
|
50559
50560
|
need2(child.kind === "object", "requires an object of model preferences");
|
|
50560
50561
|
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")
|
|
50562
|
+
if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
50562
50563
|
return true;
|
|
50563
50564
|
const effort = preferences.entries[0][1];
|
|
50564
50565
|
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -50569,7 +50570,7 @@ function inferencePreference(key, child) {
|
|
|
50569
50570
|
function settingsDigest(text2, version = 1) {
|
|
50570
50571
|
const value = parse3(text2);
|
|
50571
50572
|
value.entries = value.entries.filter(([key, child]) => {
|
|
50572
|
-
if (version
|
|
50573
|
+
if (version !== 1 && inferencePreference(key, child, version))
|
|
50573
50574
|
return false;
|
|
50574
50575
|
if (key === "model") {
|
|
50575
50576
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
@@ -50607,6 +50608,16 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
50607
50608
|
budget.remaining -= bytes;
|
|
50608
50609
|
return settingsDigest(text2, 2);
|
|
50609
50610
|
}
|
|
50611
|
+
function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
50612
|
+
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
|
|
50613
|
+
}
|
|
50614
|
+
function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
50615
|
+
need2(typeof text2 === "string", "requires settings text");
|
|
50616
|
+
const bytes = Buffer.byteLength(text2);
|
|
50617
|
+
need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
50618
|
+
budget.remaining -= bytes;
|
|
50619
|
+
return settingsDigest(text2, 3);
|
|
50620
|
+
}
|
|
50610
50621
|
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
50622
|
var init_claude_settings_witness = __esm(() => {
|
|
50612
50623
|
CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
@@ -54886,13 +54897,13 @@ function read2(path, changes) {
|
|
|
54886
54897
|
return readFileSync17(path, "utf8");
|
|
54887
54898
|
}
|
|
54888
54899
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
54889
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
54900
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
54890
54901
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
54891
54902
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
54892
|
-
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
54903
|
+
const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
54893
54904
|
if (current !== source.sha256)
|
|
54894
54905
|
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;
|
|
54906
|
+
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
54907
|
}
|
|
54897
54908
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
54898
54909
|
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 +54965,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
54954
54965
|
safe4(root2);
|
|
54955
54966
|
}
|
|
54956
54967
|
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"))
|
|
54968
|
+
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
54969
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
54959
54970
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
54960
54971
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -55085,7 +55096,7 @@ function resolveAgentDiscovery(options) {
|
|
|
55085
55096
|
verifyAgentDiscovery(retained);
|
|
55086
55097
|
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
55087
55098
|
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)))
|
|
55099
|
+
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
55100
|
unresolved("retained settings witness names another configuration source");
|
|
55090
55101
|
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
55102
|
unresolved("retained review is missing current runtime discovery coverage");
|
|
@@ -55101,7 +55112,7 @@ function resolveAgentDiscovery(options) {
|
|
|
55101
55112
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
55102
55113
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
55103
55114
|
verifyAgentDiscovery(supplied);
|
|
55104
|
-
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
55115
|
+
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
55116
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
55106
55117
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
55107
55118
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -79039,7 +79050,20 @@ class RemoteSkillsClient {
|
|
|
79039
79050
|
const url2 = new URL(upload.uploadUrl);
|
|
79040
79051
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
79041
79052
|
throw new Error("Unsafe input upload URL");
|
|
79042
|
-
const
|
|
79053
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
79054
|
+
if (upload.uploadHeaders !== undefined) {
|
|
79055
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
79056
|
+
throw new Error("Invalid input upload headers");
|
|
79057
|
+
const seen = new Set;
|
|
79058
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
79059
|
+
const lowerName = name.toLowerCase();
|
|
79060
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
79061
|
+
throw new Error("Invalid input upload headers");
|
|
79062
|
+
seen.add(lowerName);
|
|
79063
|
+
headers.set(name, value);
|
|
79064
|
+
}
|
|
79065
|
+
}
|
|
79066
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
79043
79067
|
if (!uploaded.ok)
|
|
79044
79068
|
throw new Error("Input upload failed");
|
|
79045
79069
|
await uploaded.body?.cancel();
|
|
@@ -80156,8 +80180,9 @@ Preserved original receipt: ${result2.archivePath}` : `Planned one session recon
|
|
|
80156
80180
|
});
|
|
80157
80181
|
}
|
|
80158
80182
|
function reportContextError(error61, json2 = false) {
|
|
80159
|
-
const
|
|
80160
|
-
const
|
|
80183
|
+
const storageFull = error61 instanceof Error && ["ENOSPC", "EDQUOT"].includes(error61.code ?? "");
|
|
80184
|
+
const code = error61 instanceof SkillSelectionError ? error61.code : storageFull ? "SKILLS_STORAGE_FULL" : "SKILLS_CONTEXT_FAILED";
|
|
80185
|
+
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
80186
|
if (json2)
|
|
80162
80187
|
console.log(JSON.stringify({ error: { code, message } }));
|
|
80163
80188
|
else
|
|
@@ -108616,7 +108641,7 @@ async function connectCodexHookRpc(options) {
|
|
|
108616
108641
|
}
|
|
108617
108642
|
var SUPPORTED_CODEX_HOOK_VERSIONS;
|
|
108618
108643
|
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"];
|
|
108644
|
+
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
108645
|
});
|
|
108621
108646
|
|
|
108622
108647
|
// src/lib/codex-hook-trust-identity.ts
|
|
@@ -109054,6 +109079,9 @@ function hookFailureReason(error61, profileId) {
|
|
|
109054
109079
|
const profile = typeof profileId === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) && !profileId.includes("..") ? profileId : undefined;
|
|
109055
109080
|
const identity4 = [profile ? `profile=${profile}` : undefined, owned ? `stage=${error61.stage}` : undefined].filter(Boolean).join(", ");
|
|
109056
109081
|
const prefix = `Skills context is unavailable [${code2}]${identity4 ? ` (${identity4})` : ""}.`;
|
|
109082
|
+
if (code2 === "SKILLS_STORAGE_FULL") {
|
|
109083
|
+
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.`;
|
|
109084
|
+
}
|
|
109057
109085
|
if (code2 === "PROFILE_LOCK_MISMATCH") {
|
|
109058
109086
|
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
109087
|
}
|
|
@@ -109070,6 +109098,7 @@ var CONTEXT_ERROR_CODES, HookDiagnosticError;
|
|
|
109070
109098
|
var init_hook_diagnostics = __esm(() => {
|
|
109071
109099
|
CONTEXT_ERROR_CODES = new Set([
|
|
109072
109100
|
"SKILLS_CONTEXT_FAILED",
|
|
109101
|
+
"SKILLS_STORAGE_FULL",
|
|
109073
109102
|
"PROFILE_LOCK_MISMATCH",
|
|
109074
109103
|
"PROFILE_IDENTITY_MISMATCH",
|
|
109075
109104
|
"PROFILE_APPLIED_REPORT_FAILED",
|
|
@@ -109185,10 +109214,10 @@ function agents(value) {
|
|
|
109185
109214
|
}
|
|
109186
109215
|
function registerAgentIntegration(parent) {
|
|
109187
109216
|
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;
|
|
109217
|
+
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) => {
|
|
109218
|
+
const capture2 = options.kind === "claude-settings-v3" ? captureClaudeSettingsV3 : options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
|
|
109190
109219
|
if (!capture2)
|
|
109191
|
-
throw new Error("Unsupported witness kind; select claude-settings-v2 or claude-marketplace-registry-v2");
|
|
109220
|
+
throw new Error("Unsupported witness kind; select claude-settings-v3, claude-settings-v2 or claude-marketplace-registry-v2");
|
|
109192
109221
|
await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
|
|
109193
109222
|
});
|
|
109194
109223
|
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.18",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
|
@@ -21311,7 +21311,8 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
21311
21311
|
}
|
|
21312
21312
|
let completed;
|
|
21313
21313
|
try {
|
|
21314
|
-
|
|
21314
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
21315
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
21315
21316
|
} catch (error61) {
|
|
21316
21317
|
const translated = asSkillsFleetCredentialError(error61, pointer.pointerVaultKey);
|
|
21317
21318
|
if (translated)
|
|
@@ -38126,8 +38127,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
38126
38127
|
text(source.path);
|
|
38127
38128
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
38128
38129
|
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")
|
|
38130
|
+
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));
|
|
38131
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
|
|
38131
38132
|
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
38133
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
38133
38134
|
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 +47081,7 @@ function canonical2(value) {
|
|
|
47080
47081
|
return { kind: "array", items: value.items.map(canonical2) };
|
|
47081
47082
|
return value;
|
|
47082
47083
|
}
|
|
47083
|
-
function inferencePreference(key, child) {
|
|
47084
|
+
function inferencePreference(key, child, version2) {
|
|
47084
47085
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
47085
47086
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
47086
47087
|
return false;
|
|
@@ -47093,7 +47094,7 @@ function inferencePreference(key, child) {
|
|
|
47093
47094
|
return false;
|
|
47094
47095
|
need2(child.kind === "object", "requires an object of model preferences");
|
|
47095
47096
|
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")
|
|
47097
|
+
if (version2 === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
47097
47098
|
return true;
|
|
47098
47099
|
const effort = preferences.entries[0][1];
|
|
47099
47100
|
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -47104,7 +47105,7 @@ function inferencePreference(key, child) {
|
|
|
47104
47105
|
function settingsDigest(text2, version2 = 1) {
|
|
47105
47106
|
const value = parse8(text2);
|
|
47106
47107
|
value.entries = value.entries.filter(([key, child]) => {
|
|
47107
|
-
if (version2
|
|
47108
|
+
if (version2 !== 1 && inferencePreference(key, child, version2))
|
|
47108
47109
|
return false;
|
|
47109
47110
|
if (key === "model") {
|
|
47110
47111
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
@@ -47142,6 +47143,16 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
47142
47143
|
budget.remaining -= bytes;
|
|
47143
47144
|
return settingsDigest(text2, 2);
|
|
47144
47145
|
}
|
|
47146
|
+
function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
47147
|
+
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
|
|
47148
|
+
}
|
|
47149
|
+
function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
47150
|
+
need2(typeof text2 === "string", "requires settings text");
|
|
47151
|
+
const bytes = Buffer.byteLength(text2);
|
|
47152
|
+
need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
47153
|
+
budget.remaining -= bytes;
|
|
47154
|
+
return settingsDigest(text2, 3);
|
|
47155
|
+
}
|
|
47145
47156
|
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
47157
|
var init_claude_settings_witness = __esm(() => {
|
|
47147
47158
|
CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
@@ -48867,13 +48878,13 @@ function read(path, changes) {
|
|
|
48867
48878
|
return readFileSync14(path, "utf8");
|
|
48868
48879
|
}
|
|
48869
48880
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
48870
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
48881
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
48871
48882
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
48872
48883
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
48873
|
-
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
48884
|
+
const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
48874
48885
|
if (current !== source.sha256)
|
|
48875
48886
|
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;
|
|
48887
|
+
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
48888
|
}
|
|
48878
48889
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
48879
48890
|
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 +48946,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
48935
48946
|
safe4(root);
|
|
48936
48947
|
}
|
|
48937
48948
|
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"))
|
|
48949
|
+
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
48950
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
48940
48951
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
48941
48952
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -49016,7 +49027,7 @@ function resolveAgentDiscovery(options) {
|
|
|
49016
49027
|
verifyAgentDiscovery(retained);
|
|
49017
49028
|
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
49018
49029
|
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)))
|
|
49030
|
+
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
49031
|
unresolved("retained settings witness names another configuration source");
|
|
49021
49032
|
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
49033
|
unresolved("retained review is missing current runtime discovery coverage");
|
|
@@ -49032,7 +49043,7 @@ function resolveAgentDiscovery(options) {
|
|
|
49032
49043
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
49033
49044
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
49034
49045
|
verifyAgentDiscovery(supplied);
|
|
49035
|
-
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
49046
|
+
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
49047
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
49037
49048
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
49038
49049
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -52017,7 +52028,20 @@ class RemoteSkillsClient {
|
|
|
52017
52028
|
const url2 = new URL(upload.uploadUrl);
|
|
52018
52029
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
52019
52030
|
throw new Error("Unsafe input upload URL");
|
|
52020
|
-
const
|
|
52031
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
52032
|
+
if (upload.uploadHeaders !== undefined) {
|
|
52033
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
52034
|
+
throw new Error("Invalid input upload headers");
|
|
52035
|
+
const seen = new Set;
|
|
52036
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
52037
|
+
const lowerName = name.toLowerCase();
|
|
52038
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
52039
|
+
throw new Error("Invalid input upload headers");
|
|
52040
|
+
seen.add(lowerName);
|
|
52041
|
+
headers.set(name, value);
|
|
52042
|
+
}
|
|
52043
|
+
}
|
|
52044
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
52021
52045
|
if (!uploaded.ok)
|
|
52022
52046
|
throw new Error("Input upload failed");
|
|
52023
52047
|
await uploaded.body?.cancel();
|
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.18",
|
|
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.18",
|
|
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.18",
|
|
9615
9615
|
description: "Skills library for AI coding agents",
|
|
9616
9616
|
type: "module",
|
|
9617
9617
|
bin: {
|
|
@@ -10891,7 +10891,8 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
10891
10891
|
}
|
|
10892
10892
|
let completed;
|
|
10893
10893
|
try {
|
|
10894
|
-
|
|
10894
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
10895
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
10895
10896
|
} catch (error) {
|
|
10896
10897
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
10897
10898
|
if (translated)
|
|
@@ -12874,7 +12875,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
|
12874
12875
|
need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
12875
12876
|
}
|
|
12876
12877
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
12877
|
-
function inferencePreference(key, child) {
|
|
12878
|
+
function inferencePreference(key, child, version) {
|
|
12878
12879
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
12879
12880
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
12880
12881
|
return false;
|
|
@@ -12887,7 +12888,7 @@ function inferencePreference(key, child) {
|
|
|
12887
12888
|
return false;
|
|
12888
12889
|
need2(child.kind === "object", "requires an object of model preferences");
|
|
12889
12890
|
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")
|
|
12891
|
+
if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
12891
12892
|
return true;
|
|
12892
12893
|
const effort = preferences.entries[0][1];
|
|
12893
12894
|
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -12898,7 +12899,7 @@ function inferencePreference(key, child) {
|
|
|
12898
12899
|
function settingsDigest(text2, version = 1) {
|
|
12899
12900
|
const value = parse3(text2);
|
|
12900
12901
|
value.entries = value.entries.filter(([key, child]) => {
|
|
12901
|
-
if (version
|
|
12902
|
+
if (version !== 1 && inferencePreference(key, child, version))
|
|
12902
12903
|
return false;
|
|
12903
12904
|
if (key === "model") {
|
|
12904
12905
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
@@ -12936,6 +12937,25 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
12936
12937
|
budget.remaining -= bytes;
|
|
12937
12938
|
return settingsDigest(text2, 2);
|
|
12938
12939
|
}
|
|
12940
|
+
function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12941
|
+
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
|
|
12942
|
+
}
|
|
12943
|
+
function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
12944
|
+
need2(typeof text2 === "string", "requires settings text");
|
|
12945
|
+
const bytes = Buffer.byteLength(text2);
|
|
12946
|
+
need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
12947
|
+
budget.remaining -= bytes;
|
|
12948
|
+
return settingsDigest(text2, 3);
|
|
12949
|
+
}
|
|
12950
|
+
function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
|
|
12951
|
+
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");
|
|
12952
|
+
const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
|
|
12953
|
+
const before = readSettings(reviewedSettingsPath, budget), current = readSettings(previous.path, budget);
|
|
12954
|
+
need2(settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : 2) === previous.sha256, "reviewed preimage does not match the legacy witness");
|
|
12955
|
+
const sha2562 = settingsDigest(current, 3);
|
|
12956
|
+
need2(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
|
|
12957
|
+
return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
|
|
12958
|
+
}
|
|
12939
12959
|
|
|
12940
12960
|
// src/lib/agent-integration.ts
|
|
12941
12961
|
var sha = (data) => createHash10("sha256").update(data).digest("hex");
|
|
@@ -16507,13 +16527,13 @@ function read2(path, changes) {
|
|
|
16507
16527
|
return readFileSync16(path, "utf8");
|
|
16508
16528
|
}
|
|
16509
16529
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
16510
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
16530
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
16511
16531
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
16512
16532
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
16513
|
-
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
16533
|
+
const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
16514
16534
|
if (current !== source.sha256)
|
|
16515
16535
|
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;
|
|
16536
|
+
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
16537
|
}
|
|
16518
16538
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
16519
16539
|
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 +16595,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
16575
16595
|
safe4(root2);
|
|
16576
16596
|
}
|
|
16577
16597
|
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"))
|
|
16598
|
+
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
16599
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
16580
16600
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
16581
16601
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -16718,7 +16738,7 @@ function resolveAgentDiscovery(options) {
|
|
|
16718
16738
|
verifyAgentDiscovery(retained);
|
|
16719
16739
|
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
16720
16740
|
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)))
|
|
16741
|
+
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
16742
|
unresolved("retained settings witness names another configuration source");
|
|
16723
16743
|
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
16744
|
unresolved("retained review is missing current runtime discovery coverage");
|
|
@@ -16734,7 +16754,7 @@ function resolveAgentDiscovery(options) {
|
|
|
16734
16754
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
16735
16755
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
16736
16756
|
verifyAgentDiscovery(supplied);
|
|
16737
|
-
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
16757
|
+
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
16758
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
16739
16759
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
16740
16760
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -39076,7 +39096,20 @@ class RemoteSkillsClient {
|
|
|
39076
39096
|
const url2 = new URL(upload.uploadUrl);
|
|
39077
39097
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
39078
39098
|
throw new Error("Unsafe input upload URL");
|
|
39079
|
-
const
|
|
39099
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
39100
|
+
if (upload.uploadHeaders !== undefined) {
|
|
39101
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
39102
|
+
throw new Error("Invalid input upload headers");
|
|
39103
|
+
const seen = new Set;
|
|
39104
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
39105
|
+
const lowerName = name.toLowerCase();
|
|
39106
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
39107
|
+
throw new Error("Invalid input upload headers");
|
|
39108
|
+
seen.add(lowerName);
|
|
39109
|
+
headers.set(name, value);
|
|
39110
|
+
}
|
|
39111
|
+
}
|
|
39112
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
39080
39113
|
if (!uploaded.ok)
|
|
39081
39114
|
throw new Error("Input upload failed");
|
|
39082
39115
|
await uploaded.body?.cancel();
|
|
@@ -43682,6 +43715,7 @@ export {
|
|
|
43682
43715
|
validateCron,
|
|
43683
43716
|
validateBlogArticleRunOptions,
|
|
43684
43717
|
uploadSkillsSnapshotFilesToS3,
|
|
43718
|
+
upgradeClaudeSettingsWitness,
|
|
43685
43719
|
updateSkillRun,
|
|
43686
43720
|
unsetConfig,
|
|
43687
43721
|
unpinSkill,
|
|
@@ -43860,6 +43894,7 @@ export {
|
|
|
43860
43894
|
captureDiscoveryPathSources,
|
|
43861
43895
|
captureDiscoveryDirectories,
|
|
43862
43896
|
captureDiscoveryByteSources,
|
|
43897
|
+
captureClaudeSettingsV3,
|
|
43863
43898
|
captureClaudeSettingsV2,
|
|
43864
43899
|
captureClaudeSettings,
|
|
43865
43900
|
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.18",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -33712,7 +33712,8 @@ async function resolveSkillsConnection(env = process.env, options = {}) {
|
|
|
33712
33712
|
}
|
|
33713
33713
|
let completed;
|
|
33714
33714
|
try {
|
|
33715
|
-
|
|
33715
|
+
const explicitVault = Boolean(snapshotEnv.HASNA_SECRETS_API_URL?.trim() && snapshotEnv.HASNA_SECRETS_API_KEY_OVERRIDE?.trim());
|
|
33716
|
+
completed = await completePointerCredential(SKILLS_APP, pointer, explicitVault ? snapshotEnv : env);
|
|
33716
33717
|
} catch (error) {
|
|
33717
33718
|
const translated = asSkillsFleetCredentialError(error, pointer.pointerVaultKey);
|
|
33718
33719
|
if (translated)
|
|
@@ -37545,8 +37546,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
37545
37546
|
text(source.path);
|
|
37546
37547
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
37547
37548
|
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")
|
|
37549
|
+
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));
|
|
37550
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
|
|
37550
37551
|
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
37552
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
37552
37553
|
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 +40155,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
|
40154
40155
|
need3(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
40155
40156
|
}
|
|
40156
40157
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
40157
|
-
function inferencePreference(key, child) {
|
|
40158
|
+
function inferencePreference(key, child, version) {
|
|
40158
40159
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
40159
40160
|
need3(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
40160
40161
|
return false;
|
|
@@ -40167,7 +40168,7 @@ function inferencePreference(key, child) {
|
|
|
40167
40168
|
return false;
|
|
40168
40169
|
need3(child.kind === "object", "requires an object of model preferences");
|
|
40169
40170
|
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")
|
|
40171
|
+
if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
40171
40172
|
return true;
|
|
40172
40173
|
const effort = preferences.entries[0][1];
|
|
40173
40174
|
need3(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -40178,7 +40179,7 @@ function inferencePreference(key, child) {
|
|
|
40178
40179
|
function settingsDigest(text2, version = 1) {
|
|
40179
40180
|
const value = parse6(text2);
|
|
40180
40181
|
value.entries = value.entries.filter(([key, child]) => {
|
|
40181
|
-
if (version
|
|
40182
|
+
if (version !== 1 && inferencePreference(key, child, version))
|
|
40182
40183
|
return false;
|
|
40183
40184
|
if (key === "model") {
|
|
40184
40185
|
need3(child.kind === "string", "contains an invalid model selection");
|
|
@@ -40216,6 +40217,25 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
40216
40217
|
budget.remaining -= bytes;
|
|
40217
40218
|
return settingsDigest(text2, 2);
|
|
40218
40219
|
}
|
|
40220
|
+
function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
40221
|
+
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
|
|
40222
|
+
}
|
|
40223
|
+
function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
40224
|
+
need3(typeof text2 === "string", "requires settings text");
|
|
40225
|
+
const bytes = Buffer.byteLength(text2);
|
|
40226
|
+
need3(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
40227
|
+
budget.remaining -= bytes;
|
|
40228
|
+
return settingsDigest(text2, 3);
|
|
40229
|
+
}
|
|
40230
|
+
function upgradeClaudeSettingsWitness(previous, reviewedSettingsPath) {
|
|
40231
|
+
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");
|
|
40232
|
+
const budget = { remaining: 2 * CLAUDE_SETTINGS_WITNESS_LIMITS.bytes };
|
|
40233
|
+
const before = readSettings(reviewedSettingsPath, budget), current = readSettings(previous.path, budget);
|
|
40234
|
+
need3(settingsDigest(before, previous.hashMode === "claude-settings-v1" ? 1 : 2) === previous.sha256, "reviewed preimage does not match the legacy witness");
|
|
40235
|
+
const sha2562 = settingsDigest(current, 3);
|
|
40236
|
+
need3(settingsDigest(before, 3) === sha2562, "non-preference settings changed; explicit discovery review required");
|
|
40237
|
+
return { path: previous.path, hashMode: "claude-settings-v3", sha256: sha2562 };
|
|
40238
|
+
}
|
|
40219
40239
|
|
|
40220
40240
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
40221
40241
|
import { createHash as createHash14 } from "crypto";
|
|
@@ -40325,13 +40345,13 @@ function read2(path, changes) {
|
|
|
40325
40345
|
return readFileSync12(path, "utf8");
|
|
40326
40346
|
}
|
|
40327
40347
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
40328
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
40348
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
40329
40349
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
40330
40350
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
40331
|
-
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
40351
|
+
const current = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
40332
40352
|
if (current !== source.sha256)
|
|
40333
40353
|
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;
|
|
40354
|
+
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
40355
|
}
|
|
40336
40356
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
40337
40357
|
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 +40413,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
40393
40413
|
safe4(root2);
|
|
40394
40414
|
}
|
|
40395
40415
|
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"))
|
|
40416
|
+
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
40417
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
40398
40418
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
40399
40419
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -40536,7 +40556,7 @@ function resolveAgentDiscovery(options) {
|
|
|
40536
40556
|
verifyAgentDiscovery(retained);
|
|
40537
40557
|
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
40538
40558
|
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)))
|
|
40559
|
+
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
40560
|
unresolved("retained settings witness names another configuration source");
|
|
40541
40561
|
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
40562
|
unresolved("retained review is missing current runtime discovery coverage");
|
|
@@ -40552,7 +40572,7 @@ function resolveAgentDiscovery(options) {
|
|
|
40552
40572
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
40553
40573
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
40554
40574
|
verifyAgentDiscovery(supplied);
|
|
40555
|
-
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
40575
|
+
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
40576
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
40557
40577
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
40558
40578
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -89646,7 +89666,20 @@ class RemoteSkillsClient {
|
|
|
89646
89666
|
const url2 = new URL(upload.uploadUrl);
|
|
89647
89667
|
if (url2.username || url2.password || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname)))
|
|
89648
89668
|
throw new Error("Unsafe input upload URL");
|
|
89649
|
-
const
|
|
89669
|
+
const headers = new Headers({ "Content-Type": file2.contentType ?? "application/octet-stream" });
|
|
89670
|
+
if (upload.uploadHeaders !== undefined) {
|
|
89671
|
+
if (upload.uploadHeaders === null || typeof upload.uploadHeaders !== "object" || Array.isArray(upload.uploadHeaders))
|
|
89672
|
+
throw new Error("Invalid input upload headers");
|
|
89673
|
+
const seen = new Set;
|
|
89674
|
+
for (const [name, value] of Object.entries(upload.uploadHeaders)) {
|
|
89675
|
+
const lowerName = name.toLowerCase();
|
|
89676
|
+
if (seen.has(lowerName) || !["if-none-match", "x-amz-tagging"].includes(lowerName) || typeof value !== "string" || value.length === 0 || value.length > 2048 || !/^[\x20-\x7e]+$/.test(value) || value.trim() !== value || lowerName === "if-none-match" && value !== "*")
|
|
89677
|
+
throw new Error("Invalid input upload headers");
|
|
89678
|
+
seen.add(lowerName);
|
|
89679
|
+
headers.set(name, value);
|
|
89680
|
+
}
|
|
89681
|
+
}
|
|
89682
|
+
const uploaded = await fetch(url2, { method: "PUT", body: file2.bytes, headers, redirect: "error", signal: AbortSignal.timeout(60000) });
|
|
89650
89683
|
if (!uploaded.ok)
|
|
89651
89684
|
throw new Error("Input upload failed");
|
|
89652
89685
|
await uploaded.body?.cancel();
|
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
|