@indigoai-us/hq-cli 5.101.4 → 5.101.5
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
package/dist/main.js
CHANGED
|
@@ -65,6 +65,7 @@ import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
|
|
|
65
65
|
import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
66
66
|
import { networkTransportErrorMessage } from "./utils/network-transport-error.js";
|
|
67
67
|
import { qmdNativeBindingErrorMessage } from "./utils/qmd-native-binding-error.js";
|
|
68
|
+
import { qmdMissingCollectionMessage } from "./utils/qmd-collection-missing-error.js";
|
|
68
69
|
import { isExpectedUserError } from "./utils/expected-cli-error.js";
|
|
69
70
|
import { isEpipe } from "./utils/epipe.js";
|
|
70
71
|
import { isInterceptedProcessExit } from "./utils/intercepted-process-exit.js";
|
|
@@ -438,7 +439,21 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
438
439
|
// Ordered FIRST so the narrow native-binding signature wins over the
|
|
439
440
|
// broader environmental / transport / generic branches.
|
|
440
441
|
const qmdMsg = qmdNativeBindingErrorMessage(err);
|
|
441
|
-
|
|
442
|
+
// A `hq search <query> -c <collection>` against a collection that is not
|
|
443
|
+
// indexed on THIS machine is the user's local index state, not an hq-cli
|
|
444
|
+
// defect — the remedy is an explicit `hq index sync`. finishRunQmd already
|
|
445
|
+
// types the condition QmdCollectionMissingError, but nothing caught it, so
|
|
446
|
+
// it fell through to the capture below; because the synthesized message
|
|
447
|
+
// embeds the caller's whole argv, each distinct query minted a brand-new
|
|
448
|
+
// permanent issue. Print an actionable line naming the missing collection
|
|
449
|
+
// and skip capture (HQ-CLI-S, Sentry 7672722729). Evaluated AFTER the
|
|
450
|
+
// native-binding check so that narrower signature keeps priority, and
|
|
451
|
+
// BEFORE the environmental / transport / generic branches. Scoped to
|
|
452
|
+
// caller-supplied reads (search/vsearch/query/get); a collection-missing
|
|
453
|
+
// raised by hq's OWN reconciliation (`collection add`/`context add`) names
|
|
454
|
+
// a collection hq built itself, so it stays a captured internal error.
|
|
455
|
+
const collectionMsg = qmdMsg ? null : qmdMissingCollectionMessage(err);
|
|
456
|
+
const envMsg = qmdMsg || collectionMsg ? null : environmentalFsErrorMessage(err);
|
|
442
457
|
// A raw network transport failure (undici's `TypeError: fetch failed`
|
|
443
458
|
// with a ConnectTimeoutError / ECONNREFUSED / ENOTFOUND cause) is the
|
|
444
459
|
// caller's connectivity, not an hq-cli defect. Before this branch it fell
|
|
@@ -449,10 +464,13 @@ export async function handleTopLevelError(err, deps = defaultTopLevelErrorDepend
|
|
|
449
464
|
// message that names the unreachable host, exit 1, and skip Sentry.
|
|
450
465
|
// Ordered after the environmental check so a full disk keeps its exact
|
|
451
466
|
// existing message.
|
|
452
|
-
const transportMsg = qmdMsg || envMsg ? null : networkTransportErrorMessage(err);
|
|
467
|
+
const transportMsg = qmdMsg || collectionMsg || envMsg ? null : networkTransportErrorMessage(err);
|
|
453
468
|
if (qmdMsg) {
|
|
454
469
|
deps.stderr.write(`hq: ${qmdMsg}\n`);
|
|
455
470
|
}
|
|
471
|
+
else if (collectionMsg) {
|
|
472
|
+
deps.stderr.write(`hq: ${collectionMsg}\n`);
|
|
473
|
+
}
|
|
456
474
|
else if (envMsg) {
|
|
457
475
|
deps.stderr.write(`hq: ${envMsg}\n`);
|
|
458
476
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* If `err` is a caller-driven "collection not found" from `hq search` (or its
|
|
3
|
+
* `get` sibling), return an actionable, query-free remedy naming the missing
|
|
4
|
+
* collection; otherwise return `null`. Mirrors qmdNativeBindingErrorMessage /
|
|
5
|
+
* environmentalFsErrorMessage / networkTransportErrorMessage so the top-level
|
|
6
|
+
* handler can branch on it the same way: a non-null result means
|
|
7
|
+
* print-and-skip-Sentry, null means "handle as usual (capture to Sentry)".
|
|
8
|
+
*/
|
|
9
|
+
export declare function qmdMissingCollectionMessage(err: unknown): string | null;
|
|
10
|
+
//# sourceMappingURL=qmd-collection-missing-error.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/utils/qmd-collection-missing-error.ts
|
|
2
|
+
//
|
|
3
|
+
// Classify a "collection not found" qmd failure that came from a caller-supplied
|
|
4
|
+
// READ — `hq search`, its `--mode` siblings (vsearch/query), or `hq search get`
|
|
5
|
+
// — as the user's LOCAL index state, not an hq-cli code defect. The collection
|
|
6
|
+
// the caller named is simply not indexed on this machine; the remedy is an
|
|
7
|
+
// explicit `hq index sync`, so the CLI prints an actionable line and SKIPS
|
|
8
|
+
// Sentry capture. Sibling of qmd-native-binding-error.ts (HQ-CLI-J, unbuilt
|
|
9
|
+
// bindings), environmental-error.ts (HQ-CLI-2, full disk), and
|
|
10
|
+
// network-transport-error.ts (HQ-CLI-G, connectivity): a failure that is NOT an
|
|
11
|
+
// hq-cli defect is printed with an actionable message and never filed as a crash.
|
|
12
|
+
//
|
|
13
|
+
// HQ-CLI-S (Sentry 7672722729): `hq search <query> -c <collection>` against a
|
|
14
|
+
// collection that isn't indexed locally made qmd exit 1 with `Collection not
|
|
15
|
+
// found: <collection>`. finishRunQmd already classifies that wording into the
|
|
16
|
+
// dedicated QmdCollectionMissingError subclass, but nothing caught it, so it
|
|
17
|
+
// unwound to the top-level handler's final else and was captured. Worse, the
|
|
18
|
+
// synthesized message interpolates the caller's whole argv, so each distinct
|
|
19
|
+
// query minted a brand-new permanent issue. This classifier closes that boundary.
|
|
20
|
+
//
|
|
21
|
+
// The gate is deliberately narrow on TWO axes so it can neither be tripped by
|
|
22
|
+
// user input nor silence a real bug:
|
|
23
|
+
// 1. CLASS: only a QmdCollectionMissingError (the typed subclass finishRunQmd
|
|
24
|
+
// raises for the collection-not-found wording) qualifies — never a plain
|
|
25
|
+
// QmdExitError, a native-binding failure, or any other error.
|
|
26
|
+
// 2. INVOCATION: only qmd's caller-supplied-collection READS — `search`,
|
|
27
|
+
// `vsearch`, `query`, `get` — qualify. A collection-missing raised by hq's
|
|
28
|
+
// OWN reconciliation (`collection add`/`collection list`, `context add`)
|
|
29
|
+
// names a collection hq constructed itself, so a not-found there is an
|
|
30
|
+
// internal inconsistency worth reporting: it must keep crashing and
|
|
31
|
+
// capturing, so the classifier returns null for those invocations.
|
|
32
|
+
//
|
|
33
|
+
// The collection name is read STRUCTURALLY from the error's own `args` (the
|
|
34
|
+
// token after `-c`/`--collection`), never by parsing qmd's stderr, and the
|
|
35
|
+
// caller's free-text query (args[1]) is never echoed — the same unbounded-
|
|
36
|
+
// fingerprint discipline that motivated the fix. The name is itself a
|
|
37
|
+
// user-supplied `-c` value, so before it is interpolated it is passed through
|
|
38
|
+
// redactErrorText — the same credential-redaction + control-character-flattening
|
|
39
|
+
// + length-bounding chain the generic error path uses — so a newline, terminal
|
|
40
|
+
// escape sequence, credential-like token, or oversized value can neither forge a
|
|
41
|
+
// second `hq:` line nor leak; if nothing survives redaction the message falls
|
|
42
|
+
// back to the name-free variant.
|
|
43
|
+
import { redactErrorText } from "./redact-error-text.js";
|
|
44
|
+
/** qmd subcommands whose collection is chosen by the CALLER (a read surface). */
|
|
45
|
+
const CALLER_COLLECTION_READS = new Set(["search", "vsearch", "query", "get"]);
|
|
46
|
+
/**
|
|
47
|
+
* The two-pronged, factual remedy. `hq index sync` builds the collections HQ can
|
|
48
|
+
* derive from this local root; `hq index collections` shows what is actually
|
|
49
|
+
* registered (covering a name that maps to a company not present here, which
|
|
50
|
+
* sync will never create). Neither claims the named collection will appear.
|
|
51
|
+
*/
|
|
52
|
+
function remedyMessage(collection) {
|
|
53
|
+
return collection
|
|
54
|
+
? `No local search collection named '${collection}'. Run 'hq index sync' to build it, or 'hq index collections' to see what is registered.`
|
|
55
|
+
: "No local search collection was found for that query. Run 'hq index sync' to build the local index, or 'hq index collections' to see what is registered.";
|
|
56
|
+
}
|
|
57
|
+
/** The collection name the caller passed after `-c`/`--collection`, or null. */
|
|
58
|
+
function collectionFromArgs(args) {
|
|
59
|
+
for (let i = 0; i < args.length - 1; i += 1) {
|
|
60
|
+
if (args[i] === "-c" || args[i] === "--collection") {
|
|
61
|
+
const value = args[i + 1];
|
|
62
|
+
if (typeof value === "string" && value.length > 0)
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* If `err` is a caller-driven "collection not found" from `hq search` (or its
|
|
70
|
+
* `get` sibling), return an actionable, query-free remedy naming the missing
|
|
71
|
+
* collection; otherwise return `null`. Mirrors qmdNativeBindingErrorMessage /
|
|
72
|
+
* environmentalFsErrorMessage / networkTransportErrorMessage so the top-level
|
|
73
|
+
* handler can branch on it the same way: a non-null result means
|
|
74
|
+
* print-and-skip-Sentry, null means "handle as usual (capture to Sentry)".
|
|
75
|
+
*/
|
|
76
|
+
export function qmdMissingCollectionMessage(err) {
|
|
77
|
+
if (err === null || typeof err !== "object")
|
|
78
|
+
return null;
|
|
79
|
+
const record = err;
|
|
80
|
+
if (record.name !== "QmdCollectionMissingError")
|
|
81
|
+
return null;
|
|
82
|
+
const args = Array.isArray(record.args) ? record.args : [];
|
|
83
|
+
const subcommand = args[0];
|
|
84
|
+
if (typeof subcommand !== "string" || !CALLER_COLLECTION_READS.has(subcommand)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const rawCollection = collectionFromArgs(args);
|
|
88
|
+
// The name is a user-supplied `-c` value: redact + flatten + bound it before
|
|
89
|
+
// printing so it cannot forge a second `hq:` line, emit terminal escapes, or
|
|
90
|
+
// leak a credential-shaped token. redactErrorText returns "" when nothing
|
|
91
|
+
// survives, in which case we fall back to the name-free variant.
|
|
92
|
+
const collection = rawCollection ? redactErrorText(rawCollection) || null : null;
|
|
93
|
+
return remedyMessage(collection);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=qmd-collection-missing-error.js.map
|