@hasna/hooks 0.11.7 → 0.12.1
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 +129 -4
- package/bin/hooks-mcp.js +39 -7
- package/bin/index.js +739 -129
- package/bin/native-safety-entry.js +15 -1718
- package/bin/serve.js +36 -9
- package/dist/index.js +51 -17
- package/dist/lib/codex-safety-check.d.ts +3 -0
- package/dist/lib/native-safety-registration.d.ts +49 -6
- package/dist/lib/registry.d.ts +15 -0
- package/dist/native-safety.d.ts +4 -0
- package/dist/native-safety.js +8 -4
- package/dist/sdk/index.js +1 -1
- package/hooks/hook-signed-link-guard/README.md +228 -0
- package/hooks/hook-signed-link-guard/package.json +12 -0
- package/hooks/hook-signed-link-guard/src/classify.ts +1280 -0
- package/hooks/hook-signed-link-guard/src/hook.ts +72 -0
- package/hooks/hook-signed-link-guard/src/jq.ts +545 -0
- package/hooks/hook-signed-link-guard/src/links.ts +136 -0
- package/hooks/hook-signed-link-output/README.md +47 -0
- package/hooks/hook-signed-link-output/package.json +12 -0
- package/hooks/hook-signed-link-output/src/hook.ts +111 -0
- package/hooks/hook-trash-guard/src/hook.ts +204 -80
- package/hooks/native-safety-entry.ts +28 -7
- package/package.json +2 -2
- package/scripts/validate-package.ts +12 -2
package/README.md
CHANGED
|
@@ -33,8 +33,20 @@ hooks safety startup registration-plan --target codex
|
|
|
33
33
|
hooks safety startup registration-apply --target codex --expected-plan-digest SHA256
|
|
34
34
|
hooks safety startup trust-plan --target codex --codex-command /absolute/native/codex --cwd /absolute/project
|
|
35
35
|
hooks safety startup trust-apply --target codex --codex-command /absolute/native/codex --cwd /absolute/project --expected-plan-digest SHA256
|
|
36
|
+
|
|
37
|
+
hooks safety startup registration-plan --target sumi
|
|
38
|
+
hooks safety startup registration-apply --target sumi --expected-plan-digest SHA256
|
|
36
39
|
```
|
|
37
40
|
|
|
41
|
+
Sumi runs the observer inside its own process from the bundled SDK, switched on
|
|
42
|
+
by Sumi's own `experimental.trash_readiness` setting, so there is no startup
|
|
43
|
+
command to register. For Sumi, the startup registration re-points the Hooks-owned
|
|
44
|
+
guard record, `~/.hasna/hooks/native/sumi-trash-guard.json`, at this installed
|
|
45
|
+
package. Sumi's startup check verifies that record. The registration uses the same
|
|
46
|
+
read-only plan, the exact plan digest and a retained predecessor as the other
|
|
47
|
+
targets. Unlike `hooks safety provision`, it also moves a different record that
|
|
48
|
+
still verifies. It never writes Sumi's configuration.
|
|
49
|
+
|
|
38
50
|
After registration and trust, use Trash's station setup flow to produce a new
|
|
39
51
|
ready plan. Native configuration changes must precede this plan because its
|
|
40
52
|
digest binds the selected configuration. Bind that plan explicitly:
|
|
@@ -44,9 +56,15 @@ hooks safety startup bind --target codex --binding /absolute/binding.json --expe
|
|
|
44
56
|
hooks safety startup check --target codex
|
|
45
57
|
```
|
|
46
58
|
|
|
47
|
-
The binding
|
|
59
|
+
The full binding-file contract is in
|
|
60
|
+
[docs/trash-readiness.md](docs/trash-readiness.md). It covers where each field
|
|
61
|
+
comes from, the compare-and-swap digest, the output and refusal codes, and how
|
|
62
|
+
to probe a result. The binding schema is `hasna.hooks.trash-readiness/v1`. It contains the exact
|
|
48
63
|
provider and home, `runtime:{path,sha256}`, `trash:{root,version,cliSha256}`,
|
|
49
|
-
`plan:{path,sha256}`, and `timeoutMs` (1000–60000).
|
|
64
|
+
`plan:{path,sha256}`, and `timeoutMs` (1000–60000). The plan is the ready plan
|
|
65
|
+
that `trash station plan --out` wrote, and `runtime` is that plan's `spec.runtime`.
|
|
66
|
+
`--expected-binding-sha256` is the SHA-256 of the installed binding file, or
|
|
67
|
+
`absent`. Use 45000 for a normal
|
|
50
68
|
whole-startup budget, configurable within those bounds. Existing bindings retain
|
|
51
69
|
their explicit budget until a guarded update. Claude and Codex registrations
|
|
52
70
|
allow 65 seconds for the observer's 60-second maximum plus runner overhead.
|
|
@@ -64,7 +82,10 @@ supervises only its newly created check process group, including nested clients,
|
|
|
64
82
|
and cleans it up on completion, cancellation or timeout. Selecting `sumi`
|
|
65
83
|
identifies that consumer; it does not register
|
|
66
84
|
a Sumi lifecycle event. A Sumi adapter must call the observer through its own
|
|
67
|
-
supported startup path and verify delivery separately.
|
|
85
|
+
supported startup path and verify delivery separately. A running harness keeps
|
|
86
|
+
the SDK version it started with. A 45000 ms binding needs a consumer built with
|
|
87
|
+
0.11.4 or later, because older consumers cap `timeoutMs` at 15000 and report
|
|
88
|
+
`binding_invalid`.
|
|
68
89
|
|
|
69
90
|
```bash
|
|
70
91
|
hooks --help
|
|
@@ -121,6 +142,37 @@ reinstalling the registration and reviewing the new native trust definition. Uns
|
|
|
121
142
|
installation paths are refused before settings are written. Codex also requires
|
|
122
143
|
trust through `/hooks`; registration alone does not prove interception.
|
|
123
144
|
|
|
145
|
+
The same bundled entry also evaluates the `signed-link-guard` for every Bash
|
|
146
|
+
and Monitor command (Claude Code's `Monitor` tool runs a shell command and
|
|
147
|
+
streams its stdout to the model), under either capability name (`trash-guard`
|
|
148
|
+
or `workspace-repos-guard`): a `gh` read that would print composite GitHub
|
|
149
|
+
content (bodies, comments, reviews, check links and details, raw issue and
|
|
150
|
+
pull request objects) is refused before it runs with the reason
|
|
151
|
+
`composite output withheld: signed-link shape; use bounded scalar projections per class disposition 799310`.
|
|
152
|
+
Bounded scalar reads such as `gh pr view <n> --json number,state,headRefOid`,
|
|
153
|
+
`gh pr checks <n> --json name,state,bucket` and `gh api … --jq .head.sha` pass,
|
|
154
|
+
and so does every `gh` write, including a `--body "$(cat <<'EOF' … EOF)"`
|
|
155
|
+
body. A plain `gh pr view <n>` and a plain or `--watch` `gh pr checks <n>` are
|
|
156
|
+
refused, because they print the body and the check links; the guard's README
|
|
157
|
+
lists the scalar alternatives. See `hooks/hook-signed-link-guard/README.md` for
|
|
158
|
+
the full rule list and its limits. The guard is also a catalog hook
|
|
159
|
+
(`hooks install signed-link-guard`), and `signed-link-output` is an optional
|
|
160
|
+
Claude PostToolUse backstop that replaces signed-link URLs in tool output where
|
|
161
|
+
the running Claude Code supports output replacement.
|
|
162
|
+
|
|
163
|
+
Monitor commands get every check a Bash command gets. A deletion inside a
|
|
164
|
+
Monitor command is refused instead of rewritten into Trash, because the
|
|
165
|
+
rewrite is proven only for the Bash tool; run deletions with Bash. Claude
|
|
166
|
+
registrations therefore match
|
|
167
|
+
`^(Bash|Monitor|apply_patch|ApplyPatch|functions\.apply_patch)$` (the
|
|
168
|
+
repository guard also matches `Write`, `Edit`, `MultiEdit` and
|
|
169
|
+
`NotebookEdit`). Re-registering with `--overwrite` or `registration-apply`
|
|
170
|
+
updates a registration that still has the matcher without `Monitor` in place,
|
|
171
|
+
and the Claude safety check reports that older matcher as not installed.
|
|
172
|
+
Because a registration pins the worker's digest, an installed registration
|
|
173
|
+
keeps running the previous worker until it is re-registered for the new
|
|
174
|
+
install; see "Rolling out a new native worker" below.
|
|
175
|
+
|
|
124
176
|
For the default global Claude settings path, a managed Skills installation
|
|
125
177
|
coordinates hook changes and discovery witnesses in one guarded update. Existing
|
|
126
178
|
drift or concurrent edits refuse installation before an uncoordinated write;
|
|
@@ -188,6 +240,79 @@ runtime-policy verification, native discovery and native adoption false;
|
|
|
188
240
|
MDM/server policy, workspace trust and launch flags require native acceptance.
|
|
189
241
|
|
|
190
242
|
|
|
243
|
+
## Rolling out a new native worker
|
|
244
|
+
|
|
245
|
+
Every native registration pins the SHA-256 of `bin/native-safety-entry.js`, so
|
|
246
|
+
a release that changes the worker reaches a harness only when that harness is
|
|
247
|
+
re-registered against the new install. Run each step with the new version's
|
|
248
|
+
CLI (`hooks --version`), after installing that version:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Claude Code
|
|
252
|
+
hooks safety registration-plan --target claude
|
|
253
|
+
hooks safety registration-apply --target claude --expected-plan-digest PLAN_SHA256
|
|
254
|
+
# Only where the repository guard is registered under its own name:
|
|
255
|
+
hooks safety install workspace-repos-guard --target claude --overwrite
|
|
256
|
+
|
|
257
|
+
# Codex (a changed definition runs only after it is trusted again)
|
|
258
|
+
hooks safety registration-plan --target codex
|
|
259
|
+
hooks safety registration-apply --target codex --expected-plan-digest PLAN_SHA256
|
|
260
|
+
hooks safety trust-plan --target codex --cwd /path/to/project --codex-command /absolute/path/to/native/codex
|
|
261
|
+
hooks safety trust-apply --target codex --cwd /path/to/project --codex-command /absolute/path/to/native/codex --expected-plan-digest PLAN_SHA256
|
|
262
|
+
|
|
263
|
+
# Sumi
|
|
264
|
+
hooks safety verify --target sumi # prints the current binding's sha256
|
|
265
|
+
hooks safety install trash-guard --target sumi --expected-binding-sha256 CURRENT_SHA256
|
|
266
|
+
hooks safety verify --target sumi --execution
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`hooks safety provision --target sumi`, which the package's lifecycle script
|
|
270
|
+
also runs, keeps a binding that still verifies, so it does not move Sumi to a
|
|
271
|
+
worker installed in a different tree; the explicit install above does. That
|
|
272
|
+
install refuses `--overwrite` with `input_invalid`: the expected digest is its
|
|
273
|
+
upgrade guard. A binding that no longer verifies (its worker was rewritten)
|
|
274
|
+
cannot report its digest; `hooks safety provision --target sumi` re-registers
|
|
275
|
+
that one and keeps the previous record beside it. Where
|
|
276
|
+
`hooks safety startup registration-plan` and `registration-apply` accept
|
|
277
|
+
`--target sumi` (hasna/apps#2932), that registration re-points the same record
|
|
278
|
+
through a read-only plan and an exact plan digest, and it supersedes the install
|
|
279
|
+
form above.
|
|
280
|
+
|
|
281
|
+
A running Sumi service keeps the readiness code bundled into its own build, so
|
|
282
|
+
Sumi's startup readiness warning clears only once the service runs a Sumi build
|
|
283
|
+
of 0.2.45 or later. Restarting the service is an owner decision and is not part
|
|
284
|
+
of this rollout.
|
|
285
|
+
|
|
286
|
+
Installing over the running version is a half-registration hazard.
|
|
287
|
+
`bun add -g @hasna/hooks@<version>` (or `npm install -g`) rewrites the worker
|
|
288
|
+
in place in the global package tree. Every registration still pinned to the
|
|
289
|
+
old digest then refuses every shell and patch command in its harness, since
|
|
290
|
+
the supervisor fails closed on changed bytes, until it is re-registered; the
|
|
291
|
+
lifecycle script re-provisions only Sumi. Until the last registration is
|
|
292
|
+
applied, some harnesses run the new guard and the others refuse everything.
|
|
293
|
+
Install each version into its own tree (the fleet's
|
|
294
|
+
`~/.hasna/hooks/runtime/<version>-copyfile` layout) and register from there,
|
|
295
|
+
so every old registration keeps verifying its own tree until it is moved. If
|
|
296
|
+
an in-place global upgrade cannot be avoided, re-register all three targets
|
|
297
|
+
straight after it.
|
|
298
|
+
|
|
299
|
+
The supervisor passes the worker to the runtime as one `--eval` argument, and
|
|
300
|
+
Linux limits a single argument to 131,072 bytes (32 pages of 4 KiB). The
|
|
301
|
+
worker is therefore built minified, and the package validation refuses a
|
|
302
|
+
worker at or above that size.
|
|
303
|
+
|
|
304
|
+
Known limits of the native entry:
|
|
305
|
+
|
|
306
|
+
- The signed-link guard judges command text: interpreters and script files
|
|
307
|
+
that run `gh` themselves, shell aliases and functions from earlier commands,
|
|
308
|
+
`gh` extensions, direct HTTP clients such as `curl` and `GH_DEBUG` set in the
|
|
309
|
+
harness environment are outside it (details in the guard's README).
|
|
310
|
+
- Codex's input into a running unified exec session (`write_stdin`) has not
|
|
311
|
+
been verified to pass through PreToolUse.
|
|
312
|
+
- The repository guard still reads a here-document body line by line, so prose
|
|
313
|
+
in a `--body "$(cat <<'EOF' … EOF)"` body that mentions a delete command
|
|
314
|
+
beside an unbalanced quote can still be refused by that guard.
|
|
315
|
+
|
|
191
316
|
## Codex safety readiness
|
|
192
317
|
|
|
193
318
|
```bash
|
|
@@ -206,7 +331,7 @@ architecture (arm64 or x64); script launchers are refused before execution. When
|
|
|
206
331
|
Codex is installed through npm, pass the platform package's native binary rather
|
|
207
332
|
than its JavaScript launcher. Executable ownership, permissions, ancestry and
|
|
208
333
|
bytes are checked. Supported protocol versions are 0.153.0, 0.154.0,
|
|
209
|
-
0.154.0-alpha.6.1, 0.155.0, 0.155.1, 0.156.1, 0.157.0, 0.157.1, 0.158.0 and 0.159.
|
|
334
|
+
0.154.0-alpha.6.1, 0.155.0, 0.155.1, 0.156.1, 0.157.0, 0.157.1, 0.158.0, 0.159.0 and 0.159.2;
|
|
210
335
|
other versions refuse pending compatibility validation. Updating Codex can
|
|
211
336
|
therefore require a compatible Hooks release before native safety verification
|
|
212
337
|
and trust provisioning succeed. Keep existing hook definitions and trust intact
|
package/bin/hooks-mcp.js
CHANGED
|
@@ -135,6 +135,9 @@ var init_app_home = __esm(() => {
|
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
// src/lib/registry.ts
|
|
138
|
+
function matcherFor(meta, target) {
|
|
139
|
+
return meta.targetMatchers?.[target] ?? meta.matcher;
|
|
140
|
+
}
|
|
138
141
|
function getHooksByCategory(category) {
|
|
139
142
|
return HOOKS.filter((h) => h.category === category);
|
|
140
143
|
}
|
|
@@ -148,7 +151,7 @@ function searchHooks(query) {
|
|
|
148
151
|
function getHook(name) {
|
|
149
152
|
return HOOKS.find((h) => h.name === name);
|
|
150
153
|
}
|
|
151
|
-
var CATEGORIES, HOOKS;
|
|
154
|
+
var CLAUDE_TRASH_GUARD_MATCHER = "^(Bash|Monitor|apply_patch|ApplyPatch|functions\\.apply_patch)$", CATEGORIES, HOOKS;
|
|
152
155
|
var init_registry = __esm(() => {
|
|
153
156
|
init_app_home();
|
|
154
157
|
CATEGORIES = [
|
|
@@ -213,6 +216,8 @@ var init_registry = __esm(() => {
|
|
|
213
216
|
category: "Git Safety",
|
|
214
217
|
event: "PreToolUse",
|
|
215
218
|
matcher: "^(Bash|Write|Edit|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions\\.apply_patch)$",
|
|
219
|
+
targetMatchers: { claude: "^(Bash|Monitor|Write|Edit|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions\\.apply_patch)$" },
|
|
220
|
+
legacyMatchers: ["^(Bash|Write|Edit|MultiEdit|NotebookEdit|apply_patch|ApplyPatch|functions\\.apply_patch)$"],
|
|
216
221
|
tags: ["workspace", "repos", "structure", "guard", "safety", "orgs", "multi-agent"]
|
|
217
222
|
},
|
|
218
223
|
{
|
|
@@ -223,6 +228,8 @@ var init_registry = __esm(() => {
|
|
|
223
228
|
category: "Git Safety",
|
|
224
229
|
event: "PreToolUse",
|
|
225
230
|
matcher: "^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$",
|
|
231
|
+
targetMatchers: { claude: CLAUDE_TRASH_GUARD_MATCHER },
|
|
232
|
+
legacyMatchers: ["^(Bash|apply_patch|ApplyPatch|functions\\.apply_patch)$"],
|
|
226
233
|
tags: ["rm", "delete", "trash", "recoverable", "guard", "safety", "bash"],
|
|
227
234
|
rewritesInput: true,
|
|
228
235
|
timeoutSeconds: 10
|
|
@@ -327,6 +334,28 @@ var init_registry = __esm(() => {
|
|
|
327
334
|
matcher: "Bash",
|
|
328
335
|
tags: ["security", "credentials", "secrets", "detection", "audit"]
|
|
329
336
|
},
|
|
337
|
+
{
|
|
338
|
+
name: "signed-link-guard",
|
|
339
|
+
displayName: "Signed Link Guard",
|
|
340
|
+
description: "Refuses gh reads that would print composite GitHub content (bodies, comments, reviews, commit messages, check links and details, raw issue/PR/check API objects) where signed action links appear; bounded scalar projections, diffs, lists and writes pass",
|
|
341
|
+
version: "0.1.0",
|
|
342
|
+
category: "Security",
|
|
343
|
+
event: "PreToolUse",
|
|
344
|
+
matcher: "^(Bash|Monitor)$",
|
|
345
|
+
tags: ["github", "gh", "signed-links", "capabilities", "transcript", "guard", "security"],
|
|
346
|
+
timeoutSeconds: 10
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "signed-link-output",
|
|
350
|
+
displayName: "Signed Link Output",
|
|
351
|
+
description: "Claude PostToolUse backstop: replaces signed-link URLs in tool output with a redaction marker (updatedToolOutput, where the running Claude Code supports it) and tells the model not to repeat or store the link. It runs after the tool, so it cannot undo display or other persistence",
|
|
352
|
+
version: "0.1.0",
|
|
353
|
+
category: "Security",
|
|
354
|
+
event: "PostToolUse",
|
|
355
|
+
matcher: "^(Bash|WebFetch|mcp__.*)$",
|
|
356
|
+
tags: ["github", "signed-links", "capabilities", "redaction", "transcript", "security"],
|
|
357
|
+
timeoutSeconds: 10
|
|
358
|
+
},
|
|
330
359
|
{
|
|
331
360
|
name: "phonenotify",
|
|
332
361
|
displayName: "Phone Notify",
|
|
@@ -1419,7 +1448,7 @@ var init_redact = __esm(() => {
|
|
|
1419
1448
|
];
|
|
1420
1449
|
});
|
|
1421
1450
|
|
|
1422
|
-
//
|
|
1451
|
+
// ../../node_modules/.bun/@hasna+contracts@1.2.2+944b86d8024e79fa/node_modules/@hasna/contracts/dist/client/transport.js
|
|
1423
1452
|
import { isIP } from "net";
|
|
1424
1453
|
import { spawnSync } from "child_process";
|
|
1425
1454
|
import { closeSync as closeSync22, fstatSync as fstatSync22, openSync as openSync22, readFileSync as readFileSync22 } from "fs";
|
|
@@ -3342,7 +3371,7 @@ function writeHookSettingsPreimage(path, settings, before) {
|
|
|
3342
3371
|
// src/lib/claude-skills-coordination.ts
|
|
3343
3372
|
import { createHash as createHash6 } from "crypto";
|
|
3344
3373
|
|
|
3345
|
-
//
|
|
3374
|
+
// ../../node_modules/.bun/@hasna+skills@0.9.17+a00d3064559565bb/node_modules/@hasna/skills/dist/index.js
|
|
3346
3375
|
import { existsSync as existsSync10 } from "fs";
|
|
3347
3376
|
import { homedir as homedir5 } from "os";
|
|
3348
3377
|
import { join as join9, resolve as resolve3 } from "path";
|
|
@@ -34989,6 +35018,7 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
34989
35018
|
if (uniqueEventKeys.length === 0) {
|
|
34990
35019
|
throw new Error(`Hook '${name}' has no installable events for target '${target}'`);
|
|
34991
35020
|
}
|
|
35021
|
+
const matcher = matcherFor(meta3, target);
|
|
34992
35022
|
const settings = readSettings2(scope, target);
|
|
34993
35023
|
if (!settings.hooks)
|
|
34994
35024
|
settings.hooks = {};
|
|
@@ -35023,10 +35053,12 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
35023
35053
|
}
|
|
35024
35054
|
}
|
|
35025
35055
|
if (positions.length) {
|
|
35026
|
-
if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== (meta3.matcher ?? ""))) {
|
|
35056
|
+
if (positions.length !== uniqueEventKeys.length || uniqueEventKeys.some((event) => positions.filter((position) => position.event === event).length !== 1) || positions.some((position) => (position.entry.matcher ?? "") !== matcher && !(meta3.legacyMatchers ?? []).includes(position.entry.matcher ?? ""))) {
|
|
35027
35057
|
throw new Error("Codex hook events, matchers or duplicate registrations require explicit reconciliation; no settings were changed.");
|
|
35028
35058
|
}
|
|
35029
35059
|
for (const { entry, index } of positions) {
|
|
35060
|
+
if (matcher)
|
|
35061
|
+
entry.matcher = matcher;
|
|
35030
35062
|
entry.hooks[index] = { ...entry.hooks[index], type: "command", command: hookCommand };
|
|
35031
35063
|
if (typeof meta3.timeoutSeconds === "number" && meta3.timeoutSeconds > 0)
|
|
35032
35064
|
entry.hooks[index].timeout = meta3.timeoutSeconds;
|
|
@@ -35046,8 +35078,8 @@ function registerHook(name, scope = "global", target = "claude", profile, mement
|
|
|
35046
35078
|
const entry = {
|
|
35047
35079
|
hooks: [hookEntry]
|
|
35048
35080
|
};
|
|
35049
|
-
if (
|
|
35050
|
-
entry.matcher =
|
|
35081
|
+
if (matcher) {
|
|
35082
|
+
entry.matcher = matcher;
|
|
35051
35083
|
}
|
|
35052
35084
|
settings.hooks[eventKey].push(entry);
|
|
35053
35085
|
}
|
|
@@ -35983,7 +36015,7 @@ function createHooksServer() {
|
|
|
35983
36015
|
if (meta3 && settingsExist) {
|
|
35984
36016
|
try {
|
|
35985
36017
|
const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
35986
|
-
if (!hookRegisteredInSettings(settings, name, meta3.event, meta3
|
|
36018
|
+
if (!hookRegisteredInSettings(settings, name, meta3.event, matcherFor(meta3, "claude"))) {
|
|
35987
36019
|
const eventName = meta3.event.split(":")[0];
|
|
35988
36020
|
issues.push({ hook: name, issue: `Not registered under correct event (${eventName})`, severity: "error" });
|
|
35989
36021
|
hookHealthy = false;
|