@hasna/skills 0.9.15 → 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/README.md +7 -0
- package/bin/index.js +89 -47
- package/bin/mcp.js +37 -13
- 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 +62 -14
- package/dist/lib/agent-discovery.d.ts +2 -1
- package/dist/lib/agent-integration.d.ts +5 -0
- 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 +63 -17
- package/docs/plugin-admission.md +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -373,6 +373,13 @@ system configuration, process-specific overrides, and alternate agent home
|
|
|
373
373
|
directories are outside automatic coverage and require their own integration
|
|
374
374
|
review before declaring a station migrated.
|
|
375
375
|
|
|
376
|
+
When `hook install` omits `--discovery-inputs`, it reuses an existing reviewed
|
|
377
|
+
binding only after rechecking its sources, directory membership, configuration
|
|
378
|
+
coverage, managed bridge and native trust. Unchanged reviews need no new input
|
|
379
|
+
file; drift still requires a fresh explicit review. Explicit discovery inputs
|
|
380
|
+
take precedence, including when their `agents` array omits a previously reviewed
|
|
381
|
+
agent. The default `--agent all` still includes every supported adapter.
|
|
382
|
+
|
|
376
383
|
For reviewed Claude plugins, `skills integration plugin plan`, `admit` and
|
|
377
384
|
`resolve` provide a command-source admission boundary before native discovery.
|
|
378
385
|
Original packages and migrated skill/command prompts remain in private versioned
|
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 });
|
|
@@ -51146,7 +51156,15 @@ function planAgentIntegration(options) {
|
|
|
51146
51156
|
validateProfile(profileId2);
|
|
51147
51157
|
return [agent, { command, profileId: profileId2 }];
|
|
51148
51158
|
}));
|
|
51149
|
-
const
|
|
51159
|
+
const retainedAgents = [];
|
|
51160
|
+
const discoveries = [...new Set(options.agents)].map((agent) => {
|
|
51161
|
+
const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? policy.bridge.discovery[agent] : undefined;
|
|
51162
|
+
if (retainedReview) {
|
|
51163
|
+
assertManagedAgentBridge(agent, { home, dataDir: dataDir3, projectDir: options.projectDir ?? home });
|
|
51164
|
+
retainedAgents.push(agent);
|
|
51165
|
+
}
|
|
51166
|
+
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
51167
|
+
});
|
|
51150
51168
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
|
|
51151
51169
|
const changes = [];
|
|
51152
51170
|
for (const agent of [...new Set(options.agents)]) {
|
|
@@ -51231,7 +51249,7 @@ function planAgentIntegration(options) {
|
|
|
51231
51249
|
if (JSON.stringify(policy) !== JSON.stringify(nextPolicy))
|
|
51232
51250
|
changes.push({ path: policyPath, before: previousPolicy, after: serializedPolicy });
|
|
51233
51251
|
recheckRootAliases(aliases);
|
|
51234
|
-
return { dataDir: dataDir3, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
51252
|
+
return { dataDir: dataDir3, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
51235
51253
|
}
|
|
51236
51254
|
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
51237
51255
|
try {
|
|
@@ -51269,6 +51287,8 @@ function applyAgentIntegration(plan) {
|
|
|
51269
51287
|
parseManagedSkillPolicy(change.after);
|
|
51270
51288
|
const aliases = plan.rootAliases ?? [];
|
|
51271
51289
|
recheckRootAliases(aliases);
|
|
51290
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
51291
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
51272
51292
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
51273
51293
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
51274
51294
|
for (const binding of plan.discoveryBefore ?? []) {
|
|
@@ -54741,7 +54761,7 @@ function reviewNativeHookAdditions(options) {
|
|
|
54741
54761
|
need4(Array.isArray(groups), "RECONCILE_ADDITIONS_DECLARATIONS");
|
|
54742
54762
|
const key = `${options.nativeHooksPath}:${matchedEvents[event][1]}:${groups.length}:0`;
|
|
54743
54763
|
need4(hook.key === key && !seen.has(key) && !options.recordedNativeHooks.some((originalHook) => originalHook.key === key) && !Object.hasOwn(expectedConfig.hooks.state, key), "RECONCILE_ADDITIONS_IDENTITY");
|
|
54744
|
-
need4(hook.eventName === matchedEvents[event][0] && hook.handlerType === "command" && text2(hook.command, 16384) && !commands.has(hook.command) && Number.isInteger(hook.timeoutSec) && hook.timeoutSec > 0 && hook.timeoutSec <= 60 && (event === "PreToolUse" ? text2(hook.matcher) : hook.matcher === null) && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && hook.sourcePath === options.nativeHooksPath && hook.source === "user" && hook.pluginId === null && hook.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash) && hook.enabled === true && hook.trustStatus === "trusted", "RECONCILE_ADDITIONS_IDENTITY");
|
|
54764
|
+
need4(hook.eventName === matchedEvents[event][0] && hook.handlerType === "command" && (review.version === 2 ? v2Command(hook.command) : text2(hook.command, 16384)) && !commands.has(hook.command) && Number.isInteger(hook.timeoutSec) && hook.timeoutSec > 0 && hook.timeoutSec <= 60 && (event === "PreToolUse" ? text2(hook.matcher) : hook.matcher === null) && hook.async === false && hook.statusMessage === null && hook.additionalContextLimit === null && hook.sourcePath === options.nativeHooksPath && hook.source === "user" && hook.pluginId === null && hook.isManaged === false && /^sha256:[a-f0-9]{64}$/.test(hook.currentHash) && hook.enabled === true && hook.trustStatus === "trusted", "RECONCILE_ADDITIONS_IDENTITY");
|
|
54745
54765
|
let trustState = { enabled: true, trusted_hash: hook.currentHash };
|
|
54746
54766
|
if (review.version === 2) {
|
|
54747
54767
|
const state = addition.trustState;
|
|
@@ -54769,7 +54789,7 @@ function reviewNativeHookAdditions(options) {
|
|
|
54769
54789
|
proofs.set(trustProof, { before: options.configBefore, current: options.configCurrent, journalHooks: options.journalHooks.map(({ key, currentHash }) => ({ key, currentHash })), states: Object.freeze(states), recheck });
|
|
54770
54790
|
return { hooks: added, recheck, trustProof, receipt: { ...review.version === 2 ? { version: 2 } : {}, reviewPath: reviewFile.file, reviewSha256: reviewFile.sha256, reason: review.reason, producerEvidence: review.producerEvidence, producerBefore: { configPath: producerConfig.file, configSha256: producerConfig.sha256, hooksPath: producerHooks.file, hooksSha256: producerHooks.sha256 }, additions: review.version === 1 ? added : review.additions } };
|
|
54771
54791
|
}
|
|
54772
|
-
var events, matchedEvents, hash2 = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value), text2 = (value, max2 = 2048) => typeof value === "string" && value.trim().length > 0 && value.length <= max2 && !/[\0\r\n]/.test(value), structure = (value) => JSON.parse(JSON.stringify(Bun.TOML.parse(value))), nativeKeys, proofs;
|
|
54792
|
+
var events, matchedEvents, hash2 = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value), text2 = (value, max2 = 2048) => typeof value === "string" && value.trim().length > 0 && value.length <= max2 && !/[\0\r\n]/.test(value), v2Command = (value) => typeof value === "string" && value.trim().length > 0 && value.length <= 16384 && !/[\0\r]/.test(value), structure = (value) => JSON.parse(JSON.stringify(Bun.TOML.parse(value))), nativeKeys, proofs;
|
|
54773
54793
|
var init_codex_hook_additions_review = __esm(() => {
|
|
54774
54794
|
init_codex_hook_trust_files();
|
|
54775
54795
|
init_codex_hook_trust_layout();
|
|
@@ -54843,6 +54863,7 @@ var init_codex_hook_discovery_recovery = __esm(() => {
|
|
|
54843
54863
|
import { createHash as createHash16 } from "crypto";
|
|
54844
54864
|
import { lstatSync as lstatSync20, readFileSync as readFileSync17, readdirSync as readdirSync10, realpathSync as realpathSync4 } from "fs";
|
|
54845
54865
|
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute15, join as join31, resolve as resolve15, sep as sep5 } from "path";
|
|
54866
|
+
import { isDeepStrictEqual as isDeepStrictEqual7 } from "util";
|
|
54846
54867
|
function parseConfig(text3, path, toml = false) {
|
|
54847
54868
|
try {
|
|
54848
54869
|
const value = toml ? Bun.TOML.parse(text3) : JSON.parse(text3);
|
|
@@ -54875,13 +54896,13 @@ function read2(path, changes) {
|
|
|
54875
54896
|
return readFileSync17(path, "utf8");
|
|
54876
54897
|
}
|
|
54877
54898
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
54878
|
-
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") {
|
|
54879
54900
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
54880
54901
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
54881
|
-
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;
|
|
54882
54903
|
if (current !== source.sha256)
|
|
54883
54904
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
54884
|
-
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;
|
|
54885
54906
|
}
|
|
54886
54907
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
54887
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))
|
|
@@ -54943,7 +54964,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
54943
54964
|
safe4(root2);
|
|
54944
54965
|
}
|
|
54945
54966
|
function assertMarketplaceBinding(binding, source) {
|
|
54946
|
-
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"))
|
|
54947
54968
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
54948
54969
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
54949
54970
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -54952,7 +54973,10 @@ function rebindAgentDiscovery(binding, changes) {
|
|
|
54952
54973
|
const budget = discoveryByteBudget();
|
|
54953
54974
|
return { ...binding, sources: binding.sources.map((source) => {
|
|
54954
54975
|
assertMarketplaceBinding(binding, source);
|
|
54955
|
-
|
|
54976
|
+
const sha2562 = projected(source, changes, budget);
|
|
54977
|
+
if (!changes.has(source.path) && sha2562 !== source.sha256)
|
|
54978
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
54979
|
+
return { ...source, sha256: sha2562 };
|
|
54956
54980
|
}) };
|
|
54957
54981
|
}
|
|
54958
54982
|
function assertProjectDiscovery(agent, directories, home, canonical4 = resolve15, reviewed) {
|
|
@@ -55064,6 +55088,19 @@ function resolveAgentDiscovery(options) {
|
|
|
55064
55088
|
}
|
|
55065
55089
|
}
|
|
55066
55090
|
}
|
|
55091
|
+
if (options.reviewed === undefined && options.retainedReview !== undefined) {
|
|
55092
|
+
const retained = options.retainedReview;
|
|
55093
|
+
if (retained.agent !== agent || retained.method !== "reviewed")
|
|
55094
|
+
unresolved("invalid retained review");
|
|
55095
|
+
verifyAgentDiscovery(retained);
|
|
55096
|
+
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
55097
|
+
unresolved("retained review is missing the full agent configuration source");
|
|
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)))
|
|
55099
|
+
unresolved("retained settings witness names another configuration source");
|
|
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))
|
|
55101
|
+
unresolved("retained review is missing current runtime discovery coverage");
|
|
55102
|
+
return retained;
|
|
55103
|
+
}
|
|
55067
55104
|
if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
|
|
55068
55105
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
55069
55106
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
@@ -55074,7 +55111,7 @@ function resolveAgentDiscovery(options) {
|
|
|
55074
55111
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
55075
55112
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
55076
55113
|
verifyAgentDiscovery(supplied);
|
|
55077
|
-
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)))
|
|
55078
55115
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
55079
55116
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
55080
55117
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -80129,8 +80166,9 @@ Preserved original receipt: ${result2.archivePath}` : `Planned one session recon
|
|
|
80129
80166
|
});
|
|
80130
80167
|
}
|
|
80131
80168
|
function reportContextError(error61, json2 = false) {
|
|
80132
|
-
const
|
|
80133
|
-
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.";
|
|
80134
80172
|
if (json2)
|
|
80135
80173
|
console.log(JSON.stringify({ error: { code, message } }));
|
|
80136
80174
|
else
|
|
@@ -84538,7 +84576,7 @@ var init_execution_grants = __esm(() => {
|
|
|
84538
84576
|
});
|
|
84539
84577
|
|
|
84540
84578
|
// src/lib/execution-grant-client.ts
|
|
84541
|
-
import { isDeepStrictEqual as
|
|
84579
|
+
import { isDeepStrictEqual as isDeepStrictEqual8 } from "util";
|
|
84542
84580
|
function id(value) {
|
|
84543
84581
|
if (!grantIdentifier(value))
|
|
84544
84582
|
throw new Error("Invalid execution grant profile or revision identifier.");
|
|
@@ -84590,7 +84628,7 @@ async function saveExecutionGrantPolicy(profileId, grants, revision) {
|
|
|
84590
84628
|
headers: revision === undefined ? { "If-None-Match": "*" } : { "If-Match": `"${revision}"` },
|
|
84591
84629
|
body: JSON.stringify({ grants: snapshot2 })
|
|
84592
84630
|
}));
|
|
84593
|
-
if (policy.profileId !== profileId || policy.previousRevision !== (revision ?? null) || !
|
|
84631
|
+
if (policy.profileId !== profileId || policy.previousRevision !== (revision ?? null) || !isDeepStrictEqual8(policy.grants, snapshot2)) {
|
|
84594
84632
|
throw new Error("The API did not preserve the reviewed execution grant policy; read it before retrying.");
|
|
84595
84633
|
}
|
|
84596
84634
|
return policy;
|
|
@@ -84601,7 +84639,7 @@ async function resolveExecutionGrant(input2) {
|
|
|
84601
84639
|
if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
|
|
84602
84640
|
throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
|
|
84603
84641
|
}
|
|
84604
|
-
if (!
|
|
84642
|
+
if (!isDeepStrictEqual8(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual8(value.bindings.consumer, snapshot2.consumer)) {
|
|
84605
84643
|
throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
|
|
84606
84644
|
}
|
|
84607
84645
|
return value;
|
|
@@ -108589,13 +108627,13 @@ async function connectCodexHookRpc(options) {
|
|
|
108589
108627
|
}
|
|
108590
108628
|
var SUPPORTED_CODEX_HOOK_VERSIONS;
|
|
108591
108629
|
var init_codex_hook_rpc = __esm(() => {
|
|
108592
|
-
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"];
|
|
108593
108631
|
});
|
|
108594
108632
|
|
|
108595
108633
|
// src/lib/codex-hook-trust-identity.ts
|
|
108596
108634
|
import { realpathSync as realpathSync13 } from "fs";
|
|
108597
108635
|
import { dirname as dirname30, isAbsolute as isAbsolute25, join as join56 } from "path";
|
|
108598
|
-
import { isDeepStrictEqual as
|
|
108636
|
+
import { isDeepStrictEqual as isDeepStrictEqual9 } from "util";
|
|
108599
108637
|
function inspectRecordedSkillsCli(command, expected) {
|
|
108600
108638
|
need4(isAbsolute25(command) && !command.includes("\x00"), "RECONCILE_RECORDED_COMMAND_REQUIRED");
|
|
108601
108639
|
const resolved = realpathSync13(command);
|
|
@@ -108605,7 +108643,7 @@ function inspectRecordedSkillsCli(command, expected) {
|
|
|
108605
108643
|
need4(pkg.name === "@hasna/skills" && /^\d+\.\d+\.\d+$/.test(pkg.version) && pkg.bin?.skills === "bin/index.js" && resolved === join56(dirname30(manifest.file), "bin/index.js"), "SKILLS_PACKAGE_MISMATCH");
|
|
108606
108644
|
need4((cli.stat.mode & 0o100n) !== 0n, "SKILLS_COMMAND_NOT_EXECUTABLE");
|
|
108607
108645
|
const receipt = { path: resolved, version: pkg.version, sha256: cli.sha256, manifestSha256: manifest.sha256 };
|
|
108608
|
-
need4(
|
|
108646
|
+
need4(isDeepStrictEqual9(receipt, expected), "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
108609
108647
|
return { receipt, recheck() {
|
|
108610
108648
|
need4(realpathSync13(command) === resolved, "SKILLS_COMMAND_CHANGED");
|
|
108611
108649
|
unchanged2(cli);
|
|
@@ -108641,19 +108679,19 @@ var init_codex_hook_trust_identity = __esm(() => {
|
|
|
108641
108679
|
});
|
|
108642
108680
|
|
|
108643
108681
|
// src/lib/codex-hook-policy-recovery.ts
|
|
108644
|
-
import { isDeepStrictEqual as
|
|
108682
|
+
import { isDeepStrictEqual as isDeepStrictEqual10 } from "util";
|
|
108645
108683
|
function reviewedClaudeDiscoveryChange(beforeText, currentText, options) {
|
|
108646
108684
|
need4(typeof options.reason === "string" && options.reason.trim().length > 0 && options.reason.length <= 512 && !/[\x00-\x1f\x7f]/.test(options.reason), "RECONCILE_RECOVERY_REASON_REQUIRED");
|
|
108647
108685
|
need4(options.reviewedPlanDigest === undefined || /^[a-f0-9]{64}$/.test(options.reviewedPlanDigest), "RECONCILE_RECOVERY_PLAN_CHANGED");
|
|
108648
108686
|
const before2 = parseManagedSkillPolicy(beforeText), current = parseManagedSkillPolicy(currentText);
|
|
108649
108687
|
const oldDiscovery = before2.bridge?.discovery?.claude, newDiscovery = current.bridge?.discovery?.claude;
|
|
108650
108688
|
need4(oldDiscovery?.agent === "claude" && newDiscovery?.agent === "claude" && oldDiscovery.method === "reviewed" && newDiscovery.method === "reviewed", "RECONCILE_POLICY_CHANGED");
|
|
108651
|
-
const changes = ["roots", "sources"].filter((key) => !
|
|
108689
|
+
const changes = ["roots", "sources"].filter((key) => !isDeepStrictEqual10(oldDiscovery[key], newDiscovery[key])).map((key) => ({ path: `bridge.discovery.claude.${key}`, before: oldDiscovery[key], after: newDiscovery[key] }));
|
|
108652
108690
|
need4(changes.length > 0, "RECONCILE_POLICY_CHANGED");
|
|
108653
108691
|
const compared = structuredClone(current);
|
|
108654
108692
|
compared.bridge.discovery.claude.roots = oldDiscovery.roots;
|
|
108655
108693
|
compared.bridge.discovery.claude.sources = oldDiscovery.sources;
|
|
108656
|
-
need4(
|
|
108694
|
+
need4(isDeepStrictEqual10(before2, compared), "RECONCILE_POLICY_CHANGED");
|
|
108657
108695
|
verifyAgentDiscovery(newDiscovery);
|
|
108658
108696
|
return { reason: options.reason, changes };
|
|
108659
108697
|
}
|
|
@@ -108669,7 +108707,7 @@ import { createHash as createHash27, randomUUID as randomUUID10 } from "crypto";
|
|
|
108669
108707
|
import { execFileSync } from "child_process";
|
|
108670
108708
|
import { dirname as dirname31, isAbsolute as isAbsolute26, join as join57, resolve as resolve23 } from "path";
|
|
108671
108709
|
import { homedir as homedir12 } from "os";
|
|
108672
|
-
import { isDeepStrictEqual as
|
|
108710
|
+
import { isDeepStrictEqual as isDeepStrictEqual11 } from "util";
|
|
108673
108711
|
function verifyReleaseArtifact(proof, skillsCli) {
|
|
108674
108712
|
need4(isAbsolute26(proof.packageTarPath) && !proof.packageTarPath.includes("\x00"), "RECONCILE_RELEASE_PROVENANCE_PATH");
|
|
108675
108713
|
const tarPath = realpathSync14(proof.packageTarPath);
|
|
@@ -108711,7 +108749,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108711
108749
|
const configSha256 = intent.configSha256 ?? configBefore.sha256, configVersion = intent.configVersion ?? intent.expectedVersion;
|
|
108712
108750
|
const digestInput = { version: 1, home: intent.home ?? home, configPath: intent.configPath, ...intent.nativeConfigPath !== undefined ? { nativeConfigPath: intent.nativeConfigPath } : {}, configSha256, configVersion, policySha256: intent.policySha256, hooksSha256: intent.hooksSha256, skillsCli: intent.skillsCli, nativeVersion: intent.nativeVersion, declarations, admitted };
|
|
108713
108751
|
need4(makePlanDigest(digestInput) === intent.planDigest, "RECONCILE_PLAN_CHANGED");
|
|
108714
|
-
need4(
|
|
108752
|
+
need4(isDeepStrictEqual11(intent.hooks, admitted.filter((hook) => !hook.enabled || hook.trustStatus !== "trusted")), "RECONCILE_INTENT_HOOKS_CHANGED");
|
|
108715
108753
|
need4(configBefore.sha256 === (intent.beforeSha256 ?? intent.configSha256) && hooksBefore.sha256 === intent.hooksSha256 && policyBefore.sha256 === intent.policySha256, "RECONCILE_JOURNAL_CHANGED");
|
|
108716
108754
|
const policyCurrent = snapshot(join57(dataDir3, "agent-policy.json"), true);
|
|
108717
108755
|
const { claudeDiscoveryRecovery: claudeRecovery, nativeHookAdditionsRecovery: additionsRecovery } = options;
|
|
@@ -108724,7 +108762,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108724
108762
|
if (policyCurrent.sha256 !== policyBefore.sha256 && !claudeRecovery) {
|
|
108725
108763
|
need4(options.supersedeBinding === true, "RECONCILE_POLICY_CHANGED");
|
|
108726
108764
|
const oldPolicy = JSON.parse(policyBefore.text), currentPolicy = JSON.parse(policyCurrent.text);
|
|
108727
|
-
need4(
|
|
108765
|
+
need4(isDeepStrictEqual11(withoutCodexCommand(oldPolicy), withoutCodexCommand(currentPolicy)), "RECONCILE_POLICY_CHANGED");
|
|
108728
108766
|
supersededBinding = true;
|
|
108729
108767
|
}
|
|
108730
108768
|
if (supersededBinding || recovery) {
|
|
@@ -108760,7 +108798,7 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108760
108798
|
need4(state?.enabled === true && typeof state.trusted_hash === "string" && /^sha256:[a-f0-9]{64}$/.test(state.trusted_hash), "RECONCILE_CONFIG_DRIFT");
|
|
108761
108799
|
}
|
|
108762
108800
|
} else
|
|
108763
|
-
need4(
|
|
108801
|
+
need4(isDeepStrictEqual11(expectedConfig, current), "RECONCILE_CONFIG_DRIFT");
|
|
108764
108802
|
const configDiscoveryRecovery = recovery && binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, configBefore, currentConfig, intent.hooks, additions?.trustProof) : undefined;
|
|
108765
108803
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: configDiscoveryRecovery });
|
|
108766
108804
|
const recoveryCli = recovery ? bindSkillsCli("skills", options.reviewedSkillsCli) : undefined;
|
|
@@ -108769,14 +108807,14 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108769
108807
|
const skillsCli = recovery ? inspectRecordedSkillsCli(binding.commands.codex, intent.skillsCli) : bindSkillsCli(binding.commands.codex, options.reviewedSkillsCli);
|
|
108770
108808
|
if (supersededBinding)
|
|
108771
108809
|
verifyReleaseArtifact(options.releaseProof, skillsCli);
|
|
108772
|
-
const skillsBindingChanged = !
|
|
108810
|
+
const skillsBindingChanged = !isDeepStrictEqual11(skillsCli.receipt, intent.skillsCli);
|
|
108773
108811
|
need4(!skillsBindingChanged || supersededBinding, "RECONCILE_SKILLS_BINDING_CHANGED");
|
|
108774
108812
|
if (!additions && hooksCurrent.sha256 !== intent.hooksSha256 && hooksCurrent.sha256 !== hooksBefore.sha256) {
|
|
108775
108813
|
need4(supersededBinding, "RECONCILE_HOOKS_CHANGED");
|
|
108776
108814
|
const currentDeclarations = JSON.parse(hooksCurrent.text).hooks;
|
|
108777
108815
|
for (const [event, [nativeEvent]] of Object.entries(eventNames)) {
|
|
108778
108816
|
const command = renderAgentHookCommand(binding.commands.codex, "codex", binding.profiles.codex, event);
|
|
108779
|
-
need4(
|
|
108817
|
+
need4(isDeepStrictEqual11(currentDeclarations?.[event], [{ hooks: [{ type: "command", command, timeout: 15 }] }]), "RECONCILE_BINDING_HOOK_CHANGED");
|
|
108780
108818
|
}
|
|
108781
108819
|
}
|
|
108782
108820
|
rpc = await connect({ command: options.codexCommand ?? "codex", home, codexHome });
|
|
@@ -108799,9 +108837,9 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108799
108837
|
delete identity4.command;
|
|
108800
108838
|
return identity4;
|
|
108801
108839
|
};
|
|
108802
|
-
need4(
|
|
108840
|
+
need4(isDeepStrictEqual11(stable(actual), stable(expected)), "RECONCILE_NATIVE_IDENTITY_CHANGED");
|
|
108803
108841
|
if (!plannedKeys.has(expected.key))
|
|
108804
|
-
need4(
|
|
108842
|
+
need4(isDeepStrictEqual11(actual, expected), "RECONCILE_UNRELATED_HOOK_CHANGED");
|
|
108805
108843
|
}
|
|
108806
108844
|
for (const wanted of intent.hooks) {
|
|
108807
108845
|
const matches2 = nativeHooks.filter((h2) => h2.key === wanted.key || h2.key === wanted.key.replace(join57(home, ".codex/hooks.json"), hooksPath));
|
|
@@ -108818,12 +108856,12 @@ async function reconcileCodexNativeHooks(options, connect = connectCodexHookRpc)
|
|
|
108818
108856
|
}
|
|
108819
108857
|
const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
108820
108858
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
108821
|
-
need4(layers?.length === 1 && !layers[0].disabledReason &&
|
|
108859
|
+
need4(layers?.length === 1 && !layers[0].disabledReason && isDeepStrictEqual11(layers[0].config, current), "RECONCILE_NATIVE_CONFIG_MISMATCH");
|
|
108822
108860
|
const finalDiscovery = await rpc.request("hooks/list", { cwds: [home] });
|
|
108823
|
-
need4(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks ||
|
|
108861
|
+
need4(Array.isArray(finalDiscovery?.data) && finalDiscovery.data.length === 1 && finalDiscovery.data[0].cwd === home && !finalDiscovery.data[0].errors?.length && !finalDiscovery.data[0].warnings?.length && (!recordedNativeHooks || isDeepStrictEqual11(finalDiscovery.data[0].hooks.map(safeNativeHook), nativeHooks.map(safeNativeHook))), "RECONCILE_NATIVE_STATE_CHANGED");
|
|
108824
108862
|
const finalConfig = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
108825
108863
|
const finalLayers = finalConfig?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
108826
|
-
need4(finalLayers?.length === 1 && !finalLayers[0].disabledReason &&
|
|
108864
|
+
need4(finalLayers?.length === 1 && !finalLayers[0].disabledReason && isDeepStrictEqual11(finalLayers[0].config, current), "RECONCILE_NATIVE_CONFIG_CHANGED");
|
|
108827
108865
|
if (recovery) {
|
|
108828
108866
|
for (const response of [config2, finalConfig]) {
|
|
108829
108867
|
const layerIndex = response.layers.findIndex((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
@@ -108881,7 +108919,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108881
108919
|
const groups = declarations[event];
|
|
108882
108920
|
need4(Array.isArray(groups), "DECLARATION_CHANGED");
|
|
108883
108921
|
const matches2 = groups.flatMap((group, gi) => (group.hooks ?? []).flatMap((entry, hi) => entry.command === command ? [{ group, entry, gi, hi }] : []));
|
|
108884
|
-
need4(matches2.length === 1 &&
|
|
108922
|
+
need4(matches2.length === 1 && isDeepStrictEqual11(matches2[0].group, { hooks: [{ type: "command", command, timeout: 15 }] }), "DECLARATION_CHANGED");
|
|
108885
108923
|
expected.push({ event: nativeEvent, key: `${join57(home, ".codex/hooks.json")}:${label}:${matches2[0].gi}:${matches2[0].hi}`, command });
|
|
108886
108924
|
}
|
|
108887
108925
|
codexTrustTextWitness(configFile.text, expected.map((h) => h.key));
|
|
@@ -108905,7 +108943,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108905
108943
|
const discovered = await list2(), admitted = admit(discovered);
|
|
108906
108944
|
const config2 = await rpc.request("config/read", { includeLayers: true, cwd: home });
|
|
108907
108945
|
const layers = config2?.layers?.filter((layer) => layer.name?.type === "user" && layer.name.file === nativeConfigPath && !layer.name.profile);
|
|
108908
|
-
need4(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason &&
|
|
108946
|
+
need4(layers?.length === 1 && /^sha256:[a-f0-9]{64}$/.test(layers[0].version) && !layers[0].disabledReason && isDeepStrictEqual11(layers[0].config, original), "NATIVE_CONFIG_MISMATCH");
|
|
108909
108947
|
need4(config2.config && config2.config.features?.hooks !== false, "NATIVE_HOOKS_DISABLED");
|
|
108910
108948
|
need4(config2.layers.slice(0, config2.layers.indexOf(layers[0])).every((layer) => layer.config?.hooks === undefined && layer.config?.features?.hooks === undefined), "NATIVE_CONFIG_OVERRIDDEN");
|
|
108911
108949
|
const keys3 = admitted.map((h) => h.key), planned = admitted.filter((h) => !h.enabled || h.trustStatus !== "trusted");
|
|
@@ -108944,7 +108982,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108944
108982
|
save(join57(journal, "hooks.before.json"), hooksFile.bytes);
|
|
108945
108983
|
save(join57(journal, "policy.before.json"), policyFile.bytes);
|
|
108946
108984
|
save(join57(journal, "intent.json"), json2({ ...digestInput, planDigest, hooks: planned, nativeHooks: discovered.map(safeNativeHook) }));
|
|
108947
|
-
need4(
|
|
108985
|
+
need4(isDeepStrictEqual11(admit(await list2()), admitted), "NATIVE_DISCOVERY_CHANGED");
|
|
108948
108986
|
checkInputs();
|
|
108949
108987
|
let result2;
|
|
108950
108988
|
try {
|
|
@@ -108958,7 +108996,7 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108958
108996
|
expectedConfig.hooks.state ??= {};
|
|
108959
108997
|
for (const h of planned)
|
|
108960
108998
|
expectedConfig.hooks.state[h.key] = { ...expectedConfig.hooks.state[h.key], enabled: true, trusted_hash: h.currentHash };
|
|
108961
|
-
need4(
|
|
108999
|
+
need4(isDeepStrictEqual11(structure3(after2.text), expectedConfig) && codexTrustTextWitness(after2.text, keys3) === preservationText, "PRESERVATION_FAILED");
|
|
108962
109000
|
const savedConfig = snapshot(join57(journal, "config.before.toml"), true);
|
|
108963
109001
|
need4(savedConfig.sha256 === configFile.sha256, "JOURNAL_CHANGED");
|
|
108964
109002
|
const discoveryTransition = binding.discovery?.codex ? createCodexHookDiscoveryRecovery(binding.discovery.codex, savedConfig, after2, planned) : undefined;
|
|
@@ -108966,8 +109004,8 @@ async function enrollCodexNativeHooks(options = {}, connect = connectCodexHookRp
|
|
|
108966
109004
|
unchanged2(hooksFile);
|
|
108967
109005
|
assertManagedAgentBridge("codex", { home, dataDir: dataDir3, projectDir: home, codexDiscoveryRecovery: discoveryTransition });
|
|
108968
109006
|
const current = await list2(), confirmed = admit(current);
|
|
108969
|
-
need4(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") &&
|
|
108970
|
-
need4(
|
|
109007
|
+
need4(confirmed.every((h) => h.enabled && h.trustStatus === "trusted") && isDeepStrictEqual11(confirmed.map((h) => [h.key, h.currentHash]), admitted.map((h) => [h.key, h.currentHash])), "NATIVE_NOT_ELIGIBLE");
|
|
109008
|
+
need4(isDeepStrictEqual11(current.filter((h) => !keys3.includes(h.key)), discovered.filter((h) => !keys3.includes(h.key))), "UNRELATED_HOOK_CHANGED");
|
|
108971
109009
|
unchanged2(after2);
|
|
108972
109010
|
unchanged2(savedConfig);
|
|
108973
109011
|
unchanged2(hooksFile);
|
|
@@ -109027,6 +109065,9 @@ function hookFailureReason(error61, profileId) {
|
|
|
109027
109065
|
const profile = typeof profileId === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(profileId) && !profileId.includes("..") ? profileId : undefined;
|
|
109028
109066
|
const identity4 = [profile ? `profile=${profile}` : undefined, owned ? `stage=${error61.stage}` : undefined].filter(Boolean).join(", ");
|
|
109029
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
|
+
}
|
|
109030
109071
|
if (code2 === "PROFILE_LOCK_MISMATCH") {
|
|
109031
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.`;
|
|
109032
109073
|
}
|
|
@@ -109043,6 +109084,7 @@ var CONTEXT_ERROR_CODES, HookDiagnosticError;
|
|
|
109043
109084
|
var init_hook_diagnostics = __esm(() => {
|
|
109044
109085
|
CONTEXT_ERROR_CODES = new Set([
|
|
109045
109086
|
"SKILLS_CONTEXT_FAILED",
|
|
109087
|
+
"SKILLS_STORAGE_FULL",
|
|
109046
109088
|
"PROFILE_LOCK_MISMATCH",
|
|
109047
109089
|
"PROFILE_IDENTITY_MISMATCH",
|
|
109048
109090
|
"PROFILE_APPLIED_REPORT_FAILED",
|
|
@@ -109158,10 +109200,10 @@ function agents(value) {
|
|
|
109158
109200
|
}
|
|
109159
109201
|
function registerAgentIntegration(parent) {
|
|
109160
109202
|
const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
|
|
109161
|
-
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) => {
|
|
109162
|
-
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;
|
|
109163
109205
|
if (!capture2)
|
|
109164
|
-
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");
|
|
109165
109207
|
await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
|
|
109166
109208
|
});
|
|
109167
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 });
|
|
@@ -48834,6 +48844,7 @@ var init_codex_hook_discovery_recovery = __esm(() => {
|
|
|
48834
48844
|
import { createHash as createHash14 } from "crypto";
|
|
48835
48845
|
import { lstatSync as lstatSync16, readFileSync as readFileSync14, readdirSync as readdirSync6, realpathSync as realpathSync3 } from "fs";
|
|
48836
48846
|
import { basename as basename7, dirname as dirname14, isAbsolute as isAbsolute13, join as join23, resolve as resolve13, sep as sep4 } from "path";
|
|
48847
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
48837
48848
|
function parseConfig(text2, path, toml = false) {
|
|
48838
48849
|
try {
|
|
48839
48850
|
const value = toml ? Bun.TOML.parse(text2) : JSON.parse(text2);
|
|
@@ -48866,13 +48877,13 @@ function read(path, changes) {
|
|
|
48866
48877
|
return readFileSync14(path, "utf8");
|
|
48867
48878
|
}
|
|
48868
48879
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
48869
|
-
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") {
|
|
48870
48881
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
48871
48882
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
48872
|
-
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;
|
|
48873
48884
|
if (current !== source.sha256)
|
|
48874
48885
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
48875
|
-
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;
|
|
48876
48887
|
}
|
|
48877
48888
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
48878
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))
|
|
@@ -48934,7 +48945,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
48934
48945
|
safe4(root);
|
|
48935
48946
|
}
|
|
48936
48947
|
function assertMarketplaceBinding(binding, source) {
|
|
48937
|
-
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"))
|
|
48938
48949
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
48939
48950
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
48940
48951
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -49008,6 +49019,19 @@ function resolveAgentDiscovery(options) {
|
|
|
49008
49019
|
}
|
|
49009
49020
|
}
|
|
49010
49021
|
}
|
|
49022
|
+
if (options.reviewed === undefined && options.retainedReview !== undefined) {
|
|
49023
|
+
const retained = options.retainedReview;
|
|
49024
|
+
if (retained.agent !== agent || retained.method !== "reviewed")
|
|
49025
|
+
unresolved("invalid retained review");
|
|
49026
|
+
verifyAgentDiscovery(retained);
|
|
49027
|
+
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
49028
|
+
unresolved("retained review is missing the full agent configuration source");
|
|
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)))
|
|
49030
|
+
unresolved("retained settings witness names another configuration source");
|
|
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))
|
|
49032
|
+
unresolved("retained review is missing current runtime discovery coverage");
|
|
49033
|
+
return retained;
|
|
49034
|
+
}
|
|
49011
49035
|
if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
|
|
49012
49036
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
49013
49037
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
@@ -49018,7 +49042,7 @@ function resolveAgentDiscovery(options) {
|
|
|
49018
49042
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
49019
49043
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
49020
49044
|
verifyAgentDiscovery(supplied);
|
|
49021
|
-
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)))
|
|
49022
49046
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
49023
49047
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
49024
49048
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -57344,7 +57368,7 @@ var init_execution_grants = __esm(() => {
|
|
|
57344
57368
|
});
|
|
57345
57369
|
|
|
57346
57370
|
// src/lib/execution-grant-client.ts
|
|
57347
|
-
import { isDeepStrictEqual as
|
|
57371
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
57348
57372
|
function id(value) {
|
|
57349
57373
|
if (!grantIdentifier(value))
|
|
57350
57374
|
throw new Error("Invalid execution grant profile or revision identifier.");
|
|
@@ -57387,7 +57411,7 @@ async function resolveExecutionGrant(input2) {
|
|
|
57387
57411
|
if (!grantRecord(value) || Object.keys(value).length !== 3 || !grantIdentifier(value.policyRevision) || !grantIdentifier(value.grantId) || !grantRecord(value.bindings)) {
|
|
57388
57412
|
throw new SkillSelectionError("INVALID_EXECUTION_GRANT", "The Skills API returned an invalid execution grant.");
|
|
57389
57413
|
}
|
|
57390
|
-
if (!
|
|
57414
|
+
if (!isDeepStrictEqual3(value.bindings.selection, snapshot2.selection) || !isDeepStrictEqual3(value.bindings.consumer, snapshot2.consumer)) {
|
|
57391
57415
|
throw new SkillSelectionError("EXECUTION_GRANT_SCOPE_MISMATCH", "The Skills API returned a grant for a different selection or consumer.");
|
|
57392
57416
|
}
|
|
57393
57417
|
return value;
|
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");
|
|
@@ -9093,6 +9093,7 @@ function assertAgentPolicyCollections(policy) {
|
|
|
9093
9093
|
import { createHash as createHash16 } from "crypto";
|
|
9094
9094
|
import { lstatSync as lstatSync20, readFileSync as readFileSync16, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
|
|
9095
9095
|
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute14, join as join26, resolve as resolve14, sep as sep5 } from "path";
|
|
9096
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
9096
9097
|
|
|
9097
9098
|
// node_modules/yaml/dist/index.js
|
|
9098
9099
|
var composer = require_composer();
|
|
@@ -9610,7 +9611,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9610
9611
|
// package.json
|
|
9611
9612
|
var package_default = {
|
|
9612
9613
|
name: "@hasna/skills",
|
|
9613
|
-
version: "0.9.
|
|
9614
|
+
version: "0.9.17",
|
|
9614
9615
|
description: "Skills library for AI coding agents",
|
|
9615
9616
|
type: "module",
|
|
9616
9617
|
bin: {
|
|
@@ -12873,7 +12874,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
|
12873
12874
|
need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
12874
12875
|
}
|
|
12875
12876
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
12876
|
-
function inferencePreference(key, child) {
|
|
12877
|
+
function inferencePreference(key, child, version) {
|
|
12877
12878
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
12878
12879
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
12879
12880
|
return false;
|
|
@@ -12886,7 +12887,7 @@ function inferencePreference(key, child) {
|
|
|
12886
12887
|
return false;
|
|
12887
12888
|
need2(child.kind === "object", "requires an object of model preferences");
|
|
12888
12889
|
child.entries = child.entries.filter(([model, preferences]) => {
|
|
12889
|
-
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")
|
|
12890
12891
|
return true;
|
|
12891
12892
|
const effort = preferences.entries[0][1];
|
|
12892
12893
|
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -12897,7 +12898,7 @@ function inferencePreference(key, child) {
|
|
|
12897
12898
|
function settingsDigest(text2, version = 1) {
|
|
12898
12899
|
const value = parse3(text2);
|
|
12899
12900
|
value.entries = value.entries.filter(([key, child]) => {
|
|
12900
|
-
if (version
|
|
12901
|
+
if (version !== 1 && inferencePreference(key, child, version))
|
|
12901
12902
|
return false;
|
|
12902
12903
|
if (key === "model") {
|
|
12903
12904
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
@@ -12935,6 +12936,25 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
12935
12936
|
budget.remaining -= bytes;
|
|
12936
12937
|
return settingsDigest(text2, 2);
|
|
12937
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
|
+
}
|
|
12938
12958
|
|
|
12939
12959
|
// src/lib/agent-integration.ts
|
|
12940
12960
|
var sha = (data) => createHash10("sha256").update(data).digest("hex");
|
|
@@ -13369,7 +13389,15 @@ function planAgentIntegration(options) {
|
|
|
13369
13389
|
validateProfile(profileId2);
|
|
13370
13390
|
return [agent, { command, profileId: profileId2 }];
|
|
13371
13391
|
}));
|
|
13372
|
-
const
|
|
13392
|
+
const retainedAgents = [];
|
|
13393
|
+
const discoveries = [...new Set(options.agents)].map((agent) => {
|
|
13394
|
+
const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? policy.bridge.discovery[agent] : undefined;
|
|
13395
|
+
if (retainedReview) {
|
|
13396
|
+
assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
|
|
13397
|
+
retainedAgents.push(agent);
|
|
13398
|
+
}
|
|
13399
|
+
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
13400
|
+
});
|
|
13373
13401
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
|
|
13374
13402
|
const changes = [];
|
|
13375
13403
|
for (const agent of [...new Set(options.agents)]) {
|
|
@@ -13454,7 +13482,7 @@ function planAgentIntegration(options) {
|
|
|
13454
13482
|
if (JSON.stringify(policy) !== JSON.stringify(nextPolicy))
|
|
13455
13483
|
changes.push({ path: policyPath, before: previousPolicy, after: serializedPolicy });
|
|
13456
13484
|
recheckRootAliases(aliases);
|
|
13457
|
-
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
13485
|
+
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
13458
13486
|
}
|
|
13459
13487
|
function planClaudeStopHookUpdate(options) {
|
|
13460
13488
|
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
@@ -13573,6 +13601,8 @@ function applyAgentIntegration(plan) {
|
|
|
13573
13601
|
parseManagedSkillPolicy(change.after);
|
|
13574
13602
|
const aliases = plan.rootAliases ?? [];
|
|
13575
13603
|
recheckRootAliases(aliases);
|
|
13604
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
13605
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
13576
13606
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
13577
13607
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
13578
13608
|
for (const binding of plan.discoveryBefore ?? []) {
|
|
@@ -16496,13 +16526,13 @@ function read2(path, changes) {
|
|
|
16496
16526
|
return readFileSync16(path, "utf8");
|
|
16497
16527
|
}
|
|
16498
16528
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
16499
|
-
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") {
|
|
16500
16530
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
16501
16531
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
16502
|
-
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;
|
|
16503
16533
|
if (current !== source.sha256)
|
|
16504
16534
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
16505
|
-
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;
|
|
16506
16536
|
}
|
|
16507
16537
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
16508
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))
|
|
@@ -16564,7 +16594,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
16564
16594
|
safe4(root2);
|
|
16565
16595
|
}
|
|
16566
16596
|
function assertMarketplaceBinding(binding, source) {
|
|
16567
|
-
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"))
|
|
16568
16598
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
16569
16599
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
16570
16600
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -16573,7 +16603,10 @@ function rebindAgentDiscovery(binding, changes) {
|
|
|
16573
16603
|
const budget = discoveryByteBudget();
|
|
16574
16604
|
return { ...binding, sources: binding.sources.map((source) => {
|
|
16575
16605
|
assertMarketplaceBinding(binding, source);
|
|
16576
|
-
|
|
16606
|
+
const sha2562 = projected(source, changes, budget);
|
|
16607
|
+
if (!changes.has(source.path) && sha2562 !== source.sha256)
|
|
16608
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
16609
|
+
return { ...source, sha256: sha2562 };
|
|
16577
16610
|
}) };
|
|
16578
16611
|
}
|
|
16579
16612
|
function captureDiscoveryByteSources(paths) {
|
|
@@ -16697,6 +16730,19 @@ function resolveAgentDiscovery(options) {
|
|
|
16697
16730
|
}
|
|
16698
16731
|
}
|
|
16699
16732
|
}
|
|
16733
|
+
if (options.reviewed === undefined && options.retainedReview !== undefined) {
|
|
16734
|
+
const retained = options.retainedReview;
|
|
16735
|
+
if (retained.agent !== agent || retained.method !== "reviewed")
|
|
16736
|
+
unresolved("invalid retained review");
|
|
16737
|
+
verifyAgentDiscovery(retained);
|
|
16738
|
+
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
16739
|
+
unresolved("retained review is missing the full agent configuration source");
|
|
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)))
|
|
16741
|
+
unresolved("retained settings witness names another configuration source");
|
|
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))
|
|
16743
|
+
unresolved("retained review is missing current runtime discovery coverage");
|
|
16744
|
+
return retained;
|
|
16745
|
+
}
|
|
16700
16746
|
if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
|
|
16701
16747
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
16702
16748
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
@@ -16707,7 +16753,7 @@ function resolveAgentDiscovery(options) {
|
|
|
16707
16753
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
16708
16754
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
16709
16755
|
verifyAgentDiscovery(supplied);
|
|
16710
|
-
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)))
|
|
16711
16757
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
16712
16758
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
16713
16759
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -43655,6 +43701,7 @@ export {
|
|
|
43655
43701
|
validateCron,
|
|
43656
43702
|
validateBlogArticleRunOptions,
|
|
43657
43703
|
uploadSkillsSnapshotFilesToS3,
|
|
43704
|
+
upgradeClaudeSettingsWitness,
|
|
43658
43705
|
updateSkillRun,
|
|
43659
43706
|
unsetConfig,
|
|
43660
43707
|
unpinSkill,
|
|
@@ -43833,6 +43880,7 @@ export {
|
|
|
43833
43880
|
captureDiscoveryPathSources,
|
|
43834
43881
|
captureDiscoveryDirectories,
|
|
43835
43882
|
captureDiscoveryByteSources,
|
|
43883
|
+
captureClaudeSettingsV3,
|
|
43836
43884
|
captureClaudeSettingsV2,
|
|
43837
43885
|
captureClaudeSettings,
|
|
43838
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[];
|
|
@@ -46,5 +46,6 @@ export declare function resolveAgentDiscovery(options: {
|
|
|
46
46
|
home: string;
|
|
47
47
|
agent: IntegrationAgent;
|
|
48
48
|
reviewed?: ReviewedDiscoveryInputs;
|
|
49
|
+
retainedReview?: AgentDiscoveryBinding;
|
|
49
50
|
canonical?: (path: string) => string;
|
|
50
51
|
}): AgentDiscoveryBinding;
|
|
@@ -56,6 +56,11 @@ export interface AgentIntegrationPlan {
|
|
|
56
56
|
home: string;
|
|
57
57
|
agents: IntegrationAgent[];
|
|
58
58
|
};
|
|
59
|
+
retainedReviewChecks?: {
|
|
60
|
+
home: string;
|
|
61
|
+
projectDir: string;
|
|
62
|
+
agents: IntegrationAgent[];
|
|
63
|
+
};
|
|
59
64
|
}
|
|
60
65
|
/** Parse the reviewed exact-target binding used by native migration. Paths are
|
|
61
66
|
* deliberately project-relative so a manifest cannot silently select another
|
|
@@ -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");
|
|
@@ -37704,6 +37704,7 @@ function isOwnedCliBridge(path, expectedPaths) {
|
|
|
37704
37704
|
import { createHash as createHash15 } from "crypto";
|
|
37705
37705
|
import { lstatSync as lstatSync17, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as realpathSync3 } from "fs";
|
|
37706
37706
|
import { basename as basename6, dirname as dirname13, isAbsolute as isAbsolute14, join as join20, resolve as resolve13, sep as sep3 } from "path";
|
|
37707
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
37707
37708
|
|
|
37708
37709
|
// node_modules/yaml/dist/index.js
|
|
37709
37710
|
var composer = require_composer();
|
|
@@ -40153,7 +40154,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
|
40153
40154
|
need3(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
40154
40155
|
}
|
|
40155
40156
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
40156
|
-
function inferencePreference(key, child) {
|
|
40157
|
+
function inferencePreference(key, child, version) {
|
|
40157
40158
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
40158
40159
|
need3(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
40159
40160
|
return false;
|
|
@@ -40166,7 +40167,7 @@ function inferencePreference(key, child) {
|
|
|
40166
40167
|
return false;
|
|
40167
40168
|
need3(child.kind === "object", "requires an object of model preferences");
|
|
40168
40169
|
child.entries = child.entries.filter(([model, preferences]) => {
|
|
40169
|
-
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")
|
|
40170
40171
|
return true;
|
|
40171
40172
|
const effort = preferences.entries[0][1];
|
|
40172
40173
|
need3(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -40177,7 +40178,7 @@ function inferencePreference(key, child) {
|
|
|
40177
40178
|
function settingsDigest(text2, version = 1) {
|
|
40178
40179
|
const value = parse6(text2);
|
|
40179
40180
|
value.entries = value.entries.filter(([key, child]) => {
|
|
40180
|
-
if (version
|
|
40181
|
+
if (version !== 1 && inferencePreference(key, child, version))
|
|
40181
40182
|
return false;
|
|
40182
40183
|
if (key === "model") {
|
|
40183
40184
|
need3(child.kind === "string", "contains an invalid model selection");
|
|
@@ -40215,6 +40216,25 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
40215
40216
|
budget.remaining -= bytes;
|
|
40216
40217
|
return settingsDigest(text2, 2);
|
|
40217
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
|
+
}
|
|
40218
40238
|
|
|
40219
40239
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
40220
40240
|
import { createHash as createHash14 } from "crypto";
|
|
@@ -40324,13 +40344,13 @@ function read2(path, changes) {
|
|
|
40324
40344
|
return readFileSync12(path, "utf8");
|
|
40325
40345
|
}
|
|
40326
40346
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
40327
|
-
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") {
|
|
40328
40348
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
40329
40349
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
40330
|
-
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;
|
|
40331
40351
|
if (current !== source.sha256)
|
|
40332
40352
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
40333
|
-
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;
|
|
40334
40354
|
}
|
|
40335
40355
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
40336
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))
|
|
@@ -40392,7 +40412,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
40392
40412
|
safe4(root2);
|
|
40393
40413
|
}
|
|
40394
40414
|
function assertMarketplaceBinding(binding, source) {
|
|
40395
|
-
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"))
|
|
40396
40416
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
40397
40417
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
40398
40418
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -40401,7 +40421,10 @@ function rebindAgentDiscovery(binding, changes) {
|
|
|
40401
40421
|
const budget = discoveryByteBudget();
|
|
40402
40422
|
return { ...binding, sources: binding.sources.map((source) => {
|
|
40403
40423
|
assertMarketplaceBinding(binding, source);
|
|
40404
|
-
|
|
40424
|
+
const sha2562 = projected(source, changes, budget);
|
|
40425
|
+
if (!changes.has(source.path) && sha2562 !== source.sha256)
|
|
40426
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
40427
|
+
return { ...source, sha256: sha2562 };
|
|
40405
40428
|
}) };
|
|
40406
40429
|
}
|
|
40407
40430
|
function captureDiscoveryByteSources(paths) {
|
|
@@ -40525,6 +40548,19 @@ function resolveAgentDiscovery(options) {
|
|
|
40525
40548
|
}
|
|
40526
40549
|
}
|
|
40527
40550
|
}
|
|
40551
|
+
if (options.reviewed === undefined && options.retainedReview !== undefined) {
|
|
40552
|
+
const retained = options.retainedReview;
|
|
40553
|
+
if (retained.agent !== agent || retained.method !== "reviewed")
|
|
40554
|
+
unresolved("invalid retained review");
|
|
40555
|
+
verifyAgentDiscovery(retained);
|
|
40556
|
+
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
40557
|
+
unresolved("retained review is missing the full agent configuration source");
|
|
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)))
|
|
40559
|
+
unresolved("retained settings witness names another configuration source");
|
|
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))
|
|
40561
|
+
unresolved("retained review is missing current runtime discovery coverage");
|
|
40562
|
+
return retained;
|
|
40563
|
+
}
|
|
40528
40564
|
if (options.reviewed && (options.reviewed.version !== 1 || !Array.isArray(options.reviewed.agents) || options.reviewed.agents.some((item) => !item || typeof item !== "object") || new Set(options.reviewed.agents.map((item) => item.agent)).size !== options.reviewed.agents.length))
|
|
40529
40565
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
40530
40566
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
@@ -40535,7 +40571,7 @@ function resolveAgentDiscovery(options) {
|
|
|
40535
40571
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
40536
40572
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
40537
40573
|
verifyAgentDiscovery(supplied);
|
|
40538
|
-
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)))
|
|
40539
40575
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
40540
40576
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
40541
40577
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -40718,7 +40754,7 @@ export default async ({ directory }) => {
|
|
|
40718
40754
|
}
|
|
40719
40755
|
|
|
40720
40756
|
// src/lib/agent-codex.ts
|
|
40721
|
-
import { isDeepStrictEqual as
|
|
40757
|
+
import { isDeepStrictEqual as isDeepStrictEqual4 } from "util";
|
|
40722
40758
|
var CODEX_SKILL_CONFIG_SECTIONS = /^\[\[skills\.config\]\][^\n]*(?:\n(?!\s*\[)[^\n]*)*/gm;
|
|
40723
40759
|
function assertCodexPathConfigEditable(text2) {
|
|
40724
40760
|
const before = Bun.TOML.parse(text2);
|
|
@@ -40728,7 +40764,7 @@ function assertCodexPathConfigEditable(text2) {
|
|
|
40728
40764
|
delete expected.skills.config;
|
|
40729
40765
|
try {
|
|
40730
40766
|
const withoutSections = Bun.TOML.parse(text2.replace(CODEX_SKILL_CONFIG_SECTIONS, ""));
|
|
40731
|
-
if (
|
|
40767
|
+
if (isDeepStrictEqual4(withoutSections, expected))
|
|
40732
40768
|
return;
|
|
40733
40769
|
} catch {}
|
|
40734
40770
|
throw new Error("Codex skill path controls require ordinary [[skills.config]] TOML tables; preserve and convert inline or dotted config arrays before running skills hook install");
|
|
@@ -40766,7 +40802,7 @@ function disableCodexBundledSkills(text2) {
|
|
|
40766
40802
|
const candidates = new Set;
|
|
40767
40803
|
const consider = (candidate2) => {
|
|
40768
40804
|
try {
|
|
40769
|
-
if (
|
|
40805
|
+
if (isDeepStrictEqual4(Bun.TOML.parse(candidate2), expected))
|
|
40770
40806
|
candidates.add(candidate2);
|
|
40771
40807
|
} catch {}
|
|
40772
40808
|
};
|
|
@@ -41224,7 +41260,15 @@ function planAgentIntegration(options) {
|
|
|
41224
41260
|
validateProfile(profileId2);
|
|
41225
41261
|
return [agent, { command, profileId: profileId2 }];
|
|
41226
41262
|
}));
|
|
41227
|
-
const
|
|
41263
|
+
const retainedAgents = [];
|
|
41264
|
+
const discoveries = [...new Set(options.agents)].map((agent) => {
|
|
41265
|
+
const retainedReview = options.discoveryInputs === undefined && policy.bridge?.discovery?.[agent]?.method === "reviewed" ? policy.bridge.discovery[agent] : undefined;
|
|
41266
|
+
if (retainedReview) {
|
|
41267
|
+
assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: options.projectDir ?? home });
|
|
41268
|
+
retainedAgents.push(agent);
|
|
41269
|
+
}
|
|
41270
|
+
return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
41271
|
+
});
|
|
41228
41272
|
const nativeSkills = inventoryNativeSkills(home, { includeVendor: true, guardHermes: options.agents.includes("hermes"), agents: options.agents, projectDir: options.projectDir, agentRoots: discoveries.flatMap((binding) => binding.roots.map((path) => ({ agent: binding.agent, path }))), allowRootAliases: options.allowRootAliases });
|
|
41229
41273
|
const changes = [];
|
|
41230
41274
|
for (const agent of [...new Set(options.agents)]) {
|
|
@@ -41309,7 +41353,7 @@ function planAgentIntegration(options) {
|
|
|
41309
41353
|
if (JSON.stringify(policy) !== JSON.stringify(nextPolicy))
|
|
41310
41354
|
changes.push({ path: policyPath, before: previousPolicy, after: serializedPolicy });
|
|
41311
41355
|
recheckRootAliases(aliases);
|
|
41312
|
-
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
41356
|
+
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
|
|
41313
41357
|
}
|
|
41314
41358
|
function planClaudeStopHookUpdate(options) {
|
|
41315
41359
|
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
@@ -41428,6 +41472,8 @@ function applyAgentIntegration(plan) {
|
|
|
41428
41472
|
parseManagedSkillPolicy(change.after);
|
|
41429
41473
|
const aliases = plan.rootAliases ?? [];
|
|
41430
41474
|
recheckRootAliases(aliases);
|
|
41475
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
41476
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
41431
41477
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
41432
41478
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
41433
41479
|
for (const binding of plan.discoveryBefore ?? []) {
|
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
|