@indigoai-us/hq-cli 5.345.82 → 5.345.83

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.83] — 2026-10-11
6
+ - HQ-root scaffolds now package the shared runtime used by generated shell forwarders.
7
+ - Lanes hook installation now replaces stale hq-cli drain registrations across
8
+ Claude settings and Codex config, including entries whose old install path no
9
+ longer exists. Other hooks are preserved, duplicate drains are removed, and a
10
+ repeat install leaves the registration file byte-identical.
11
+
5
12
  ## [5.345.82] — 2026-10-11
6
13
  - Session-start health no longer treats an ordinary HQ Core upgrade warning
7
14
  as an unrepaired doctor finding. `hq doctor` still shows the upgrade;
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env bash
2
+ # Shared runtime for generated hq CLI forwarders.
3
+
4
+ forwarder_exec() {
5
+ local script_name="$1" min_cli="$2" root_mode="$3" command="$4" stdin_closed="$5"
6
+ shift 5
7
+ local forwarder_library_dir="${BASH_SOURCE[0]%/*}"
8
+ local path_rule index value path_option path_value
9
+ local -a forwarded_args path_rules path_options path_values converted_values command_parts
10
+ forwarded_args=("$@")
11
+ local forwarded_arg_count=$#
12
+
13
+ if [ "$root_mode" = live-project ]; then
14
+ HQ_ROOT="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$(cd "$SCRIPT_DIR/../.." && pwd)}}"
15
+ elif [ "$root_mode" = live ]; then
16
+ HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
17
+ fi
18
+
19
+ if ! command -v hq >/dev/null 2>&1; then
20
+ printf "%s: requires the hq CLI — this script's implementation now ships with it.\n" "$script_name" >&2
21
+ printf 'Install it with: npm install -g @indigoai-us/hq-cli\n' >&2
22
+ exit 127
23
+ fi
24
+
25
+ [ "$forwarder_library_dir" != "${BASH_SOURCE[0]}" ] || forwarder_library_dir=.
26
+ if [ -f "$forwarder_library_dir/hq-cli-floor.sh" ]; then
27
+ # shellcheck source=lib/hq-cli-floor.sh
28
+ . "$forwarder_library_dir/hq-cli-floor.sh"
29
+ hq_cli_floor_check "$script_name" "$min_cli"
30
+ fi
31
+
32
+ if [ "$stdin_closed" = true ]; then
33
+ if ! { true 3<&0; } 2>/dev/null; then
34
+ export HQ_CORE_STDIN_CLOSED=1
35
+ fi
36
+ fi
37
+
38
+ if [ "$root_mode" = live ] || [ "$root_mode" = live-project ]; then
39
+ if command -v cygpath >/dev/null 2>&1; then
40
+ HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
41
+ fi
42
+ fi
43
+
44
+ if command -v cygpath >/dev/null 2>&1; then
45
+ IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
46
+ for path_rule in "${path_rules[@]}"; do
47
+ case "$path_rule" in
48
+ none) ;;
49
+ position:*)
50
+ index="${path_rule#position:}"
51
+ if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
52
+ ;;
53
+ first-nonoption)
54
+ for ((index = 0; index < forwarded_arg_count; index++)); do
55
+ case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
56
+ done
57
+ ;;
58
+ all-nonoptions)
59
+ for ((index = 0; index < forwarded_arg_count; index++)); do
60
+ case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
61
+ done
62
+ ;;
63
+ options:*)
64
+ IFS=, read -r -a path_options <<< "${path_rule#options:}"
65
+ for ((index = 0; index < forwarded_arg_count; index++)); do
66
+ for path_option in "${path_options[@]}"; do
67
+ case "${forwarded_args[index]}" in
68
+ "$path_option")
69
+ index=$((index + 1))
70
+ if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
71
+ break
72
+ ;;
73
+ "$path_option"=*)
74
+ value="${forwarded_args[index]#*=}"
75
+ case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
76
+ break
77
+ ;;
78
+ esac
79
+ done
80
+ done
81
+ ;;
82
+ comma:*)
83
+ path_option="${path_rule#comma:}"
84
+ for ((index = 0; index < forwarded_arg_count; index++)); do
85
+ case "${forwarded_args[index]}" in
86
+ "$path_option")
87
+ index=$((index + 1))
88
+ if (( index < forwarded_arg_count )); then
89
+ IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
90
+ converted_values=()
91
+ for path_value in "${path_values[@]}"; do
92
+ case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
93
+ done
94
+ value="$(IFS=,; printf '%s' "${converted_values[*]}")"
95
+ forwarded_args[index]="$value"
96
+ fi
97
+ break
98
+ ;;
99
+ "$path_option"=*)
100
+ value="${forwarded_args[index]#*=}"
101
+ case "$value" in
102
+ /*,*|/*)
103
+ IFS=, read -r -a path_values <<< "$value"
104
+ converted_values=()
105
+ for path_value in "${path_values[@]}"; do
106
+ case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
107
+ done
108
+ value="$(IFS=,; printf '%s' "${converted_values[*]}")"
109
+ forwarded_args[index]="${path_option}=$value"
110
+ ;;
111
+ esac
112
+ break
113
+ ;;
114
+ esac
115
+ done
116
+ ;;
117
+ *) printf 'unknown path_operands rule: %s\n' "$path_rule" >&2; exit 70 ;;
118
+ esac
119
+ done
120
+ fi
121
+
122
+ IFS=' ' read -r -a command_parts <<< "$command"
123
+ if [ "$root_mode" = live ] || [ "$root_mode" = live-project ]; then
124
+ if [ "$forwarded_arg_count" -gt 0 ]; then
125
+ MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" "${command_parts[@]}" "${forwarded_args[@]}"
126
+ else
127
+ MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" "${command_parts[@]}"
128
+ fi
129
+ elif [ "$forwarded_arg_count" -gt 0 ]; then
130
+ MSYS2_ARG_CONV_EXCL='*' exec hq core "${command_parts[@]}" "${forwarded_args[@]}"
131
+ else
132
+ MSYS2_ARG_CONV_EXCL='*' exec hq core "${command_parts[@]}"
133
+ fi
134
+ }
135
+
136
+ convert_forwarded_path() {
137
+ local arg_index="$1"
138
+ local value="${forwarded_args[$arg_index]}"
139
+ case "$value" in
140
+ /*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
141
+ esac
142
+ }
@@ -268,6 +268,7 @@ export const SCAFFOLD_ONLY_ASSETS = [
268
268
  { asset: "core/scripts/lib/session-scope-capability.sh", root: "live", summary: "Session scope-capability lib sourced by hq-session" },
269
269
  { asset: "core/scripts/lib/session-id.sh", root: "live", summary: "Session-id resolver lib sourced by hq-session" },
270
270
  { asset: "core/scripts/lib/hq-cli-floor.sh", root: "live", summary: "Shared CLI version-floor helper sourced by generated forwarders" },
271
+ { asset: "core/scripts/lib/forwarder.sh", root: "live", summary: "Shared runtime sourced by generated scaffold forwarders" },
271
272
  { asset: "core/scripts/lib/work-mesh-enqueue.sh", root: "live", summary: "Work Mesh Live spool enqueue lib sourced by hooks" },
272
273
  { asset: "core/scripts/lib/work-mesh-live-hook.sh", root: "live", summary: "Work Mesh Live hook helpers sourced by rebind" },
273
274
  { asset: "core/scripts/lib/work-mesh-live-rebind.sh", root: "live", summary: "Work Mesh Live session rebind library sourced by hooks and the native rollback command" },
@@ -149,9 +149,18 @@ function isInstalledDrainEntry(entryPath) {
149
149
  * Both paths target the same hq-cli drain entry, so recognize an installed
150
150
  * package entry alongside the exact entry belonging to this CLI invocation.
151
151
  */
152
- function drainCommandsMatch(candidate, expected) {
153
- if (candidate === expected)
154
- return true;
152
+ function drainCommandsMatch(candidate, expected, requireInstalledPaths = false) {
153
+ if (candidate === expected) {
154
+ if (!requireInstalledPaths)
155
+ return true;
156
+ const withoutProviderEnv = candidate.startsWith("HQ_LANE_PROVIDER=grok ")
157
+ ? candidate.slice("HQ_LANE_PROVIDER=grok ".length)
158
+ : candidate;
159
+ const currentArgv = parseShellQuotedArguments(withoutProviderEnv);
160
+ return currentArgv !== null && currentArgv.length === 2 &&
161
+ path.parse(currentArgv[0]).name.toLowerCase() === "node" &&
162
+ fs.existsSync(currentArgv[0]) && fs.existsSync(currentArgv[1]);
163
+ }
155
164
  const withoutProviderEnv = candidate.startsWith("HQ_LANE_PROVIDER=grok ")
156
165
  ? candidate.slice("HQ_LANE_PROVIDER=grok ".length)
157
166
  : candidate;
@@ -162,9 +171,9 @@ function drainCommandsMatch(candidate, expected) {
162
171
  return false;
163
172
  if (!isInstalledDrainEntry(argv[1]))
164
173
  return false;
165
- return fs.existsSync(argv[0]) && fs.existsSync(argv[1]);
174
+ return !requireInstalledPaths || (fs.existsSync(argv[0]) && fs.existsSync(argv[1]));
166
175
  }
167
- function groupsContainDrainCommand(groups, expected) {
176
+ function groupsContainDrainCommand(groups, expected, requireInstalledPaths = false) {
168
177
  if (!Array.isArray(groups))
169
178
  return false;
170
179
  return groups.some((group) => {
@@ -173,29 +182,51 @@ function groupsContainDrainCommand(groups, expected) {
173
182
  return group.hooks.some((hook) => isPlainObject(hook) &&
174
183
  hook.type === "command" &&
175
184
  typeof hook.command === "string" &&
176
- drainCommandsMatch(hook.command, expected));
185
+ drainCommandsMatch(hook.command, expected, requireInstalledPaths));
177
186
  });
178
187
  }
179
- function tomlHasDrainCommand(parsed, expected) {
188
+ function tomlHasDrainCommand(parsed, expected, requireInstalledPaths = false) {
180
189
  const hooks = isPlainObject(parsed.hooks) ? parsed.hooks : null;
181
190
  return hooks !== null &&
182
- ["PreToolUse", "PostToolUse", "Stop"].every((event) => groupsContainDrainCommand(hooks[event], expected));
191
+ ["PreToolUse", "PostToolUse", "Stop"].every((event) => groupsContainDrainCommand(hooks[event], expected, requireInstalledPaths));
183
192
  }
184
- function removeCommandFromGroups(groups, command) {
193
+ function removeDrainCommandsFromGroups(groups, expected, legacyCommand) {
185
194
  let changed = false;
195
+ let keptCurrent = false;
186
196
  const next = groups.flatMap((group) => {
187
197
  if (!isPlainObject(group) || !Array.isArray(group.hooks))
188
198
  return [group];
189
- const hooks = group.hooks.filter((hook) => !(isPlainObject(hook) &&
190
- hook.type === "command" &&
191
- hook.command === command));
199
+ const hooks = group.hooks.filter((hook) => {
200
+ if (!isPlainObject(hook) || hook.type !== "command" || typeof hook.command !== "string")
201
+ return true;
202
+ const isCurrent = hook.command === expected;
203
+ const isDrain = isCurrent || drainCommandsMatch(hook.command, expected) || hook.command === legacyCommand;
204
+ if (isCurrent && !keptCurrent) {
205
+ keptCurrent = true;
206
+ return true;
207
+ }
208
+ if (isDrain)
209
+ changed = true;
210
+ return !isDrain;
211
+ });
192
212
  if (hooks.length === group.hooks.length)
193
213
  return [group];
194
- changed = true;
195
214
  return hooks.length === 0 ? [] : [{ ...group, hooks }];
196
215
  });
197
216
  return { groups: next, changed };
198
217
  }
218
+ function groupHasExactlyOneCommand(groups, command) {
219
+ let count = 0;
220
+ for (const group of groups) {
221
+ if (!isPlainObject(group) || !Array.isArray(group.hooks))
222
+ continue;
223
+ for (const hook of group.hooks) {
224
+ if (isPlainObject(hook) && hook.type === "command" && hook.command === command)
225
+ count += 1;
226
+ }
227
+ }
228
+ return count === 1;
229
+ }
199
230
  export function renderDrainHookJson(shPath, provider = "grok") {
200
231
  const command = drainHookCommand(shPath, provider);
201
232
  const registration = {
@@ -237,21 +268,18 @@ export function mergeClaudeSettingsHook(existingText, command, settingsPath, leg
237
268
  }
238
269
  eventGroups.set(event, groups);
239
270
  }
240
- const pre = eventGroups.get("PreToolUse");
241
- if (!Array.isArray(pre)) {
242
- throw new LanesError("settings_unparseable", `${settingsPath} has a non-array "hooks.PreToolUse"; refusing to rewrite it`, { path: settingsPath });
243
- }
244
- const migration = legacyCommand && legacyCommand !== command
245
- ? removeCommandFromGroups(pre, legacyCommand)
246
- : { groups: pre, changed: false };
247
- const allRegistered = events.every((event) => groupsContainCommand(eventGroups.get(event), command));
248
- if (allRegistered && !migration.changed) {
271
+ const cleanups = new Map(events.map((event) => [
272
+ event,
273
+ removeDrainCommandsFromGroups(eventGroups.get(event), command, legacyCommand),
274
+ ]));
275
+ if (events.every((event) => groupHasExactlyOneCommand(cleanups.get(event).groups, command) &&
276
+ !cleanups.get(event).changed)) {
249
277
  return { text: existingText ?? "", changed: false };
250
278
  }
251
279
  const nextHooks = { ...hooks };
252
280
  for (const event of events) {
253
- const groups = event === "PreToolUse" ? migration.groups : eventGroups.get(event);
254
- nextHooks[event] = groupsContainCommand(groups, command) ? groups : [
281
+ const groups = cleanups.get(event).groups;
282
+ nextHooks[event] = groupHasExactlyOneCommand(groups, command) ? groups : [
255
283
  ...groups,
256
284
  { hooks: [{ type: "command", command, timeout: DRAIN_HOOK_TIMEOUT_SECONDS }] },
257
285
  ];
@@ -405,6 +433,145 @@ function parseTomlOrThrow(text, configPath, stage) {
405
433
  }
406
434
  return parsed;
407
435
  }
436
+ function renderTomlInline(value) {
437
+ if (typeof value === "string")
438
+ return tomlBasicString(value);
439
+ if (typeof value === "boolean" || typeof value === "number")
440
+ return String(value);
441
+ if (Array.isArray(value))
442
+ return `[${value.map(renderTomlInline).join(", ")}]`;
443
+ if (isPlainObject(value)) {
444
+ const fields = Object.entries(value).map(([key, entry]) => `${JSON.stringify(key)} = ${renderTomlInline(entry)}`);
445
+ return `{ ${fields.join(", ")} }`;
446
+ }
447
+ throw new LanesError("config_toml_migration_failed", "Could not safely render an inline Codex hook while removing stale lane drains");
448
+ }
449
+ function isDrainHookValue(hook, expected, legacyCommand) {
450
+ return isPlainObject(hook) && hook.type === "command" && typeof hook.command === "string" &&
451
+ (drainCommandsMatch(hook.command, expected) || hook.command === legacyCommand);
452
+ }
453
+ function removeStaleCodexDrainHooks(text, parsed, expected, legacyCommand, configPath) {
454
+ const hooks = isPlainObject(parsed.hooks) ? parsed.hooks : {};
455
+ const lines = text.split("\n");
456
+ const spans = [];
457
+ let changed = false;
458
+ for (const event of ["PreToolUse", "PostToolUse", "Stop"]) {
459
+ const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
460
+ const parentIndexes = lines.flatMap((line, index) => {
461
+ const header = tomlHeader(line);
462
+ return header?.array && header.path === `hooks.${event}` ? [index] : [];
463
+ });
464
+ if (parentIndexes.length !== groups.length) {
465
+ throw new LanesError("config_toml_migration_failed", `Could not safely map ${event} drain hooks in ${configPath}; refusing to add a second drain`, { path: configPath, event, groups: groups.length, headers: parentIndexes.length });
466
+ }
467
+ let keptCurrent = false;
468
+ for (let groupIndex = 0; groupIndex < parentIndexes.length; groupIndex += 1) {
469
+ const start = parentIndexes[groupIndex];
470
+ let end = start + 1;
471
+ for (; end < lines.length; end += 1) {
472
+ const header = tomlHeader(lines[end]);
473
+ if (!header)
474
+ continue;
475
+ if (header.array && header.path === `hooks.${event}`)
476
+ break;
477
+ if (header.path.startsWith(`hooks.${event}.`))
478
+ continue;
479
+ break;
480
+ }
481
+ const hookHeaders = lines.flatMap((line, index) => {
482
+ if (index <= start || index >= end)
483
+ return [];
484
+ const header = tomlHeader(line);
485
+ return header?.array && header.path === `hooks.${event}.hooks` ? [index] : [];
486
+ });
487
+ const group = groups[groupIndex];
488
+ const groupHooks = isPlainObject(group) && Array.isArray(group.hooks) ? group.hooks : [];
489
+ if (hookHeaders.length !== groupHooks.length) {
490
+ const keptInlineHooks = [];
491
+ let inlineNeedsEdit = false;
492
+ for (const hook of groupHooks) {
493
+ const command = isPlainObject(hook) && hook.type === "command" && typeof hook.command === "string"
494
+ ? hook.command
495
+ : undefined;
496
+ if (!command) {
497
+ keptInlineHooks.push(hook);
498
+ continue;
499
+ }
500
+ const current = command === expected;
501
+ const drain = isDrainHookValue(hook, expected, legacyCommand);
502
+ if (!drain || (current && !keptCurrent)) {
503
+ if (current)
504
+ keptCurrent = true;
505
+ keptInlineHooks.push(hook);
506
+ }
507
+ else {
508
+ inlineNeedsEdit = true;
509
+ }
510
+ }
511
+ if (!inlineNeedsEdit)
512
+ continue;
513
+ if (hookHeaders.length === 0) {
514
+ const hooksLine = lines.findIndex((line, index) => index > start && index < end && /^\s*hooks\s*=/.test(line));
515
+ if (hooksLine === -1) {
516
+ throw new LanesError("config_toml_migration_failed", `Could not safely find inline ${event} hooks in ${configPath}; refusing to add a second drain`, { path: configPath, event, groupIndex });
517
+ }
518
+ changed = true;
519
+ if (keptInlineHooks.length === 0) {
520
+ spans.push({ start, end });
521
+ }
522
+ else {
523
+ const indent = /^\s*/.exec(lines[hooksLine])?.[0] ?? "";
524
+ lines[hooksLine] = `${indent}hooks = ${renderTomlInline(keptInlineHooks)}`;
525
+ }
526
+ continue;
527
+ }
528
+ throw new LanesError("config_toml_migration_failed", `Could not safely map ${event} hook entries in ${configPath}; refusing to add a second drain`, { path: configPath, event, groupIndex, hooks: groupHooks.length, headers: hookHeaders.length });
529
+ }
530
+ const removeHooks = [];
531
+ for (let hookIndex = 0; hookIndex < hookHeaders.length; hookIndex += 1) {
532
+ const hookStart = hookHeaders[hookIndex];
533
+ let hookEnd = hookStart + 1;
534
+ for (; hookEnd < end; hookEnd += 1) {
535
+ const header = tomlHeader(lines[hookEnd]);
536
+ if (header?.array && header.path === `hooks.${event}.hooks`)
537
+ break;
538
+ if (header && !header.path.startsWith(`hooks.${event}.hooks.`))
539
+ break;
540
+ }
541
+ const hook = groupHooks[hookIndex];
542
+ const command = isPlainObject(hook) && hook.type === "command" && typeof hook.command === "string"
543
+ ? hook.command
544
+ : undefined;
545
+ if (!command)
546
+ continue;
547
+ const current = command === expected;
548
+ const drain = drainCommandsMatch(command, expected) || command === legacyCommand;
549
+ if (!drain || (current && !keptCurrent)) {
550
+ if (current)
551
+ keptCurrent = true;
552
+ continue;
553
+ }
554
+ removeHooks.push({ start: hookStart, end: hookEnd });
555
+ }
556
+ if (removeHooks.length === 0)
557
+ continue;
558
+ changed = true;
559
+ if (removeHooks.length === hookHeaders.length) {
560
+ spans.push({ start, end });
561
+ }
562
+ else {
563
+ spans.push(...removeHooks);
564
+ }
565
+ }
566
+ }
567
+ if (!changed)
568
+ return { text, changed: false };
569
+ for (const span of spans.sort((a, b) => b.start - a.start)) {
570
+ lines.splice(span.start, span.end - span.start);
571
+ }
572
+ const cleaned = lines.join("\n");
573
+ return { text: cleaned, changed: true };
574
+ }
408
575
  /**
409
576
  * Set `hooks = true` inside `[features]` by editing lines, so the operator's
410
577
  * comments and layout survive. A `[features]` header that exists gets the
@@ -442,12 +609,21 @@ export function mergeCodexConfigHook(existingText, command, configPath, legacyCo
442
609
  const original = existingText ?? "";
443
610
  const parsed = parseTomlOrThrow(original, configPath, "before merge");
444
611
  const migration = removeLegacyCodexHook(original, parsed, legacyCommand, configPath);
445
- const textAfterMigration = migration.text;
612
+ const parsedAfterMigration = parseTomlOrThrow(migration.text, configPath, "after legacy hook removal");
613
+ const drainCleanup = removeStaleCodexDrainHooks(migration.text, parsedAfterMigration, command, legacyCommand, configPath);
614
+ const changedByCleanup = migration.changed || drainCleanup.changed;
615
+ const textAfterMigration = drainCleanup.text;
446
616
  const current = parseTomlOrThrow(textAfterMigration, configPath, "after legacy hook removal");
447
617
  const hasPreToolCommand = tomlHasCommand(current, command);
448
618
  const hasCommand = tomlHasDrainCommand(current, command);
449
619
  const featureOn = tomlHooksFeatureEnabled(current);
450
- if (hasCommand && featureOn && !migration.changed) {
620
+ if (hasCommand && featureOn && !changedByCleanup &&
621
+ ["PreToolUse", "PostToolUse", "Stop"].every((event) => {
622
+ const groups = isPlainObject(current.hooks) && Array.isArray(current.hooks[event])
623
+ ? current.hooks[event]
624
+ : [];
625
+ return groupHasExactlyOneCommand(groups, command);
626
+ })) {
451
627
  return { text: original, changed: false };
452
628
  }
453
629
  let text = textAfterMigration;
@@ -509,7 +685,7 @@ export function probeDrainHookRegistration(provider, providerHome) {
509
685
  const toml = parseToml(text);
510
686
  return {
511
687
  ...base,
512
- registered: isPlainObject(toml) && tomlHasDrainCommand(toml, command) && tomlHooksFeatureEnabled(toml),
688
+ registered: isPlainObject(toml) && tomlHasDrainCommand(toml, command, true) && tomlHooksFeatureEnabled(toml),
513
689
  };
514
690
  }
515
691
  const json = JSON.parse(text);
@@ -517,7 +693,7 @@ export function probeDrainHookRegistration(provider, providerHome) {
517
693
  return {
518
694
  ...base,
519
695
  registered: hooks !== null &&
520
- (parsed === "grok" ? ["PreToolUse"] : ["PreToolUse", "PostToolUse", "Stop"]).every((event) => groupsContainDrainCommand(hooks[event], command)),
696
+ (parsed === "grok" ? ["PreToolUse"] : ["PreToolUse", "PostToolUse", "Stop"]).every((event) => groupsContainDrainCommand(hooks[event], command, true)),
521
697
  };
522
698
  }
523
699
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.82",
3
+ "version": "5.345.83",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {