@hasna/skills 0.9.12 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +97 -22
- package/bin/maintenance.js +73 -0
- package/bin/mcp.js +63 -18
- package/bin/migrate.js +36 -1
- package/bin/server.js +96 -25
- package/bin/worker.js +74 -8
- package/dist/index.d.ts +4 -3
- package/dist/index.js +142 -40
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/agent-integration.d.ts +9 -1
- package/dist/lib/claude-marketplace-registry.d.ts +6 -0
- package/dist/lib/claude-settings-witness.d.ts +12 -1
- package/dist/sdk/index.js +213 -45
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/sqlite-store.d.ts +2 -1
- package/dist/server/store.d.ts +3 -1
- package/dist/server/types.d.ts +18 -0
- package/docs/plugin-admission.md +60 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9055,10 +9055,10 @@ 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"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
|
|
9059
|
-
if (source.hashMode === "claude-settings-v1")
|
|
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")
|
|
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
|
-
if (source.hashMode === "claude-marketplace-registry")
|
|
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");
|
|
9063
9063
|
if (source.hashMode === "claude-plugin-registry") {
|
|
9064
9064
|
requireBound(agent === "claude");
|
|
@@ -9610,7 +9610,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
|
|
|
9610
9610
|
// package.json
|
|
9611
9611
|
var package_default = {
|
|
9612
9612
|
name: "@hasna/skills",
|
|
9613
|
-
version: "0.9.
|
|
9613
|
+
version: "0.9.14",
|
|
9614
9614
|
description: "Skills library for AI coding agents",
|
|
9615
9615
|
type: "module",
|
|
9616
9616
|
bin: {
|
|
@@ -12846,30 +12846,59 @@ function canonical2(value) {
|
|
|
12846
12846
|
return { kind: "array", items: value.items.map(canonical2) };
|
|
12847
12847
|
return value;
|
|
12848
12848
|
}
|
|
12849
|
-
|
|
12849
|
+
var CLAUDE_COORDINATED_HOOK_EVENTS = ["PreToolUse", "PostToolUse", "Stop", "Notification", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart"];
|
|
12850
|
+
function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
12851
|
+
need2(Array.isArray(events) && events.length > 0 && events.length <= CLAUDE_COORDINATED_HOOK_EVENTS.length && new Set(events).size === events.length && events.every((event) => CLAUDE_COORDINATED_HOOK_EVENTS.includes(event)), "requires explicit supported hook events");
|
|
12852
|
+
const label = events.join(",");
|
|
12850
12853
|
function bounded(text2) {
|
|
12851
|
-
need2(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes,
|
|
12854
|
+
need2(typeof text2 === "string" && Buffer.byteLength(text2) <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes, `${label} update exceeds its byte limit`);
|
|
12852
12855
|
return parse3(text2);
|
|
12853
12856
|
}
|
|
12854
12857
|
const original = bounded(before ?? "{}"), next = bounded(replacement);
|
|
12855
12858
|
const hadHooks = original.entries.some(([key]) => key === "hooks");
|
|
12856
|
-
function
|
|
12859
|
+
function withoutOwnedEvents(value) {
|
|
12857
12860
|
const hooks = value.entries.find(([key]) => key === "hooks")?.[1];
|
|
12858
12861
|
if (hooks !== undefined) {
|
|
12859
|
-
need2(hooks.kind === "object",
|
|
12860
|
-
const
|
|
12861
|
-
|
|
12862
|
-
|
|
12862
|
+
need2(hooks.kind === "object", `${label} update requires an object of hooks`);
|
|
12863
|
+
for (const event of events) {
|
|
12864
|
+
const selected = hooks.entries.find(([key]) => key === event)?.[1];
|
|
12865
|
+
need2(selected === undefined || selected.kind === "array", `${event} update requires a ${event} array`);
|
|
12866
|
+
}
|
|
12867
|
+
hooks.entries = hooks.entries.filter(([key]) => !events.includes(key));
|
|
12863
12868
|
if (!hadHooks && hooks.entries.length === 0)
|
|
12864
12869
|
value.entries = value.entries.filter(([key]) => key !== "hooks");
|
|
12865
12870
|
}
|
|
12866
12871
|
return canonical2(value);
|
|
12867
12872
|
}
|
|
12868
|
-
need2(JSON.stringify(
|
|
12873
|
+
need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
12869
12874
|
}
|
|
12870
|
-
|
|
12875
|
+
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
12876
|
+
function inferencePreference(key, child) {
|
|
12877
|
+
if (key === "skipDangerousModePermissionPrompt") {
|
|
12878
|
+
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
12879
|
+
return false;
|
|
12880
|
+
}
|
|
12881
|
+
if (key === "effortLevel") {
|
|
12882
|
+
need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
|
|
12883
|
+
return true;
|
|
12884
|
+
}
|
|
12885
|
+
if (key !== "modelSettings")
|
|
12886
|
+
return false;
|
|
12887
|
+
need2(child.kind === "object", "requires an object of model preferences");
|
|
12888
|
+
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
|
+
return true;
|
|
12891
|
+
const effort = preferences.entries[0][1];
|
|
12892
|
+
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
12893
|
+
return false;
|
|
12894
|
+
});
|
|
12895
|
+
return child.entries.length === 0;
|
|
12896
|
+
}
|
|
12897
|
+
function settingsDigest(text2, version = 1) {
|
|
12871
12898
|
const value = parse3(text2);
|
|
12872
12899
|
value.entries = value.entries.filter(([key, child]) => {
|
|
12900
|
+
if (version === 2 && inferencePreference(key, child))
|
|
12901
|
+
return false;
|
|
12873
12902
|
if (key === "model") {
|
|
12874
12903
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
12875
12904
|
return !BUILTIN_MODEL_SELECTIONS.has(child.value);
|
|
@@ -12884,7 +12913,7 @@ function settingsDigest(text2) {
|
|
|
12884
12913
|
}
|
|
12885
12914
|
return true;
|
|
12886
12915
|
});
|
|
12887
|
-
return createHash9("sha256").update(
|
|
12916
|
+
return createHash9("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
|
|
12888
12917
|
}
|
|
12889
12918
|
function hashClaudeSettingsReplacement(text2, budget) {
|
|
12890
12919
|
need2(typeof text2 === "string", "requires settings text");
|
|
@@ -12896,6 +12925,16 @@ function hashClaudeSettingsReplacement(text2, budget) {
|
|
|
12896
12925
|
function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12897
12926
|
return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
|
|
12898
12927
|
}
|
|
12928
|
+
function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12929
|
+
return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
|
|
12930
|
+
}
|
|
12931
|
+
function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
12932
|
+
need2(typeof text2 === "string", "requires settings text");
|
|
12933
|
+
const bytes = Buffer.byteLength(text2);
|
|
12934
|
+
need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
12935
|
+
budget.remaining -= bytes;
|
|
12936
|
+
return settingsDigest(text2, 2);
|
|
12937
|
+
}
|
|
12899
12938
|
|
|
12900
12939
|
// src/lib/agent-integration.ts
|
|
12901
12940
|
var sha = (data) => createHash10("sha256").update(data).digest("hex");
|
|
@@ -13418,6 +13457,34 @@ function planAgentIntegration(options) {
|
|
|
13418
13457
|
return { dataDir: dataDir2, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
|
|
13419
13458
|
}
|
|
13420
13459
|
function planClaudeStopHookUpdate(options) {
|
|
13460
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
|
|
13461
|
+
}
|
|
13462
|
+
function planClaudePreToolUseHookUpdate(options) {
|
|
13463
|
+
return planClaudeHookEventsUpdate({ ...options, events: ["PreToolUse"] });
|
|
13464
|
+
}
|
|
13465
|
+
function claudeSettingsConsumers(home, path, discoveries, aliases) {
|
|
13466
|
+
const canonical3 = (value) => canonicalAgentPath(value, aliases);
|
|
13467
|
+
const containsSettings = (root) => {
|
|
13468
|
+
const distance = relative4(canonical3(root), path);
|
|
13469
|
+
return distance === "" || distance !== ".." && !distance.startsWith(`..${sep4}`) && !isAbsolute9(distance);
|
|
13470
|
+
};
|
|
13471
|
+
return discoveries.filter((binding) => binding.agent === "claude" || binding.agent === "opencode" || canonical3(join16(home, AGENT_ADAPTERS[binding.agent].config)) === path || binding.sources.some((source) => canonical3(source.path) === path) || binding.roots.some(containsSettings) || binding.directories?.some((directory) => containsSettings(directory.path))).map((binding) => binding.agent);
|
|
13472
|
+
}
|
|
13473
|
+
function verifyCoordinatedDiscovery(binding, home, aliases) {
|
|
13474
|
+
try {
|
|
13475
|
+
if (binding.method !== "automatic" && binding.method !== "reviewed")
|
|
13476
|
+
throw new Error("Invalid managed discovery method");
|
|
13477
|
+
verifyAgentDiscovery(binding);
|
|
13478
|
+
if (binding.method === "automatic") {
|
|
13479
|
+
const current = resolveAgentDiscovery({ home, agent: binding.agent, canonical: (path) => canonicalAgentPath(path, aliases) });
|
|
13480
|
+
if (JSON.stringify(current) !== JSON.stringify(binding))
|
|
13481
|
+
throw new Error("Configured native discovery roots changed during Claude update");
|
|
13482
|
+
}
|
|
13483
|
+
} catch (error) {
|
|
13484
|
+
throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13487
|
+
function planClaudeHookEventsUpdate(options) {
|
|
13421
13488
|
const home = resolve9(options.home ?? homedir2()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
|
|
13422
13489
|
const policyPath = join16(dataDir2, "agent-policy.json");
|
|
13423
13490
|
assertSafePath(policyPath);
|
|
@@ -13435,31 +13502,42 @@ function planClaudeStopHookUpdate(options) {
|
|
|
13435
13502
|
const before = lstatSync14(path, { throwIfNoEntry: false }) ? new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(readNativeBytes(path, 1024 * 1024)) : null;
|
|
13436
13503
|
if ((before === null ? "absent" : sha(before)) !== options.expectedSettingsSha256)
|
|
13437
13504
|
throw new Error("Claude settings preimage changed");
|
|
13438
|
-
|
|
13505
|
+
assertClaudeHookEventsReplacement(before, options.replacement, options.events);
|
|
13439
13506
|
const agents = bridge.agents;
|
|
13440
13507
|
const discoveries = Object.entries(bridge.discovery).map(([agent, binding]) => {
|
|
13441
|
-
if (!agents.includes(agent) || binding?.agent !== agent)
|
|
13442
|
-
throw new Error("Invalid managed discovery owner");
|
|
13508
|
+
if (!agents.includes(agent) || binding?.agent !== agent || typeof bridge.commands?.[agent] !== "string" || typeof bridge.profiles?.[agent] !== "string")
|
|
13509
|
+
throw new Error("Invalid managed discovery owner or command/profile binding");
|
|
13443
13510
|
return binding;
|
|
13444
13511
|
});
|
|
13445
|
-
if (discoveries.length !== agents.length)
|
|
13446
|
-
throw new Error("Missing managed discovery coverage");
|
|
13447
|
-
for (const
|
|
13512
|
+
if (discoveries.length !== agents.length || !agents.includes("claude"))
|
|
13513
|
+
throw new Error("Missing managed Claude discovery coverage");
|
|
13514
|
+
for (const binding of discoveries)
|
|
13515
|
+
verifyCoordinatedDiscovery(binding, home, aliases);
|
|
13516
|
+
const consumers = claudeSettingsConsumers(home, path, discoveries, aliases);
|
|
13517
|
+
for (const agent of consumers)
|
|
13448
13518
|
assertManagedAgentBridge(agent, { home, dataDir: dataDir2, projectDir: home });
|
|
13449
13519
|
const replacements = new Map(before === options.replacement ? [] : [[path, options.replacement]]);
|
|
13450
|
-
const discoveryAfter = discoveries.map((binding) =>
|
|
13520
|
+
const discoveryAfter = discoveries.map((binding) => {
|
|
13521
|
+
const next = rebindAgentDiscovery(binding, replacements);
|
|
13522
|
+
for (const [index, source] of binding.sources.entries()) {
|
|
13523
|
+
if (source.path !== path && JSON.stringify(next.sources[index]) !== JSON.stringify(source)) {
|
|
13524
|
+
throw new Error(`Unrelated native discovery input changed during Claude update: ${source.path}`);
|
|
13525
|
+
}
|
|
13526
|
+
}
|
|
13527
|
+
return JSON.stringify(next) === JSON.stringify(binding) ? binding : next;
|
|
13528
|
+
});
|
|
13451
13529
|
const nextPolicy = { ...policy, bridge: { ...bridge, discovery: Object.fromEntries(discoveryAfter.map((binding) => [binding.agent, binding])) } };
|
|
13452
13530
|
const changes = before === options.replacement ? [] : [{ path, before, after: options.replacement }];
|
|
13453
13531
|
if (JSON.stringify(nextPolicy) !== JSON.stringify(policy))
|
|
13454
13532
|
changes.push({ path: policyPath, before: snapshot.text, after: serializeManagedSkillPolicy(nextPolicy) });
|
|
13455
13533
|
recheckRootAliases(aliases);
|
|
13456
13534
|
const nativeSkills = inventoryNativeSkills(home, {
|
|
13457
|
-
agents,
|
|
13535
|
+
agents: consumers,
|
|
13458
13536
|
projectDir: home,
|
|
13459
13537
|
includeVendor: true,
|
|
13460
|
-
guardHermes:
|
|
13538
|
+
guardHermes: consumers.includes("hermes"),
|
|
13461
13539
|
allowRootAliases: aliases.length > 0,
|
|
13462
|
-
agentRoots: discoveries.flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
13540
|
+
agentRoots: discoveries.filter((binding) => consumers.includes(binding.agent)).flatMap((binding) => binding.roots.map((path2) => ({ agent: binding.agent, path: path2 })))
|
|
13463
13541
|
});
|
|
13464
13542
|
return {
|
|
13465
13543
|
dataDir: dataDir2,
|
|
@@ -13470,7 +13548,7 @@ function planClaudeStopHookUpdate(options) {
|
|
|
13470
13548
|
discoveryBefore: discoveries,
|
|
13471
13549
|
discoveryAfter,
|
|
13472
13550
|
rootAliases: aliases,
|
|
13473
|
-
managedAgentChecks: { home, agents:
|
|
13551
|
+
managedAgentChecks: { home, agents: consumers }
|
|
13474
13552
|
};
|
|
13475
13553
|
}
|
|
13476
13554
|
function atomicWrite2(path, content) {
|
|
@@ -13497,8 +13575,12 @@ function applyAgentIntegration(plan) {
|
|
|
13497
13575
|
recheckRootAliases(aliases);
|
|
13498
13576
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
13499
13577
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
13500
|
-
for (const binding of plan.discoveryBefore ?? [])
|
|
13501
|
-
|
|
13578
|
+
for (const binding of plan.discoveryBefore ?? []) {
|
|
13579
|
+
if (plan.managedAgentChecks)
|
|
13580
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
13581
|
+
else
|
|
13582
|
+
verifyAgentDiscovery(binding);
|
|
13583
|
+
}
|
|
13502
13584
|
if (plan.observedPolicy && currentText(plan.observedPolicy.path) !== plan.observedPolicy.before)
|
|
13503
13585
|
throw new Error("Agent policy changed after planning");
|
|
13504
13586
|
for (const change of plan.changes)
|
|
@@ -13530,8 +13612,12 @@ function applyAgentIntegration(plan) {
|
|
|
13530
13612
|
written.push({ path: change.path, before: change.before, after });
|
|
13531
13613
|
}
|
|
13532
13614
|
recheckRootAliases(aliases);
|
|
13533
|
-
for (const binding of plan.discoveryAfter ?? [])
|
|
13534
|
-
|
|
13615
|
+
for (const binding of plan.discoveryAfter ?? []) {
|
|
13616
|
+
if (plan.managedAgentChecks)
|
|
13617
|
+
verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
|
|
13618
|
+
else
|
|
13619
|
+
verifyAgentDiscovery(binding);
|
|
13620
|
+
}
|
|
13535
13621
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
13536
13622
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
13537
13623
|
atomicWrite2(join16(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha(change.before), afterHash: sha(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
|
|
@@ -16261,9 +16347,13 @@ function textField(value) {
|
|
|
16261
16347
|
need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
16262
16348
|
return value.value;
|
|
16263
16349
|
}
|
|
16264
|
-
function normalizeRow(row) {
|
|
16265
|
-
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
|
|
16266
|
-
|
|
16350
|
+
function normalizeRow(row, version = 1) {
|
|
16351
|
+
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"])) {
|
|
16352
|
+
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
16353
|
+
return;
|
|
16354
|
+
const autoUpdate = field(row, "autoUpdate");
|
|
16355
|
+
need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
16356
|
+
}
|
|
16267
16357
|
const source = field(row, "source");
|
|
16268
16358
|
if (source?.kind !== "object")
|
|
16269
16359
|
return;
|
|
@@ -16290,6 +16380,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
|
|
|
16290
16380
|
const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
16291
16381
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
16292
16382
|
}
|
|
16383
|
+
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
16384
|
+
const value = parse6(readRegistry2(path, budget));
|
|
16385
|
+
for (const [, row] of value.entries)
|
|
16386
|
+
normalizeRow(row, 2);
|
|
16387
|
+
const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
16388
|
+
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
16389
|
+
}
|
|
16293
16390
|
|
|
16294
16391
|
// src/lib/codex-hook-discovery-recovery.ts
|
|
16295
16392
|
import { createHash as createHash15 } from "crypto";
|
|
@@ -16393,18 +16490,18 @@ function read2(path, changes) {
|
|
|
16393
16490
|
return readFileSync16(path, "utf8");
|
|
16394
16491
|
}
|
|
16395
16492
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
16396
|
-
if (source.hashMode === "claude-settings-v1") {
|
|
16493
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
16397
16494
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
16398
16495
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
16399
|
-
const current = captureClaudeSettings(source.path, budget).sha256;
|
|
16496
|
+
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
16400
16497
|
if (current !== source.sha256)
|
|
16401
16498
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
16402
|
-
return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
|
|
16499
|
+
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
|
|
16403
16500
|
}
|
|
16404
|
-
if (source.hashMode === "claude-marketplace-registry") {
|
|
16501
|
+
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
16405
16502
|
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))
|
|
16406
16503
|
throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
|
|
16407
|
-
const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
|
|
16504
|
+
const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
|
|
16408
16505
|
if (sha2562 !== source.sha256)
|
|
16409
16506
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
16410
16507
|
return sha2562;
|
|
@@ -16461,9 +16558,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
16461
16558
|
safe4(root2);
|
|
16462
16559
|
}
|
|
16463
16560
|
function assertMarketplaceBinding(binding, source) {
|
|
16464
|
-
if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
16561
|
+
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
16465
16562
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
16466
|
-
if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
16563
|
+
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
16467
16564
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
16468
16565
|
}
|
|
16469
16566
|
function rebindAgentDiscovery(binding, changes) {
|
|
@@ -16604,7 +16701,7 @@ function resolveAgentDiscovery(options) {
|
|
|
16604
16701
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
16605
16702
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
16606
16703
|
verifyAgentDiscovery(supplied);
|
|
16607
|
-
if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
|
|
16704
|
+
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
16608
16705
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
16609
16706
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
16610
16707
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -43613,6 +43710,8 @@ export {
|
|
|
43613
43710
|
planSkillsS3SnapshotUpload,
|
|
43614
43711
|
planPluginAdmission,
|
|
43615
43712
|
planClaudeStopHookUpdate,
|
|
43713
|
+
planClaudePreToolUseHookUpdate,
|
|
43714
|
+
planClaudeHookEventsUpdate,
|
|
43616
43715
|
planAgentIntegration,
|
|
43617
43716
|
pinSkill,
|
|
43618
43717
|
pinProjectSkill,
|
|
@@ -43728,7 +43827,9 @@ export {
|
|
|
43728
43827
|
captureDiscoveryPathSources,
|
|
43729
43828
|
captureDiscoveryDirectories,
|
|
43730
43829
|
captureDiscoveryByteSources,
|
|
43830
|
+
captureClaudeSettingsV2,
|
|
43731
43831
|
captureClaudeSettings,
|
|
43832
|
+
captureClaudeMarketplaceRegistryV2,
|
|
43732
43833
|
captureClaudeMarketplaceRegistry,
|
|
43733
43834
|
canonicalizeManifest,
|
|
43734
43835
|
buildSkillsS3ObjectUrl,
|
|
@@ -43814,6 +43915,7 @@ export {
|
|
|
43814
43915
|
DEFAULT_EXPORT_DIR,
|
|
43815
43916
|
ContentHashInputError,
|
|
43816
43917
|
CONTENT_HASH_LIMITS,
|
|
43918
|
+
CLAUDE_COORDINATED_HOOK_EVENTS,
|
|
43817
43919
|
CATEGORIES,
|
|
43818
43920
|
BASIC_SKILL_NAMES,
|
|
43819
43921
|
ARTICLE_GENERATION_SLUG,
|
|
@@ -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";
|
|
9
|
+
hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-marketplace-registry-v2";
|
|
10
10
|
managedPlugins?: ManagedPluginRegistrationWitness[];
|
|
11
11
|
format?: "json" | "toml" | "yaml";
|
|
12
12
|
fields?: string[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AgentDiscoveryBinding, type ReviewedDiscoveryInputs } from "./agent-discovery.js";
|
|
2
2
|
import { type IntegrationAgent } from "./agent-adapters.js";
|
|
3
3
|
import type { CodexHookDiscoveryRecovery } from "./codex-hook-discovery-recovery.js";
|
|
4
|
+
import { type ClaudeCoordinatedHookEvent } from "./claude-settings-witness.js";
|
|
4
5
|
export type { IntegrationAgent } from "./agent-adapters.js";
|
|
5
6
|
export type ContextHookEvent = "UserPromptSubmit" | "SessionStart" | "SubagentStart";
|
|
6
7
|
export interface AgentRootAlias {
|
|
@@ -96,11 +97,18 @@ export declare function planAgentIntegration(options: {
|
|
|
96
97
|
* every other settings change and owns discovery witnesses and guarded writes.
|
|
97
98
|
* No policy means this installer has no Skills integration to update.
|
|
98
99
|
*/
|
|
99
|
-
export
|
|
100
|
+
export interface ClaudeHookUpdateOptions {
|
|
100
101
|
home?: string;
|
|
101
102
|
dataDir?: string;
|
|
102
103
|
expectedSettingsSha256: string;
|
|
103
104
|
replacement: string;
|
|
105
|
+
}
|
|
106
|
+
export declare function planClaudeStopHookUpdate(options: ClaudeHookUpdateOptions): AgentIntegrationPlan | null;
|
|
107
|
+
/** Cooperating safety installers may replace only PreToolUse. The caller owns
|
|
108
|
+
* its exact hook authorization; Skills retains discovery and native guards. */
|
|
109
|
+
export declare function planClaudePreToolUseHookUpdate(options: ClaudeHookUpdateOptions): AgentIntegrationPlan | null;
|
|
110
|
+
export declare function planClaudeHookEventsUpdate(options: ClaudeHookUpdateOptions & {
|
|
111
|
+
events: readonly ClaudeCoordinatedHookEvent[];
|
|
104
112
|
}): AgentIntegrationPlan | null;
|
|
105
113
|
export declare function applyAgentIntegration(plan: AgentIntegrationPlan): {
|
|
106
114
|
changed: string[];
|
|
@@ -15,3 +15,9 @@ export declare function captureClaudeMarketplaceRegistry(path: string, budget?:
|
|
|
15
15
|
hashMode: "claude-marketplace-registry";
|
|
16
16
|
sha256: string;
|
|
17
17
|
};
|
|
18
|
+
/** Explicit v2 review also recognizes rows with a bound boolean autoUpdate. */
|
|
19
|
+
export declare function captureClaudeMarketplaceRegistryV2(path: string, budget?: ClaudeMarketplaceRegistryBudget): {
|
|
20
|
+
path: string;
|
|
21
|
+
hashMode: "claude-marketplace-registry-v2";
|
|
22
|
+
sha256: string;
|
|
23
|
+
};
|
|
@@ -9,12 +9,16 @@ export declare const CLAUDE_SETTINGS_WITNESS_LIMITS: Readonly<{
|
|
|
9
9
|
export interface ClaudeSettingsWitnessBudget {
|
|
10
10
|
remaining: number;
|
|
11
11
|
}
|
|
12
|
-
/** Cooperating installers may replace
|
|
12
|
+
/** Cooperating installers may replace only their explicitly selected event. Compare the strict syntax
|
|
13
13
|
* trees, not JSON.parse values or the preference-tolerant witness digest: all
|
|
14
14
|
* other hooks, discovery settings, unknown values and number spellings remain
|
|
15
15
|
* identical. Whitespace and object-key order have no configuration meaning.
|
|
16
16
|
*/
|
|
17
|
+
export declare const CLAUDE_COORDINATED_HOOK_EVENTS: readonly ["PreToolUse", "PostToolUse", "Stop", "Notification", "SessionStart", "SessionEnd", "UserPromptSubmit", "SubagentStart"];
|
|
18
|
+
export type ClaudeCoordinatedHookEvent = typeof CLAUDE_COORDINATED_HOOK_EVENTS[number];
|
|
19
|
+
export declare function assertClaudeHookEventsReplacement(before: string | null, replacement: string, events: readonly ClaudeCoordinatedHookEvent[]): void;
|
|
17
20
|
export declare function assertClaudeStopHookReplacement(before: string | null, replacement: string): void;
|
|
21
|
+
export declare function assertClaudePreToolUseHookReplacement(before: string | null, replacement: string): void;
|
|
18
22
|
/** Hash only reviewed installer-rendered settings after the disk preimage has been verified. */
|
|
19
23
|
export declare function hashClaudeSettingsReplacement(text: string, budget: ClaudeSettingsWitnessBudget): string;
|
|
20
24
|
/** Explicit capture never converts a stored legacy raw witness or changes settings. */
|
|
@@ -23,3 +27,10 @@ export declare function captureClaudeSettings(path: string, budget?: ClaudeSetti
|
|
|
23
27
|
hashMode: "claude-settings-v1";
|
|
24
28
|
sha256: string;
|
|
25
29
|
};
|
|
30
|
+
/** A new explicit review is required; v1 witnesses retain their old meaning. */
|
|
31
|
+
export declare function captureClaudeSettingsV2(path: string, budget?: ClaudeSettingsWitnessBudget): {
|
|
32
|
+
path: string;
|
|
33
|
+
hashMode: "claude-settings-v2";
|
|
34
|
+
sha256: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function hashClaudeSettingsReplacementV2(text: string, budget: ClaudeSettingsWitnessBudget): string;
|