@indigoai-us/hq-cli 5.309.0 → 5.309.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/CHANGELOG.md +13 -0
- package/dist/command-catalog.generated.d.ts +4 -1
- package/dist/command-catalog.generated.js +5 -1
- package/dist/commands/agents.js +6 -0
- package/dist/commands/core-checkpoint-gate-setting.d.ts +0 -10
- package/dist/commands/core-checkpoint-gate-setting.js +3 -3
- package/dist/commands/core-ontology.d.ts +1 -9
- package/dist/commands/core-ontology.js +4 -4
- package/dist/commands/core-policy.d.ts +0 -3
- package/dist/commands/core-policy.js +3 -3
- package/dist/commands/files.js +1 -1
- package/dist/commands/index-cmd.js +1 -1
- package/dist/commands/integrations-api.d.ts +9 -4
- package/dist/commands/integrations-api.js +9 -6
- package/dist/commands/integrations-manage.js +11 -3
- package/dist/commands/integrations.d.ts +8 -0
- package/dist/commands/integrations.js +83 -0
- package/dist/commands/lanes-checks.d.ts +0 -2
- package/dist/commands/lanes-checks.js +2 -2
- package/dist/commands/lanes.d.ts +0 -11
- package/dist/commands/lanes.js +6 -6
- package/dist/commands/mcp-registration.d.ts +5 -11
- package/dist/commands/mcp-registration.js +2 -6
- package/dist/lib/core-utils/sentry-report.d.ts +5 -11
- package/dist/lib/core-utils/sentry-report.js +2 -2
- package/dist/lib/db/local.d.ts +0 -5
- package/dist/lib/db/local.js +1 -1
- package/dist/lib/db/migrate.d.ts +0 -12
- package/dist/lib/db/migrate.js +5 -5
- package/dist/lib/db/sql.d.ts +0 -10
- package/dist/lib/db/sql.js +4 -8
- package/dist/lib/doctor/checks/codex-wiring.js +1 -33
- package/dist/lib/doctor/checks/grok-wiring.js +1 -34
- package/dist/lib/doctor/deep/effects.js +1 -3
- package/dist/lib/doctor/deep/sandbox.js +1 -3
- package/dist/lib/doctor/fixtures/schema.js +1 -3
- package/dist/lib/doctor/shared.d.ts +8 -0
- package/dist/lib/doctor/shared.js +38 -0
- package/dist/lib/lanes/autopilot.js +6 -4
- package/dist/lib/monitor.d.ts +2 -6
- package/dist/lib/monitor.js +5 -5
- package/dist/lib/report-publish.d.ts +1 -10
- package/dist/lib/report-publish.js +3 -9
- package/dist/lib/search-index/index.d.ts +7 -0
- package/dist/lib/search-index/index.js +21 -2
- package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +5 -0
- package/dist/lib/search-index/qmd-exit-diagnostics.js +40 -0
- package/dist/lib/service-manager/launchd.d.ts +0 -7
- package/dist/lib/service-manager/launchd.js +2 -2
- package/dist/main.js +7 -2
- package/dist/startup-registration.js +9 -1
- package/dist/utils/client-health.d.ts +0 -4
- package/dist/utils/client-health.js +2 -2
- package/dist/utils/feedback-diagnostics.d.ts +2 -1
- package/dist/utils/feedback-log-bundle.d.ts +0 -25
- package/dist/utils/feedback-log-bundle.js +5 -5
- package/dist/utils/feedback-logs.d.ts +4 -38
- package/dist/utils/feedback-logs.js +7 -7
- package/dist/utils/feedback-screenshots.d.ts +0 -1
- package/dist/utils/feedback-screenshots.js +1 -1
- package/dist/utils/install-tree-torn.d.ts +19 -13
- package/dist/utils/install-tree-torn.js +77 -2
- package/dist/utils/local-files-overview.d.ts +0 -6
- package/dist/utils/local-files-overview.js +1 -1
- package/dist/utils/qmd-model-download-error.d.ts +0 -8
- package/dist/utils/qmd-model-download-error.js +1 -1
- package/dist/utils/qmd-query-document-error.d.ts +0 -6
- package/dist/utils/qmd-query-document-error.js +1 -1
- package/dist/utils/scaffold-fast-import-guard.d.ts +0 -1
- package/dist/utils/scaffold-fast-import-guard.js +1 -1
- package/dist/utils/scaffold-sibling-references.d.ts +3 -2
- package/dist/utils/sentry-fingerprint.js +17 -0
- package/dist/utils/version-gate.d.ts +4 -0
- package/dist/utils/version-gate.js +13 -0
- package/package.json +1 -1
|
@@ -609,7 +609,7 @@ export function installTargetPresent(target, fs = nodeInstallTreeFs) {
|
|
|
609
609
|
* exactly as long as the contract legitimises holding the lock, and never longer.
|
|
610
610
|
* Overridable by the seam (env HQ_INSTALL_SETTLE_LOCK_TIMEOUT_MS).
|
|
611
611
|
*/
|
|
612
|
-
|
|
612
|
+
const DEFAULT_INSTALL_SETTLE_LOCK_WAIT_MS = UPDATE_LOCK_STALE_MS;
|
|
613
613
|
/** The scope/leaf split of CLI_NAME, e.g. `@indigoai-us` / `hq-cli`. */
|
|
614
614
|
function cliNameParts() {
|
|
615
615
|
const slash = CLI_NAME.indexOf("/");
|
|
@@ -652,6 +652,68 @@ function retiredSiblingPresent(packageRoot, fs) {
|
|
|
652
652
|
const entries = fs.readdirSync(parent); // caller treats a throw as "not ready"
|
|
653
653
|
return entries.some((entry) => entry.startsWith(stagingPrefix));
|
|
654
654
|
}
|
|
655
|
+
/** Reduce an observed sibling basename to a closed kind without retaining it. */
|
|
656
|
+
function retiredSiblingKind(packageRoot, fs) {
|
|
657
|
+
if (!packageRoot)
|
|
658
|
+
return "unknown";
|
|
659
|
+
try {
|
|
660
|
+
const { leaf } = cliNameParts();
|
|
661
|
+
const prefix = "." + leaf + "-";
|
|
662
|
+
const siblings = fs
|
|
663
|
+
.readdirSync(path.dirname(packageRoot))
|
|
664
|
+
.filter((entry) => entry.startsWith(prefix))
|
|
665
|
+
.sort();
|
|
666
|
+
if (siblings.length === 0)
|
|
667
|
+
return "unknown";
|
|
668
|
+
const name = siblings[0];
|
|
669
|
+
if (/^\.hq-cli-staging-[A-Za-z0-9-]+$/.test(name))
|
|
670
|
+
return "hq_staging";
|
|
671
|
+
if (/^\.hq-cli-backup-[A-Za-z0-9-]+$/.test(name))
|
|
672
|
+
return "hq_backup";
|
|
673
|
+
if (/^\.hq-cli-[A-Za-z0-9]{8}$/.test(name))
|
|
674
|
+
return "npm_hash_retired";
|
|
675
|
+
return "other";
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
return "unknown";
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
/** Classify current target/manifest state without retaining paths or file contents. */
|
|
682
|
+
function installTargetState(target, packageRoot, fs) {
|
|
683
|
+
if (target.kind === "unknown")
|
|
684
|
+
return "unknown";
|
|
685
|
+
if (!installTargetPresent(target, fs))
|
|
686
|
+
return "missing";
|
|
687
|
+
if (!packageRoot)
|
|
688
|
+
return "unknown";
|
|
689
|
+
try {
|
|
690
|
+
const raw = fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8");
|
|
691
|
+
let manifest;
|
|
692
|
+
try {
|
|
693
|
+
manifest = JSON.parse(raw);
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
return "manifest_invalid";
|
|
697
|
+
}
|
|
698
|
+
return manifest.name === CLI_NAME ? "manifest_valid" : "manifest_invalid";
|
|
699
|
+
}
|
|
700
|
+
catch (error) {
|
|
701
|
+
return error?.code === "ENOENT" ? "missing" : "unreadable";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/** Read only the lock's recorded owner PID; never retain or report the PID. */
|
|
705
|
+
function updaterPidAlive(lockPath, fs, isPidAlive) {
|
|
706
|
+
try {
|
|
707
|
+
const lock = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
|
|
708
|
+
if (typeof lock.pid !== "number" || !Number.isSafeInteger(lock.pid) || lock.pid <= 0) {
|
|
709
|
+
return "unknown";
|
|
710
|
+
}
|
|
711
|
+
return isPidAlive(lock.pid) ? "true" : "false";
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
return "unknown";
|
|
715
|
+
}
|
|
716
|
+
}
|
|
655
717
|
/** Snapshot retired sibling trees so changes below their root stay observable. */
|
|
656
718
|
function retiredSiblingActivitySignature(packageRoot, fs) {
|
|
657
719
|
const { leaf } = cliNameParts();
|
|
@@ -851,7 +913,7 @@ function canProbeUnsettledRetiredSibling(snapshot) {
|
|
|
851
913
|
return snapshot.observed ||
|
|
852
914
|
(snapshot.nonRetiredObserved && snapshot.retiredSiblingPresent === true);
|
|
853
915
|
}
|
|
854
|
-
|
|
916
|
+
const DEFAULT_INSTALL_TREE_STALE_PROBE_MS = 1_500;
|
|
855
917
|
/**
|
|
856
918
|
* Poll until the install tree has been continuously READY for `quietMs`, or the
|
|
857
919
|
* wait exhausts its bounds. READY means: (i) no FRESH update lock held by another
|
|
@@ -986,6 +1048,9 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
986
1048
|
sawTreeChange,
|
|
987
1049
|
staleTree: false,
|
|
988
1050
|
retiredSiblingPresent: retiredPresent,
|
|
1051
|
+
retiredSiblingKind: retiredSiblingKind(args.packageRoot, fs),
|
|
1052
|
+
installTargetState: installTargetState(args.target, args.packageRoot, fs),
|
|
1053
|
+
updaterPidAlive: updaterPidAlive(args.lockPath, fs, isPidAlive),
|
|
989
1054
|
};
|
|
990
1055
|
}
|
|
991
1056
|
if (!ready &&
|
|
@@ -1007,6 +1072,9 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
1007
1072
|
staleTree: lastActivitySnapshot.observed,
|
|
1008
1073
|
retiredSiblingPresent: retiredPresent,
|
|
1009
1074
|
retiredSiblingUnverified: !lastActivitySnapshot.observed && lastActivitySnapshot.retiredSiblingPresent === true,
|
|
1075
|
+
retiredSiblingKind: retiredSiblingKind(args.packageRoot, fs),
|
|
1076
|
+
installTargetState: installTargetState(args.target, args.packageRoot, fs),
|
|
1077
|
+
updaterPidAlive: updaterPidAlive(args.lockPath, fs, isPidAlive),
|
|
1010
1078
|
};
|
|
1011
1079
|
}
|
|
1012
1080
|
// Give up when the TREE has had a full unlocked budget with no writer visible,
|
|
@@ -1029,6 +1097,9 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
1029
1097
|
sawTreeChange,
|
|
1030
1098
|
staleTree: false,
|
|
1031
1099
|
retiredSiblingPresent: retiredPresent,
|
|
1100
|
+
retiredSiblingKind: retiredSiblingKind(args.packageRoot, fs),
|
|
1101
|
+
installTargetState: installTargetState(args.target, args.packageRoot, fs),
|
|
1102
|
+
updaterPidAlive: updaterPidAlive(args.lockPath, fs, isPidAlive),
|
|
1032
1103
|
};
|
|
1033
1104
|
}
|
|
1034
1105
|
// The first poll at which the wait has reached `deadlineMs` while a lock is
|
|
@@ -1129,6 +1200,10 @@ export class InstallTreeTornError extends Error {
|
|
|
1129
1200
|
lockTool: boundedDiagnosticValue(init.lockTool ?? "", LOCK_TOOL_BYTES),
|
|
1130
1201
|
node: process.version,
|
|
1131
1202
|
errorName: boundedDiagnosticValue(init.classified.errorName ?? "", ERRORNAME_BYTES),
|
|
1203
|
+
retiredSiblingKind: init.retiredSiblingKind ?? "unknown",
|
|
1204
|
+
installTargetState: init.installTargetState ?? "unknown",
|
|
1205
|
+
versionGatePhase: init.versionGatePhase ?? "unknown",
|
|
1206
|
+
updaterPidAlive: init.updaterPidAlive ?? "unknown",
|
|
1132
1207
|
};
|
|
1133
1208
|
if (typeof init.retiredSiblingPresent === "boolean") {
|
|
1134
1209
|
diagnostics.retiredSiblingPresent = init.retiredSiblingPresent;
|
|
@@ -26,12 +26,6 @@
|
|
|
26
26
|
* must never break a heartbeat, let alone a CLI command.
|
|
27
27
|
*/
|
|
28
28
|
import { type ClientHealthLocalFilesOverview } from "./client-health-contract.js";
|
|
29
|
-
/**
|
|
30
|
-
* Only the last N bytes of the sync log are read for error-line counting —
|
|
31
|
-
* this bounds memory/CPU regardless of how large the on-disk log has grown.
|
|
32
|
-
* Metadata (size/age) still reflects the FULL file via `fs.stat`.
|
|
33
|
-
*/
|
|
34
|
-
export declare const LOCAL_FILES_OVERVIEW_TAIL_READ_BYTES: number;
|
|
35
29
|
/**
|
|
36
30
|
* Count lines that look like an error marker in the last
|
|
37
31
|
* {@link LOCAL_FILES_OVERVIEW_TAIL_READ_BYTES} of `filePath`, capped at
|
|
@@ -36,7 +36,7 @@ import { CLIENT_HEALTH_MAX_ERROR_LINE_COUNT, CLIENT_HEALTH_MAX_FILE_AGE_SECONDS,
|
|
|
36
36
|
* this bounds memory/CPU regardless of how large the on-disk log has grown.
|
|
37
37
|
* Metadata (size/age) still reflects the FULL file via `fs.stat`.
|
|
38
38
|
*/
|
|
39
|
-
|
|
39
|
+
const LOCAL_FILES_OVERVIEW_TAIL_READ_BYTES = 2 * 1024 * 1024; // 2 MiB
|
|
40
40
|
/** Case-insensitive marker matched against each line (or JSON-lines `level`). */
|
|
41
41
|
const ERROR_MARKER = /error/i;
|
|
42
42
|
const JSON_LEVEL_ERROR = /"level"\s*:\s*"error"\s*(?=[,}])/i;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The actionable remedy shown to the caller. Input-free — nothing is
|
|
3
|
-
* interpolated from the error, the argv, or qmd's output — so there is no
|
|
4
|
-
* injection surface. The disposition holds for every reading of a vanished
|
|
5
|
-
* `.ipull` partial (a concurrent finisher, a cache eviction, a partially-written
|
|
6
|
-
* file): it is transient and retryable, and re-running resumes the download.
|
|
7
|
-
*/
|
|
8
|
-
export declare const QMD_MODEL_DOWNLOAD_REMEDY: string;
|
|
9
1
|
/**
|
|
10
2
|
* True when `err` is a qmd interrupted-model-download failure: qmd's OWN captured
|
|
11
3
|
* streams carry BOTH an ENOENT-rename signature AND a `.ipull` partial-download
|
|
@@ -50,7 +50,7 @@ const MODEL_FETCH_READS = new Set(["query", "vsearch", "embed"]);
|
|
|
50
50
|
* `.ipull` partial (a concurrent finisher, a cache eviction, a partially-written
|
|
51
51
|
* file): it is transient and retryable, and re-running resumes the download.
|
|
52
52
|
*/
|
|
53
|
-
|
|
53
|
+
const QMD_MODEL_DOWNLOAD_REMEDY = "hq's local search couldn't finish downloading a model it needs: the partial " +
|
|
54
54
|
"download file disappeared before it could be saved, which usually means " +
|
|
55
55
|
"another hq or qmd process on this machine finished or cleared the same " +
|
|
56
56
|
"download. Re-run the command — the download resumes.";
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The actionable remedy shown to the caller. Input-free: nothing is interpolated
|
|
3
|
-
* from the error, the argv, or qmd's output, so there is no injection surface —
|
|
4
|
-
* matching the bounded-fingerprint discipline of its siblings.
|
|
5
|
-
*/
|
|
6
|
-
export declare const QMD_QUERY_DOCUMENT_REMEDY: string;
|
|
7
1
|
/**
|
|
8
2
|
* True when `err` carries, in qmd's OWN captured streams, a query-document
|
|
9
3
|
* syntax diagnostic — i.e. qmd's `query` parser rejected the positional as a
|
|
@@ -61,7 +61,7 @@ const QUERY_DOCUMENT_READS = new Set(["query"]);
|
|
|
61
61
|
* from the error, the argv, or qmd's output, so there is no injection surface —
|
|
62
62
|
* matching the bounded-fingerprint discipline of its siblings.
|
|
63
63
|
*/
|
|
64
|
-
|
|
64
|
+
const QMD_QUERY_DOCUMENT_REMEDY = "Hybrid search was given a multi-line query, and qmd read it as a structured " +
|
|
65
65
|
"query document whose lines must each begin with a lex:, vec:, hyde:, or " +
|
|
66
66
|
"intent: prefix. Put your search terms on a single line, or prefix every line, " +
|
|
67
67
|
"then run the command again.";
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
export declare function forbiddenFastPathModules(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): string[];
|
|
2
1
|
export declare function assertFastPathStaysCommandGraphFree(loadedModules: readonly string[], distDirectory: string, allowedCommandModules?: readonly string[]): void;
|
|
3
2
|
//# sourceMappingURL=scaffold-fast-import-guard.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
|
|
3
|
+
function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules = []) {
|
|
4
4
|
const dist = path.resolve(distDirectory);
|
|
5
5
|
const main = path.join(dist, "main.js");
|
|
6
6
|
const commands = `${path.join(dist, "commands")}${path.sep}`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
type SiblingAssetReference = {
|
|
2
2
|
referringAsset: string;
|
|
3
3
|
targetAsset: string;
|
|
4
4
|
};
|
|
5
|
-
|
|
5
|
+
type HqRootAssetReference = {
|
|
6
6
|
referringAsset: string;
|
|
7
7
|
reference: string;
|
|
8
8
|
};
|
|
@@ -18,4 +18,5 @@ export type ScaffoldReferenceScan = {
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function scanScaffoldSiblingReferences(scaffoldRoot: string): ScaffoldReferenceScan;
|
|
20
20
|
export declare function assertNoMissingScaffoldSiblings(scan: ScaffoldReferenceScan): void;
|
|
21
|
+
export {};
|
|
21
22
|
//# sourceMappingURL=scaffold-sibling-references.d.ts.map
|
|
@@ -250,6 +250,23 @@ export function installTreeTornSentryTagsFor(err) {
|
|
|
250
250
|
if (typeof diagnostics.retiredSiblingPresent === "boolean") {
|
|
251
251
|
tags.retired_sibling = diagnostics.retiredSiblingPresent ? "present" : "absent";
|
|
252
252
|
}
|
|
253
|
+
const retiredSiblingKind = diagnostics.retiredSiblingKind;
|
|
254
|
+
tags.retired_sibling_kind = typeof retiredSiblingKind === "string" &&
|
|
255
|
+
["npm_hash_retired", "hq_staging", "hq_backup", "other", "unknown"].includes(retiredSiblingKind)
|
|
256
|
+
? retiredSiblingKind
|
|
257
|
+
: "unknown";
|
|
258
|
+
const targetState = diagnostics.installTargetState;
|
|
259
|
+
tags.install_target_state = typeof targetState === "string" &&
|
|
260
|
+
["missing", "manifest_valid", "manifest_invalid", "unreadable", "unknown"].includes(targetState)
|
|
261
|
+
? targetState
|
|
262
|
+
: "unknown";
|
|
263
|
+
const gatePhase = diagnostics.versionGatePhase;
|
|
264
|
+
tags.version_gate_phase = typeof gatePhase === "string" &&
|
|
265
|
+
["preflight", "lock_acquired", "installer_running", "verifying", "rollback", "complete", "failed", "unknown"].includes(gatePhase)
|
|
266
|
+
? gatePhase
|
|
267
|
+
: "unknown";
|
|
268
|
+
const pidAlive = diagnostics.updaterPidAlive;
|
|
269
|
+
tags.updater_pid_alive = pidAlive === "true" || pidAlive === "false" ? pidAlive : "unknown";
|
|
253
270
|
if (recoveryOutcome)
|
|
254
271
|
tags.recovery_outcome = recoveryOutcome;
|
|
255
272
|
const lockTool = diagnostics.lockTool;
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { buildSpawnPlan, quoteForWindowsShell } from "./windows-spawn.js";
|
|
31
31
|
import { type UpdateLockHandle } from "./update-lock.js";
|
|
32
|
+
export type VersionGatePhase = "preflight" | "lock_acquired" | "installer_running" | "verifying" | "rollback" | "complete" | "failed" | "unknown";
|
|
33
|
+
/** The last closed version-gate phase observed in this process. */
|
|
34
|
+
export declare function getVersionGatePhase(): VersionGatePhase;
|
|
32
35
|
/** Which package manager owns the running global install. */
|
|
33
36
|
export type InstallManager = "npm" | "pnpm" | "bun";
|
|
34
37
|
export interface VersionCheckResponse {
|
|
@@ -489,6 +492,7 @@ export declare function enforceVersionGate(onUpdateRecommended?: (decision: Vers
|
|
|
489
492
|
export declare function shouldSkipGate(argv: readonly string[]): boolean;
|
|
490
493
|
export declare const __test__: {
|
|
491
494
|
CLIENT_ID: string;
|
|
495
|
+
getVersionGatePhase: typeof getVersionGatePhase;
|
|
492
496
|
CONVERGENCE_TIMEOUT_MS: number;
|
|
493
497
|
DEFAULT_UPDATE_TIMEOUT_MS: number;
|
|
494
498
|
ENDPOINT_PATH: string;
|
|
@@ -45,6 +45,11 @@ const CLIENT_ID = "hq-cli";
|
|
|
45
45
|
const ENDPOINT_PATH = "/v1/client-version/check";
|
|
46
46
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
47
47
|
const LATEST_PACKAGE_SPEC = `${CLI_NAME}@latest`;
|
|
48
|
+
let currentVersionGatePhase = "unknown";
|
|
49
|
+
/** The last closed version-gate phase observed in this process. */
|
|
50
|
+
export function getVersionGatePhase() {
|
|
51
|
+
return currentVersionGatePhase;
|
|
52
|
+
}
|
|
48
53
|
function isOptedOut() {
|
|
49
54
|
return process.env.HQ_NO_UPDATE_CHECK === "1";
|
|
50
55
|
}
|
|
@@ -1291,6 +1296,7 @@ export function checkUpdateConvergence(targetVersion, deps = {}) {
|
|
|
1291
1296
|
* gate returns and lets the user's command run on the current version.
|
|
1292
1297
|
*/
|
|
1293
1298
|
function enforceUpdateRequired(decision, deps = {}) {
|
|
1299
|
+
currentVersionGatePhase = "preflight";
|
|
1294
1300
|
const banner = chalk.red.bold(`✗ hq-cli ${CLI_VERSION} is below the minimum required version (${decision.minVersion}).`);
|
|
1295
1301
|
console.error(banner);
|
|
1296
1302
|
if (decision.message)
|
|
@@ -1341,6 +1347,7 @@ function enforceUpdateRequired(decision, deps = {}) {
|
|
|
1341
1347
|
console.error(chalk.dim(" Another hq updater is already installing (update lock held); continuing on the current version."));
|
|
1342
1348
|
return;
|
|
1343
1349
|
}
|
|
1350
|
+
currentVersionGatePhase = "lock_acquired";
|
|
1344
1351
|
let attempt;
|
|
1345
1352
|
try {
|
|
1346
1353
|
attempt = attemptRequiredUpdate(decision, deps, install);
|
|
@@ -1406,6 +1413,7 @@ function attemptRequiredUpdate(decision, deps, install) {
|
|
|
1406
1413
|
const updateEnv = pnpmUpdateEnv(install);
|
|
1407
1414
|
let result;
|
|
1408
1415
|
let noticeFlush = null;
|
|
1416
|
+
currentVersionGatePhase = "installer_running";
|
|
1409
1417
|
if (isManagedOutsideNpm) {
|
|
1410
1418
|
console.error(chalk.dim(` Detected a ${install.manager}-managed global install; updating with ${install.manager}`));
|
|
1411
1419
|
console.error(chalk.dim(` Running: ${primaryCmd} ${primaryArgs.join(" ")}`));
|
|
@@ -1475,6 +1483,7 @@ function attemptRequiredUpdate(decision, deps, install) {
|
|
|
1475
1483
|
result = sudoResult;
|
|
1476
1484
|
}
|
|
1477
1485
|
if (!result.ok) {
|
|
1486
|
+
currentVersionGatePhase = "failed";
|
|
1478
1487
|
console.error(chalk.red(`✗ Update failed${result.detail ? `: ${result.detail}` : ""}.`));
|
|
1479
1488
|
// If the npm prefix is still unwritable after the unprivileged and sudo
|
|
1480
1489
|
// attempts, explain the permission wall plainly instead of leaving exit 75
|
|
@@ -1524,11 +1533,14 @@ function attemptRequiredUpdate(decision, deps, install) {
|
|
|
1524
1533
|
// install shadowing the copy we just wrote is reported as the failed update
|
|
1525
1534
|
// it is rather than as a success the caller will keep retrying — see
|
|
1526
1535
|
// checkUpdateConvergence.
|
|
1536
|
+
currentVersionGatePhase = "verifying";
|
|
1527
1537
|
const converged = (deps.checkConvergence ?? checkUpdateConvergence)(decision.latestVersion);
|
|
1528
1538
|
if (converged === false) {
|
|
1539
|
+
currentVersionGatePhase = "failed";
|
|
1529
1540
|
console.error(chalk.red(`✗ Update did not take effect: \`hq\` on PATH still resolves a different build than ${decision.latestVersion}.`));
|
|
1530
1541
|
return 75;
|
|
1531
1542
|
}
|
|
1543
|
+
currentVersionGatePhase = "complete";
|
|
1532
1544
|
console.error(chalk.green(`✓ Updated to hq-cli ${decision.latestVersion}. Rerun your command.`));
|
|
1533
1545
|
return 0;
|
|
1534
1546
|
}
|
|
@@ -1585,6 +1597,7 @@ export function shouldSkipGate(argv) {
|
|
|
1585
1597
|
}
|
|
1586
1598
|
export const __test__ = {
|
|
1587
1599
|
CLIENT_ID,
|
|
1600
|
+
getVersionGatePhase,
|
|
1588
1601
|
CONVERGENCE_TIMEOUT_MS,
|
|
1589
1602
|
DEFAULT_UPDATE_TIMEOUT_MS,
|
|
1590
1603
|
ENDPOINT_PATH,
|