@indigoai-us/hq-cli 5.345.68 → 5.345.69

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 (34) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/assets/scaffold/core/scripts/hq-dm-bind.sh +5 -0
  3. package/dist/commands/core-checkpoint-stop-gate-native.js +13 -1
  4. package/dist/commands/core-delegate-w1b.js +2 -2
  5. package/dist/commands/core-delegate.js +2 -2
  6. package/dist/commands/core-dm-bind-native.d.ts +1 -1
  7. package/dist/commands/core-dm-bind-native.js +55 -52
  8. package/dist/commands/core-handoff-fast.js +13 -8
  9. package/dist/commands/core-handoff.js +10 -2
  10. package/dist/commands/core-native-family-flag.d.ts +7 -0
  11. package/dist/commands/core-native-family-flag.js +21 -0
  12. package/dist/commands/core-native-handoff-flag.js +2 -2
  13. package/dist/commands/core-native-hook-helpers-flag.js +2 -2
  14. package/dist/commands/core-native-jobs-flag.js +2 -2
  15. package/dist/commands/core-native-policy-flag.js +2 -2
  16. package/dist/commands/core-native-session-flag.js +2 -2
  17. package/dist/commands/core-native-utilities-flag.js +2 -2
  18. package/dist/commands/core-ontology-native.js +2 -2
  19. package/dist/commands/core-refresh-vault-access-native.js +14 -1
  20. package/dist/fast-core.js +12 -2
  21. package/dist/fast-scaffold.d.ts +3 -0
  22. package/dist/fast-scaffold.js +14 -0
  23. package/dist/lib/flag-registry.d.ts +4 -1
  24. package/dist/lib/flag-registry.js +18 -6
  25. package/dist/lib/flag-snapshot-file.d.ts +2 -0
  26. package/dist/lib/flag-snapshot-file.js +30 -0
  27. package/dist/lib/process-flag-company-context.d.ts +2 -0
  28. package/dist/lib/process-flag-company-context.js +15 -0
  29. package/dist/native-fast-route.d.ts +2 -2
  30. package/dist/native-fast-route.js +6 -8
  31. package/dist/utils/hq-spawn.d.ts +4 -0
  32. package/dist/utils/hq-spawn.js +11 -0
  33. package/dist/utils/resident-package-preload.js +4 -1
  34. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.69] — 2026-10-10
6
+ - 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
+
5
8
  ## [5.345.68] — 2026-10-09
6
9
 
7
10
  - Marketplace pack installs now require a signature from a pinned platform key. `--allow-unsigned` is limited to local directory sources; `HQ_PACK_PUBLIC_KEY` is development-only.
@@ -53,6 +53,11 @@
53
53
  # Need from you: line
54
54
  set -euo pipefail
55
55
 
56
+ if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
57
+ sed -n '2,/^set -euo pipefail$/ { /^set -euo pipefail$/!p; }' "$0"
58
+ exit 0
59
+ fi
60
+
56
61
  HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
57
62
  SESSION_SH="$HQ_ROOT/core/scripts/hq-session.sh"
58
63
 
@@ -1,5 +1,6 @@
1
1
  /** Native fast-route implementation of checkpoint-stop-gate.sh. */
2
2
  import * as childProcess from "node:child_process";
3
+ import { planHqSpawn } from "../utils/hq-spawn.js";
3
4
  import * as fs from "node:fs";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";
@@ -85,7 +86,18 @@ const defaults = () => ({
85
86
  return false;
86
87
  },
87
88
  spawnHqDetached: (args, env) => {
88
- const child = childProcess.spawn("hq", args, { detached: true, stdio: "ignore", env: env ?? process.env });
89
+ const childEnv = env ?? process.env;
90
+ const spawnPlan = planHqSpawn(args, childEnv, process.cwd());
91
+ const child = childProcess.spawn(spawnPlan.cmd, spawnPlan.args, {
92
+ detached: true,
93
+ stdio: "ignore",
94
+ env: childEnv,
95
+ shell: spawnPlan.shell,
96
+ ...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
97
+ });
98
+ child.once("error", (error) => {
99
+ process.stderr.write(`${error.message}\n`);
100
+ });
89
101
  child.unref();
90
102
  },
91
103
  });
@@ -1215,8 +1215,8 @@ export function registerCoreDelegateW1b(delegate, core) {
1215
1215
  const options = core.opts();
1216
1216
  const root = resolveLiveRoot({ hqRoot: options.hqRoot });
1217
1217
  const context = { cwd: root, root };
1218
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
1219
- if (!resolveProcessFlagGate(FLAG, { fallback: false }, () => false)) {
1218
+ const { coreNativeDelegateEnabled } = await import("./core-native-family-flag.js");
1219
+ if (!await coreNativeDelegateEnabled()) {
1220
1220
  const result = runBundledScript({ asset: ASSETS[name], args, cwd: root, hqRoot: root });
1221
1221
  if (result.code !== 0)
1222
1222
  process.exit(result.code);
@@ -1022,8 +1022,8 @@ export function registerCoreDelegateCommand(core, registerWave1b) {
1022
1022
  const context = rootMode === "cwd"
1023
1023
  ? { cwd: process.cwd() }
1024
1024
  : (() => { const root = resolveLiveRoot({ hqRoot: scope.hqRoot }); return { cwd: root, root }; })();
1025
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
1026
- if (!resolveProcessFlagGate(CORE_NATIVE_DELEGATE_FLAG, { fallback: false }, () => false)) {
1025
+ const { coreNativeDelegateEnabled } = await import("./core-native-family-flag.js");
1026
+ if (!await coreNativeDelegateEnabled()) {
1027
1027
  const result = runBundledScript({ asset: SCRIPT_ASSETS[name], args, cwd: context.cwd, hqRoot: context.root });
1028
1028
  if (result.code !== 0)
1029
1029
  process.exit(result.code);
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import type { Command } from "commander";
4
4
  import { type UtilityOutput } from "./core-native-utilities.js";
5
5
  export declare const DM_BIND_ASSET = "core/scripts/hq-dm-bind.sh";
6
- export declare const DM_BIND_HELP = "# hq-dm-bind.sh \u2014 bind the current session to one HQ DM channel, post\n# structured updates to it, and listen for replies (feedback / requests).\n#\n# Usage:\n# core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to \"now\"\n# core/scripts/hq-dm-bind.sh status # show the binding and cursor\n# core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]\n# core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)\n# core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)\n# core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention\n# core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it\n# core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]\n# # block until a new message arrives (or timeout)\n# core/scripts/hq-dm-bind.sh unbind\n#\n# The binding lives in the session's meta.yaml (`dm_channel`) via hq-session.sh;\n# the cursor is `workspace/sessions/<sid>/dm-bind.cursor` (the last seen sort\n# key from `hq dm channel --json`). `listen` is meant to run detached\n# (Bash run_in_background): it exits 0 and prints the new messages the moment\n# one lands, exits 3 on timeout with nothing new.\n#\n# Every post opens with an @-mention of every other member of the channel, so\n# the update reaches people (HQ mentions are structured: the `hq` CLI resolves\n# the `@\"Display Name\"` tokens against the roster and sends the real mention\n";
6
+ export declare const DM_BIND_HELP = "# hq-dm-bind.sh \u2014 bind the current session to one HQ DM channel, post\n# structured updates to it, and listen for replies (feedback / requests).\n#\n# Usage:\n# core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to \"now\"\n# core/scripts/hq-dm-bind.sh status # show the binding and cursor\n# core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]\n# core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)\n# core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)\n# core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention\n# core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it\n# core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]\n# # block until a new message arrives (or timeout)\n# core/scripts/hq-dm-bind.sh unbind\n#\n# The binding lives in the session's meta.yaml (`dm_channel`) via hq-session.sh;\n# the cursor is `workspace/sessions/<sid>/dm-bind.cursor` (the last seen sort\n# key from `hq dm channel --json`). `listen` is meant to run detached\n# (Bash run_in_background): it exits 0 and prints the new messages the moment\n# one lands, exits 3 on timeout with nothing new.\n#\n# Every post opens with an @-mention of every other member of the channel, so\n# the update reaches people (HQ mentions are structured: the `hq` CLI resolves\n# the `@\"Display Name\"` tokens against the roster and sends the real mention\n# list; an unresolvable name fails the post rather than posting text that looks\n# addressed but is not). The CLI rewrites a resolved `@\"Display Name\"` token to\n# `@Display Name`, so the room never shows the quotes.\n#\n# Posts thread by topic. The first post on a topic (`--topic`, else the title)\n# is a top-level root; every later post on the same topic in this session goes\n# out as a reply under it. `--under <id>` replies under an existing thread that\n# someone else (or an earlier session) started \u2014 a top-level post is only for a\n# subject the channel has no thread for yet. `--new-thread` starts a fresh root. The topic \u2192 root\n# map is `workspace/sessions/<sid>/dm-bind.threads`.\n#\n# A post ALWAYS mentions someone. `--to <name>` (repeatable) narrows the line to\n# the named members, for a reply to one person; without it the line names every\n# other human member (bots are left out unless named with --to, because a tagged\n# bot wakes and replies). There is no way to post unmentioned: an update nobody is\n# notified about does not get read. `post` refuses when the roster cannot be\n# read rather than sending a silent update.\n#\n# Post format (kept deliberately plain \u2014 see .claude/skills/dm-bind/SKILL.md):\n# @\"Ada Lovelace\" @\"Grace Hopper\"\n#\n# <title> \u2014 <state>\n#\n# \u2022 line\n# \u2022 line\n#\n# Next: line\n# Need from you: line\n";
7
7
  export type DmBindDeps = {
8
8
  root: string;
9
9
  cwd: string;
@@ -3,54 +3,50 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
+ import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
6
7
  import { resolveLiveRoot } from "../utils/hq-roots.js";
8
+ import { planHqSpawn } from "../utils/hq-spawn.js";
7
9
  import { bundledShellUnboundValue, registerNativeUtility, utilityError } from "./core-native-utilities.js";
8
10
  export const DM_BIND_ASSET = "core/scripts/hq-dm-bind.sh";
9
- export const DM_BIND_HELP = `# hq-dm-bind.sh — bind the current session to one HQ DM channel, post
10
- # structured updates to it, and listen for replies (feedback / requests).
11
- #
12
- # Usage:
13
- # core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to "now"
14
- # core/scripts/hq-dm-bind.sh status # show the binding and cursor
15
- # core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]
16
- # core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)
17
- # core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)
18
- # core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention
19
- # core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it
20
- # core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]
21
- # # block until a new message arrives (or timeout)
22
- # core/scripts/hq-dm-bind.sh unbind
23
- #
24
- # The binding lives in the session's meta.yaml (\`dm_channel\`) via hq-session.sh;
25
- # the cursor is \`workspace/sessions/<sid>/dm-bind.cursor\` (the last seen sort
26
- # key from \`hq dm channel --json\`). \`listen\` is meant to run detached
27
- # (Bash run_in_background): it exits 0 and prints the new messages the moment
28
- # one lands, exits 3 on timeout with nothing new.
29
- #
30
- # Every post opens with an @-mention of every other member of the channel, so
31
- # the update reaches people (HQ mentions are structured: the \`hq\` CLI resolves
32
- # the \`@"Display Name"\` tokens against the roster and sends the real mention
33
- `;
34
- export const createDmBindDeps = (root, cwd, env, now, home = env.HOME || os.homedir()) => ({
35
- root, cwd, env, home, now, fs,
36
- runHq: (args, childEnv) => {
37
- const result = spawnSync("hq", args, { cwd, env: { ...childEnv, HQ_ROOT: root }, encoding: "utf8" });
38
- return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
39
- },
40
- runHqSession: (args, childEnv) => {
41
- const result = spawnSync("bash", [path.join(root, "core/scripts/hq-session.sh"), ...args], { cwd, env: childEnv, encoding: "utf8" });
42
- return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
43
- },
44
- fetch: globalThis.fetch,
45
- readStdin: () => new Promise((resolve, reject) => {
46
- let data = "";
47
- process.stdin.setEncoding("utf8");
48
- process.stdin.on("data", (chunk) => { data += chunk; });
49
- process.stdin.on("end", () => resolve(data));
50
- process.stdin.on("error", reject);
51
- }),
52
- sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
53
- });
11
+ export const DM_BIND_HELP = "# hq-dm-bind.sh \u2014 bind the current session to one HQ DM channel, post\n# structured updates to it, and listen for replies (feedback / requests).\n#\n# Usage:\n# core/scripts/hq-dm-bind.sh bind <channel> # bind + set the read cursor to \"now\"\n# core/scripts/hq-dm-bind.sh status # show the binding and cursor\n# core/scripts/hq-dm-bind.sh post --title <t> [--state <s>] [--line <l>]... [--next <l>]... [--ask <l>]... [--to <name>]... [--topic <t>] [--under <id>] [--new-thread]\n# core/scripts/hq-dm-bind.sh post --title <t> - < body.md # body from stdin (already structured)\n# core/scripts/hq-dm-bind.sh threads # list the channel's recent top-level threads (id, replies, author, text)\n# core/scripts/hq-dm-bind.sh roster # print the channel members a post will @-mention\n# core/scripts/hq-dm-bind.sh poll # print new messages since the cursor, advance it\n# core/scripts/hq-dm-bind.sh listen [--interval 60] [--timeout 1800]\n# # block until a new message arrives (or timeout)\n# core/scripts/hq-dm-bind.sh unbind\n#\n# The binding lives in the session's meta.yaml (`dm_channel`) via hq-session.sh;\n# the cursor is `workspace/sessions/<sid>/dm-bind.cursor` (the last seen sort\n# key from `hq dm channel --json`). `listen` is meant to run detached\n# (Bash run_in_background): it exits 0 and prints the new messages the moment\n# one lands, exits 3 on timeout with nothing new.\n#\n# Every post opens with an @-mention of every other member of the channel, so\n# the update reaches people (HQ mentions are structured: the `hq` CLI resolves\n# the `@\"Display Name\"` tokens against the roster and sends the real mention\n# list; an unresolvable name fails the post rather than posting text that looks\n# addressed but is not). The CLI rewrites a resolved `@\"Display Name\"` token to\n# `@Display Name`, so the room never shows the quotes.\n#\n# Posts thread by topic. The first post on a topic (`--topic`, else the title)\n# is a top-level root; every later post on the same topic in this session goes\n# out as a reply under it. `--under <id>` replies under an existing thread that\n# someone else (or an earlier session) started \u2014 a top-level post is only for a\n# subject the channel has no thread for yet. `--new-thread` starts a fresh root. The topic \u2192 root\n# map is `workspace/sessions/<sid>/dm-bind.threads`.\n#\n# A post ALWAYS mentions someone. `--to <name>` (repeatable) narrows the line to\n# the named members, for a reply to one person; without it the line names every\n# other human member (bots are left out unless named with --to, because a tagged\n# bot wakes and replies). There is no way to post unmentioned: an update nobody is\n# notified about does not get read. `post` refuses when the roster cannot be\n# read rather than sending a silent update.\n#\n# Post format (kept deliberately plain \u2014 see .claude/skills/dm-bind/SKILL.md):\n# @\"Ada Lovelace\" @\"Grace Hopper\"\n#\n# <title> \u2014 <state>\n#\n# \u2022 line\n# \u2022 line\n#\n# Next: line\n# Need from you: line\n";
12
+ export const createDmBindDeps = (root, cwd, env, now, home = env.HOME || os.homedir()) => {
13
+ const d = {
14
+ root, cwd, env, home, now, fs,
15
+ runHq: (args, childEnv) => {
16
+ const envWithRoot = { ...childEnv, HQ_ROOT: root };
17
+ const spawnPlan = planHqSpawn(args, envWithRoot, cwd, d.platform ?? process.platform, (candidate) => {
18
+ try {
19
+ return d.fs.statSync(candidate).isFile();
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ });
25
+ const result = spawnSync(spawnPlan.cmd, spawnPlan.args, {
26
+ cwd,
27
+ env: envWithRoot,
28
+ encoding: "utf8",
29
+ shell: spawnPlan.shell,
30
+ ...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
31
+ });
32
+ return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
33
+ },
34
+ runHqSession: (args, childEnv) => {
35
+ const result = spawnSync("bash", [path.join(root, "core/scripts/hq-session.sh"), ...args], { cwd, env: childEnv, encoding: "utf8" });
36
+ return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
37
+ },
38
+ fetch: globalThis.fetch,
39
+ readStdin: () => new Promise((resolve, reject) => {
40
+ let data = "";
41
+ process.stdin.setEncoding("utf8");
42
+ process.stdin.on("data", (chunk) => { data += chunk; });
43
+ process.stdin.on("end", () => resolve(data));
44
+ process.stdin.on("error", reject);
45
+ }),
46
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
47
+ };
48
+ return d;
49
+ };
54
50
  const DM_BIND_API_TIMEOUT_MS = 15_000;
55
51
  const fail = (message, code = 2) => utilityError(`hq-dm-bind: ${message}`, code);
56
52
  const sessionDirectory = (d, sid) => path.join(d.root, "workspace/sessions", sid);
@@ -89,19 +85,24 @@ function ensureDir(d, sid) {
89
85
  return directory;
90
86
  }
91
87
  function hasHqOnPath(d) {
88
+ if ((d.platform ?? process.platform) === "win32") {
89
+ return resolveWindowsExecutable("hq", d.env, (candidate) => {
90
+ try {
91
+ return d.fs.statSync(candidate).isFile();
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ }, d.cwd) !== null;
97
+ }
92
98
  for (const directory of (d.env.PATH ?? "").split(path.delimiter)) {
93
99
  if (!directory)
94
100
  continue;
95
- // runHq spawns "hq" without a shell. On Windows that resolves only .exe and
96
- // .com files, and Windows file modes carry no execute bits, so accept exactly
97
- // what the spawn can launch. npm's hq.cmd shim needs a shell and is not
98
- // accepted here.
99
- const windows = (d.platform ?? process.platform) === "win32";
100
- const names = windows ? ["hq.exe", "hq.com"] : ["hq"];
101
+ const names = ["hq"];
101
102
  for (const name of names) {
102
103
  try {
103
104
  const stat = d.fs.statSync(path.join(directory, name));
104
- if (stat.isFile() && (windows || (stat.mode & 0o111) !== 0))
105
+ if (stat.isFile() && (stat.mode & 0o111) !== 0)
105
106
  return true;
106
107
  }
107
108
  catch { /* PATH entry is absent */ }
@@ -646,6 +647,8 @@ async function listen(d, args) {
646
647
  return { code: 3, stdout: output, stderr: "" };
647
648
  }
648
649
  export async function runDmBind(args, deps) {
650
+ if (args[0] === "-h" || args[0] === "--help")
651
+ return { code: 0, stdout: DM_BIND_HELP, stderr: "" };
649
652
  if (!hasHqOnPath(deps))
650
653
  return fail("missing dependency: hq");
651
654
  const [command = "", ...rest] = args;
@@ -2,6 +2,7 @@
2
2
  import { spawn, spawnSync } from "node:child_process";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
+ import { planHqSpawn } from "../utils/hq-spawn.js";
5
6
  /**
6
7
  * `handoff finalize --help` is historically an unknown-argument error. Keep
7
8
  * that fast error path out of the large handoff implementation module.
@@ -48,20 +49,24 @@ export function handoffFinalizeHelpResult(args, root, tmpRoot = "/tmp") {
48
49
  return { code: 2, stdout: "", stderr: "Unknown arg: --help\n" };
49
50
  }
50
51
  async function launchHandoffPost(args, root) {
52
+ const childEnv = {
53
+ ...process.env,
54
+ HQ_ROOT: root,
55
+ CLAUDE_PROJECT_DIR: root,
56
+ PWD: root,
57
+ HQ_NATIVE_HANDOFF_POST_CHILD: "1",
58
+ };
59
+ const spawnPlan = planHqSpawn(["core", "--hq-root", root, "handoff", "post", ...args], childEnv, root);
51
60
  return await new Promise((resolve) => {
52
61
  try {
53
- const child = spawn("hq", ["core", "--hq-root", root, "handoff", "post", ...args], {
62
+ const child = spawn(spawnPlan.cmd, spawnPlan.args, {
54
63
  cwd: root,
55
- env: {
56
- ...process.env,
57
- HQ_ROOT: root,
58
- CLAUDE_PROJECT_DIR: root,
59
- PWD: root,
60
- HQ_NATIVE_HANDOFF_POST_CHILD: "1",
61
- },
64
+ env: childEnv,
62
65
  detached: true,
63
66
  windowsHide: true,
64
67
  stdio: "ignore",
68
+ shell: spawnPlan.shell,
69
+ ...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
65
70
  });
66
71
  child.once("error", (error) => {
67
72
  process.stderr.write(`${error.message}\n`);
@@ -1,5 +1,6 @@
1
1
  /** Native implementations of the handoff scaffold command family. */
2
2
  import { spawn, spawnSync } from "node:child_process";
3
+ import { planHqSpawn } from "../utils/hq-spawn.js";
3
4
  import * as fs from "node:fs";
4
5
  import * as os from "node:os";
5
6
  import * as path from "node:path";
@@ -876,14 +877,17 @@ async function launchHandoffPost(args, root) {
876
877
  delete childEnv[key];
877
878
  childEnv.PWD = shellDisplayPath(root);
878
879
  }
880
+ const spawnPlan = planHqSpawn(["core", "--hq-root", root, "handoff", "post", ...args], childEnv, root);
879
881
  return await new Promise((resolve) => {
880
882
  try {
881
- const child = spawn("hq", ["core", "--hq-root", root, "handoff", "post", ...args], {
883
+ const child = spawn(spawnPlan.cmd, spawnPlan.args, {
882
884
  cwd: root,
883
885
  env: childEnv,
884
886
  detached: true,
885
887
  windowsHide: true,
886
888
  stdio: "ignore",
889
+ shell: spawnPlan.shell,
890
+ ...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
887
891
  });
888
892
  child.once("error", (error) => resolve({ code: 127, stdout: "", stderr: `${error.message}\n` }));
889
893
  child.once("spawn", () => {
@@ -1036,11 +1040,15 @@ export function runHandoffPostWorker(args, context) {
1036
1040
  log(`workspace-sync: skipped (thread mirror missing for ${company})`);
1037
1041
  continue;
1038
1042
  }
1039
- const pushed = spawnSync("hq", ["sync", "push", "--company", company, `companies/${company}/workspace`], {
1043
+ const syncArgs = ["sync", "push", "--company", company, `companies/${company}/workspace`];
1044
+ const spawnPlan = planHqSpawn(syncArgs, process.env, root);
1045
+ const pushed = spawnSync(spawnPlan.cmd, spawnPlan.args, {
1040
1046
  cwd: root,
1041
1047
  env: process.env,
1042
1048
  encoding: "utf8",
1043
1049
  stdio: ["ignore", "pipe", "pipe"],
1050
+ shell: spawnPlan.shell,
1051
+ ...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
1044
1052
  });
1045
1053
  recordCommand(logMain, {
1046
1054
  code: pushed.status ?? 1,
@@ -0,0 +1,7 @@
1
+ /** Shared fail-safe readers for shipped native core families. */
2
+ export declare function coreNativeFamilyEnabled(flagKey: string): Promise<boolean>;
3
+ export declare const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
4
+ export declare const CORE_NATIVE_ONTOLOGY_FLAG = "core-native-ontology";
5
+ export declare function coreNativeDelegateEnabled(): Promise<boolean>;
6
+ export declare function coreNativeOntologyEnabled(): Promise<boolean>;
7
+ //# sourceMappingURL=core-native-family-flag.d.ts.map
@@ -0,0 +1,21 @@
1
+ /** Shared fail-safe readers for shipped native core families. */
2
+ import { readLocalOverride } from "@indigoai-us/hq-flags-client";
3
+ export async function coreNativeFamilyEnabled(flagKey) {
4
+ const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
5
+ const override = readLocalOverride(flagKey, process.env);
6
+ if (override !== null)
7
+ return override;
8
+ return resolveProcessFlagGate(flagKey, { fallback: true }, () => true, undefined, {
9
+ acceptStaleSnapshot: true,
10
+ killSwitchWithoutCompany: true,
11
+ });
12
+ }
13
+ export const CORE_NATIVE_DELEGATE_FLAG = "core-native-delegate";
14
+ export const CORE_NATIVE_ONTOLOGY_FLAG = "core-native-ontology";
15
+ export function coreNativeDelegateEnabled() {
16
+ return import("../lib/flag-registry.js").then(({ resolveProcessFlagGate }) => resolveProcessFlagGate(CORE_NATIVE_DELEGATE_FLAG, { fallback: false }, () => false));
17
+ }
18
+ export function coreNativeOntologyEnabled() {
19
+ return coreNativeFamilyEnabled(CORE_NATIVE_ONTOLOGY_FLAG);
20
+ }
21
+ //# sourceMappingURL=core-native-family-flag.js.map
@@ -1,7 +1,7 @@
1
1
  /** Default-off rollout gate for the native Wave 2 worktree/resume helpers. */
2
2
  export const CORE_NATIVE_HANDOFF_FLAG = "core-native-handoff";
3
3
  export async function coreNativeHandoffEnabled() {
4
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
5
- return resolveProcessFlagGate(CORE_NATIVE_HANDOFF_FLAG, { fallback: false }, () => false);
4
+ const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
5
+ return coreNativeFamilyEnabled(CORE_NATIVE_HANDOFF_FLAG);
6
6
  }
7
7
  //# sourceMappingURL=core-native-handoff-flag.js.map
@@ -1,7 +1,7 @@
1
1
  /** Default-off rollout gate for Wave 8 hook-time helper commands. */
2
2
  export const CORE_NATIVE_HOOK_HELPERS_FLAG = "core-native-hook-helpers";
3
3
  export async function coreNativeHookHelpersEnabled() {
4
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
5
- return resolveProcessFlagGate(CORE_NATIVE_HOOK_HELPERS_FLAG, { fallback: false }, () => false);
4
+ const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
5
+ return coreNativeFamilyEnabled(CORE_NATIVE_HOOK_HELPERS_FLAG);
6
6
  }
7
7
  //# sourceMappingURL=core-native-hook-helpers-flag.js.map
@@ -1,7 +1,7 @@
1
1
  /** Default-off rollout gate for the native scheduled-jobs helpers. */
2
2
  export const CORE_NATIVE_JOBS_FLAG = "core-native-jobs";
3
3
  export async function coreNativeJobsEnabled() {
4
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
5
- return resolveProcessFlagGate(CORE_NATIVE_JOBS_FLAG, { fallback: false }, () => false);
4
+ const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
5
+ return coreNativeFamilyEnabled(CORE_NATIVE_JOBS_FLAG);
6
6
  }
7
7
  //# sourceMappingURL=core-native-jobs-flag.js.map
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export const CORE_NATIVE_POLICY_FLAG = 'core-native-policy';
8
8
  export async function coreNativePolicyEnabled() {
9
- const { resolveProcessFlagGate } = await import('../lib/flag-registry.js');
10
- return resolveProcessFlagGate(CORE_NATIVE_POLICY_FLAG, { fallback: false }, () => false);
9
+ const { coreNativeFamilyEnabled } = await import('./core-native-family-flag.js');
10
+ return coreNativeFamilyEnabled(CORE_NATIVE_POLICY_FLAG);
11
11
  }
12
12
  //# sourceMappingURL=core-native-policy-flag.js.map
@@ -1,7 +1,7 @@
1
1
  /** Default-off rollout gate for native session and Stop-hook plumbing. */
2
2
  export const CORE_NATIVE_SESSION_FLAG = "core-native-session";
3
3
  export async function coreNativeSessionEnabled() {
4
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
5
- return resolveProcessFlagGate(CORE_NATIVE_SESSION_FLAG, { fallback: false }, () => false);
4
+ const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
5
+ return coreNativeFamilyEnabled(CORE_NATIVE_SESSION_FLAG);
6
6
  }
7
7
  //# sourceMappingURL=core-native-session-flag.js.map
@@ -1,7 +1,7 @@
1
1
  /** Default-off rollout gate for native operator utility commands. */
2
2
  export const CORE_NATIVE_UTILITIES_FLAG = "core-native-utilities";
3
3
  export async function coreNativeUtilitiesEnabled() {
4
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
5
- return resolveProcessFlagGate(CORE_NATIVE_UTILITIES_FLAG, { fallback: false }, () => false);
4
+ const { coreNativeFamilyEnabled } = await import("./core-native-family-flag.js");
5
+ return coreNativeFamilyEnabled(CORE_NATIVE_UTILITIES_FLAG);
6
6
  }
7
7
  //# sourceMappingURL=core-native-utilities-flag.js.map
@@ -1170,8 +1170,8 @@ export function registerCoreOntologyNativeCommands(ontology, rootFor) {
1170
1170
  const forwarded = commandIndex >= 0 ? argv.slice(commandIndex + 1)
1171
1171
  : commandContext.args.length > 0 ? commandContext.args : args;
1172
1172
  const liveRoot = entry.root === "live" ? rootFor() : process.cwd();
1173
- const { resolveProcessFlagGate } = await import("../lib/flag-registry.js");
1174
- if (!resolveProcessFlagGate(FLAG, { fallback: false }, () => false)) {
1173
+ const { coreNativeOntologyEnabled } = await import("./core-native-family-flag.js");
1174
+ if (!await coreNativeOntologyEnabled()) {
1175
1175
  const result = runBundledScript({
1176
1176
  asset: `${ASSET}${entry.asset}`,
1177
1177
  args: forwarded,
@@ -2,8 +2,10 @@
2
2
  import { spawnSync } from "node:child_process";
3
3
  import * as fs from "node:fs";
4
4
  import * as path from "node:path";
5
+ import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
5
6
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
6
7
  import { registerNativeUtility } from "./core-native-utilities.js";
8
+ import { planHqSpawn } from "../utils/hq-spawn.js";
7
9
  export const REFRESH_VAULT_ACCESS_ASSET = "core/scripts/refresh-vault-access.sh";
8
10
  export const REFRESH_VAULT_ACCESS_HELP = `# hq-core: public
9
11
  # refresh-vault-access.sh — build/refresh the local vault-access manifest that
@@ -39,7 +41,15 @@ export const REFRESH_VAULT_ACCESS_HELP = `# hq-core: public
39
41
  #
40
42
  `.replace(/^# ?/gm, "");
41
43
  function defaultHq(args, cwd, env) {
42
- const result = spawnSync("hq", args, { cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
44
+ const spawnPlan = planHqSpawn(args, env, cwd);
45
+ const result = spawnSync(spawnPlan.cmd, spawnPlan.args, {
46
+ cwd,
47
+ env,
48
+ encoding: "utf8",
49
+ stdio: ["ignore", "pipe", "pipe"],
50
+ shell: spawnPlan.shell,
51
+ ...(spawnPlan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
52
+ });
43
53
  if (result.error)
44
54
  return { code: 127, stdout: "", stderr: result.error.message, missing: result.error.code === "ENOENT" };
45
55
  return { code: result.status ?? 128, stdout: String(result.stdout ?? ""), stderr: String(result.stderr ?? "") };
@@ -47,6 +57,9 @@ function defaultHq(args, cwd, env) {
47
57
  function executableOnPath(name, value) {
48
58
  if (!value)
49
59
  return false;
60
+ if (process.platform === "win32" && name === "hq") {
61
+ return resolveWindowsExecutable(name, { PATH: value }) !== null;
62
+ }
50
63
  for (const directory of value.split(path.delimiter)) {
51
64
  if (!directory)
52
65
  continue;
package/dist/fast-core.js CHANGED
@@ -9,7 +9,7 @@
9
9
  import * as fs from "node:fs";
10
10
  import * as path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
- import { parseFastCore } from "./fast-scaffold.js";
12
+ import { isDefaultOnFastFamily, parseFastCore } from "./fast-scaffold.js";
13
13
  const FAST_ROUTE_PROFILE_TOKEN = "hq-fast-route";
14
14
  function wantsFastRouteProfile(env) {
15
15
  return env.NODE_DEBUG === FAST_ROUTE_PROFILE_TOKEN;
@@ -50,7 +50,16 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
50
50
  let familyEnabled = true;
51
51
  if (entry.kind === "native" && entry.familyFlagKey) {
52
52
  const envOverride = localBooleanFlagOverride(entry.familyFlagKey, process.env);
53
- familyEnabled = envOverride ?? await (await import("./native-fast-route.js")).fastFlagEnabled(entry.familyFlagKey, process.env, { numericEnvironmentOverride: false });
53
+ if (envOverride !== null) {
54
+ familyEnabled = envOverride;
55
+ }
56
+ else {
57
+ const { fastFlagEnabled, fastFlagEnabledDefaultOn } = await import("./native-fast-route.js");
58
+ const readFamilyFlag = isDefaultOnFastFamily(entry.familyFlagKey)
59
+ ? fastFlagEnabledDefaultOn
60
+ : fastFlagEnabled;
61
+ familyEnabled = await readFamilyFlag(entry.familyFlagKey, process.env, { numericEnvironmentOverride: false });
62
+ }
54
63
  }
55
64
  if (entry.kind === "native" && entry.fallbackToCommandGraph && !familyEnabled) {
56
65
  const { runCli } = await import("./main.js");
@@ -164,6 +173,7 @@ export async function runFastCore(argv, parsed = parseFastCore(argv)) {
164
173
  args: parsed.args,
165
174
  cwd,
166
175
  ...(injectRoot ? { hqRoot } : {}),
176
+ msysScriptPath: process.platform === "win32",
167
177
  ...(Object.keys(childEnv).length ? { env: childEnv } : {}),
168
178
  });
169
179
  // Propagate verbatim: these scripts use their exit code as their interface.
@@ -49,6 +49,9 @@ export type FastScaffoldScript = {
49
49
  /** Whether the bundled fast-route module closure needs Node's network timeout patch. */
50
50
  requiresNodeNetworkCompatibility?: boolean;
51
51
  };
52
+ /** Family kill switches whose owner-approved default is enabled. */
53
+ export declare const CORE_NATIVE_DEFAULT_ON_FAMILY_FLAGS: readonly ["core-native-session", "core-native-hook-helpers", "core-native-policy", "core-native-utilities", "core-native-jobs", "core-native-ontology", "core-native-handoff"];
54
+ export declare function isDefaultOnFastFamily(flagKey: string): boolean;
52
55
  /** The relocated scripts that qualify for the entrypoint fast path. */
53
56
  export declare const FAST_SCAFFOLD_SCRIPTS: readonly FastScaffoldScript[];
54
57
  export type ParsedFastCore = {
@@ -9,6 +9,20 @@
9
9
  * on every turn's Stop. The entrypoint routes eligible commands before loading
10
10
  * the graph, and this module holds the parser + table that routing needs.
11
11
  */
12
+ /** Family kill switches whose owner-approved default is enabled. */
13
+ export const CORE_NATIVE_DEFAULT_ON_FAMILY_FLAGS = [
14
+ "core-native-session",
15
+ "core-native-hook-helpers",
16
+ "core-native-policy",
17
+ "core-native-utilities",
18
+ "core-native-jobs",
19
+ "core-native-ontology",
20
+ "core-native-handoff",
21
+ ];
22
+ const defaultOnFamilyFlags = new Set(CORE_NATIVE_DEFAULT_ON_FAMILY_FLAGS);
23
+ export function isDefaultOnFastFamily(flagKey) {
24
+ return defaultOnFamilyFlags.has(flagKey);
25
+ }
12
26
  /** The relocated scripts that qualify for the entrypoint fast path. */
13
27
  export const FAST_SCAFFOLD_SCRIPTS = [
14
28
  {
@@ -67,7 +67,10 @@ export declare function resolveFlagGate(reader: FlagReader | null | undefined, f
67
67
  /** Resolve one explicit company's flag without relying on process-wide binding. */
68
68
  export declare function resolveCompanyFlagGate(companyUid: string, idToken: string, flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, dependencies?: CompanyFlagGateDependencies): Promise<boolean>;
69
69
  /** Resolve a process gate against the shared snapshot reader, when configured. */
70
- export declare function resolveProcessFlagGate(flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, companyUid?: string): boolean;
70
+ export declare function resolveProcessFlagGate(flagKey: string, lookup: FlagLookupOptions, legacyFallback: () => boolean, companyUid?: string, options?: {
71
+ acceptStaleSnapshot?: boolean;
72
+ killSwitchWithoutCompany?: boolean;
73
+ }): boolean;
71
74
  /**
72
75
  * The registry snapshot held by this process, or null when none has arrived
73
76
  * (no endpoint configured, offline, or still fetching). Callers that must
@@ -9,9 +9,9 @@ import { Worker } from "node:worker_threads";
9
9
  import { createFlagClient, isFlagSnapshot, readLocalOverride, resolveFlag, } from "@indigoai-us/hq-flags-client";
10
10
  import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
11
11
  import { FLAG_REGISTRY_WORKER_SOURCE } from "./flag-registry-worker-source.generated.js";
12
- import { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
12
+ import { resolveFlagSnapshotCompanyUid, resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
13
13
  import { registerFlagRegistryRefreshWaiter } from "./flag-registry-refresh-wait.js";
14
- import { flagSnapshotWriteFailureTags, readFlagSnapshotFile, writePersonFlagSnapshotFile, writeFlagSnapshotFile, } from "./flag-snapshot-file.js";
14
+ import { flagSnapshotWriteFailureTags, readDefaultOnFlagSnapshot, readFlagSnapshotFile, writePersonFlagSnapshotFile, writeFlagSnapshotFile, } from "./flag-snapshot-file.js";
15
15
  function captureFlagRegistryException(error, tags, sentry) {
16
16
  if (!sentry)
17
17
  return;
@@ -348,10 +348,20 @@ export async function resolveCompanyFlagGate(companyUid, idToken, flagKey, looku
348
348
  }
349
349
  }
350
350
  /** Resolve a process gate against the shared snapshot reader, when configured. */
351
- export function resolveProcessFlagGate(flagKey, lookup, legacyFallback, companyUid) {
351
+ export function resolveProcessFlagGate(flagKey, lookup, legacyFallback, companyUid, options = {}) {
352
+ const defaultOnKillSwitch = options.killSwitchWithoutCompany === true;
353
+ const resolvedCompanyUid = defaultOnKillSwitch
354
+ ? resolveFlagSnapshotCompanyUid()
355
+ : resolveProcessFlagCompanyContext().companyUid;
352
356
  const targetCompanyUid = companyUid ?? (processReader
353
- ? refreshCompanyUid
354
- : resolveProcessFlagCompanyContext().companyUid);
357
+ ? refreshCompanyUid ?? (defaultOnKillSwitch ? resolvedCompanyUid : undefined)
358
+ : resolvedCompanyUid);
359
+ if (!targetCompanyUid && defaultOnKillSwitch) {
360
+ const override = readLocalOverride(flagKey, process.env);
361
+ if (override !== null)
362
+ return override;
363
+ return readDefaultOnFlagSnapshot(flagKey);
364
+ }
355
365
  const processSnapshotMatchesTarget = refreshCompanyUid === targetCompanyUid;
356
366
  if (processReader && heldSnapshot && processSnapshotMatchesTarget) {
357
367
  return resolveFlagGate(processReader, flagKey, lookup, legacyFallback);
@@ -360,7 +370,9 @@ export function resolveProcessFlagGate(flagKey, lookup, legacyFallback, companyU
360
370
  // Use the last validated, fresh company snapshot in that window, and when
361
371
  // the process reader was initialized for a different company scope.
362
372
  if (targetCompanyUid) {
363
- const cached = readFlagSnapshotFile(targetCompanyUid);
373
+ const cached = readFlagSnapshotFile(targetCompanyUid, options.acceptStaleSnapshot
374
+ ? { maxAgeMs: Number.POSITIVE_INFINITY }
375
+ : {});
364
376
  if (cached) {
365
377
  const snapshot = { version: 0, flags: cached.flags };
366
378
  const reader = createProcessSnapshotReader([targetCompanyUid], () => snapshot);
@@ -37,4 +37,6 @@ export declare function readFlagSnapshotFile(companyUid: string, options?: {
37
37
  now?: number;
38
38
  maxAgeMs?: number;
39
39
  }): PersistedFlagSnapshot | null;
40
+ /** Resolve a default-on kill switch from offline snapshots, allowing stale decisions. */
41
+ export declare function readDefaultOnFlagSnapshot(flagKey: string, companyUid?: string, env?: NodeJS.ProcessEnv): boolean;
40
42
  //# sourceMappingURL=flag-snapshot-file.d.ts.map
@@ -372,4 +372,34 @@ export function readFlagSnapshotFile(companyUid, options = {}) {
372
372
  }
373
373
  return primary;
374
374
  }
375
+ /** Resolve a default-on kill switch from offline snapshots, allowing stale decisions. */
376
+ export function readDefaultOnFlagSnapshot(flagKey, companyUid, env = process.env) {
377
+ const options = { env, maxAgeMs: Number.POSITIVE_INFINITY };
378
+ if (companyUid)
379
+ return readFlagSnapshotFile(companyUid, options)?.flags[flagKey] !== false;
380
+ const directories = new Set([path.dirname(personFlagSnapshotPath(env))]);
381
+ const fallbackProbe = fallbackSnapshotPath("cmp_000", false, env);
382
+ if (fallbackProbe)
383
+ directories.add(path.dirname(fallbackProbe));
384
+ for (const directory of directories) {
385
+ let names;
386
+ try {
387
+ names = fs.readdirSync(directory);
388
+ }
389
+ catch {
390
+ continue;
391
+ }
392
+ for (const name of names) {
393
+ if (name === "snapshot-person.json") {
394
+ if (readPersonFlagSnapshotFile(options)?.flags[flagKey] === false)
395
+ return false;
396
+ continue;
397
+ }
398
+ const match = /^snapshot-(cmp_[A-Za-z0-9]{3,128})\.json$/.exec(name);
399
+ if (match?.[1] && readFlagSnapshotFile(match[1], options)?.flags[flagKey] === false)
400
+ return false;
401
+ }
402
+ }
403
+ return true;
404
+ }
375
405
  //# sourceMappingURL=flag-snapshot-file.js.map
@@ -3,4 +3,6 @@ import { type ProcessFlagCompanyContext } from "./process-flag-company-context-l
3
3
  export type { ProcessFlagCompanyContext } from "./process-flag-company-context-light.js";
4
4
  /** Resolve the same company identity the process-wide flag reader uses. */
5
5
  export declare function resolveProcessFlagCompanyContext(env?: NodeJS.ProcessEnv): ProcessFlagCompanyContext;
6
+ /** Resolve the process scope, then the explicitly selected device default, without network access. */
7
+ export declare function resolveFlagSnapshotCompanyUid(env?: NodeJS.ProcessEnv): string | undefined;
6
8
  //# sourceMappingURL=process-flag-company-context.d.ts.map
@@ -1,5 +1,7 @@
1
1
  /** Company scope shared by the full flag registry and its fast-path reader. */
2
2
  import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
3
+ import { readDeviceConfig } from "./work-context/config.js";
4
+ import { workContextRoot } from "./work-context/paths.js";
3
5
  import { resolveDefaultHqRoot } from "../utils/hq-root-resolver.js";
4
6
  import { normalizedCompanyUid, resolveLightProcessFlagCompanyContext, } from "./process-flag-company-context-light.js";
5
7
  function companyUidForContext(context, env = process.env) {
@@ -20,4 +22,17 @@ export function resolveProcessFlagCompanyContext(env = process.env) {
20
22
  ...(context.companySlug ? { companySlug: context.companySlug } : {}),
21
23
  };
22
24
  }
25
+ /** Resolve the process scope, then the explicitly selected device default, without network access. */
26
+ export function resolveFlagSnapshotCompanyUid(env = process.env) {
27
+ const contextUid = resolveProcessFlagCompanyContext(env).companyUid;
28
+ if (contextUid)
29
+ return contextUid;
30
+ try {
31
+ const defaultCompany = readDeviceConfig({ root: workContextRoot(undefined, env) }).defaultCompany;
32
+ return defaultCompany?.enabled ? normalizedCompanyUid(defaultCompany.uid) : undefined;
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
23
38
  //# sourceMappingURL=process-flag-company-context.js.map
@@ -1,10 +1,10 @@
1
- /** Default-off flag check for native entrypoint routes. */
1
+ /** Fast-route flag check for native entrypoint routes. */
2
2
  export declare function nativeFastRouteEnabled(argv: readonly string[], env?: NodeJS.ProcessEnv): Promise<boolean>;
3
3
  /** Reads a default-off fast-route gate from its local override or fresh snapshot. */
4
4
  export declare function fastFlagEnabled(flagKey: string, env?: NodeJS.ProcessEnv, options?: {
5
5
  numericEnvironmentOverride?: boolean;
6
6
  }): Promise<boolean>;
7
- /** Reads a fail-safe flag that stays enabled unless a fresh value disables it. */
7
+ /** Reads an enabled-by-default flag; any validated explicit false stays a kill switch, even when stale. */
8
8
  export declare function fastFlagEnabledDefaultOn(flagKey: string, env?: NodeJS.ProcessEnv, options?: {
9
9
  numericEnvironmentOverride?: boolean;
10
10
  }): Promise<boolean>;
@@ -1,11 +1,11 @@
1
- /** Default-off flag check for native entrypoint routes. */
1
+ /** Fast-route flag check for native entrypoint routes. */
2
2
  import { localOverrideEnvVar, readLocalOverride } from "@indigoai-us/hq-flags-client";
3
3
  import { parseFastCore } from "./fast-scaffold.js";
4
4
  export async function nativeFastRouteEnabled(argv, env = process.env) {
5
5
  const parsed = parseFastCore(argv);
6
6
  if (!parsed || parsed.entry.kind !== "native")
7
7
  return false;
8
- return fastFlagEnabled(parsed.entry.flagKey, env);
8
+ return fastFlagEnabledDefaultOn(parsed.entry.flagKey, env);
9
9
  }
10
10
  /** Reads a default-off fast-route gate from its local override or fresh snapshot. */
11
11
  export async function fastFlagEnabled(flagKey, env = process.env, options = {}) {
@@ -28,20 +28,18 @@ export async function fastFlagEnabled(flagKey, env = process.env, options = {})
28
28
  return false;
29
29
  }
30
30
  }
31
- /** Reads a fail-safe flag that stays enabled unless a fresh value disables it. */
31
+ /** Reads an enabled-by-default flag; any validated explicit false stays a kill switch, even when stale. */
32
32
  export async function fastFlagEnabledDefaultOn(flagKey, env = process.env, options = {}) {
33
33
  try {
34
34
  const override = nativeRouteOverride(flagKey, env, options.numericEnvironmentOverride !== false);
35
35
  if (override !== null)
36
36
  return override;
37
- const [{ resolveProcessFlagCompanyContext }, { readFlagSnapshotFile }] = await Promise.all([
37
+ const [{ resolveFlagSnapshotCompanyUid }, { readDefaultOnFlagSnapshot }] = await Promise.all([
38
38
  import("./lib/process-flag-company-context.js"),
39
39
  import("./lib/flag-snapshot-file.js"),
40
40
  ]);
41
- const { companyUid } = resolveProcessFlagCompanyContext(env);
42
- if (!companyUid)
43
- return true;
44
- return readFlagSnapshotFile(companyUid, { env })?.flags[flagKey] !== false;
41
+ const companyUid = resolveFlagSnapshotCompanyUid(env);
42
+ return readDefaultOnFlagSnapshot(flagKey, companyUid, env);
45
43
  }
46
44
  catch {
47
45
  // This flag is a kill switch: missing scope or cache data must leave the feature enabled.
@@ -0,0 +1,4 @@
1
+ import { type SpawnPlan } from "./windows-spawn.js";
2
+ /** Resolve the installed hq executable before applying the platform spawn plan. */
3
+ export declare function planHqSpawn(args: readonly string[], env: NodeJS.ProcessEnv, cwd: string, platform?: NodeJS.Platform, existsSync?: (candidate: string) => boolean): SpawnPlan;
4
+ //# sourceMappingURL=hq-spawn.d.ts.map
@@ -0,0 +1,11 @@
1
+ import * as fs from "node:fs";
2
+ import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
3
+ import { planResolvedCommandSpawn } from "./windows-spawn.js";
4
+ /** Resolve the installed hq executable before applying the platform spawn plan. */
5
+ export function planHqSpawn(args, env, cwd, platform = process.platform, existsSync = fs.existsSync) {
6
+ const executable = platform === "win32"
7
+ ? resolveWindowsExecutable("hq", env, existsSync, cwd) ?? "hq"
8
+ : "hq";
9
+ return planResolvedCommandSpawn(executable, args, env, platform);
10
+ }
11
+ //# sourceMappingURL=hq-spawn.js.map
@@ -42,7 +42,7 @@ export const RESIDENT_PACKAGE_MODULES = {
42
42
  "bot-logs-follow": [],
43
43
  mcp: ["../lib/mcp/tools.js", "../lib/mcp/prompts.js", "../lib/anywhere-person-setting.js", "../utils/cognito-session.js", "@indigoai-us/hq-cloud", "../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client"],
44
44
  childWrapper: ["../main.js", "../lib/service-manager/startup-migration.js"],
45
- coreJobRun: ["../lib/flag-registry.js"],
45
+ coreJobRun: ["../commands/core-native-jobs-flag.js", "../commands/core-native-family-flag.js", "../lib/flag-registry.js", "../lib/flag-snapshot-file.js"],
46
46
  "daemon-call": ["../lib/daemon/protocol.js"],
47
47
  daemon: [
48
48
  "../sentry.js",
@@ -203,7 +203,10 @@ const LOADERS = {
203
203
  () => import("../lib/service-manager/startup-migration.js"),
204
204
  ],
205
205
  coreJobRun: [
206
+ () => import("../commands/core-native-jobs-flag.js"),
207
+ () => import("../commands/core-native-family-flag.js"),
206
208
  () => import("../lib/flag-registry.js"),
209
+ () => import("../lib/flag-snapshot-file.js"),
207
210
  ],
208
211
  "daemon-call": [
209
212
  () => import("../lib/daemon/protocol.js"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.68",
3
+ "version": "5.345.69",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {