@indigoai-us/hq-cli 5.345.69 → 5.345.70

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,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.70] — 2026-10-10
6
+ - Native `hq core` fast-route imports now use the bounded torn-install recovery path. If an update temporarily removes a route module, the CLI waits for the package tree to settle and re-executes once; a module that remains missing prints the reinstall remedy.
7
+
5
8
  ## [5.345.69] — 2026-10-10
6
9
  - Supported native core handlers now run by default without a flag snapshot. Explicit false values still turn them off; delegate and worker-skills remain default-off pending parity.
7
10
 
@@ -7,17 +7,26 @@
7
7
  * module. The registration in `core.ts` remains the complete fallback path.
8
8
  */
9
9
  import { parseFastCore } from "./fast-scaffold.js";
10
+ type RecoveredModule<T> = {
11
+ module?: T;
12
+ reexecStatus?: number;
13
+ };
14
+ type RecoverFastImportOptions = {
15
+ reexecOnSettled?: boolean;
16
+ };
17
+ type RecoverFastImport = <T>(load: () => Promise<T>, options?: RecoverFastImportOptions) => Promise<RecoveredModule<T>>;
10
18
  export declare function readStdinIfPiped(stream: NodeJS.ReadStream, maxBytes?: number): Promise<string>;
11
19
  /**
12
20
  * Run the fast-path core command named in `argv` and exit with its code.
13
21
  * Only call when {@link isFastCoreRequest} is true; a non-match returns without
14
22
  * doing anything.
15
23
  */
16
- export declare function runFastCore(argv: readonly string[], parsed?: ReturnType<typeof parseFastCore>): Promise<void>;
24
+ export declare function runFastCore(argv: readonly string[], parsed?: ReturnType<typeof parseFastCore>, recoverImport?: RecoverFastImport): Promise<void>;
17
25
  /**
18
26
  * Resolve valid explicit roots for the two measured hook routes without loading
19
27
  * the general root resolver. Anything uncertain falls through to that resolver,
20
28
  * which retains its diagnostics and cwd-discovery behavior.
21
29
  */
22
30
  export declare function resolveExplicitFastLiveRoot(hqRootArg: string | undefined, env: NodeJS.ProcessEnv): string | undefined;
31
+ export {};
23
32
  //# sourceMappingURL=fast-core.d.ts.map
package/dist/fast-core.js CHANGED
@@ -33,7 +33,7 @@ export async function readStdinIfPiped(stream, maxBytes) {
33
33
  * Only call when {@link isFastCoreRequest} is true; a non-match returns without
34
34
  * doing anything.
35
35
  */
36
- export async function runFastCore(argv, parsed = parseFastCore(argv)) {
36
+ export async function runFastCore(argv, parsed = parseFastCore(argv), recoverImport = async (load) => ({ module: await load() })) {
37
37
  if (!parsed)
38
38
  return;
39
39
  const profile = wantsFastRouteProfile(process.env);
@@ -54,7 +54,14 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
54
54
  familyEnabled = envOverride;
55
55
  }
56
56
  else {
57
- const { fastFlagEnabled, fastFlagEnabledDefaultOn } = await import("./native-fast-route.js");
57
+ const loaded = await recoverImport(() => import("./native-fast-route.js"));
58
+ if (loaded.reexecStatus !== undefined) {
59
+ process.exitCode = loaded.reexecStatus;
60
+ return;
61
+ }
62
+ if (!loaded.module)
63
+ throw new Error("Native fast-route module did not load");
64
+ const { fastFlagEnabled, fastFlagEnabledDefaultOn } = loaded.module;
58
65
  const readFamilyFlag = isDefaultOnFastFamily(entry.familyFlagKey)
59
66
  ? fastFlagEnabledDefaultOn
60
67
  : fastFlagEnabled;
@@ -62,15 +69,28 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
62
69
  }
63
70
  }
64
71
  if (entry.kind === "native" && entry.fallbackToCommandGraph && !familyEnabled) {
65
- const { runCli } = await import("./main.js");
66
- await runCli();
72
+ const loaded = await recoverImport(() => import("./main.js"));
73
+ if (loaded.reexecStatus !== undefined) {
74
+ process.exitCode = loaded.reexecStatus;
75
+ return;
76
+ }
77
+ if (!loaded.module)
78
+ throw new Error("Main command graph did not load");
79
+ await loaded.module.runCli();
67
80
  return;
68
81
  }
69
82
  familyFlagMs = profileElapsed(profileStart, profileMark());
70
83
  const useShell = entry.kind === "native" && Boolean(entry.familyFlagKey) && !familyEnabled;
71
84
  let worktreeScopeFixEnabled;
72
85
  if (entry.name === "repo-run-registry") {
73
- const { coreRrrWorktreeScopeFixFastFlagEnabled } = await import("./commands/core-rrr-worktree-scope-fix-fast-flag.js");
86
+ const loaded = await recoverImport(() => import("./commands/core-rrr-worktree-scope-fix-fast-flag.js"));
87
+ if (loaded.reexecStatus !== undefined) {
88
+ process.exitCode = loaded.reexecStatus;
89
+ return;
90
+ }
91
+ if (!loaded.module)
92
+ throw new Error("Repo-run-registry flag module did not load");
93
+ const { coreRrrWorktreeScopeFixFastFlagEnabled } = loaded.module;
74
94
  worktreeScopeFixEnabled = await coreRrrWorktreeScopeFixFastFlagEnabled(process.env);
75
95
  }
76
96
  const shouldInjectRoot = useShell
@@ -96,14 +116,27 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
96
116
  hqRoot = shortcutRoot;
97
117
  }
98
118
  else {
99
- const { resolveLiveRoot } = await import("./utils/hq-roots-core.js");
119
+ const loaded = await recoverImport(() => import("./utils/hq-roots-core.js"));
120
+ if (loaded.reexecStatus !== undefined) {
121
+ process.exitCode = loaded.reexecStatus;
122
+ return;
123
+ }
124
+ if (!loaded.module)
125
+ throw new Error("HQ root resolver did not load");
126
+ const { resolveLiveRoot } = loaded.module;
100
127
  try {
101
128
  hqRoot = resolveLiveRoot(parsed.hqRootArg ? { hqRoot: parsed.hqRootArg } : {});
102
129
  }
103
130
  catch {
104
131
  if (entry.kind === "native") {
105
- const { runCli } = await import("./main.js");
106
- await runCli();
132
+ const loaded = await recoverImport(() => import("./main.js"));
133
+ if (loaded.reexecStatus !== undefined) {
134
+ process.exitCode = loaded.reexecStatus;
135
+ return;
136
+ }
137
+ if (!loaded.module)
138
+ throw new Error("Main command graph did not load");
139
+ await loaded.module.runCli();
107
140
  return;
108
141
  }
109
142
  hqRoot = undefined;
@@ -113,9 +146,22 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
113
146
  rootResolutionMs = profileElapsed(rootResolutionStartedAt, profileMark());
114
147
  if (entry.kind === "native" && !useShell) {
115
148
  const handlerImportStartedAt = profileMark();
116
- const { installFastPathStreamErrorHandlers } = await import("./utils/fast-stream-errors.js");
117
- installFastPathStreamErrorHandlers();
118
- const handlerModule = await import(entry.modulePath);
149
+ const errorHandlers = await recoverImport(() => import("./utils/fast-stream-errors.js"));
150
+ if (errorHandlers.reexecStatus !== undefined) {
151
+ process.exitCode = errorHandlers.reexecStatus;
152
+ return;
153
+ }
154
+ if (!errorHandlers.module)
155
+ throw new Error("Fast-path stream error handlers did not load");
156
+ errorHandlers.module.installFastPathStreamErrorHandlers();
157
+ const loadedHandler = await recoverImport(() => import(entry.modulePath));
158
+ if (loadedHandler.reexecStatus !== undefined) {
159
+ process.exitCode = loadedHandler.reexecStatus;
160
+ return;
161
+ }
162
+ if (!loadedHandler.module)
163
+ throw new Error("Native fast handler module did not load");
164
+ const handlerModule = loadedHandler.module;
119
165
  const handler = handlerModule[entry.exportName];
120
166
  if (typeof handler !== "function") {
121
167
  throw new Error(`Native fast handler ${entry.exportName} was not exported`);
@@ -140,8 +186,16 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
140
186
  if (!entry.fallbackToCommandGraph) {
141
187
  throw new Error(`Native fast handler ${entry.exportName} requested an unavailable command-graph fallback`);
142
188
  }
143
- const { runCli } = await import("./main.js");
144
- await runCli();
189
+ // The fast handler has already run (and may have consumed stdin). If a
190
+ // torn main import is detected now, report it without replaying the command.
191
+ const loaded = await recoverImport(() => import("./main.js"), { reexecOnSettled: false });
192
+ if (loaded.reexecStatus !== undefined) {
193
+ process.exitCode = loaded.reexecStatus;
194
+ return;
195
+ }
196
+ if (!loaded.module)
197
+ throw new Error("Main command graph did not load");
198
+ await loaded.module.runCli();
145
199
  return;
146
200
  }
147
201
  // Let Node drain handler output before terminating. The timeout guard's
@@ -167,7 +221,14 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
167
221
  ...(entry.name === "repo-run-registry" ? { HQ_FLAG_CORE_RRR_WORKTREE_SCOPE_FIX: String(worktreeScopeFixEnabled) } : {}),
168
222
  };
169
223
  // Keep the rollback runner off native commands' import path.
170
- const { runBundledScript } = await import("./utils/run-bundled-script.js");
224
+ const runner = await recoverImport(() => import("./utils/run-bundled-script.js"));
225
+ if (runner.reexecStatus !== undefined) {
226
+ process.exitCode = runner.reexecStatus;
227
+ return;
228
+ }
229
+ if (!runner.module)
230
+ throw new Error("Bundled-script runner did not load");
231
+ const { runBundledScript } = runner.module;
171
232
  const { code } = runBundledScript({
172
233
  asset,
173
234
  args: parsed.args,
package/dist/index.js CHANGED
@@ -59,6 +59,80 @@ async function runMainWithInstallTreeRecovery() {
59
59
  throw error;
60
60
  }
61
61
  }
62
+ /**
63
+ * Fast-route imports happen before main.ts command registration, so the
64
+ * registration recovery seam cannot see a torn fast module unless each import
65
+ * is run through the same bounded recovery boundary. Imports before handler
66
+ * execution may re-exec after a settled repair; the post-handler fallback passes
67
+ * reexecOnSettled=false so a torn main import is reported without replaying the
68
+ * handler or losing the stdin it already consumed.
69
+ */
70
+ const recoverFastImport = async (load, options = {}) => {
71
+ try {
72
+ return { module: await load() };
73
+ }
74
+ catch {
75
+ // Keep the clean hook path free of startup-registration's update-lock and
76
+ // package-root imports. Only a failed import pays for the recovery seam.
77
+ const { registerCommandsWithRecovery } = await import("./startup-registration.js");
78
+ let module;
79
+ try {
80
+ const result = await registerCommandsWithRecovery({
81
+ register: async () => {
82
+ module = await load();
83
+ },
84
+ argv: process.argv,
85
+ env: process.env,
86
+ entryPath: fileURLToPath(import.meta.url),
87
+ stderr: process.stderr,
88
+ reexecOnSettled: options.reexecOnSettled,
89
+ });
90
+ return result.reexecStatus === undefined
91
+ ? { module }
92
+ : { reexecStatus: result.reexecStatus };
93
+ }
94
+ catch (error) {
95
+ // Fast-route imports precede main.ts/Sentry initialization. When recovery
96
+ // cannot repair a still-absent module, report it here as the registration
97
+ // path does instead of relying on the later unhandled-rejection boundary.
98
+ const installTree = await import("./utils/install-tree-torn.js");
99
+ if (!installTree.isInstallTreeTornError(error))
100
+ throw error;
101
+ process.stderr.write(`${installTree.installTreeTornStderrLine(error)}\n`);
102
+ const sentry = await import("./sentry.js");
103
+ sentry.initSentry();
104
+ sentry.Sentry.captureException(error, installTree.installTreeTornCaptureContext(error));
105
+ sentry.finishSentrySession();
106
+ await sentry.Sentry.flush(2_000);
107
+ return { reexecStatus: 1 };
108
+ }
109
+ }
110
+ };
111
+ async function runFastCoreWithRecovery(parsed) {
112
+ const loaded = await recoverFastImport(() => import("./fast-core.js"));
113
+ if (loaded.reexecStatus !== undefined) {
114
+ process.exitCode = loaded.reexecStatus;
115
+ return;
116
+ }
117
+ if (!loaded.module)
118
+ throw new Error("Fast core module did not load");
119
+ await loaded.module.runFastCore(process.argv, parsed, recoverFastImport);
120
+ }
121
+ async function runNativeFastRouteWithRecovery(parsed) {
122
+ const loaded = await recoverFastImport(() => import("./native-fast-route.js"));
123
+ if (loaded.reexecStatus !== undefined) {
124
+ process.exitCode = loaded.reexecStatus;
125
+ return;
126
+ }
127
+ if (!loaded.module)
128
+ throw new Error("Native fast-route module did not load");
129
+ if (await loaded.module.nativeFastRouteEnabled(process.argv)) {
130
+ await runFastCoreWithRecovery(parsed);
131
+ }
132
+ else {
133
+ await runMainWithInstallTreeRecovery();
134
+ }
135
+ }
62
136
  function dispatchCliEntry() {
63
137
  const versionRequest = isVersionRequest(process.argv);
64
138
  const helpRequest = !versionRequest && isCommandHelpRequest(process.argv);
@@ -99,21 +173,13 @@ function dispatchCliEntry() {
99
173
  // An explicit enabled override has already decided the global gate. Skip the
100
174
  // snapshot reader import on hot native plumbing paths; runFastCore still
101
175
  // checks the command-family flag before choosing native or shell rollback.
102
- void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv, parsedFastCore));
176
+ void runFastCoreWithRecovery(parsedFastCore);
103
177
  }
104
178
  else if (parsedFastCore?.entry.kind === "native") {
105
179
  // Native routes are default-off. Resolve the company-scoped persisted flag
106
180
  // without loading the command graph; a miss or stale snapshot takes the
107
181
  // existing full-graph route.
108
- void import("./native-fast-route.js").then(async ({ nativeFastRouteEnabled }) => {
109
- if (await nativeFastRouteEnabled(process.argv)) {
110
- const { runFastCore } = await import("./fast-core.js");
111
- await runFastCore(process.argv, parsedFastCore);
112
- }
113
- else {
114
- await runMainWithInstallTreeRecovery();
115
- }
116
- });
182
+ void runNativeFastRouteWithRecovery(parsedFastCore);
117
183
  }
118
184
  else if (parseSecretsEnvFastRequest(process.argv)) {
119
185
  const request = parseSecretsEnvFastRequest(process.argv);
@@ -138,7 +204,7 @@ function dispatchCliEntry() {
138
204
  // Hot relocated plumbing (`hq core hq-session`, `hq core checkpoint-stop-gate`)
139
205
  // dispatches straight to the bundled-script runner, so it never pays for the
140
206
  // ~60-module command graph main.ts imports. See src/fast-scaffold.ts.
141
- void import("./fast-core.js").then(({ runFastCore }) => runFastCore(process.argv, parsedFastCore));
207
+ void runFastCoreWithRecovery(parsedFastCore);
142
208
  }
143
209
  else {
144
210
  // Keep the command lifecycle detached from module evaluation, as it was
@@ -88,6 +88,8 @@ export interface RegisterCommandsWithRecoveryArgs {
88
88
  /** The RUNNING install's own `dist/index.js` — the safe re-exec target. */
89
89
  entryPath: string;
90
90
  stderr: Pick<typeof process.stderr, "write">;
91
+ /** False when command execution already happened and must not be replayed. */
92
+ reexecOnSettled?: boolean;
91
93
  deps?: RegisterRecoveryDeps;
92
94
  }
93
95
  export interface RegisterCommandsWithRecoveryResult {
@@ -179,6 +179,26 @@ export async function registerCommandsWithRecovery(args) {
179
179
  const treeState = result.sawLock || result.sawRetired || result.sawTreeChange
180
180
  ? "in_flight"
181
181
  : "stale";
182
+ if (args.reexecOnSettled === false) {
183
+ throw new InstallTreeTornError({
184
+ cause: err,
185
+ classified,
186
+ packageRoot,
187
+ versionGatePhase: readVersionGatePhase(deps),
188
+ outcome: "replay-unsafe",
189
+ treeState,
190
+ attempt: 1,
191
+ waitedMs: result.waitedMs,
192
+ sawLock: result.sawLock,
193
+ sawRetired: result.sawRetired,
194
+ retiredSiblingPresent: result.retiredSiblingPresent,
195
+ retiredSiblingKind: result.retiredSiblingKind,
196
+ installTargetState: result.installTargetState,
197
+ updaterPidAlive: result.updaterPidAlive,
198
+ sawTreeChange: result.sawTreeChange,
199
+ lockTool: result.lockTool,
200
+ });
201
+ }
182
202
  const child = spawn(process.execPath, [...execArgv, entryPath, ...argv.slice(2)], {
183
203
  stdio: "inherit",
184
204
  env: {
@@ -336,7 +336,7 @@ export interface InstallRewriteSignalArgs {
336
336
  */
337
337
  export declare function installRewriteInProgress(args: InstallRewriteSignalArgs): boolean;
338
338
  /** The recovery OUTCOME, which — not the dialect — decides capture. */
339
- export type InstallTreeTornOutcome = "guarded" | "unsettled" | "reexec-failed" | "lock-held" | "stale_tree" | "retired_unverified";
339
+ export type InstallTreeTornOutcome = "guarded" | "replay-unsafe" | "unsettled" | "reexec-failed" | "lock-held" | "stale_tree" | "retired_unverified";
340
340
  type InstallTreeState = "in_flight" | "stale";
341
341
  export type InstallTreeTornDiagnostics = {
342
342
  dialect: ModuleErrorDialect;
@@ -185,6 +185,7 @@ const KNOWN_INSTALL_TREE_UPDATERS = new Map([
185
185
  ]);
186
186
  const KNOWN_INSTALL_TREE_OUTCOMES = new Set([
187
187
  "guarded",
188
+ "replay-unsafe",
188
189
  "unsettled",
189
190
  "reexec-failed",
190
191
  "lock-held",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.69",
3
+ "version": "5.345.70",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {