@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/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.12",
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
- function assertClaudeStopHookReplacement(before, replacement) {
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, "Stop update exceeds its byte limit");
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 withoutStop(value) {
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", "Stop update requires an object of hooks");
12860
- const stop = hooks.entries.find(([key]) => key === "Stop")?.[1];
12861
- need2(stop === undefined || stop.kind === "array", "Stop update requires a Stop array");
12862
- hooks.entries = hooks.entries.filter(([key]) => key !== "Stop");
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(withoutStop(original)) === JSON.stringify(withoutStop(next)), "Stop update changed settings outside hooks.Stop");
12873
+ need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
12869
12874
  }
12870
- function settingsDigest(text2) {
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("hasna.skills.claude-settings.v1\x00").update(JSON.stringify(canonical2(value))).digest("hex");
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
- assertClaudeStopHookReplacement(before, options.replacement);
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 agent of agents)
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) => rebindAgentDiscovery(binding, replacements));
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: agents.includes("hermes"),
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: [...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
- verifyAgentDiscovery(binding);
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
- verifyAgentDiscovery(binding);
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
- return;
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 declare function planClaudeStopHookUpdate(options: {
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 Stop only. Compare the strict syntax
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;