@indigoai-us/hq-cli 5.345.28 → 5.345.30

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,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.30] — 2026-10-06
6
+ - Sanitize malformed journal scope identifiers before composing per-scope check IDs.
7
+
8
+ - Sync the bundled outpost reconciler with staging's Bash 3.2-safe PATH handling.
9
+
10
+ ## [5.345.29] — 2026-10-06
11
+ - Route `hq core checkpoint` through an isolated fast entry and defer identity and ontology imports to the paths that use them.
12
+
13
+ - Add folder-based company registry resolution to the native resolve-company fast path.
14
+
5
15
  ## [5.345.28] — 2026-10-06
6
16
  - Limit `hq meetings live` to active, recent, and imminent notetakers by default, and avoid transcript requests for inactive meetings.
7
17
 
@@ -73,7 +73,7 @@ if [ "$DRY_RUN" -eq 1 ]; then
73
73
  done
74
74
  SECRET_NAMES="(none — no .env.schema found)"
75
75
  if [ -n "$SCHEMA" ]; then
76
- SECRET_NAMES="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*=' "$SCHEMA" | cut -d= -f1 | sort -u | tr '\n' ' ')"
76
+ SECRET_NAMES="$(grep -E '^[A-Za-z_][A-Za-z0-9_]*(/[A-Za-z_][A-Za-z0-9_]*)*=' "$SCHEMA" | cut -d= -f1 | sort -u | tr '\n' ' ')"
77
77
  [ -n "$SECRET_NAMES" ] || SECRET_NAMES="(none declared)"
78
78
  fi
79
79
 
@@ -8,26 +8,36 @@
8
8
  #
9
9
  # Output: {"company":"<slug>","source":"prompt|session|device_default|none"}
10
10
  # Always exits 0 and never writes state.
11
+ #
12
+ # Folder mode (--path <dir>): ask the repo-to-company registry through
13
+ # `$HQ_CLI_BIN resolve-company --path <dir> --json` (HQ_CLI_BIN defaults to
14
+ # `hq`). Sources: registry (linked folder), registry_miss (not linked), or
15
+ # unavailable (CLI missing, failed, timed out, or returned bad output). A
16
+ # registry answer is accepted only when it names a real companies/<slug>
17
+ # directory. Folder mode never falls back to prompt, session, or device default.
11
18
 
12
19
  set -uo pipefail
13
20
 
14
21
  ROOT=""
15
22
  PROMPT=""
16
23
  HAVE_PROMPT=0
24
+ FOLDER=""
25
+ HAVE_FOLDER=0
17
26
 
18
27
  while [ $# -gt 0 ]; do
19
28
  case "$1" in
20
29
  --root) ROOT="${2:-}"; shift 2 || shift ;;
21
30
  --prompt) PROMPT="${2:-}"; HAVE_PROMPT=1; shift 2 || shift ;;
31
+ --path) FOLDER="${2:-}"; HAVE_FOLDER=1; shift 2 || shift ;;
22
32
  --help|-h)
23
- sed -n '2,14p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
33
+ sed -n '2,21p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
24
34
  exit 0
25
35
  ;;
26
36
  *) shift ;;
27
37
  esac
28
38
  done
29
39
 
30
- if [ "$HAVE_PROMPT" -eq 0 ] && [ ! -t 0 ]; then
40
+ if [ "$HAVE_PROMPT" -eq 0 ] && [ "$HAVE_FOLDER" -eq 0 ] && [ ! -t 0 ]; then
31
41
  PROMPT="$(cat 2>/dev/null || true)"
32
42
  fi
33
43
  : "${PROMPT:=}"
@@ -41,6 +51,150 @@ if [ -z "$ROOT" ]; then
41
51
  ROOT="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && cd .. && pwd)}}"
42
52
  fi
43
53
 
54
+ # Runs a command with a hard deadline (HQ_RESOLVE_TIMEOUT_MS, default 2000).
55
+ run_with_timeout() {
56
+ command -v node >/dev/null 2>&1 || return 127
57
+ node -e '
58
+ const { spawn } = require("child_process");
59
+ const command = process.argv[1];
60
+ const args = process.argv.slice(2);
61
+ const timeoutMs = Number(process.env.HQ_RESOLVE_TIMEOUT_MS) || 2000;
62
+ if (!command) process.exitCode = 127;
63
+ else {
64
+ let child;
65
+ try {
66
+ child = spawn(command, args, { detached: true, stdio: ["ignore", "pipe", "ignore"] });
67
+ } catch {
68
+ process.exitCode = 127;
69
+ }
70
+ if (child) {
71
+ let exited = false;
72
+ let exitStatus = 1;
73
+ let timedOut = false;
74
+ let drainExpired = false;
75
+ let pendingWrites = 0;
76
+ let finished = false;
77
+ let drainTimer;
78
+ let timeoutKillTimer;
79
+ let timeoutFinalTimer;
80
+ let deadlineTimer;
81
+ const signalGroup = (signal) => {
82
+ if (!child.pid) return;
83
+ try {
84
+ if (process.platform === "win32") child.kill(signal);
85
+ else process.kill(-child.pid, signal);
86
+ } catch {}
87
+ };
88
+ const maybeFinish = () => {
89
+ if (finished || !exited || pendingWrites !== 0 || (!drainExpired && !child.stdout.readableEnded)) return;
90
+ finished = true;
91
+ clearTimeout(deadlineTimer);
92
+ clearTimeout(drainTimer);
93
+ clearTimeout(timeoutKillTimer);
94
+ clearTimeout(timeoutFinalTimer);
95
+ signalGroup("SIGTERM");
96
+ process.exitCode = timedOut ? 124 : exitStatus;
97
+ process.exit();
98
+ };
99
+ const writeChunk = (chunk) => {
100
+ pendingWrites += 1;
101
+ process.stdout.write(chunk, () => {
102
+ pendingWrites -= 1;
103
+ maybeFinish();
104
+ });
105
+ };
106
+ child.stdout.on("data", writeChunk);
107
+ child.stdout.once("end", () => {
108
+ drainExpired = true;
109
+ maybeFinish();
110
+ });
111
+ child.once("error", () => {
112
+ exited = true;
113
+ exitStatus = 127;
114
+ drainExpired = true;
115
+ maybeFinish();
116
+ });
117
+ child.once("exit", (code) => {
118
+ exited = true;
119
+ exitStatus = typeof code === "number" ? code : 1;
120
+ signalGroup("SIGTERM");
121
+ if (!child.stdout.readableEnded) {
122
+ drainTimer = setTimeout(() => {
123
+ child.stdout.pause();
124
+ let chunk;
125
+ while ((chunk = child.stdout.read()) !== null) writeChunk(chunk);
126
+ process.stdout.write("", () => {
127
+ drainExpired = true;
128
+ maybeFinish();
129
+ });
130
+ }, 100);
131
+ } else {
132
+ drainExpired = true;
133
+ }
134
+ maybeFinish();
135
+ });
136
+ deadlineTimer = setTimeout(() => {
137
+ timedOut = true;
138
+ signalGroup("SIGTERM");
139
+ timeoutKillTimer = setTimeout(() => signalGroup("SIGKILL"), 100);
140
+ timeoutFinalTimer = setTimeout(() => {
141
+ if (!exited) {
142
+ exited = true;
143
+ exitStatus = 124;
144
+ child.stdout.destroy();
145
+ drainExpired = true;
146
+ maybeFinish();
147
+ }
148
+ }, 1000);
149
+ }, timeoutMs);
150
+ }
151
+ }
152
+ ' "$@"
153
+ }
154
+
155
+ unavailable() {
156
+ printf 'resolve-company: WARNING company registry unavailable (%s); binding personal, not guessing a company\n' "$1" >&2
157
+ emit "" "unavailable"
158
+ }
159
+
160
+ if [ "$HAVE_FOLDER" -eq 1 ]; then
161
+ [ -n "$FOLDER" ] || unavailable "empty --path"
162
+ case "$FOLDER" in
163
+ /*|[A-Za-z]:/*|[A-Za-z]:\\*|\\\\*) ;;
164
+ *)
165
+ caller_cwd="${HQ_CALLER_CWD:-$PWD}"
166
+ caller_cwd="$(cd "$caller_cwd" 2>/dev/null && pwd -P)" || unavailable "caller working directory unavailable"
167
+ if [ "$FOLDER" = "." ]; then FOLDER="$caller_cwd"; else FOLDER="$caller_cwd/$FOLDER"; fi
168
+ ;;
169
+ esac
170
+ cli="${HQ_CLI_BIN:-hq}"
171
+ command -v "$cli" >/dev/null 2>&1 || unavailable "'$cli' not found"
172
+ command -v jq >/dev/null 2>&1 || unavailable "jq not found"
173
+ status=0
174
+ if command -v node >/dev/null 2>&1; then
175
+ out="$(HQ_NO_UPDATE_CHECK=1 HQ_RESOLVE_TIMEOUT_MS="${HQ_RESOLVE_TIMEOUT_MS:-5000}" run_with_timeout "$cli" resolve-company --path "$FOLDER" --json)" || status=$?
176
+ else
177
+ out="$("$cli" resolve-company --path "$FOLDER" --json 2>/dev/null)" || status=$?
178
+ fi
179
+ kind="$(printf '%s' "$out" | jq -r 'if type != "object" then "bad" elif .company == null then "miss" elif (.company | type) == "string" then "hit" else "bad" end' 2>/dev/null | sed 's/\r$//' || printf 'bad')"
180
+ if [ "$status" -ne 0 ] && ! { [ "$status" -eq 1 ] && [ "$kind" = "miss" ]; }; then
181
+ unavailable "'$cli resolve-company' exited $status"
182
+ fi
183
+ case "$kind" in
184
+ miss) emit "" "registry_miss" ;;
185
+ hit) ;;
186
+ *) unavailable "'$cli resolve-company' returned unreadable output" ;;
187
+ esac
188
+ slug="$(printf '%s' "$out" | jq -r '.company' | sed 's/\r$//')"
189
+ case "$slug" in
190
+ ''|personal|_*|*[!A-Za-z0-9_-]*) unavailable "registry returned invalid company '$slug'" ;;
191
+ esac
192
+ if [ ! -d "$ROOT/companies/$slug" ] || [ -L "$ROOT/companies/$slug" ]; then
193
+ unavailable "registry company '$slug' has no companies/$slug directory in $ROOT"
194
+ fi
195
+ emit "$slug" "registry"
196
+ fi
197
+
44
198
  MANIFEST="$ROOT/companies/manifest.yaml"
45
199
  [ -f "$MANIFEST" ] || emit "" "none"
46
200
 
@@ -65,7 +219,7 @@ SLUGS="$(
65
219
  [ -n "$SLUGS" ] || emit "" "none"
66
220
 
67
221
  is_known_slug() {
68
- printf '%s\n' "$SLUGS" | grep -Fx -- "$1" >/dev/null
222
+ printf '%s\n' "$SLUGS" | grep -Fx "$1" >/dev/null
69
223
  }
70
224
 
71
225
  prompt_slug() {
@@ -134,72 +288,6 @@ is_fleet_identity() {
134
288
  return 1
135
289
  }
136
290
 
137
- run_with_timeout() {
138
- command -v node >/dev/null 2>&1 || return 127
139
- node -e '
140
- const { spawn } = require("child_process");
141
- const command = process.argv[1];
142
- if (!command) process.exit(127);
143
- const child = spawn(command, process.argv.slice(2), { detached: true, stdio: ["ignore", "pipe", "ignore"] });
144
- let finished = false;
145
- let exited = false;
146
- let exitStatus = 1;
147
- let flushedAfterExit = false;
148
- let pendingWrites = 0;
149
- let stdoutEnded = false;
150
- let drainTimer;
151
- const finishWhenReady = () => {
152
- if (finished || !exited || pendingWrites !== 0) return;
153
- if (!stdoutEnded && !flushedAfterExit) return;
154
- finished = true;
155
- clearTimeout(timer);
156
- clearTimeout(drainTimer);
157
- try { process.kill(-child.pid, "SIGTERM"); } catch (_) {}
158
- process.exit(exitStatus);
159
- };
160
- const forward = (chunk) => {
161
- pendingWrites++;
162
- process.stdout.write(chunk, () => {
163
- pendingWrites--;
164
- finishWhenReady();
165
- });
166
- };
167
- const finish = (status) => {
168
- if (finished) return;
169
- finished = true;
170
- clearTimeout(timer);
171
- clearTimeout(drainTimer);
172
- try { process.kill(-child.pid, "SIGTERM"); } catch (_) {}
173
- process.exit(status);
174
- };
175
- child.stdout.on("data", forward);
176
- child.stdout.once("end", () => {
177
- stdoutEnded = true;
178
- finishWhenReady();
179
- });
180
- child.once("error", () => finish(127));
181
- child.once("exit", (code) => {
182
- exited = true;
183
- exitStatus = typeof code === "number" ? code : 1;
184
- try { process.kill(-child.pid, "SIGTERM"); } catch (_) {}
185
- // Normally EOF follows child exit once the pipe has drained. A detached
186
- // grandchild can keep the pipe open, so bound that wait and flush buffered
187
- // bytes after a short drain window.
188
- drainTimer = setTimeout(() => {
189
- child.stdout.pause();
190
- let chunk;
191
- while ((chunk = child.stdout.read()) !== null) forward(chunk);
192
- process.stdout.write("", () => {
193
- flushedAfterExit = true;
194
- finishWhenReady();
195
- });
196
- }, 250);
197
- finishWhenReady();
198
- });
199
- const timer = setTimeout(() => finish(124), 2000);
200
- ' "$@"
201
- }
202
-
203
291
  if ! is_fleet_identity && command -v hq >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
204
292
  DEFAULT_JSON="$(run_with_timeout hq mesh context default get --json 2>/dev/null || true)"
205
293
  DEFAULT_SLUG="$(printf '%s' "$DEFAULT_JSON" | jq -er '(.defaultCompany // .) as $default | if $default.enabled == true and ($default.source // "") != "disabled" and ($default.needsChoice // false) == false and ($default.slug | type == "string") then $default.slug else empty end' 2>/dev/null || true)"
@@ -0,0 +1,3 @@
1
+ /** Dispatch through the same command action as the full graph, without loading it. */
2
+ export declare function runCheckpointFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined, _commandName: string, hqRootArg: string | undefined): Promise<number>;
3
+ //# sourceMappingURL=core-checkpoint-fast.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { Command, Option } from "commander";
2
+ import { registerCoreCheckpointCommand } from "./core-checkpoint-native.js";
3
+ /** Dispatch through the same command action as the full graph, without loading it. */
4
+ export async function runCheckpointFast(args, _stdin, _cwd, hqRoot, _commandName, hqRootArg) {
5
+ const core = new Command("hq core")
6
+ .addOption(new Option("--hq-root <path>", "HQ installation to operate on (live-root commands)").hideHelp())
7
+ .addHelpText("afterAll", "exact-company-directory-match-v1");
8
+ registerCoreCheckpointCommand(core, hqRoot);
9
+ const parseArgs = [
10
+ ...(hqRootArg || hqRoot ? ["--hq-root", hqRootArg ?? hqRoot] : []),
11
+ "checkpoint",
12
+ ...args,
13
+ ];
14
+ await core.parseAsync(parseArgs, { from: "user" });
15
+ return typeof process.exitCode === "number" ? process.exitCode : 0;
16
+ }
17
+ //# sourceMappingURL=core-checkpoint-fast.js.map
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `hq core checkpoint` — synchronous end-of-turn state capture plus an
3
+ * optional detached HQ-maintenance sibling.
4
+ *
5
+ * The foreground command intentionally performs only local filesystem and git
6
+ * reads/writes. It never waits for the sibling, and it never performs network
7
+ * I/O itself.
8
+ */
9
+ import * as fs from "node:fs";
10
+ import { Command } from "commander";
11
+ type Backend = "claude" | "codex" | "grok" | "none";
12
+ type SpawnableBackend = Exclude<Backend, "none">;
13
+ /**
14
+ * Printed after every non-dry-run checkpoint, real or --idle. Agents were
15
+ * treating the checkpoint call as the end of the turn, leaving their actual
16
+ * findings only in the checkpoint payload — which the user never sees. This
17
+ * line rides the command output (the one channel guaranteed to reach the
18
+ * calling agent) to force the user-facing reply. Deliberately conditional on
19
+ * whether the reply was already delivered this turn: an unconditional
20
+ * "restate in full" made agents repeat a reply the user had already seen
21
+ * (both the CLI and the desktop app render pre-tool-call text), while an
22
+ * unconditional "no commentary after" hid the reply from agents that had not
23
+ * yet written one. The Stop gate makes the same distinction from the
24
+ * transcript — and backstops this reminder mechanically: a satisfying
25
+ * checkpoint on a work turn whose genuine reply is still missing is blocked
26
+ * once with a dedicated reply demand. This line covers proactive checkpoints
27
+ * the gate never sees. Mirrors the hq-core policies
28
+ * `checkpoint-is-bookkeeping-not-user-communication` and
29
+ * `checkpoint-is-not-the-user-report`.
30
+ */
31
+ export declare const CHECKPOINT_REPLY_REMINDER: string;
32
+ /**
33
+ * The --idle variant: an idle-only turn (nothing ran, nothing to report) is
34
+ * exempt at the Stop gate, so this text must never order a reply into
35
+ * existence — but an --idle after read-only work is NOT exempt (the gate
36
+ * demands the findings), so the conditional half still points there.
37
+ */
38
+ export declare const CHECKPOINT_IDLE_REPLY_REMINDER: string;
39
+ export declare function renderSiblingPrompt(runDir: string, payloadPath: string, options?: {
40
+ company?: string | null;
41
+ hqRoot?: string;
42
+ }): string;
43
+ export type WriteStampsOptions = {
44
+ /**
45
+ * Filesystem seam (default the real `node:fs`); injected in tests to force a
46
+ * permission errno so the HqStateWriteError conversion can be proven without a
47
+ * real unwritable path.
48
+ */
49
+ fs?: Pick<typeof fs, "mkdirSync" | "writeFileSync" | "renameSync" | "unlinkSync">;
50
+ /** Runtime seam for proving the Windows-only atomic path without host assumptions. */
51
+ platform?: NodeJS.Platform;
52
+ /** Synchronous delay seam for the bounded Windows lock retry. */
53
+ waitForRetry?: (delayMs: number) => void;
54
+ };
55
+ export declare function writeStamps(liveRoot: string, sessionId: string | undefined, options?: WriteStampsOptions): string[];
56
+ /**
57
+ * Preference order for `--backend auto`: the caller's own backend first, so a
58
+ * claude session keeps using claude and a codex session keeps using codex
59
+ * (same-engine continuity), then the fallback priority claude -> codex -> grok
60
+ * for whatever the caller is not. Deterministic and env-only, so it is unit
61
+ * tested directly. `preferGrok` (the company's grok-backend flag) puts grok
62
+ * ahead of that order and leaves the rest as the fallback.
63
+ */
64
+ export declare function autoBackendPreference(preferGrok?: boolean): SpawnableBackend[];
65
+ /**
66
+ * Whether the payload company's last ontology plan recorded
67
+ * checkpoint.ontology.grok-backend as on. Reads only the local state file the
68
+ * detached sibling writes, so a foreground checkpoint never waits on the flag
69
+ * registry; a flag change takes effect from the checkpoint after the next plan.
70
+ */
71
+ export declare function companyPrefersGrok(liveRoot: string, companySlug: string | null | undefined): Promise<boolean>;
72
+ export declare function siblingArgs(backend: SpawnableBackend, prompt: string): string[];
73
+ /** Attach the native checkpoint command to the hidden `hq core` group. */
74
+ export declare function registerCoreCheckpointCommand(core: Command, preResolvedHqRoot?: string): void;
75
+ export {};
76
+ //# sourceMappingURL=core-checkpoint-native.d.ts.map