@indigoai-us/hq-cli 5.338.0 → 5.339.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.339.0] — 2026-10-03
6
+ - Reduce cold-start imports for the `checkpoint-stop-gate` and `session-journal` native fast routes.
7
+
5
8
  ## [5.338.0] — 2026-10-03
6
9
  - Preserve policy-trigger mutual exclusion without `flock` and honor configured stale-run thresholds without `yq`.
7
10
  - Add a manual macOS VM parity workflow for checking HQ CLI behavior in Tart.
@@ -1,4 +1,7 @@
1
- import { type SessionHelperRuntimeOverrides } from "./core-session-helper-runtime.js";
1
+ type SessionHelperRuntimeOverrides = {
2
+ env?: NodeJS.ProcessEnv;
3
+ now?: () => Date;
4
+ };
2
5
  export type SessionJournalOperationResult = {
3
6
  code: number;
4
7
  stdout: Buffer;
@@ -8,4 +11,5 @@ export type SessionJournalOperationResult = {
8
11
  export declare function runSessionJournalOperation(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, dependencies?: SessionHelperRuntimeOverrides): SessionJournalOperationResult;
9
12
  /** Native implementation of the fail-soft session journal helper. */
10
13
  export declare function runSessionJournalFast(args: readonly string[], stdin: string, cwd: string, hqRoot: string | undefined, dependencies?: SessionHelperRuntimeOverrides): number;
14
+ export {};
11
15
  //# sourceMappingURL=core-session-journal-native.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { resolveSessionHelperRuntime } from "./core-session-helper-runtime.js";
4
- import { resolveBundledAsset } from "../utils/hq-roots-core.js";
3
+ import { fileURLToPath } from "node:url";
5
4
  const USAGE = `session-journal.sh: usage:
6
5
  session-journal.sh write "<title>" [--files f1,f2,...] [--body-file PATH] [--session KEY]
7
6
  session-journal.sh list [--date YYYY-MM-DD]
@@ -56,6 +55,7 @@ function yamlDoubleQuoted(value) {
56
55
  }
57
56
  const warning = (message) => { process.stderr.write(`session-journal: ${message}\n`); };
58
57
  const journalDir = (root, date) => path.join(root, "workspace/threads/journal", date);
58
+ const sessionJournalAsset = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../assets/scaffold/core/scripts/session-journal.sh");
59
59
  const hasFile = (file) => {
60
60
  try {
61
61
  return fs.statSync(file).isFile();
@@ -190,11 +190,13 @@ function runToolCounter(root, action, sessionArg, env, now, writers) {
190
190
  const counter = path.join(dir, `.tool-count-${key}`);
191
191
  const lock = `${counter}.lock`;
192
192
  const reportDirectoryWriteFailure = (error, line) => {
193
- const code = error.code;
194
- if (code !== "EISDIR")
193
+ if (error.code !== "EISDIR")
195
194
  return;
196
- const asset = resolveBundledAsset("core/scripts/session-journal.sh");
197
- writers.stderr(`${asset}: line ${line}: ${counter}: Is a directory\n`);
195
+ if (!fs.existsSync(sessionJournalAsset)) {
196
+ throw new Error("Bundled asset is missing from the installed package: assets/scaffold/core/scripts/session-journal.sh\n" +
197
+ "This is a packaging fault — the file exists in source but was not published.");
198
+ }
199
+ writers.stderr(`${sessionJournalAsset}: line ${line}: ${counter}: Is a directory\n`);
198
200
  };
199
201
  if (action === "increment") {
200
202
  withCounterLock(lock, () => {
@@ -243,7 +245,10 @@ export function runSessionJournalFast(args, stdin, cwd, hqRoot, dependencies = {
243
245
  }
244
246
  function executeSessionJournal(args, cwd, hqRoot, dependencies, writers) {
245
247
  const warning = (message) => { writers.stderr(`session-journal: ${message}\n`); };
246
- const runtime = resolveSessionHelperRuntime(dependencies);
248
+ const runtime = {
249
+ env: dependencies.env ?? process.env,
250
+ now: dependencies.now ?? (() => new Date()),
251
+ };
247
252
  const today = () => runtime.now().toISOString().slice(0, 10);
248
253
  const nowIso = () => runtime.now().toISOString().replace(/\.\d{3}Z$/, "Z");
249
254
  const nowHhmm = () => runtime.now().toISOString().slice(11, 16) + "Z";
@@ -14,4 +14,10 @@ export declare function readStdinIfPiped(stream: NodeJS.ReadStream): Promise<str
14
14
  * doing anything.
15
15
  */
16
16
  export declare function runFastCore(argv: readonly string[], parsed?: ReturnType<typeof parseFastCore>): Promise<void>;
17
+ /**
18
+ * Resolve valid explicit roots for the two measured hook routes without loading
19
+ * the general root resolver. Anything uncertain falls through to that resolver,
20
+ * which retains its diagnostics and cwd-discovery behavior.
21
+ */
22
+ export declare function resolveExplicitFastLiveRoot(hqRootArg: string | undefined, env: NodeJS.ProcessEnv): string | undefined;
17
23
  //# sourceMappingURL=fast-core.d.ts.map
package/dist/fast-core.js CHANGED
@@ -6,7 +6,14 @@
6
6
  * entries use the bundled-script runner; native entries import their one handler
7
7
  * module. The registration in `core.ts` remains the complete fallback path.
8
8
  */
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import { fileURLToPath } from "node:url";
9
12
  import { parseFastCore } from "./fast-scaffold.js";
13
+ const FAST_ROUTE_PROFILE_TOKEN = "hq-fast-route";
14
+ function wantsFastRouteProfile(env) {
15
+ return env.NODE_DEBUG === FAST_ROUTE_PROFILE_TOKEN;
16
+ }
10
17
  export async function readStdinIfPiped(stream) {
11
18
  if (stream.isTTY)
12
19
  return "";
@@ -23,6 +30,14 @@ export async function readStdinIfPiped(stream) {
23
30
  export async function runFastCore(argv, parsed = parseFastCore(argv)) {
24
31
  if (!parsed)
25
32
  return;
33
+ const profile = wantsFastRouteProfile(process.env);
34
+ const profileStart = profile ? process.hrtime.bigint() : undefined;
35
+ let familyFlagMs = 0;
36
+ let rootResolutionMs = 0;
37
+ let handlerImportMs = 0;
38
+ let stdinReadMs = 0;
39
+ const profileMark = () => profile ? process.hrtime.bigint() : undefined;
40
+ const profileElapsed = (start, end) => start === undefined || end === undefined ? 0 : Number(end - start) / 1_000_000;
26
41
  const entry = parsed.entry;
27
42
  // The full graph's process gate accepts textual true/false overrides. It ignores
28
43
  // numeric env values, so preserve that behavior for family flags here too.
@@ -36,6 +51,7 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
36
51
  await runCli();
37
52
  return;
38
53
  }
54
+ familyFlagMs = profileElapsed(profileStart, profileMark());
39
55
  const useShell = entry.kind === "native" && Boolean(entry.familyFlagKey) && !familyEnabled;
40
56
  let worktreeScopeFixEnabled;
41
57
  if (entry.name === "repo-run-registry") {
@@ -53,21 +69,32 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
53
69
  // fail-soft behavior; newly routed commands fall back to the full graph so
54
70
  // its expected-user-error formatting remains authoritative.
55
71
  let hqRoot;
72
+ const rootResolutionStartedAt = profileMark();
56
73
  if (shouldInjectRoot) {
57
- const { resolveLiveRoot } = await import("./utils/hq-roots-core.js");
58
- try {
59
- hqRoot = resolveLiveRoot(parsed.hqRootArg ? { hqRoot: parsed.hqRootArg } : {});
74
+ const shortcutRoot = entry.name === "checkpoint-stop-gate" || entry.name === "session-journal"
75
+ ? resolveExplicitFastLiveRoot(parsed.hqRootArg, process.env)
76
+ : undefined;
77
+ if (shortcutRoot !== undefined) {
78
+ hqRoot = shortcutRoot;
60
79
  }
61
- catch {
62
- if (entry.kind === "native") {
63
- const { runCli } = await import("./main.js");
64
- await runCli();
65
- return;
80
+ else {
81
+ const { resolveLiveRoot } = await import("./utils/hq-roots-core.js");
82
+ try {
83
+ hqRoot = resolveLiveRoot(parsed.hqRootArg ? { hqRoot: parsed.hqRootArg } : {});
84
+ }
85
+ catch {
86
+ if (entry.kind === "native") {
87
+ const { runCli } = await import("./main.js");
88
+ await runCli();
89
+ return;
90
+ }
91
+ hqRoot = undefined;
66
92
  }
67
- hqRoot = undefined;
68
93
  }
69
94
  }
95
+ rootResolutionMs = profileElapsed(rootResolutionStartedAt, profileMark());
70
96
  if (entry.kind === "native" && !useShell) {
97
+ const handlerImportStartedAt = profileMark();
71
98
  const { installFastPathStreamErrorHandlers } = await import("./utils/fast-stream-errors.js");
72
99
  installFastPathStreamErrorHandlers();
73
100
  const handlerModule = await import(entry.modulePath);
@@ -75,8 +102,21 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
75
102
  if (typeof handler !== "function") {
76
103
  throw new Error(`Native fast handler ${entry.exportName} was not exported`);
77
104
  }
105
+ handlerImportMs = profileElapsed(handlerImportStartedAt, profileMark());
106
+ const stdinStartedAt = profileMark();
78
107
  const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
79
108
  const stdin = readsStdin ? await readStdinIfPiped(process.stdin) : "";
109
+ stdinReadMs = profileElapsed(stdinStartedAt, profileMark());
110
+ if (profile && profileStart !== undefined) {
111
+ process.stderr.write(`HQ_FAST_ROUTE_PROFILE ${JSON.stringify({
112
+ command: entry.name,
113
+ pre_handler_ms: profileElapsed(profileStart, profileMark()),
114
+ family_flag_ms: familyFlagMs,
115
+ root_resolution_ms: rootResolutionMs,
116
+ handler_import_ms: handlerImportMs,
117
+ stdin_read_ms: stdinReadMs,
118
+ })}\n`);
119
+ }
80
120
  const code = await handler(parsed.args, stdin, process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg, worktreeScopeFixEnabled);
81
121
  if (code === undefined) {
82
122
  if (!entry.fallbackToCommandGraph) {
@@ -120,6 +160,29 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
120
160
  // Propagate verbatim: these scripts use their exit code as their interface.
121
161
  process.exit(code);
122
162
  }
163
+ /**
164
+ * Resolve valid explicit roots for the two measured hook routes without loading
165
+ * the general root resolver. Anything uncertain falls through to that resolver,
166
+ * which retains its diagnostics and cwd-discovery behavior.
167
+ */
168
+ export function resolveExplicitFastLiveRoot(hqRootArg, env) {
169
+ const candidate = hqRootArg || env.HQ_ROOT || env.CLAUDE_PROJECT_DIR;
170
+ if (!candidate)
171
+ return undefined;
172
+ try {
173
+ const resolved = fs.realpathSync(candidate);
174
+ const packageRoot = fs.realpathSync(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
175
+ const relative = path.relative(packageRoot, resolved);
176
+ if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)))
177
+ return undefined;
178
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory())
179
+ return undefined;
180
+ return resolved;
181
+ }
182
+ catch {
183
+ return undefined;
184
+ }
185
+ }
123
186
  /** Match hq-flags-client's literal true/false env override without loading its module. */
124
187
  function localBooleanFlagOverride(flagKey, env) {
125
188
  const envKey = `HQ_FLAG_${flagKey.replace(/[.-]/g, "_").toUpperCase()}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.338.0",
3
+ "version": "5.339.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {