@indigoai-us/hq-cli 5.109.12 → 5.109.13

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,27 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.109.13] — 2026-09-13
6
+
7
+ ### Fixed
8
+
9
+ - A background reinstall no longer turns into a crash report in the CommonJS twin
10
+ of the shape 5.109.12 fixed for ES modules (HQ-CLI-20). When another program
11
+ reinstalls hq globally while a command is starting, it rewrites hq's files one
12
+ by one over a few seconds. If a bundled CommonJS dependency (here `ajv`, reached
13
+ while `hq core timeout-guard` registers its commands) had just resolved one of
14
+ its own files and that file vanished before Node read it, the low-level `ENOENT …
15
+ open` came from inside Node's CommonJS module loader — a shape the recovery only
16
+ recognized for ES modules, so it was still filed as a crash. It is now recognized
17
+ as "your install was mid-update, not an hq bug": the command waits for the
18
+ reinstall to settle and re-runs once on the healed tree, and the same noise is
19
+ dropped on the routes that bypass the top-level handler. The gate is deliberately
20
+ narrow — it fires only when Node's own loader was reading a module's source (the
21
+ frame directly beneath the file read is a `cjs/loader` frame, never the
22
+ dependency's own code) — so a third-party package whose own startup code reads a
23
+ missing file, or a fault in hq's own shipped files, still reports exactly once
24
+ (Sentry HQ-CLI-20).
25
+
5
26
  ## [5.109.12] — 2026-09-12
6
27
 
7
28
  ### Fixed
package/dist/main.js CHANGED
@@ -565,8 +565,9 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
565
565
  // install left a bundled file unwritten (HQ-CLI-1N, a CJS relative-sibling
566
566
  // MODULE_NOT_FOUND; HQ-CLI-1Q, a CJS BARE-specifier MODULE_NOT_FOUND for a
567
567
  // dependency the requiring third-party package's own manifest declares), or
568
- // a concurrent global install rewrote the running tree so an ESM module
569
- // present at resolve was gone at read (HQ-CLI-1M, an esm-loader ENOENT).
568
+ // a concurrent global install rewrote the running tree so a module present
569
+ // at resolve was gone at read (HQ-CLI-1M, an esm-loader ENOENT; HQ-CLI-20,
570
+ // its CJS loader-read twin).
570
571
  // All carry no hq-cli frames and reached the final else, filing a bare
571
572
  // crash and an unactionable line. An incomplete install is the caller's
572
573
  // machine, the disposition HQ-CLI-Y already established for the qmd CHILD —
@@ -574,11 +575,12 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
574
575
  // environmental family (after the typed qmd carriers and the hq state-write
575
576
  // carrier, before environmentalFsErrorMessage): the signatures are disjoint
576
577
  // — ENVIRONMENTAL_FS_CODES is only ENOSPC/EDQUOT/EROFS (never
577
- // ENOENT/MODULE_NOT_FOUND), no qmd carrier sets requireStack or an
578
- // esm-loader frame, and the classified file must sit under
579
- // <packageRoot>/node_modules — so ordering changes nothing that exists. The
578
+ // ENOENT/MODULE_NOT_FOUND), no qmd carrier sets requireStack or a loader
579
+ // source-read frame (esm/load or cjs/loader), and the classified file must
580
+ // sit under <packageRoot>/node_modules — so ordering changes nothing that
581
+ // exists. The
580
582
  // UNATTRIBUTABLE shapes are deliberately NOT suppressed and are captured
581
- // WITH bounded context on the generic path below: an esm-loader ENOENT
583
+ // WITH bounded context on the generic path below: a loader/resolver ENOENT
582
584
  // whose path did not survive, and a bare miss the requirer does NOT declare
583
585
  // (a possible undeclared/peer dependency, now named in the context).
584
586
  const incompleteInstallMsg = qmdMsg || collectionMsg || terminatedMsg || llmDisabledMsg || moduleMissingMsg || storeMissingMsg || storeUnopenableMsg || queryDocumentMsg || modelDownloadMsg || workdirMissingMsg || stateWriteMsg
@@ -679,9 +681,10 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
679
681
  // A genuinely unclassified fault is still captured exactly once. Two
680
682
  // shapes attach bounded, hq-derived context so the next occurrence
681
683
  // carries the evidence this one lacked: a qmd spawn-level failure the fix
682
- // could not attribute (HQ-CLI-1A), and an esm-loader ENOENT whose path
683
- // did not survive delivery (HQ-CLI-1M the unattributable incomplete-
684
- // install shape). Every other error captures bare, exactly as before.
684
+ // could not attribute (HQ-CLI-1A), and a loader/resolver ENOENT whose path
685
+ // did not survive delivery (HQ-CLI-1M esm-load, HQ-CLI-1S resolve-time,
686
+ // HQ-CLI-20 CJS load-time the unattributable incomplete-install shape).
687
+ // Every other error captures bare, exactly as before.
685
688
  const spawnContext = qmdSpawnFailureCaptureContext(err);
686
689
  const installContext = incompleteInstallCaptureContext(err);
687
690
  if (spawnContext || installContext) {
package/dist/sentry.js CHANGED
@@ -37,7 +37,9 @@ export function epipeAwareBeforeSend(event, hint) {
37
37
  // (HQ-CLI-1N, a CJS relative-sibling MODULE_NOT_FOUND under its bundled
38
38
  // node_modules; HQ-CLI-1Q, a CJS BARE-specifier MODULE_NOT_FOUND the requiring
39
39
  // third-party package's own manifest declares; HQ-CLI-1M, an esm-loader ENOENT
40
- // for a file present at resolve and gone at read). handleTopLevelError already
40
+ // for a file present at resolve and gone at read; HQ-CLI-20, its CJS loader-read
41
+ // twin — a bundled CJS dep gone between resolve and the loader's source read).
42
+ // handleTopLevelError already
41
43
  // prints the reinstall remedy for the top-level route; dropping the event here
42
44
  // suppresses the fatal regardless of route — the unhandled-rejection boundary,
43
45
  // the command-level captureException sites, and bin/hq-auth-refresh —
@@ -50,7 +52,7 @@ export function epipeAwareBeforeSend(event, hint) {
50
52
  if (incompleteInstallMessage(hint?.originalException))
51
53
  return null;
52
54
  // An incomplete-install shape that is NOT suppressed (a bare miss the requirer
53
- // does not declare, or a path-less esm-loader ENOENT) should still arrive
55
+ // does not declare, or a path-less loader/resolver ENOENT) should still arrive
54
56
  // ATTRIBUTABLE on every capture route, not only the top-level boundary:
55
57
  // handleTopLevelError in main.ts is the ONLY caller of
56
58
  // incompleteInstallCaptureContext, so events reaching this hook directly — the
@@ -50,6 +50,8 @@ export type IncompleteInstallEsmDiagnostics = {
50
50
  esmLoaderFrame: boolean;
51
51
  /** The stack carried a module-resolver frame — the HQ-CLI-1S resolve-time route. */
52
52
  resolverFrame: boolean;
53
+ /** The stack carried a CJS loader (source-read) frame — the HQ-CLI-20 route. */
54
+ cjsLoaderFrame: boolean;
53
55
  code: string;
54
56
  };
55
57
  /**
@@ -119,9 +121,10 @@ export type IncompleteInstallDiagnostics = Partial<IncompleteInstallEsmDiagnosti
119
121
  * (HQ-CLI-1N) → { code, relativeSpecifier:true, packageRoot,
120
122
  * packageRootResolved, resolver, requirerScope };
121
123
  * - a loader/resolver ENOENT whose path did not survive delivery (HQ-CLI-1M
122
- * esm-load open, HQ-CLI-1S resolve-time lstat) where neither the exception
123
- * value nor node_system_error carried a `path` → { packageRoot,
124
- * packageJsonExists, nodeModulesExists, esmLoaderFrame, resolverFrame, code };
124
+ * esm-load open, HQ-CLI-1S resolve-time lstat, HQ-CLI-20 CJS load-time open)
125
+ * where neither the exception value nor node_system_error carried a `path` →
126
+ * { packageRoot, packageJsonExists, nodeModulesExists, esmLoaderFrame,
127
+ * resolverFrame, cjsLoaderFrame, code };
125
128
  * - a module-EVALUATION throw that reached the boundary raw (HQ-CLI-1T) →
126
129
  * { evalThrow, errorName, throwSitePackage, packageRoot, packageRootResolved,
127
130
  * resolver }.
@@ -7,7 +7,7 @@
7
7
  // failure inside THIS process rather than a qmd child's captured stderr, which
8
8
  // is the gap HQ-CLI-Y's classifier cannot cover.
9
9
  //
10
- // Three shapes, one cause — a partial/interrupted global install left a file
10
+ // Four shapes, one cause — a partial/interrupted global install left a file
11
11
  // unwritten, or a concurrent global install rewrote the running tree
12
12
  // underneath a command:
13
13
  //
@@ -36,6 +36,14 @@
36
36
  // ERR_MODULE_NOT_FOUND at resolve, never ENOENT at load; an ENOENT at load
37
37
  // proves the file vanished between resolve and read.
38
38
  //
39
+ // HQ-CLI-20 (Sentry 7724280783) — CJS load, in-process. The CJS twin of
40
+ // HQ-CLI-1M: `hq core timeout-guard` registers the full command graph, which
41
+ // reaches a bundled CommonJS dependency (ajv) whose internal require() resolved
42
+ // a sibling from Module._pathCache and then read its source after a concurrent
43
+ // reinstall had vanished it. Node raised `ENOENT` with syscall 'open' from the
44
+ // CJS loader's source read (defaultLoadImpl/loadSource) — a cjs/loader frame but
45
+ // NO esm/load frame, so the esm-loader gate missed it and it filed a bare crash.
46
+ //
39
47
  // All shapes carry no hq-cli frames, reach the boundary's final `else`, and —
40
48
  // before this classifier — filed a bare captureException plus an unactionable
41
49
  // `hq: <fallback>` line. The disposition is the one HQ-CLI-Y already
@@ -59,12 +67,15 @@
59
67
  // optional-dependency miss (not guaranteed installed), and an undeclared or
60
68
  // peer-only miss (`Cannot find module 'mqtt'` from a requirer that does not
61
69
  // declare it) all stay loud — the last two enriched with both package names.
62
- // 3. The ESM shape additionally requires an esm-loader frame — an ordinary
63
- // `fs.readFileSync` ENOENT written by hq's own code stays captured.
70
+ // 3. The loader shapes additionally require a loader SOURCE-READ frame — the ESM
71
+ // shape an `esm/load` frame, the CJS shape (HQ-CLI-20) a cjs/loader frame
72
+ // immediately beneath the node:fs read. An ordinary `fs.readFileSync` ENOENT
73
+ // written by hq's own code, or one escaping a third-party module's own
74
+ // EVALUATION, carries no such frame and stays captured.
64
75
  import * as fs from "fs";
65
76
  import * as path from "path";
66
77
  import { packageRoot, stringDerivedPackageRoot } from "./hq-roots.js";
67
- import { evalThrowShape, packageNameOf } from "./install-tree-torn.js";
78
+ import { evalThrowShape, hasCjsLoaderSourceReadFrame, packageNameOf } from "./install-tree-torn.js";
68
79
  import { boundedDiagnosticValue } from "./package-root-diagnostics.js";
69
80
  /**
70
81
  * The actionable remedy shown to the operator. Input-free — nothing from the
@@ -348,6 +359,25 @@ function isResolveEnoent(err) {
348
359
  record.syscall === "lstat" &&
349
360
  hasResolverFrame(record.stack));
350
361
  }
362
+ /** True when `stack` is a CJS loader SOURCE-READ stack (HQ-CLI-20). */
363
+ function hasCjsLoaderFrame(stack) {
364
+ return typeof stack === "string" && hasCjsLoaderSourceReadFrame(stack);
365
+ }
366
+ /**
367
+ * The CJS loader-read ENOENT SIGNATURE (HQ-CLI-20): `code === 'ENOENT'`,
368
+ * `syscall === 'open'`, and a CJS loader SOURCE-READ stack (the positional gate
369
+ * shared from install-tree-torn.ts) — the CJS twin of {@link isEsmLoaderEnoent},
370
+ * independent of whether a usable `path` survived. The message classifier
371
+ * additionally requires a `path` under node_modules.
372
+ */
373
+ function isCjsLoaderEnoent(err) {
374
+ if (err === null || typeof err !== "object")
375
+ return false;
376
+ const record = err;
377
+ return (record.code === "ENOENT" &&
378
+ record.syscall === "open" &&
379
+ hasCjsLoaderFrame(record.stack));
380
+ }
351
381
  /**
352
382
  * If `err` is an in-process incomplete-install module-load failure — the CJS
353
383
  * relative-sibling shape (HQ-CLI-1N), the ESM vanished-at-read shape (HQ-CLI-1M),
@@ -421,6 +451,17 @@ export function incompleteInstallMessage(err, resolvePackageRoot = safePackageRo
421
451
  hasResolverFrame(record.stack)) {
422
452
  return isUnderNodeModules(record.path, root) ? INCOMPLETE_INSTALL_REMEDY : null;
423
453
  }
454
+ // Shape B'' (CJS load-time, HQ-CLI-20): the CJS twin of Shape B — an ENOENT from
455
+ // the CJS loader's source read (syscall 'open' + a CJS loader SOURCE-READ stack,
456
+ // NO esm/load frame) for a bundled dependency file present at resolve and gone at
457
+ // read. Same disposition as B: a vanished file under the running install's
458
+ // node_modules is a torn install, so print the remedy and skip capture; a miss
459
+ // under the install's own dist/ or assets/ stays captured.
460
+ if (record.syscall === "open" &&
461
+ typeof record.path === "string" &&
462
+ hasCjsLoaderFrame(record.stack)) {
463
+ return isUnderNodeModules(record.path, root) ? INCOMPLETE_INSTALL_REMEDY : null;
464
+ }
424
465
  return null;
425
466
  }
426
467
  /** A readFileSync surface, defaulting to the real fs when the caller injects none. */
@@ -550,9 +591,10 @@ function relativeSpecifierCaptureContext(err, resolvePackageRoot, fileSystem) {
550
591
  * (HQ-CLI-1N) → { code, relativeSpecifier:true, packageRoot,
551
592
  * packageRootResolved, resolver, requirerScope };
552
593
  * - a loader/resolver ENOENT whose path did not survive delivery (HQ-CLI-1M
553
- * esm-load open, HQ-CLI-1S resolve-time lstat) where neither the exception
554
- * value nor node_system_error carried a `path` → { packageRoot,
555
- * packageJsonExists, nodeModulesExists, esmLoaderFrame, resolverFrame, code };
594
+ * esm-load open, HQ-CLI-1S resolve-time lstat, HQ-CLI-20 CJS load-time open)
595
+ * where neither the exception value nor node_system_error carried a `path` →
596
+ * { packageRoot, packageJsonExists, nodeModulesExists, esmLoaderFrame,
597
+ * resolverFrame, cjsLoaderFrame, code };
556
598
  * - a module-EVALUATION throw that reached the boundary raw (HQ-CLI-1T) →
557
599
  * { evalThrow, errorName, throwSitePackage, packageRoot, packageRootResolved,
558
600
  * resolver }.
@@ -570,11 +612,12 @@ export function incompleteInstallCaptureContext(err, resolvePackageRoot = safePa
570
612
  const relative = relativeSpecifierCaptureContext(err, resolvePackageRoot, fileSystem);
571
613
  if (relative)
572
614
  return relative;
573
- // (C) HQ-CLI-1M / HQ-CLI-1S — the path-less loader/resolver ENOENT, made
574
- // attributable. An ENOENT whose `path` survived and sits under node_modules is
575
- // classified and printed above, never captured; only the unattributable shape
576
- // (no usable path, or a path outside node_modules) reaches here.
577
- if (isEsmLoaderEnoent(err) || isResolveEnoent(err)) {
615
+ // (C) HQ-CLI-1M / HQ-CLI-1S / HQ-CLI-20 — the path-less loader/resolver ENOENT
616
+ // (esm-load open, resolve-time lstat, or CJS load-time open), made attributable.
617
+ // An ENOENT whose `path` survived and sits under node_modules is classified and
618
+ // printed above, never captured; only the unattributable shape (no usable path,
619
+ // or a path outside node_modules) reaches here.
620
+ if (isEsmLoaderEnoent(err) || isResolveEnoent(err) || isCjsLoaderEnoent(err)) {
578
621
  if (incompleteInstallMessage(err, resolvePackageRoot, readFileFrom(fileSystem)) === null) {
579
622
  const record = err;
580
623
  const code = typeof record.code === "string" ? record.code : "";
@@ -593,9 +636,11 @@ export function incompleteInstallCaptureContext(err, resolvePackageRoot = safePa
593
636
  packageRootResolved: root !== null,
594
637
  resolver: resolution.source,
595
638
  // Which route raised it — the esm-loader (source-read) frame for 1M, the
596
- // resolver frame for 1S; either or both are recorded from the stack.
639
+ // resolver frame for 1S, the CJS loader source-read frame for HQ-CLI-20;
640
+ // any are recorded from the stack.
597
641
  esmLoaderFrame: hasEsmLoaderFrame(record.stack),
598
642
  resolverFrame: hasResolverFrame(record.stack),
643
+ cjsLoaderFrame: hasCjsLoaderFrame(record.stack),
599
644
  code: boundedDiagnosticValue(code, CODE_DIAGNOSTIC_BYTES),
600
645
  };
601
646
  // Report the existence booleans ONLY when a root was resolved. Reported for
@@ -32,9 +32,10 @@
32
32
  /**
33
33
  * The loader-error `code`s recovery classifies. The first two mean "a module
34
34
  * could not be resolved"; `ENOENT` is the loader/resolver dialect where a module
35
- * was present at one step and gone at the next (HQ-CLI-1M esm-load open, HQ-CLI-1S
36
- * resolve-time lstat); `EVAL_THROW` is the synthetic code for a module-EVALUATION
37
- * throw that carries NO real `code` (HQ-CLI-1T) — see {@link ModuleErrorDialect}.
35
+ * was present at one step and gone at the next (HQ-CLI-1M esm-load open, HQ-CLI-20
36
+ * cjs-load open, HQ-CLI-1S resolve-time lstat); `EVAL_THROW` is the synthetic code
37
+ * for a module-EVALUATION throw that carries NO real `code` (HQ-CLI-1T) — see
38
+ * {@link ModuleErrorDialect}.
38
39
  */
39
40
  export type ModuleNotFoundCode = "ERR_MODULE_NOT_FOUND" | "MODULE_NOT_FOUND" | "ENOENT" | "EVAL_THROW";
40
41
  /**
@@ -56,6 +57,13 @@ export type ModuleNotFoundCode = "ERR_MODULE_NOT_FOUND" | "MODULE_NOT_FOUND" | "
56
57
  * - `esm-enoent`: ESM loader ENOENT (HQ-CLI-1M) — a module present at RESOLVE
57
58
  * and gone at READ, so getSourceSync/openSync raises ENOENT
58
59
  * (not ERR_MODULE_NOT_FOUND). `err.path` is the vanished file.
60
+ * - `cjs-enoent`: CJS loader ENOENT (HQ-CLI-20) — the CJS twin of esm-enoent: a
61
+ * require() whose target was present at RESOLVE (its filename
62
+ * held in Module._pathCache) and gone at READ, so the CJS
63
+ * loader's source read (defaultLoadImpl/loadSource, or the older
64
+ * Module._extensions..js handler) raises ENOENT with syscall
65
+ * 'open' and a cjs/loader frame but NO esm/load frame. `err.path`
66
+ * is the vanished file.
59
67
  * - `resolve-enoent`: ESM/CJS RESOLVE-time ENOENT (HQ-CLI-1S) — a path component
60
68
  * stat'ed OK, then vanished before realpathSync's lstat, so the
61
69
  * resolver (finalizeResolution / Module._findPath→toRealPath)
@@ -69,7 +77,7 @@ export type ModuleNotFoundCode = "ERR_MODULE_NOT_FOUND" | "MODULE_NOT_FOUND" | "
69
77
  * - `unknown`: a module-not-found whose message did not parse; recovery
70
78
  * still waits on the lock / retired-dir / quiet signals.
71
79
  */
72
- export type ModuleErrorDialect = "esm-path" | "esm-package" | "cjs-path" | "cjs-package" | "cjs-relative" | "esm-enoent" | "resolve-enoent" | "eval-throw" | "unknown";
80
+ export type ModuleErrorDialect = "esm-path" | "esm-package" | "cjs-path" | "cjs-package" | "cjs-relative" | "esm-enoent" | "cjs-enoent" | "resolve-enoent" | "eval-throw" | "unknown";
73
81
  /**
74
82
  * The missing thing, re-resolvable by the readiness probe:
75
83
  * - `path`: an absolute filesystem path (a `.js` file, or a package dir).
@@ -115,6 +123,7 @@ export interface ClassifiedModuleError {
115
123
  /** The thrown error's `name` for the eval-throw dialect (HQ-CLI-1T), else absent. */
116
124
  errorName?: string;
117
125
  }
126
+ export declare function hasCjsLoaderSourceReadFrame(stack: string): boolean;
118
127
  /**
119
128
  * Reduce a bare specifier to its PACKAGE name: `@scope/name/sub` → `@scope/name`,
120
129
  * `name/sub` → `name`, `name` → `name`. This is the unit the readiness probe
@@ -82,6 +82,51 @@ const ESM_EVAL_FRAME = /node:internal[\\/]modules[\\/]esm[\\/]module_job\b/;
82
82
  function hasModuleEvalFrame(stack) {
83
83
  return CJS_EVAL_FRAME.test(stack) || ESM_EVAL_FRAME.test(stack);
84
84
  }
85
+ /**
86
+ * The Node CJS loader's SOURCE-READ position (HQ-CLI-20) — the CJS twin of
87
+ * {@link ESM_LOADER_FRAME}. A CJS require whose target was present at RESOLVE (its
88
+ * filename cached in Module._pathCache) and gone at READ fails inside the loader's
89
+ * source read: `readFileSync (node:fs)` called by `defaultLoadImpl`/`loadSource`
90
+ * (Node ≥22) or, on older Node, the `Module._extensions..js` extension handler —
91
+ * all frames in `node:internal/modules/cjs/loader`, and (unlike HQ-CLI-1M) with NO
92
+ * `esm/load` frame.
93
+ *
94
+ * The discriminator is POSITIONAL: the innermost `node:fs` read frame's
95
+ * IMMEDIATELY-following (caller) frame must be a cjs/loader frame that is NOT a
96
+ * module-EVALUATION frame (Module._compile / wrapSafe). That is what proves the
97
+ * LOADER was reading the module's source. An ordinary `fs.readFileSync`/`openSync`
98
+ * ENOENT that merely ESCAPES a third-party module's own EVALUATION has the
99
+ * third-party FILE directly beneath `node:fs`, not a cjs/loader frame, so it stays
100
+ * captured (both shapes reproduced frame-for-frame on Node v22.23.1). The
101
+ * positional rule is the PRIMARY discriminator; the source-read helper NAMES are
102
+ * an accepted closed set, not the sole requirement, so a renamed helper still
103
+ * classifies.
104
+ *
105
+ * The whole-stack presence of Module._compile is deliberately NOT a rejection
106
+ * signal: under a raised Error.stackTraceLimit the torn shape ALSO carries the
107
+ * requiring module's own Module._compile frame — always at depth 11, past the
108
+ * default limit of 10 — so only the frame immediately beneath the node:fs read is
109
+ * consulted. Exported so incomplete-install-error.ts (which already imports from
110
+ * this module) shares the exact gate rather than duplicating the positional logic,
111
+ * mirroring how {@link evalThrowShape} and {@link packageNameOf} are shared.
112
+ */
113
+ const NODE_FS_READ_FRAME = /^at (?:[\w$.]+\.)?(?:readFileSync|openSync) \(node:fs\b/;
114
+ const CJS_LOADER_FRAME = /node:internal[\\/]modules[\\/]cjs[\\/]loader\b/;
115
+ export function hasCjsLoaderSourceReadFrame(stack) {
116
+ const frames = stack
117
+ .split("\n")
118
+ .map((line) => line.trim())
119
+ .filter((line) => line.startsWith("at "));
120
+ for (let i = 0; i < frames.length; i++) {
121
+ if (!NODE_FS_READ_FRAME.test(frames[i]))
122
+ continue;
123
+ const caller = frames[i + 1];
124
+ if (caller === undefined)
125
+ return false;
126
+ return CJS_LOADER_FRAME.test(caller) && !CJS_EVAL_FRAME.test(caller);
127
+ }
128
+ return false;
129
+ }
85
130
  /**
86
131
  * A stack LOCATION token — the absolute filesystem path in either an
87
132
  * `at <fn> (<abs>:L:C)` frame or a bare `<abs>:L` header line (the source
@@ -214,6 +259,31 @@ export function classifyModuleNotFound(err) {
214
259
  target: { kind: "path", path: record.path },
215
260
  };
216
261
  }
262
+ // cjs-loader source-read ENOENT (HQ-CLI-20): the CJS twin of esm-enoent — a
263
+ // require() whose target was present at RESOLVE (its filename held in
264
+ // Module._pathCache) and gone at READ, so the CJS loader's source read raises
265
+ // ENOENT with syscall 'open' and a cjs/loader frame but NO esm/load frame
266
+ // (which is why arm (0) missed it and classifyModuleNotFound returned null,
267
+ // filing the bare HQ-CLI-20 capture). Gated on the FULL conjunction — code
268
+ // ENOENT AND syscall 'open' AND a string path AND the POSITIONAL CJS
269
+ // source-read gate — so a third-party module's own top-level fs.readFileSync
270
+ // ENOENT (module EVALUATION, its own frame directly beneath node:fs) stays null
271
+ // and keeps its capture. Ordered AFTER the esm-enoent arm so HQ-CLI-1M keeps
272
+ // its byte-for-byte classification. Target 'path': the vanished file is a
273
+ // regular .js/.json file Node's CJS LOAD_AS_FILE step re-resolves, so the probe
274
+ // and settle wait reuse the same machinery as esm-enoent, unchanged.
275
+ if (record.syscall === "open" &&
276
+ typeof record.path === "string" &&
277
+ typeof record.stack === "string" &&
278
+ hasCjsLoaderSourceReadFrame(record.stack)) {
279
+ return {
280
+ code,
281
+ dialect: "cjs-enoent",
282
+ specifier: record.path,
283
+ importer: "",
284
+ target: { kind: "path", path: record.path },
285
+ };
286
+ }
217
287
  // resolve-time ENOENT (HQ-CLI-1S): Node's resolver stat'ed a path OK, then
218
288
  // realpathSync lstat'ed a component that a concurrent reinstall renamed away
219
289
  // between the two, throwing ENOENT with syscall `lstat` from a RESOLVER frame
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.109.12",
3
+ "version": "5.109.13",
4
4
  "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {