@hasna/hooks 0.10.7 → 0.10.8
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 +39 -1
- package/bin/hooks-mcp.js +52 -19
- package/bin/index.js +239 -80
- package/bin/native-safety-entry.js +196 -15
- package/dist/index.js +52 -19
- package/dist/lib/native-safety.d.ts +4 -0
- package/dist/native-safety.d.ts +6 -0
- package/dist/native-safety.js +18 -0
- package/hooks/hook-workspace-repos-guard/README.md +12 -0
- package/hooks/hook-workspace-repos-guard/src/hook.ts +247 -14
- package/package.json +2 -2
- package/scripts/ensure-profiles-dir.mjs +65 -1
package/README.md
CHANGED
|
@@ -393,8 +393,46 @@ commands are credential-free and return compact JSON. Verification explicitly
|
|
|
393
393
|
reports `nativeAdoptionVerified: false`: a fresh native Sumi tool invocation is
|
|
394
394
|
still required to prove that the installed harness uses this binding.
|
|
395
395
|
|
|
396
|
+
The binding is provisioned from the package's own install, upgrade and safety
|
|
397
|
+
surfaces, so a rollout cannot leave Sumi's fail-closed check unbound:
|
|
398
|
+
|
|
399
|
+
- the package lifecycle script (`postinstall`) provisions it for a packaged
|
|
400
|
+
install — a copy inside a `node_modules` directory, never a source checkout;
|
|
401
|
+
- `hooks install` and `hooks upgrade` apply the same step after a successful
|
|
402
|
+
hook install or package upgrade, and report it as `sumiBinding` in `--json`;
|
|
403
|
+
- `hooks safety provision` runs it on demand and is credential-free.
|
|
404
|
+
|
|
405
|
+
Every surface is idempotent and shares one policy: a missing binding is created,
|
|
406
|
+
a byte-identical one is written nowhere, a different binding that still verifies
|
|
407
|
+
is left intact, and one whose pinned runtime or worker no longer verifies (an
|
|
408
|
+
upgrade rewrote the worker, or the runtime prefix was pruned) is re-registered
|
|
409
|
+
with the previous record preserved beside it as `<path>.before-<sha256>`. An
|
|
410
|
+
unreadable or unrecognized record is refused rather than overwritten, and
|
|
411
|
+
provisioning never fails the calling command — it reports a stable `code`,
|
|
412
|
+
because an unavailable Trash guard must not break a hook install. bun blocks
|
|
413
|
+
lifecycle scripts for untrusted dependencies and `--ignore-scripts` skips them,
|
|
414
|
+
so the CLI surfaces carry the same policy wherever the lifecycle script does not
|
|
415
|
+
run. A harness rollout that installs this package with scripts disabled must run
|
|
416
|
+
one of those surfaces; that is the harness owner's half of this contract.
|
|
417
|
+
|
|
418
|
+
The binding pins the absolute paths and sha256 of the Bun runtime and the worker
|
|
419
|
+
bundle as they exist when it is registered, and every evaluation re-verifies
|
|
420
|
+
those exact bytes. A worker inside a tree a package upgrade rewrites in place —
|
|
421
|
+
bun's global install root, its cache, or an isolated `node_modules/.bun/…` store —
|
|
422
|
+
is therefore only valid until that tree is upgraded; the next rewrite refuses
|
|
423
|
+
every command until provisioning runs again. Provisioning still closes the
|
|
424
|
+
immediate gap (an unbound station has no shell at all) but reports it: every
|
|
425
|
+
result carries `durable`, and a false value prints the repair step. Provision
|
|
426
|
+
rollouts from a stable, versioned install tree (for example the fleet's
|
|
427
|
+
`~/.hasna/hooks/runtime/<version>-copyfile` layout) and re-run
|
|
428
|
+
`hooks safety provision` after upgrading a tree the binding points at.
|
|
429
|
+
|
|
396
430
|
The owner-only `~/.hasna/hooks/native/sumi-trash-guard.json` pins the supervisor's
|
|
397
|
-
runtime, worker, home and PATH.
|
|
431
|
+
runtime, worker, home and PATH. Owner and mode are part of the trust decision: a
|
|
432
|
+
record that is not owned by the current user, or that group or others can read or
|
|
433
|
+
write, is refused with the failing property named — never trusted, adopted or
|
|
434
|
+
silently replaced — because a rewritable binding can point the supervisor at a
|
|
435
|
+
different worker. Identical setup is a no-op. An upgrade requires
|
|
398
436
|
`--expected-binding-sha256 <current-digest>` and preserves the previous record.
|
|
399
437
|
An interrupted lock or unexpected current record requires reconciliation; setup
|
|
400
438
|
does not overwrite it. This file is native configuration, not a Hooks app store.
|
package/bin/hooks-mcp.js
CHANGED
|
@@ -3185,7 +3185,7 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
3185
3185
|
// src/lib/claude-skills-coordination.ts
|
|
3186
3186
|
import { createHash as createHash5 } from "crypto";
|
|
3187
3187
|
|
|
3188
|
-
// ../../node_modules/.bun/@hasna+skills@0.9.
|
|
3188
|
+
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
3189
3189
|
import { existsSync as existsSync9 } from "fs";
|
|
3190
3190
|
import { homedir as homedir4 } from "os";
|
|
3191
3191
|
import { join as join8, resolve as resolve3 } from "path";
|
|
@@ -3198,6 +3198,7 @@ import { basename as basename2, isAbsolute as isAbsolute22, resolve as resolve22
|
|
|
3198
3198
|
import { createHash as createHash16 } from "crypto";
|
|
3199
3199
|
import { lstatSync as lstatSync20, readFileSync as readFileSync16, readdirSync as readdirSync9, realpathSync as realpathSync4 } from "fs";
|
|
3200
3200
|
import { basename as basename8, dirname as dirname16, isAbsolute as isAbsolute14, join as join26, resolve as resolve14, sep as sep5 } from "path";
|
|
3201
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "util";
|
|
3201
3202
|
import { dirname as dirname32, isAbsolute as isAbsolute3, join as join72, resolve as resolve32 } from "path";
|
|
3202
3203
|
import { lstatSync as lstatSync3, readFileSync as readFileSync6, opendirSync } from "fs";
|
|
3203
3204
|
import { createHash as createHash22 } from "crypto";
|
|
@@ -10678,8 +10679,8 @@ function assertAgentPolicyCollections(policy) {
|
|
|
10678
10679
|
text(source.path);
|
|
10679
10680
|
requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
|
|
10680
10681
|
if (source.hashMode !== undefined)
|
|
10681
|
-
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));
|
|
10682
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
|
|
10682
|
+
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));
|
|
10683
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3")
|
|
10683
10684
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute22(source.path) && resolve22(source.path) === source.path && basename2(source.path) === "settings.json");
|
|
10684
10685
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
|
|
10685
10686
|
requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute22(source.path) && resolve22(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
|
|
@@ -11174,7 +11175,7 @@ function hashDiscoveryPathFile(path, budget, changes) {
|
|
|
11174
11175
|
}
|
|
11175
11176
|
var package_default = {
|
|
11176
11177
|
name: "@hasna/skills",
|
|
11177
|
-
version: "0.9.
|
|
11178
|
+
version: "0.9.17",
|
|
11178
11179
|
description: "Skills library for AI coding agents",
|
|
11179
11180
|
type: "module",
|
|
11180
11181
|
bin: {
|
|
@@ -12029,7 +12030,7 @@ function assertClaudeHookEventsReplacement(before, replacement, events) {
|
|
|
12029
12030
|
need2(JSON.stringify(withoutOwnedEvents(original)) === JSON.stringify(withoutOwnedEvents(next)), `${label} update changed settings outside hooks.${label}`);
|
|
12030
12031
|
}
|
|
12031
12032
|
var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
|
|
12032
|
-
function inferencePreference(key, child) {
|
|
12033
|
+
function inferencePreference(key, child, version) {
|
|
12033
12034
|
if (key === "skipDangerousModePermissionPrompt") {
|
|
12034
12035
|
need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
|
|
12035
12036
|
return false;
|
|
@@ -12042,7 +12043,7 @@ function inferencePreference(key, child) {
|
|
|
12042
12043
|
return false;
|
|
12043
12044
|
need2(child.kind === "object", "requires an object of model preferences");
|
|
12044
12045
|
child.entries = child.entries.filter(([model, preferences]) => {
|
|
12045
|
-
if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
12046
|
+
if (version === 2 && !BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
|
|
12046
12047
|
return true;
|
|
12047
12048
|
const effort = preferences.entries[0][1];
|
|
12048
12049
|
need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
|
|
@@ -12053,7 +12054,7 @@ function inferencePreference(key, child) {
|
|
|
12053
12054
|
function settingsDigest(text2, version = 1) {
|
|
12054
12055
|
const value = parse3(text2);
|
|
12055
12056
|
value.entries = value.entries.filter(([key, child]) => {
|
|
12056
|
-
if (version
|
|
12057
|
+
if (version !== 1 && inferencePreference(key, child, version))
|
|
12057
12058
|
return false;
|
|
12058
12059
|
if (key === "model") {
|
|
12059
12060
|
need2(child.kind === "string", "contains an invalid model selection");
|
|
@@ -12091,6 +12092,16 @@ function hashClaudeSettingsReplacementV2(text2, budget) {
|
|
|
12091
12092
|
budget.remaining -= bytes;
|
|
12092
12093
|
return settingsDigest(text2, 2);
|
|
12093
12094
|
}
|
|
12095
|
+
function captureClaudeSettingsV3(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
12096
|
+
return { path, hashMode: "claude-settings-v3", sha256: settingsDigest(readSettings(path, budget), 3) };
|
|
12097
|
+
}
|
|
12098
|
+
function hashClaudeSettingsReplacementV3(text2, budget) {
|
|
12099
|
+
need2(typeof text2 === "string", "requires settings text");
|
|
12100
|
+
const bytes = Buffer.byteLength(text2);
|
|
12101
|
+
need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
|
|
12102
|
+
budget.remaining -= bytes;
|
|
12103
|
+
return settingsDigest(text2, 3);
|
|
12104
|
+
}
|
|
12094
12105
|
var sha = (data) => createHash10("sha256").update(data).digest("hex");
|
|
12095
12106
|
var ROOTS = NATIVE_SKILL_ROOTS;
|
|
12096
12107
|
function assertSafePath(path) {
|
|
@@ -12544,6 +12555,8 @@ function applyAgentIntegration(plan) {
|
|
|
12544
12555
|
parseManagedSkillPolicy(change.after);
|
|
12545
12556
|
const aliases = plan.rootAliases ?? [];
|
|
12546
12557
|
recheckRootAliases(aliases);
|
|
12558
|
+
for (const agent of plan.retainedReviewChecks?.agents ?? [])
|
|
12559
|
+
assertManagedAgentBridge(agent, { ...plan.retainedReviewChecks, dataDir: plan.dataDir });
|
|
12547
12560
|
for (const agent of plan.managedAgentChecks?.agents ?? [])
|
|
12548
12561
|
assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
|
|
12549
12562
|
for (const binding of plan.discoveryBefore ?? []) {
|
|
@@ -13557,13 +13570,17 @@ function unchanged2(before) {
|
|
|
13557
13570
|
const after = snapshot(before.file, before.privateFile, before.readOnlyPackage ? { readOnlyPackage: true } : {});
|
|
13558
13571
|
need4(sameStat(before.stat, after.stat) && after.sha256 === before.sha256, "INPUT_CHANGED");
|
|
13559
13572
|
}
|
|
13560
|
-
var
|
|
13573
|
+
var events = { UserPromptSubmit: ["userPromptSubmit", "user_prompt_submit"], SessionStart: ["sessionStart", "session_start"], SubagentStart: ["subagentStart", "subagent_start"] };
|
|
13574
|
+
var matchedEvents = { ...events, PreToolUse: ["preToolUse", "pre_tool_use"] };
|
|
13561
13575
|
var proofs = new WeakMap;
|
|
13576
|
+
var sha2 = (text2) => createHash15("sha256").update(text2).digest("hex");
|
|
13577
|
+
var proofs2 = new WeakMap;
|
|
13562
13578
|
function assertCodexHookDiscoveryRecovery(binding, witness) {
|
|
13563
|
-
const proof =
|
|
13579
|
+
const proof = proofs2.get(witness);
|
|
13564
13580
|
need4(!!proof && binding.agent === "codex" && witness.discoverySha256 === sha2(JSON.stringify(binding)), "RECONCILE_DISCOVERY_RECOVERY_CHANGED");
|
|
13565
13581
|
unchanged2(proof.before);
|
|
13566
13582
|
unchanged2(proof.current);
|
|
13583
|
+
proof.recheckAdditions();
|
|
13567
13584
|
}
|
|
13568
13585
|
function verifiesCodexHookDiscoverySource(binding, source, currentDigest, witness) {
|
|
13569
13586
|
if (!witness || source.path !== witness.configPath || source.hashMode !== "bytes")
|
|
@@ -13605,13 +13622,13 @@ function read2(path, changes) {
|
|
|
13605
13622
|
return readFileSync16(path, "utf8");
|
|
13606
13623
|
}
|
|
13607
13624
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
13608
|
-
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
13625
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") {
|
|
13609
13626
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
13610
13627
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
13611
|
-
const current2 = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
13628
|
+
const current2 = (source.hashMode === "claude-settings-v3" ? captureClaudeSettingsV3 : source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
13612
13629
|
if (current2 !== source.sha256)
|
|
13613
13630
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
13614
|
-
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current2;
|
|
13631
|
+
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v3" ? hashClaudeSettingsReplacementV3 : source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current2;
|
|
13615
13632
|
}
|
|
13616
13633
|
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
13617
13634
|
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))
|
|
@@ -13673,7 +13690,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
13673
13690
|
safe4(root2);
|
|
13674
13691
|
}
|
|
13675
13692
|
function assertMarketplaceBinding(binding, source) {
|
|
13676
|
-
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
13693
|
+
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"))
|
|
13677
13694
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
13678
13695
|
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
13679
13696
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
@@ -13682,7 +13699,10 @@ function rebindAgentDiscovery(binding, changes) {
|
|
|
13682
13699
|
const budget = discoveryByteBudget();
|
|
13683
13700
|
return { ...binding, sources: binding.sources.map((source) => {
|
|
13684
13701
|
assertMarketplaceBinding(binding, source);
|
|
13685
|
-
|
|
13702
|
+
const sha2562 = projected(source, changes, budget);
|
|
13703
|
+
if (!changes.has(source.path) && sha2562 !== source.sha256)
|
|
13704
|
+
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
13705
|
+
return { ...source, sha256: sha2562 };
|
|
13686
13706
|
}) };
|
|
13687
13707
|
}
|
|
13688
13708
|
function assertProjectDiscovery(agent, directories, home, canonical4 = resolve14, reviewed) {
|
|
@@ -13794,6 +13814,19 @@ function resolveAgentDiscovery(options) {
|
|
|
13794
13814
|
}
|
|
13795
13815
|
}
|
|
13796
13816
|
}
|
|
13817
|
+
if (options.reviewed === undefined && options.retainedReview !== undefined) {
|
|
13818
|
+
const retained = options.retainedReview;
|
|
13819
|
+
if (retained.agent !== agent || retained.method !== "reviewed")
|
|
13820
|
+
unresolved("invalid retained review");
|
|
13821
|
+
verifyAgentDiscovery(retained);
|
|
13822
|
+
if (!retained.sources.some((source) => source.path === canonical4(configPath) && source.format === undefined && source.fields === undefined))
|
|
13823
|
+
unresolved("retained review is missing the full agent configuration source");
|
|
13824
|
+
if (retained.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
|
|
13825
|
+
unresolved("retained settings witness names another configuration source");
|
|
13826
|
+
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))
|
|
13827
|
+
unresolved("retained review is missing current runtime discovery coverage");
|
|
13828
|
+
return retained;
|
|
13829
|
+
}
|
|
13797
13830
|
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))
|
|
13798
13831
|
throw new Error("Invalid --discovery-inputs version or agents");
|
|
13799
13832
|
const review = options.reviewed?.agents.find((item) => item.agent === agent);
|
|
@@ -13804,7 +13837,7 @@ function resolveAgentDiscovery(options) {
|
|
|
13804
13837
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
13805
13838
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
13806
13839
|
verifyAgentDiscovery(supplied);
|
|
13807
|
-
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
13840
|
+
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2" || source.hashMode === "claude-settings-v3") && source.path !== canonical4(configPath)))
|
|
13808
13841
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
13809
13842
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
13810
13843
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -34314,8 +34347,8 @@ var SESSION_RECONCILIATION_PLAN_TTL_MS = 5 * 60 * 1000;
|
|
|
34314
34347
|
// src/lib/claude-skills-coordination.ts
|
|
34315
34348
|
function coordinateClaudeSettings(before, replacement, options) {
|
|
34316
34349
|
const prior = JSON.parse(before ?? "{}"), next = JSON.parse(replacement);
|
|
34317
|
-
const
|
|
34318
|
-
const changed = [...
|
|
34350
|
+
const events2 = new Set([...Object.keys(prior.hooks ?? {}), ...Object.keys(next.hooks ?? {})]);
|
|
34351
|
+
const changed = [...events2].filter((event) => JSON.stringify(prior.hooks?.[event]) !== JSON.stringify(next.hooks?.[event]));
|
|
34319
34352
|
const selected = changed.length ? changed : ["PreToolUse"];
|
|
34320
34353
|
const plan = planClaudeHookEventsUpdate({
|
|
34321
34354
|
...options,
|
|
@@ -34603,7 +34636,7 @@ function detectConflict(name, scope, target) {
|
|
|
34603
34636
|
const meta3 = isNativeSafetyName(name) ? getHook(name) : resolveHookMeta(name);
|
|
34604
34637
|
if (!meta3 || !meta3.matcher)
|
|
34605
34638
|
return;
|
|
34606
|
-
const
|
|
34639
|
+
const events2 = new Set(getHookEvents(meta3));
|
|
34607
34640
|
const registered = getRegisteredHooksForTarget(scope, target);
|
|
34608
34641
|
for (const existingName of registered) {
|
|
34609
34642
|
if (existingName === name)
|
|
@@ -34611,7 +34644,7 @@ function detectConflict(name, scope, target) {
|
|
|
34611
34644
|
const existing = getHook(existingName);
|
|
34612
34645
|
if (!existing || !existing.matcher)
|
|
34613
34646
|
continue;
|
|
34614
|
-
if (!getHookEvents(existing).some((event) =>
|
|
34647
|
+
if (!getHookEvents(existing).some((event) => events2.has(event)))
|
|
34615
34648
|
continue;
|
|
34616
34649
|
if (matchersOverlap(meta3.matcher.toLowerCase(), existing.matcher.toLowerCase())) {
|
|
34617
34650
|
return `conflicts with '${existingName}' (same event ${meta3.event}, overlapping matcher '${existing.matcher}')`;
|