@hasna/skills 0.10.34 → 0.10.36

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.
@@ -133,6 +133,33 @@ export declare function planAgentIntegration(options: {
133
133
  reviewedCacheAlias?: string;
134
134
  codexNativeCatalog?: CodexNativeSkillCatalog;
135
135
  }): AgentIntegrationPlan;
136
+ export interface ClaudeManagedHookProjection {
137
+ schema: "hasna.claude-managed-hook-projection.v1";
138
+ targetSha256: string;
139
+ sourceSha256: string;
140
+ replacements: Array<{
141
+ event: string;
142
+ groupIndex: number;
143
+ hookIndex: number;
144
+ beforeSha256: string;
145
+ command: string;
146
+ }>;
147
+ }
148
+ export interface ClaudeManagedHookProjectionOptions {
149
+ home?: string;
150
+ dataDir?: string;
151
+ projectDir?: string;
152
+ /** Exact caller-owned bytes, never a new native home or a path to be written. */
153
+ targetSettings: string;
154
+ expectedTargetSha256: string;
155
+ /** Supply the preceding plan's source witness when checking it before use. */
156
+ expectedSourceSha256?: string;
157
+ }
158
+ /** Read-only projection for a copied Claude configuration. This does not install
159
+ * hooks, adopt target discovery, change policy, or confer write/admission authority.
160
+ * The caller must preserve the full target, hold its owning writer lease, replan
161
+ * against both witnesses, CAS the full bytes and revalidate after application. */
162
+ export declare function planClaudeManagedHookProjection(options: ClaudeManagedHookProjectionOptions): ClaudeManagedHookProjection;
136
163
  /** Plan an explicitly authorized Stop hook replacement without adopting drift.
137
164
  * The caller owns authorization of its exact Stop hook delta; Skills refuses
138
165
  * every other settings change and owns discovery witnesses and guarded writes.
@@ -28,7 +28,7 @@ export interface CodexPluginSourceInput {
28
28
  type Read = (path: string) => string;
29
29
  /** Capability controls remain bound even after every native Skill body disappears. */
30
30
  export declare function reviewedCodexPluginCapabilitiesUnchanged(cache: string, controls: CodexPluginSkillControl[], read: Read): boolean;
31
- export declare function reviewCodexPluginControls(catalog: CodexNativeSkillCatalog, documents: string[], cache: string, cwd: string, read: Read, rules: unknown, pluginSettings?: unknown): {
31
+ export declare function reviewCodexPluginControls(catalog: CodexNativeSkillCatalog, documents: string[], cache: string, cwd: string, read: Read, rules: unknown, pluginSettings?: unknown, plannedRules?: unknown): {
32
32
  skills: CodexPluginSkillControl[];
33
33
  inactivePlugins: CodexInactivePluginControl[];
34
34
  sourceInputs: CodexPluginSourceInput[];
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.10.34",
32376
+ version: "0.10.36",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -38659,7 +38659,7 @@ function reviewedCodexPluginCapabilitiesUnchanged(cache, controls, read) {
38659
38659
  return false;
38660
38660
  }
38661
38661
  }
38662
- function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules, pluginSettings) {
38662
+ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules, pluginSettings, plannedRules) {
38663
38663
  if (!supportsCodexNativeCapability(catalog?.version, "qualified-skill-catalog") || catalog.cwd !== cwd)
38664
38664
  refuse3();
38665
38665
  const skills = projectCodexNativeSkillCatalog({ data: [{ cwd, errors: [], skills: catalog.skills }] }, cwd);
@@ -38730,7 +38730,9 @@ function reviewCodexPluginControls(catalog, documents, cache, cwd, read, rules,
38730
38730
  remotePluginId = plugin.remotePluginId;
38731
38731
  }
38732
38732
  const exactPathDeny = Array.isArray(rules) && rules.some((rule) => rule?.path === document && rule?.enabled === false);
38733
- if (!exactPathDeny || !isCodexNativeSkillDisabled({ name: parsed.name, path: document, pluginId: expectedPluginId, enabled: true }, rules))
38733
+ const nativeSkill = { name: parsed.name, path: document, pluginId: expectedPluginId, enabled: true };
38734
+ const plannedPathDeny = catalog.version === "codex-cli 0.160.0" && remotePluginId !== undefined && Array.isArray(rules) && rules.some((rule) => typeof rule?.name === "string" && rule.name.trim() === parsed.name && rule.enabled === false) && !rules.some((rule) => (rule?.path === document || typeof rule?.name === "string" && rule.name.trim() === parsed.name) && rule.enabled !== false) && Array.isArray(plannedRules) && plannedRules.some((rule) => rule?.path === document && rule.enabled === false) && isCodexNativeSkillDisabled(nativeSkill, plannedRules);
38735
+ if (!exactPathDeny && !plannedPathDeny || !isCodexNativeSkillDisabled(nativeSkill, rules))
38734
38736
  refuse3();
38735
38737
  }
38736
38738
  result.push({ ...parsed, pluginId: expectedPluginId, ...remotePluginId ? { remotePluginId } : {} });
@@ -42695,12 +42697,15 @@ function disableCodexBundledSkills(text2) {
42695
42697
  // src/lib/agent-integration.ts
42696
42698
  var sha4 = (data) => createHash18("sha256").update(data).digest("hex");
42697
42699
  var ROOTS = NATIVE_SKILL_ROOTS;
42698
- function hasNoDarwinAcl(fd) {
42700
+ function hasTrustedDarwinAcl(fd, allowDenyOnly = false) {
42699
42701
  try {
42700
- const { dlopen, read: read3 } = __require("bun:ffi");
42702
+ const { dlopen, read: read3, ptr } = __require("bun:ffi");
42701
42703
  const library = dlopen("/usr/lib/libSystem.B.dylib", {
42702
42704
  acl_get_fd: { args: ["i32"], returns: "ptr" },
42703
42705
  acl_free: { args: ["ptr"], returns: "i32" },
42706
+ acl_valid: { args: ["ptr"], returns: "i32" },
42707
+ acl_get_entry: { args: ["ptr", "i32", "ptr"], returns: "i32" },
42708
+ acl_get_tag_type: { args: ["ptr", "ptr"], returns: "i32" },
42704
42709
  __error: { args: [], returns: "ptr" }
42705
42710
  });
42706
42711
  try {
@@ -42709,8 +42714,23 @@ function hasNoDarwinAcl(fd) {
42709
42714
  return false;
42710
42715
  const acl = library.symbols.acl_get_fd(fd);
42711
42716
  if (acl) {
42712
- library.symbols.acl_free(acl);
42713
- return false;
42717
+ try {
42718
+ if (!allowDenyOnly || library.symbols.acl_valid(acl) !== 0)
42719
+ return false;
42720
+ const entry = new BigUint64Array(1), tag = new Int32Array(1);
42721
+ for (let count = 0;count <= 128; count++) {
42722
+ const status = library.symbols.acl_get_entry(acl, count === 0 ? 0 : -1, ptr(entry));
42723
+ if (status !== 0) {
42724
+ return status === -1 && read3.i32(errno) === 22 && library.symbols.acl_valid(acl) === 0;
42725
+ }
42726
+ const value = read3.ptr(ptr(entry));
42727
+ if (count === 128 || !value || library.symbols.acl_get_tag_type(value, ptr(tag)) !== 0 || tag[0] !== 2)
42728
+ return false;
42729
+ }
42730
+ return false;
42731
+ } finally {
42732
+ library.symbols.acl_free(acl);
42733
+ }
42714
42734
  }
42715
42735
  return (acl === null || acl === 0) && read3.i32(errno) === 2;
42716
42736
  } finally {
@@ -42740,7 +42760,7 @@ function isSystemRootAlias(path) {
42740
42760
  throw new Error("Unprotected system temporary directory");
42741
42761
  const fd = openSync16(directory, constants12.O_RDONLY | constants12.O_DIRECTORY | constants12.O_NOFOLLOW);
42742
42762
  try {
42743
- if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !hasNoDarwinAcl(fd) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2))
42763
+ if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2) || !hasTrustedDarwinAcl(fd) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(stat2))
42744
42764
  throw new Error("Unverified system alias directory ACL");
42745
42765
  } finally {
42746
42766
  closeSync16(fd);
@@ -43269,15 +43289,21 @@ function planAgentIntegration(options) {
43269
43289
  }
43270
43290
  return resolveAgentDiscovery({ home, agent, reviewed: options.discoveryInputs, retainedReview, canonical: (path) => canonicalAgentPath(path, aliases) });
43271
43291
  });
43272
- const codexConfig = options.agents.includes("codex") ? Bun.TOML.parse(readOptional(canonicalAgentPath(join23(home, ".codex", "config.toml"), aliases)) ?? "") : {};
43292
+ const codexConfigBefore = options.agents.includes("codex") ? readOptional(canonicalAgentPath(join23(home, ".codex", "config.toml"), aliases)) : null;
43293
+ const codexConfig = Bun.TOML.parse(codexConfigBefore ?? "");
43273
43294
  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, reviewedCacheAlias: options.reviewedCacheAlias, disabledVendorPaths: disabledCodexSkillPaths(codexConfig, aliases) });
43295
+ const codexPathConfig = options.agents.includes("codex") ? disableCodexSkills(codexConfigBefore ?? "", nativeSkills, aliases, canonicalAgentPath(join23(home, ".codex/skills", CLI_BRIDGE_NAME), aliases)) : undefined;
43296
+ const plannedCodexRules = codexPathConfig === undefined ? undefined : Bun.TOML.parse(codexPathConfig).skills?.config ?? [];
43274
43297
  const observedNativeSources = [];
43275
43298
  const codexPluginControls = options.codexNativeCatalog ? reviewCodexPluginControls(options.codexNativeCatalog, nativeSkills.filter((entry) => entry.agent === "codex" && entry.vendor).map((entry) => join23(entry.path, "SKILL.md")), canonicalAgentPath(join23(home, ".codex/plugins/cache"), aliases), resolve16(options.projectDir ?? home), (path) => {
43276
43299
  const bytes = readNativeBytes(path, 1024 * 1024);
43277
43300
  observedNativeSources.push({ path, sha256: sha4(bytes) });
43278
43301
  return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
43279
- }, codexConfig.skills?.config ?? [], codexConfig.plugins) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
43302
+ }, codexConfig.skills?.config ?? [], codexConfig.plugins, plannedCodexRules) : { skills: policy.bridge?.codexPluginSkills ?? [], inactivePlugins: policy.bridge?.codexInactivePlugins ?? [], sourceInputs: policy.bridge?.discovery?.codex?.codexInstallationInputs?.plugins ?? [] };
43280
43303
  const codexPluginSkills = codexPluginControls.skills;
43304
+ if (options.agents.includes("codex") && !options.codexNativeCatalog && codexPluginSkills.length && !reviewedCodexPluginCapabilitiesUnchanged(canonicalAgentPath(join23(home, ".codex/plugins/cache"), aliases), codexPluginSkills, (path) => new TextDecoder("utf-8", { fatal: true }).decode(readNativeBytes(path, 1024 * 1024)))) {
43305
+ throw new Error("NATIVE_SKILL_DRIFT: reviewed native plugin capability controls changed; provide a fresh native catalog and discovery review");
43306
+ }
43281
43307
  const codexInactivePlugins = codexPluginControls.inactivePlugins;
43282
43308
  const codexDiscovery = discoveries.find((binding) => binding.agent === "codex");
43283
43309
  const codexPluginSourceInputs = codexPluginControls.sourceInputs.filter((input) => codexDiscovery?.method === "reviewed" && codexDiscovery.roots.some((root2) => root2 === input.sourceRoot || root2.startsWith(input.sourceRoot + sep5) || input.sourceRoot.startsWith(root2 + sep5)) && codexDiscovery.sources.some((source) => source.path === join23(input.sourceRoot, ".codex-plugin/plugin.json") && source.sha256 !== null && source.format === undefined && source.fields === undefined));
@@ -43352,7 +43378,9 @@ function planAgentIntegration(options) {
43352
43378
  changes.push({ path, before, after });
43353
43379
  if (agent === "codex") {
43354
43380
  const configPath = canonicalAgentPath(join23(home, ".codex", "config.toml"), aliases), previous = readOptional(configPath);
43355
- const next = disableReviewedCodexPluginNames(disableCodexSkills(previous ?? "", nativeSkills, aliases, bridgePath), codexPluginSkills);
43381
+ if (previous !== codexConfigBefore)
43382
+ throw new Error(`Configuration changed during planning: ${configPath}`);
43383
+ const next = disableReviewedCodexPluginNames(codexPathConfig, codexPluginSkills);
43356
43384
  if (next !== (previous ?? ""))
43357
43385
  changes.push({ path: configPath, before: previous, after: next });
43358
43386
  }
@@ -43388,6 +43416,171 @@ function planAgentIntegration(options) {
43388
43416
  recheckRootAliases(aliases);
43389
43417
  return { dataDir: dataDir2, profileId, changes, nativeSkills, ...observedNativeSources.length ? { observedNativeSources } : {}, ...nextPolicy.bridge.codexPluginSkillReview ? { codexPluginSkillReview: nextPolicy.bridge.codexPluginSkillReview } : {}, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {}, ...retainedAgents.length ? { retainedReviewChecks: { home, projectDir: options.projectDir ?? home, agents: retainedAgents } } : {} };
43390
43418
  }
43419
+ function hookProjectionRefusal(reason) {
43420
+ throw new Error(`CLAUDE_MANAGED_HOOK_${reason}`);
43421
+ }
43422
+ function projectedHookAcl(path, observed) {
43423
+ if (process.platform !== "darwin")
43424
+ return;
43425
+ const identity4 = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.ctimeNs}:${stat2.uid}:${stat2.mode}:${stat2.nlink}:${stat2.size}:${stat2.mtimeNs}`;
43426
+ const fd = openSync16(path, constants12.O_RDONLY | constants12.O_NOFOLLOW | constants12.O_NONBLOCK | (observed.isDirectory() ? constants12.O_DIRECTORY : 0));
43427
+ try {
43428
+ if (identity4(fstatSync16(fd, { bigint: true })) !== identity4(observed) || !hasTrustedDarwinAcl(fd, true) || identity4(fstatSync16(fd, { bigint: true })) !== identity4(observed) || identity4(lstatSync20(path, { bigint: true })) !== identity4(observed))
43429
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43430
+ } finally {
43431
+ closeSync16(fd);
43432
+ }
43433
+ }
43434
+ function projectedHookExecutable(command, expectedResolved) {
43435
+ try {
43436
+ if (!isAbsolute17(command) || /[\0\r\n]/.test(command) || resolve16(command) !== command)
43437
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43438
+ command = canonicalSystemPath(command);
43439
+ const parents3 = (file = command) => {
43440
+ const result = [];
43441
+ for (let path = dirname15(file);; path = dirname15(path)) {
43442
+ const stat2 = lstatSync20(path, { bigint: true });
43443
+ if (!stat2.isDirectory() || (stat2.mode & 0o022n) !== 0n && !(stat2.uid === 0n && (stat2.mode & 0o1000n) !== 0n))
43444
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43445
+ projectedHookAcl(path, stat2);
43446
+ result.push(`${path}:${stat2.dev}:${stat2.ino}:${stat2.uid}:${stat2.mode}`);
43447
+ if (dirname15(path) === path)
43448
+ break;
43449
+ }
43450
+ return result;
43451
+ };
43452
+ const parentIdentity = parents3(), link = lstatSync20(command, { bigint: true });
43453
+ if (link.uid !== BigInt(process.getuid()) || !link.isFile() && !link.isSymbolicLink())
43454
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43455
+ const linkIdentity = (stat2) => `${stat2.dev}:${stat2.ino}:${stat2.mode}:${stat2.uid}:${stat2.ctimeNs}`;
43456
+ const resolved = realpathSync8(command);
43457
+ if (expectedResolved !== undefined && resolved !== expectedResolved)
43458
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43459
+ const resolvedParentIdentity = parents3(resolved);
43460
+ const manifest = snapshot(join23(dirname15(dirname15(resolved)), "package.json"), false, { readOnlyPackage: true });
43461
+ projectedHookAcl(manifest.file, manifest.stat);
43462
+ const pkg = JSON.parse(manifest.text);
43463
+ if (pkg.name !== "@hasna/skills" || !/^\d+\.\d+\.\d+$/.test(pkg.version) || pkg.bin?.skills !== "bin/index.js" || resolved !== join23(dirname15(manifest.file), "bin/index.js"))
43464
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43465
+ const cli = snapshot(resolved, false, { readOnlyPackage: true });
43466
+ projectedHookAcl(cli.file, cli.stat);
43467
+ if ((cli.stat.mode & 0o100n) === 0n)
43468
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43469
+ const recheck = () => {
43470
+ try {
43471
+ if (realpathSync8(command) !== resolved || linkIdentity(lstatSync20(command, { bigint: true })) !== linkIdentity(link) || !isDeepStrictEqual6(parents3(), parentIdentity) || !isDeepStrictEqual6(parents3(resolved), resolvedParentIdentity))
43472
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43473
+ projectedHookAcl(cli.file, cli.stat);
43474
+ projectedHookAcl(manifest.file, manifest.stat);
43475
+ unchanged2(cli);
43476
+ unchanged2(manifest);
43477
+ } catch {
43478
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43479
+ }
43480
+ };
43481
+ recheck();
43482
+ return { resolved, sha256: cli.sha256, manifestSha256: manifest.sha256, recheck };
43483
+ } catch {
43484
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43485
+ }
43486
+ }
43487
+ function planClaudeManagedHookProjection(options) {
43488
+ if (typeof options.targetSettings !== "string" || Buffer.byteLength(options.targetSettings) > 1024 * 1024)
43489
+ hookProjectionRefusal("TARGET_INVALID");
43490
+ const targetSha256 = sha4(options.targetSettings);
43491
+ if (!/^[a-f0-9]{64}$/.test(options.expectedTargetSha256) || options.expectedTargetSha256 !== targetSha256)
43492
+ hookProjectionRefusal("TARGET_CHANGED");
43493
+ let target;
43494
+ try {
43495
+ target = jsonObject(options.targetSettings, "target");
43496
+ } catch {
43497
+ hookProjectionRefusal("TARGET_INVALID");
43498
+ }
43499
+ const home = resolve16(options.home ?? homedir3()), dataDir2 = options.dataDir ?? getDataDirReadOnly();
43500
+ const policyPath = join23(dataDir2, "agent-policy.json");
43501
+ let policyBytes, settingsBytes, settingsPath, command, profile;
43502
+ try {
43503
+ policyBytes = readNativeBytes(policyPath, 1024 * 1024);
43504
+ const snapshot2 = readManagedSkillPolicySnapshot(dataDir2);
43505
+ if (!snapshot2 || snapshot2.text !== policyBytes.toString("utf8"))
43506
+ hookProjectionRefusal("SOURCE_UNVERIFIED");
43507
+ const binding = snapshot2.value.bridge;
43508
+ settingsPath = canonicalAgentPath(join23(home, AGENT_ADAPTERS.claude.config), binding?.rootAliases ?? []);
43509
+ settingsBytes = readNativeBytes(settingsPath, 1024 * 1024);
43510
+ command = binding?.commands?.claude;
43511
+ profile = binding?.profiles?.claude;
43512
+ if (typeof command !== "string" || typeof profile !== "string")
43513
+ hookProjectionRefusal("SOURCE_UNVERIFIED");
43514
+ assertManagedAgentBridge("claude", { home, dataDir: dataDir2, projectDir: options.projectDir, profileId: profile });
43515
+ } catch {
43516
+ hookProjectionRefusal("SOURCE_UNVERIFIED");
43517
+ }
43518
+ const executable = projectedHookExecutable(command);
43519
+ const sourceSha256 = sha4(JSON.stringify({
43520
+ home,
43521
+ policyPath,
43522
+ policy: sha4(policyBytes),
43523
+ settingsPath,
43524
+ settings: sha4(settingsBytes),
43525
+ executable: { path: executable.resolved, sha256: executable.sha256, manifestSha256: executable.manifestSha256 }
43526
+ }));
43527
+ if (options.expectedSourceSha256 !== undefined && options.expectedSourceSha256 !== sourceSha256)
43528
+ hookProjectionRefusal("SOURCE_CHANGED");
43529
+ const replacements = [];
43530
+ const observed = [executable];
43531
+ if (target.hooks !== undefined) {
43532
+ if (!target.hooks || typeof target.hooks !== "object" || Array.isArray(target.hooks))
43533
+ hookProjectionRefusal("TARGET_INVALID");
43534
+ for (const event of [...AGENT_ADAPTERS.claude.events, "PreToolUse"]) {
43535
+ const groups = target.hooks[event];
43536
+ if (groups === undefined)
43537
+ continue;
43538
+ if (!Array.isArray(groups))
43539
+ hookProjectionRefusal("TARGET_INVALID");
43540
+ let recognized = 0;
43541
+ for (const [groupIndex, group] of groups.entries()) {
43542
+ if (!group || !Array.isArray(group.hooks))
43543
+ hookProjectionRefusal("TARGET_INVALID");
43544
+ for (const [hookIndex, hook] of group.hooks.entries()) {
43545
+ if (hook?.type !== "command" || typeof hook.command !== "string")
43546
+ continue;
43547
+ if (!hook.command.includes("hook user-prompt --agent claude"))
43548
+ continue;
43549
+ const match = /^('(?:[^']|'\\'')*') hook user-prompt --agent claude --selection-profile ([A-Za-z0-9][A-Za-z0-9._-]{0,127}) --event ([A-Za-z]+)$/.exec(hook.command);
43550
+ if (!match || match[3] !== event)
43551
+ hookProjectionRefusal("UNRECOGNIZED");
43552
+ const oldCommand = match[1].slice(1, -1).replaceAll("'\\''", "'");
43553
+ let rendered;
43554
+ try {
43555
+ rendered = renderAgentHookCommand(oldCommand, "claude", match[2], event);
43556
+ } catch {
43557
+ hookProjectionRefusal("UNRECOGNIZED");
43558
+ }
43559
+ if (rendered !== hook.command)
43560
+ hookProjectionRefusal("UNRECOGNIZED");
43561
+ const legacy = projectedHookExecutable(oldCommand, executable.resolved);
43562
+ if (legacy.resolved !== executable.resolved || legacy.sha256 !== executable.sha256 || legacy.manifestSha256 !== executable.manifestSha256)
43563
+ hookProjectionRefusal("EXECUTABLE_UNVERIFIED");
43564
+ observed.push(legacy);
43565
+ if (++recognized > 1)
43566
+ hookProjectionRefusal("AMBIGUOUS");
43567
+ const replacement = renderAgentHookCommand(command, "claude", profile, event);
43568
+ if (replacement !== hook.command)
43569
+ replacements.push({ event, groupIndex, hookIndex, beforeSha256: sha4(hook.command), command: replacement });
43570
+ }
43571
+ }
43572
+ }
43573
+ }
43574
+ try {
43575
+ if (!readNativeBytes(policyPath, 1024 * 1024).equals(policyBytes) || !readNativeBytes(settingsPath, 1024 * 1024).equals(settingsBytes))
43576
+ hookProjectionRefusal("SOURCE_CHANGED");
43577
+ } catch {
43578
+ hookProjectionRefusal("SOURCE_CHANGED");
43579
+ }
43580
+ for (const item of observed)
43581
+ item.recheck();
43582
+ return { schema: "hasna.claude-managed-hook-projection.v1", targetSha256, sourceSha256, replacements };
43583
+ }
43391
43584
  function planClaudeStopHookUpdate(options) {
43392
43585
  return planClaudeHookEventsUpdate({ ...options, events: ["Stop"] });
43393
43586
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.34",
3
+ "version": "0.10.36",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {