@indigoai-us/hq-cli 5.103.27 → 5.103.29
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 +4 -0
- package/dist/commands/integrations-core.d.ts +15 -3
- package/dist/commands/integrations-core.js +25 -3
- package/dist/commands/integrations-manage.js +4 -1
- package/dist/commands/integrations.js +2 -1
- package/dist/lib/doctor/checks/integrations.d.ts +8 -23
- package/dist/lib/doctor/checks/integrations.js +38 -166
- package/dist/lib/integrations/health.d.ts +78 -0
- package/dist/lib/integrations/health.js +255 -0
- package/dist/lib/integrations/provider-slug.d.ts +10 -0
- package/dist/lib/integrations/provider-slug.js +12 -0
- package/dist/lib/search-index/index.d.ts +22 -0
- package/dist/lib/search-index/index.js +75 -19
- package/dist/main.js +24 -2
- package/dist/utils/qmd-module-missing-error.d.ts +41 -0
- package/dist/utils/qmd-module-missing-error.js +130 -0
- package/dist/utils/sentry-fingerprint.js +96 -3
- package/package.json +2 -2
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// src/utils/qmd-module-missing-error.ts
|
|
2
|
+
//
|
|
3
|
+
// Classify a qmd failure caused by a MISSING JAVASCRIPT MODULE in qmd's own
|
|
4
|
+
// bundled dependency tree — Node could not load a file qmd needs — as the
|
|
5
|
+
// caller's INCOMPLETE INSTALL, not an hq-cli code defect. hq-cli ships qmd and
|
|
6
|
+
// its dependencies, so a `Cannot find module …` from the qmd child means a
|
|
7
|
+
// partial or interrupted global install (`npm i -g` / `pnpm add -g`) left some
|
|
8
|
+
// files unwritten. That is the caller's install shape, not a bug HQ can fix in
|
|
9
|
+
// code, so the CLI surfaces an actionable reinstall remedy and SKIPS Sentry
|
|
10
|
+
// capture. Sibling of qmd-native-binding-error.ts (HQ-CLI-J, unbuilt
|
|
11
|
+
// better-sqlite3), qmd-collection-missing-error.ts (HQ-CLI-S),
|
|
12
|
+
// qmd-llm-disabled-error.ts (HQ-CLI 7688850003) and qmd-terminated-error.ts
|
|
13
|
+
// (HQ-CLI 7677702704): a failure that is NOT an hq-cli defect is printed with an
|
|
14
|
+
// actionable message and never filed as a crash.
|
|
15
|
+
//
|
|
16
|
+
// HQ-CLI-Y (Sentry 7690356976): a qmd child died with `Cannot find module
|
|
17
|
+
// './stringifyComment.js'`, its `Require stack:` rooted at
|
|
18
|
+
// /usr/lib/node_modules/@indigoai-us/hq-cli/node_modules/yaml/dist/stringify/… .
|
|
19
|
+
// Two compounding defects filed it as a high-priority crash AND minted a NEW
|
|
20
|
+
// permanent issue per child failure: finishRunQmd's generic message
|
|
21
|
+
// interpolated the caller's whole argv (query, -c value, absolute paths) AND
|
|
22
|
+
// embedded the child's RAW multi-line stderr, whose ` at <fn> (<file>:<line>)`
|
|
23
|
+
// lines the Sentry Node SDK then parsed as REAL frames of the hq-cli process —
|
|
24
|
+
// so grouping rode foreign frames and every distinct child failure split into
|
|
25
|
+
// its own issue. Bounding the message (search-index/index.ts) fixes the
|
|
26
|
+
// cardinality; this classifier closes the boundary so the broken-install
|
|
27
|
+
// condition reaches the operator as an actionable reinstall, not an unfixable
|
|
28
|
+
// crash.
|
|
29
|
+
//
|
|
30
|
+
// The gate is deliberately narrow on TWO axes, read ONLY from qmd's OWN captured
|
|
31
|
+
// streams (never the synthesized message — even now that it names the subcommand
|
|
32
|
+
// only, the message must never be the classification substrate), so a user
|
|
33
|
+
// searching for the phrase "Cannot find module" can never trip it:
|
|
34
|
+
// 1. RESOLUTION TOKEN: the module-resolution failure itself — `Cannot find
|
|
35
|
+
// module`, or the CJS/ESM `MODULE_NOT_FOUND` / `ERR_MODULE_NOT_FOUND` code.
|
|
36
|
+
// 2. CORROBORATOR: a Node module-loader artifact a bare query cannot
|
|
37
|
+
// synthesize — a `Require stack:` header, a `node_modules/` path, or the
|
|
38
|
+
// ESM `imported from` clause. Either axis alone would be too loose.
|
|
39
|
+
// The better-sqlite3 native-binding failure (HQ-CLI-J) carries neither
|
|
40
|
+
// resolution token, so it does not match here and keeps its own narrower remedy
|
|
41
|
+
// — and the top-level handler checks that classifier FIRST regardless of this
|
|
42
|
+
// one, so the more-actionable approve-builds remedy always wins when both could.
|
|
43
|
+
/** The module-resolution failure token. `MODULE_NOT_FOUND` covers the ESM
|
|
44
|
+
* `ERR_MODULE_NOT_FOUND` code as a substring; `Cannot find module` is the CJS
|
|
45
|
+
* loader's message text. */
|
|
46
|
+
const MODULE_RESOLUTION_TOKEN = /cannot find module|MODULE_NOT_FOUND/i;
|
|
47
|
+
/** A Node module-loader artifact a user's free-text query cannot synthesize. */
|
|
48
|
+
const MODULE_LOADER_ARTIFACT = /Require stack:|node_modules[\\/]|imported from/i;
|
|
49
|
+
/** qmd subcommands whose collection/query is chosen by the CALLER (read surface). */
|
|
50
|
+
const CALLER_READS = new Set(["search", "vsearch", "query", "get"]);
|
|
51
|
+
/**
|
|
52
|
+
* The actionable remedy shown to the operator. Input-free — nothing is
|
|
53
|
+
* interpolated from the error, the argv, or qmd's output — so there is no
|
|
54
|
+
* injection surface, matching the bounded-fingerprint discipline of its
|
|
55
|
+
* siblings. Names the concrete reinstall for both a global npm and a global
|
|
56
|
+
* pnpm install.
|
|
57
|
+
*
|
|
58
|
+
* Covers BOTH qmd origins deliberately, because they cannot be told apart at
|
|
59
|
+
* runtime: HQ_QMD_BIN is honoured verbatim, and it is also the seam a dev build
|
|
60
|
+
* (and this repo's e2e harness) uses to inject the bundled qmd, so a truthy
|
|
61
|
+
* HQ_QMD_BIN is NOT a reliable "external qmd" signal. A missing module is not an
|
|
62
|
+
* hq-cli code defect on either path, so the remedy — not the classification —
|
|
63
|
+
* carries the origin nuance: it names the bundled reinstall first, then the
|
|
64
|
+
* HQ_QMD_BIN case so an operator running their own qmd is pointed at that
|
|
65
|
+
* install rather than only at hq.
|
|
66
|
+
*/
|
|
67
|
+
export const QMD_MODULE_MISSING_REMEDY = "hq's local search index can't start: one of its bundled modules is missing, " +
|
|
68
|
+
"which means the hq install tree is incomplete — a partial or interrupted " +
|
|
69
|
+
"install can leave some dependencies unwritten. Reinstall hq and run the " +
|
|
70
|
+
"command again: for a global install run `npm i -g @indigoai-us/hq-cli` (or " +
|
|
71
|
+
"the pnpm equivalent, `pnpm add -g @indigoai-us/hq-cli`). If you point " +
|
|
72
|
+
"HQ_QMD_BIN at your own qmd, that install is missing the module instead — " +
|
|
73
|
+
"reinstall its dependencies, or unset HQ_QMD_BIN to use the bundled copy.";
|
|
74
|
+
/**
|
|
75
|
+
* The qmd process's OWN captured diagnostic — stderr then stdout. Unlike
|
|
76
|
+
* qmd-native-binding-error.ts this NEVER falls back to the synthesized
|
|
77
|
+
* `message`: the message can carry the caller's query, so reading it would let
|
|
78
|
+
* free text trip the classifier. A value with no captured streams yields "". A
|
|
79
|
+
* bare string is treated as captured text directly (test convenience).
|
|
80
|
+
*/
|
|
81
|
+
function capturedStreams(err) {
|
|
82
|
+
if (typeof err === "string")
|
|
83
|
+
return err;
|
|
84
|
+
if (err === null || typeof err !== "object")
|
|
85
|
+
return "";
|
|
86
|
+
const record = err;
|
|
87
|
+
const stderr = typeof record.stderr === "string" ? record.stderr : "";
|
|
88
|
+
const stdout = typeof record.stdout === "string" ? record.stdout : "";
|
|
89
|
+
return stderr || stdout ? `${stderr}\n${stdout}` : "";
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* True when `err` carries, in qmd's OWN captured streams, a module-resolution
|
|
93
|
+
* failure corroborated by a Node module-loader artifact — i.e. qmd's dependency
|
|
94
|
+
* tree is incomplete on this machine. finishRunQmd calls this on the raw streams
|
|
95
|
+
* to type the condition {@link import('../lib/search-index/index.js').QmdModuleMissingError};
|
|
96
|
+
* a true result there means the caller should reinstall.
|
|
97
|
+
*/
|
|
98
|
+
export function isQmdModuleMissingError(err) {
|
|
99
|
+
const text = capturedStreams(err);
|
|
100
|
+
if (!text)
|
|
101
|
+
return false;
|
|
102
|
+
return MODULE_RESOLUTION_TOKEN.test(text) && MODULE_LOADER_ARTIFACT.test(text);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* If `err` is a caller-driven qmd module-missing failure (a QmdModuleMissingError
|
|
106
|
+
* from `hq search` or its `vsearch`/`query`/`get` siblings), return the
|
|
107
|
+
* actionable, input-free reinstall remedy; otherwise return `null`. Mirrors
|
|
108
|
+
* qmdNativeBindingErrorMessage / qmdMissingCollectionMessage so the top-level
|
|
109
|
+
* handler branches the same way: a non-null result means print-and-skip-Sentry,
|
|
110
|
+
* null means "handle as usual (capture to Sentry)".
|
|
111
|
+
*
|
|
112
|
+
* Gated on the TYPED CLASS plus a caller-reads invocation allow-list: a
|
|
113
|
+
* module-missing raised by hq's OWN reconciliation (`collection list`, `context
|
|
114
|
+
* add`, …) points at a packaging fault hq itself could be responsible for, so it
|
|
115
|
+
* stays a captured internal error rather than being silenced as the user's
|
|
116
|
+
* install.
|
|
117
|
+
*/
|
|
118
|
+
export function qmdModuleMissingMessage(err) {
|
|
119
|
+
if (err === null || typeof err !== "object")
|
|
120
|
+
return null;
|
|
121
|
+
const record = err;
|
|
122
|
+
if (record.name !== "QmdModuleMissingError")
|
|
123
|
+
return null;
|
|
124
|
+
const args = Array.isArray(record.args) ? record.args : [];
|
|
125
|
+
const subcommand = args[0];
|
|
126
|
+
if (typeof subcommand !== "string" || !CALLER_READS.has(subcommand))
|
|
127
|
+
return null;
|
|
128
|
+
return QMD_MODULE_MISSING_REMEDY;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=qmd-module-missing-error.js.map
|
|
@@ -65,13 +65,84 @@ const DEFAULT_GROUPING = "{{ default }}";
|
|
|
65
65
|
* request identifier spliced into the name — could otherwise mint an unbounded
|
|
66
66
|
* number of groups DESPITE the bounded discriminator. The finite-cardinality
|
|
67
67
|
* promise must hold for the WHOLE key, so any name outside this set collapses to
|
|
68
|
-
* FALLBACK_ERROR_NAME.
|
|
69
|
-
*
|
|
68
|
+
* FALLBACK_ERROR_NAME. IntegrationsCliError carries `rpcCode`/`status`; the Qmd*
|
|
69
|
+
* errors carry a process EXIT CODE in `status` and are fingerprinted on the
|
|
70
|
+
* dedicated qmd branch below (allow-listed here so their names appear verbatim
|
|
71
|
+
* rather than collapsing). Add a name here only when a new bounded carrier
|
|
70
72
|
* genuinely warrants its own family of groups.
|
|
71
73
|
*/
|
|
72
|
-
const KNOWN_ERROR_NAMES = new Set([
|
|
74
|
+
const KNOWN_ERROR_NAMES = new Set([
|
|
75
|
+
"IntegrationsCliError",
|
|
76
|
+
// qmd process failures — all extend QmdExitError except QmdBinaryMissingError,
|
|
77
|
+
// which shares the `Qmd` prefix the qmd branch keys on (HQ-CLI-Y, 7690356976).
|
|
78
|
+
"QmdExitError",
|
|
79
|
+
"QmdBinaryMissingError",
|
|
80
|
+
"QmdCollectionMissingError",
|
|
81
|
+
"QmdCollectionExistsError",
|
|
82
|
+
"QmdTerminatedError",
|
|
83
|
+
"QmdLlmDisabledError",
|
|
84
|
+
"QmdModuleMissingError",
|
|
85
|
+
]);
|
|
73
86
|
/** Fixed bucket for any error name outside the closed allowlist. */
|
|
74
87
|
const FALLBACK_ERROR_NAME = "other";
|
|
88
|
+
/**
|
|
89
|
+
* The CLOSED set of qmd subcommands worth their own group — the surfaces hq
|
|
90
|
+
* drives (caller reads plus reconciliation). Anything else, including a missing
|
|
91
|
+
* or non-string args[0], collapses to `qmd:other`, so the subcommand axis is
|
|
92
|
+
* finite no matter what argv qmd was handed (a query value can never reach it).
|
|
93
|
+
*/
|
|
94
|
+
const KNOWN_QMD_SUBCOMMANDS = new Set([
|
|
95
|
+
"search", "vsearch", "query", "get",
|
|
96
|
+
"collection", "context", "embed", "status", "update", "cleanup",
|
|
97
|
+
]);
|
|
98
|
+
/**
|
|
99
|
+
* The small set of qmd process EXIT CODES worth their own group. qmd exits 1 for
|
|
100
|
+
* a general error; any other numeric code collapses to `exit:other`, so the
|
|
101
|
+
* exit-code axis stays finite. A signalled child (`status` null) never reaches
|
|
102
|
+
* this set — it groups by its signal instead (see {@link qmdDispositionToken}).
|
|
103
|
+
*/
|
|
104
|
+
const KNOWN_QMD_EXIT_CODES = new Set([1, 2]);
|
|
105
|
+
/**
|
|
106
|
+
* qmd child TERMINATION SIGNALS worth their own group. A signalled qmd child
|
|
107
|
+
* (QmdTerminatedError: `status` null, `signal` set) carries no exit code, so
|
|
108
|
+
* without a signal axis every native crash would collapse into one `exit:other`
|
|
109
|
+
* bucket — merging the distinct failures (SIGSEGV vs SIGABRT vs SIGBUS) the
|
|
110
|
+
* termination policy deliberately keeps on separate reporting paths. Bounded:
|
|
111
|
+
* any signal outside this closed set collapses to `signal:other`, so the axis
|
|
112
|
+
* stays finite no matter what killed the child.
|
|
113
|
+
*/
|
|
114
|
+
const KNOWN_QMD_SIGNALS = new Set([
|
|
115
|
+
"SIGSEGV", "SIGABRT", "SIGBUS", "SIGILL", "SIGFPE",
|
|
116
|
+
"SIGKILL", "SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT",
|
|
117
|
+
]);
|
|
118
|
+
/** The bounded qmd subcommand token (`qmd:<sub>`), read only from args[0]. */
|
|
119
|
+
function qmdSubcommandToken(args) {
|
|
120
|
+
const first = Array.isArray(args) ? args[0] : undefined;
|
|
121
|
+
return typeof first === "string" && KNOWN_QMD_SUBCOMMANDS.has(first)
|
|
122
|
+
? `qmd:${first}`
|
|
123
|
+
: "qmd:other";
|
|
124
|
+
}
|
|
125
|
+
/** The bounded qmd exit-code token (`exit:<code>`), read only from `status`. */
|
|
126
|
+
function qmdExitToken(status) {
|
|
127
|
+
return typeof status === "number" &&
|
|
128
|
+
Number.isInteger(status) &&
|
|
129
|
+
KNOWN_QMD_EXIT_CODES.has(status)
|
|
130
|
+
? `exit:${status}`
|
|
131
|
+
: "exit:other";
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The bounded qmd DISPOSITION token — the fingerprint's fourth component. A
|
|
135
|
+
* signalled child (a non-empty `signal` string, `status` null) groups by
|
|
136
|
+
* `signal:<name>` from the closed allow-list so distinct native crashes stay in
|
|
137
|
+
* distinct groups; an ordinary exit groups by `exit:<code>`. Both inputs are
|
|
138
|
+
* bounded, so the axis stays finite regardless of what qmd reported.
|
|
139
|
+
*/
|
|
140
|
+
function qmdDispositionToken(status, signal) {
|
|
141
|
+
if (typeof signal === "string" && signal.length > 0) {
|
|
142
|
+
return KNOWN_QMD_SIGNALS.has(signal) ? `signal:${signal}` : "signal:other";
|
|
143
|
+
}
|
|
144
|
+
return qmdExitToken(status);
|
|
145
|
+
}
|
|
75
146
|
/**
|
|
76
147
|
* Return a bounded `event.fingerprint` array for `err`, or `null` when `err`
|
|
77
148
|
* carries no discriminator from the closed allowlist (in which case the caller
|
|
@@ -98,6 +169,28 @@ export function sentryFingerprintFor(err) {
|
|
|
98
169
|
const rawName = typeof record.name === "string" && record.name.length > 0 ? record.name : null;
|
|
99
170
|
if (rawName === null)
|
|
100
171
|
return null;
|
|
172
|
+
// qmd process failures carry a PROCESS EXIT CODE in `status` (never an HTTP
|
|
173
|
+
// status) plus a subcommand in args[0]. Fingerprint them on a dedicated,
|
|
174
|
+
// fully-bounded key BEFORE the HTTP-status branch so an exit code is never read
|
|
175
|
+
// as an HTTP status, and so grouping is one group per (subcommand, disposition)
|
|
176
|
+
// instead of one per query — the cardinality blow-up that minted a new
|
|
177
|
+
// permanent issue per qmd child failure (HQ-CLI-Y, Sentry 7690356976). The
|
|
178
|
+
// fourth component is the DISPOSITION: an ordinary exit groups by `exit:<code>`,
|
|
179
|
+
// while a signalled child (QmdTerminatedError: status null, signal set) groups
|
|
180
|
+
// by `signal:<name>` so distinct native crashes (SIGSEGV/SIGABRT/SIGBUS) stay
|
|
181
|
+
// in distinct groups rather than collapsing into one `exit:other`. Keyed on the
|
|
182
|
+
// `Qmd` name prefix; the name is still bounded to the closed allowlist, so an
|
|
183
|
+
// unexpected Qmd* name collapses to FALLBACK_ERROR_NAME while keeping the
|
|
184
|
+
// bounded qmd discriminators.
|
|
185
|
+
if (rawName.startsWith("Qmd")) {
|
|
186
|
+
const qmdName = KNOWN_ERROR_NAMES.has(rawName) ? rawName : FALLBACK_ERROR_NAME;
|
|
187
|
+
return [
|
|
188
|
+
DEFAULT_GROUPING,
|
|
189
|
+
qmdName,
|
|
190
|
+
qmdSubcommandToken(record.args),
|
|
191
|
+
qmdDispositionToken(record.status, record.signal),
|
|
192
|
+
];
|
|
193
|
+
}
|
|
101
194
|
const name = KNOWN_ERROR_NAMES.has(rawName) ? rawName : FALLBACK_ERROR_NAME;
|
|
102
195
|
const rpcCode = record.rpcCode;
|
|
103
196
|
if (typeof rpcCode === "number" && Number.isInteger(rpcCode)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.103.
|
|
3
|
+
"version": "5.103.29",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
32
32
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
33
|
-
"@indigoai-us/hq-cloud": "~6.15.
|
|
33
|
+
"@indigoai-us/hq-cloud": "~6.15.95",
|
|
34
34
|
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
35
35
|
"@sentry/node": "^10.49.0",
|
|
36
36
|
"@tobilu/qmd": "2.5.3",
|