@indigoai-us/hq-cli 5.309.0 → 5.310.0

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.
Files changed (84) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/assets/scaffold/core/scripts/hq-dm-bind.sh +585 -0
  3. package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +152 -0
  4. package/dist/command-catalog.generated.d.ts +30 -1
  5. package/dist/command-catalog.generated.js +37 -1
  6. package/dist/commands/agents.js +6 -0
  7. package/dist/commands/core-checkpoint-gate-setting.d.ts +0 -10
  8. package/dist/commands/core-checkpoint-gate-setting.js +3 -3
  9. package/dist/commands/core-dm-bind-native.d.ts +32 -0
  10. package/dist/commands/core-dm-bind-native.js +668 -0
  11. package/dist/commands/core-native-utilities.d.ts +2 -1
  12. package/dist/commands/core-native-utilities.js +4 -2
  13. package/dist/commands/core-ontology.d.ts +1 -9
  14. package/dist/commands/core-ontology.js +4 -4
  15. package/dist/commands/core-policy.d.ts +0 -3
  16. package/dist/commands/core-policy.js +3 -3
  17. package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +32 -0
  18. package/dist/commands/core-work-mesh-bind-trusted-native.js +190 -0
  19. package/dist/commands/core.js +6 -0
  20. package/dist/commands/files.js +1 -1
  21. package/dist/commands/index-cmd.js +1 -1
  22. package/dist/commands/integrations-api.d.ts +9 -4
  23. package/dist/commands/integrations-api.js +9 -6
  24. package/dist/commands/integrations-manage.js +11 -3
  25. package/dist/commands/integrations.d.ts +8 -0
  26. package/dist/commands/integrations.js +83 -0
  27. package/dist/commands/lanes-checks.d.ts +0 -2
  28. package/dist/commands/lanes-checks.js +2 -2
  29. package/dist/commands/lanes.d.ts +0 -11
  30. package/dist/commands/lanes.js +6 -6
  31. package/dist/commands/mcp-registration.d.ts +5 -11
  32. package/dist/commands/mcp-registration.js +2 -6
  33. package/dist/lib/core-utils/sentry-report.d.ts +5 -11
  34. package/dist/lib/core-utils/sentry-report.js +2 -2
  35. package/dist/lib/db/local.d.ts +0 -5
  36. package/dist/lib/db/local.js +1 -1
  37. package/dist/lib/db/migrate.d.ts +0 -12
  38. package/dist/lib/db/migrate.js +5 -5
  39. package/dist/lib/db/sql.d.ts +0 -10
  40. package/dist/lib/db/sql.js +4 -8
  41. package/dist/lib/doctor/checks/codex-wiring.js +1 -33
  42. package/dist/lib/doctor/checks/grok-wiring.js +1 -34
  43. package/dist/lib/doctor/deep/effects.js +1 -3
  44. package/dist/lib/doctor/deep/sandbox.js +1 -3
  45. package/dist/lib/doctor/fixtures/schema.js +1 -3
  46. package/dist/lib/doctor/shared.d.ts +8 -0
  47. package/dist/lib/doctor/shared.js +38 -0
  48. package/dist/lib/lanes/autopilot.js +6 -4
  49. package/dist/lib/monitor.d.ts +2 -6
  50. package/dist/lib/monitor.js +5 -5
  51. package/dist/lib/report-publish.d.ts +1 -10
  52. package/dist/lib/report-publish.js +3 -9
  53. package/dist/lib/search-index/index.d.ts +7 -0
  54. package/dist/lib/search-index/index.js +21 -2
  55. package/dist/lib/search-index/qmd-exit-diagnostics.d.ts +5 -0
  56. package/dist/lib/search-index/qmd-exit-diagnostics.js +40 -0
  57. package/dist/lib/service-manager/launchd.d.ts +0 -7
  58. package/dist/lib/service-manager/launchd.js +2 -2
  59. package/dist/main.js +7 -2
  60. package/dist/startup-registration.js +9 -1
  61. package/dist/utils/client-health.d.ts +0 -4
  62. package/dist/utils/client-health.js +2 -2
  63. package/dist/utils/feedback-diagnostics.d.ts +2 -1
  64. package/dist/utils/feedback-log-bundle.d.ts +0 -25
  65. package/dist/utils/feedback-log-bundle.js +5 -5
  66. package/dist/utils/feedback-logs.d.ts +4 -38
  67. package/dist/utils/feedback-logs.js +7 -7
  68. package/dist/utils/feedback-screenshots.d.ts +0 -1
  69. package/dist/utils/feedback-screenshots.js +1 -1
  70. package/dist/utils/install-tree-torn.d.ts +19 -13
  71. package/dist/utils/install-tree-torn.js +77 -2
  72. package/dist/utils/local-files-overview.d.ts +0 -6
  73. package/dist/utils/local-files-overview.js +1 -1
  74. package/dist/utils/qmd-model-download-error.d.ts +0 -8
  75. package/dist/utils/qmd-model-download-error.js +1 -1
  76. package/dist/utils/qmd-query-document-error.d.ts +0 -6
  77. package/dist/utils/qmd-query-document-error.js +1 -1
  78. package/dist/utils/scaffold-fast-import-guard.d.ts +0 -1
  79. package/dist/utils/scaffold-fast-import-guard.js +1 -1
  80. package/dist/utils/scaffold-sibling-references.d.ts +3 -2
  81. package/dist/utils/sentry-fingerprint.js +17 -0
  82. package/dist/utils/version-gate.d.ts +4 -0
  83. package/dist/utils/version-gate.js +13 -0
  84. package/package.json +1 -1
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { vaultApiFetch } from "./vault-api.js";
4
4
  export const MAX_SCREENSHOTS = 5;
5
5
  // Per-image ceiling. Screenshots are PNG/JPEG captures; 10 MB is generous.
6
- export const MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
6
+ const MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024;
7
7
  // Extension → content type. Must stay in sync with the server's allowed set
8
8
  // (hq-pro feedback-screenshots.ts ALLOWED_CONTENT_TYPES).
9
9
  const EXT_CONTENT_TYPE = {
@@ -37,7 +37,7 @@
37
37
  * for a module-EVALUATION throw that carries NO real `code` (HQ-CLI-1T) — see
38
38
  * {@link ModuleErrorDialect}.
39
39
  */
40
- export type ModuleNotFoundCode = "ERR_MODULE_NOT_FOUND" | "MODULE_NOT_FOUND" | "ENOENT" | "EVAL_THROW";
40
+ type ModuleNotFoundCode = "ERR_MODULE_NOT_FOUND" | "MODULE_NOT_FOUND" | "ENOENT" | "EVAL_THROW";
41
41
  /**
42
42
  * The closed set of resolution-failure dialects, verified on Node v22.23.1 and
43
43
  * the observed Node 24 synchronous ESM loader shape (the @sentry/node
@@ -78,7 +78,7 @@ export type ModuleNotFoundCode = "ERR_MODULE_NOT_FOUND" | "MODULE_NOT_FOUND" | "
78
78
  * - `unknown`: a module-not-found whose message did not parse; recovery
79
79
  * still waits on the lock / retired-dir / quiet signals.
80
80
  */
81
- export type ModuleErrorDialect = "esm-path" | "esm-package" | "cjs-path" | "cjs-package" | "cjs-relative" | "esm-enoent" | "cjs-enoent" | "resolve-enoent" | "eval-throw" | "unknown";
81
+ type ModuleErrorDialect = "esm-path" | "esm-package" | "cjs-path" | "cjs-package" | "cjs-relative" | "esm-enoent" | "cjs-enoent" | "resolve-enoent" | "eval-throw" | "unknown";
82
82
  /**
83
83
  * The missing thing, re-resolvable by the readiness probe:
84
84
  * - `path`: an absolute filesystem path (a `.js` file, or a package dir).
@@ -214,15 +214,6 @@ export interface InstallTreeFs {
214
214
  * Any filesystem error reads as "not present" rather than throwing.
215
215
  */
216
216
  export declare function installTargetPresent(target: ModuleErrorTarget, fs?: InstallTreeFs): boolean;
217
- /**
218
- * Default ceiling on the CUMULATIVE time the wait may spend observing a FRESH
219
- * foreign lock held by a live pid. Equal to the shared update-lock contract's own
220
- * staleness window ({@link UPDATE_LOCK_STALE_MS} — "no healthy npm install -g of
221
- * this package runs 10 minutes"), so the wait trusts a live cooperating writer for
222
- * exactly as long as the contract legitimises holding the lock, and never longer.
223
- * Overridable by the seam (env HQ_INSTALL_SETTLE_LOCK_TIMEOUT_MS).
224
- */
225
- export declare const DEFAULT_INSTALL_SETTLE_LOCK_WAIT_MS: number;
226
217
  export interface WaitForInstallTreeSettledArgs {
227
218
  target: ModuleErrorTarget;
228
219
  /** The running install's package dir (from resolveRunningInstall), or null. */
@@ -276,8 +267,14 @@ export interface WaitForInstallTreeSettledResult {
276
267
  retiredSiblingPresent?: boolean | null;
277
268
  /** A stable partial retired-sibling scan ended the wait without claiming the tree is stale. */
278
269
  retiredSiblingUnverified?: boolean;
270
+ retiredSiblingKind?: RetiredSiblingKind;
271
+ installTargetState?: InstallTargetState;
272
+ updaterPidAlive?: UpdaterPidAlive;
279
273
  }
280
- export declare const DEFAULT_INSTALL_TREE_STALE_PROBE_MS = 1500;
274
+ type RetiredSiblingKind = "npm_hash_retired" | "hq_staging" | "hq_backup" | "other" | "unknown";
275
+ type InstallTargetState = "missing" | "manifest_valid" | "manifest_invalid" | "unreadable" | "unknown";
276
+ type UpdaterPidAlive = "true" | "false" | "unknown";
277
+ type VersionGatePhase = "preflight" | "lock_acquired" | "installer_running" | "verifying" | "rollback" | "complete" | "failed" | "unknown";
281
278
  /**
282
279
  * Poll until the install tree has been continuously READY for `quietMs`, or the
283
280
  * wait exhausts its bounds. READY means: (i) no FRESH update lock held by another
@@ -337,7 +334,7 @@ export interface InstallRewriteSignalArgs {
337
334
  export declare function installRewriteInProgress(args: InstallRewriteSignalArgs): boolean;
338
335
  /** The recovery OUTCOME, which — not the dialect — decides capture. */
339
336
  export type InstallTreeTornOutcome = "guarded" | "unsettled" | "reexec-failed" | "lock-held" | "stale_tree" | "retired_unverified";
340
- export type InstallTreeState = "in_flight" | "stale";
337
+ type InstallTreeState = "in_flight" | "stale";
341
338
  export type InstallTreeTornDiagnostics = {
342
339
  dialect: ModuleErrorDialect;
343
340
  code: ModuleNotFoundCode;
@@ -362,6 +359,10 @@ export type InstallTreeTornDiagnostics = {
362
359
  retiredSiblingPresent?: boolean;
363
360
  /** The thrown error's `name` for the eval-throw dialect (HQ-CLI-1T), else "". */
364
361
  errorName: string;
362
+ retiredSiblingKind: RetiredSiblingKind;
363
+ installTargetState: InstallTargetState;
364
+ versionGatePhase: VersionGatePhase;
365
+ updaterPidAlive: UpdaterPidAlive;
365
366
  };
366
367
  export interface InstallTreeTornInit {
367
368
  /** The original loader error, carried unchanged as `cause`. */
@@ -383,6 +384,10 @@ export interface InstallTreeTornInit {
383
384
  lockTool?: string;
384
385
  /** Final retired sibling presence, or null when the scope directory was unreadable. */
385
386
  retiredSiblingPresent?: boolean | null;
387
+ retiredSiblingKind?: RetiredSiblingKind;
388
+ installTargetState?: InstallTargetState;
389
+ versionGatePhase?: VersionGatePhase;
390
+ updaterPidAlive?: UpdaterPidAlive;
386
391
  }
387
392
  /**
388
393
  * A torn-install failure that stays VISIBLE in Sentry with bounded, hq-derived
@@ -431,4 +436,5 @@ export declare const INSTALL_TREE_LOCK_HELD_REMEDY: string;
431
436
  * {@link INSTALL_TREE_TORN_REMEDY} byte-for-byte.
432
437
  */
433
438
  export declare function installTreeTornStderrLine(err: InstallTreeTornError): string;
439
+ export {};
434
440
  //# sourceMappingURL=install-tree-torn.d.ts.map
@@ -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
- export const DEFAULT_INSTALL_SETTLE_LOCK_WAIT_MS = UPDATE_LOCK_STALE_MS;
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
- export const DEFAULT_INSTALL_TREE_STALE_PROBE_MS = 1_500;
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
- export const LOCAL_FILES_OVERVIEW_TAIL_READ_BYTES = 2 * 1024 * 1024; // 2 MiB
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
- export const QMD_MODEL_DOWNLOAD_REMEDY = "hq's local search couldn't finish downloading a model it needs: the partial " +
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
- export const QMD_QUERY_DOCUMENT_REMEDY = "Hybrid search was given a multi-line query, and qmd read it as a structured " +
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
- export function forbiddenFastPathModules(loadedModules, distDirectory, allowedCommandModules = []) {
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
- export type SiblingAssetReference = {
1
+ type SiblingAssetReference = {
2
2
  referringAsset: string;
3
3
  targetAsset: string;
4
4
  };
5
- export type HqRootAssetReference = {
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.309.0",
3
+ "version": "5.310.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {