@indigoai-us/hq-cli 5.345.114 → 5.345.115

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 (54) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/assets/scaffold/core/scripts/hq-delegate-send.sh +5 -1
  3. package/dist/commands/bot-jobs.d.ts +1 -11
  4. package/dist/commands/bot-jobs.js +13 -34
  5. package/dist/commands/core-audit-log-native.js +1 -1
  6. package/dist/commands/core-checkpoint-native.d.ts +27 -0
  7. package/dist/commands/core-checkpoint-native.js +85 -2
  8. package/dist/commands/core-dm-bind-native.js +1 -1
  9. package/dist/commands/core-git-pack-extension-check-native.js +1 -1
  10. package/dist/commands/core-job-native-utils.d.ts +2 -2
  11. package/dist/commands/core-job-native-utils.js +3 -11
  12. package/dist/commands/core-job-notify-native.js +1 -2
  13. package/dist/commands/core-job-probe-native.d.ts +2 -3
  14. package/dist/commands/core-job-probe-native.js +14 -22
  15. package/dist/commands/core-job-remediate-native.js +1 -2
  16. package/dist/commands/core-job-run-native.js +58 -54
  17. package/dist/commands/core-jobs-native.d.ts +1 -3
  18. package/dist/commands/core-jobs-native.js +16 -271
  19. package/dist/commands/core-jobs-schedule.d.ts +7 -0
  20. package/dist/commands/core-jobs-schedule.js +243 -0
  21. package/dist/commands/core-native-utilities.d.ts +2 -3
  22. package/dist/commands/core-native-utilities.js +2 -19
  23. package/dist/commands/core-pin-native.js +1 -1
  24. package/dist/commands/core-refresh-vault-access-native.js +1 -1
  25. package/dist/commands/core-resolve-company-command.js +2 -17
  26. package/dist/commands/core-work-mesh-bind-trusted-native.js +1 -1
  27. package/dist/commands/core.js +1 -17
  28. package/dist/fast-scaffold.d.ts +1 -1
  29. package/dist/fast-scaffold.js +0 -38
  30. package/dist/lib/daemon/catalog.d.ts +0 -2
  31. package/dist/lib/daemon/catalog.js +3 -6
  32. package/dist/lib/daemon/run.js +0 -7
  33. package/dist/utils/resident-package-preload.js +2 -7
  34. package/package.json +1 -1
  35. package/assets/scaffold/core/scripts/audit-log.sh +0 -384
  36. package/assets/scaffold/core/scripts/git-pack-extension-check.sh +0 -164
  37. package/assets/scaffold/core/scripts/hq-dm-bind.sh +0 -590
  38. package/assets/scaffold/core/scripts/hq-job-notify.sh +0 -389
  39. package/assets/scaffold/core/scripts/hq-job-probe.sh +0 -865
  40. package/assets/scaffold/core/scripts/hq-job-remediate.sh +0 -359
  41. package/assets/scaffold/core/scripts/hq-job-run.sh +0 -1025
  42. package/assets/scaffold/core/scripts/hq-pin.sh +0 -118
  43. package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +0 -310
  44. package/assets/scaffold/core/scripts/jobs-validate.sh +0 -482
  45. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +0 -836
  46. package/assets/scaffold/core/scripts/refresh-vault-access.sh +0 -188
  47. package/assets/scaffold/core/scripts/resolve-company.sh +0 -331
  48. package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +0 -184
  49. package/dist/commands/core-job-post-exit-assets.d.ts +0 -8
  50. package/dist/commands/core-job-post-exit-assets.js +0 -37
  51. package/dist/commands/core-native-jobs-flag.d.ts +0 -4
  52. package/dist/commands/core-native-jobs-flag.js +0 -7
  53. package/dist/commands/core-native-utilities-flag.d.ts +0 -4
  54. package/dist/commands/core-native-utilities-flag.js +0 -7
package/CHANGELOG.md CHANGED
@@ -2,7 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.115] — 2026-10-13
6
+ - The bundled delegate-send shell now caches the Git Bash `jq --binary` capability probe for one invocation instead of starting `jq.exe` before every jq operation.
7
+ - Remove the bundled scheduled jobs shell copies and route scheduled job units, probes, validation, notifications, and reconciliation through native HQ core commands. Fixed shell-output fixtures retain differential coverage for all 238 cases, and the published E2E matrix now runs native-only probes for these commands.
8
+
9
+ - `hq core checkpoint` now runs its background sibling under a supervisor that
10
+ stops it after 30 minutes and kills any process it left behind, and the
11
+ sibling prompt forbids unbounded searches over the workspace folder.
12
+
5
13
  ## [5.345.114] — 2026-10-13
14
+ - The native `hq core` utilities no longer include bundled shell fallbacks. Published E2E now probes each command directly.
15
+
6
16
  - `hq capacity login <provider> --account <id>` signs a registered account
7
17
  in again into its existing home. The account id, home, and Keychain item
8
18
  stay the same. `--account default` (or the id registered at `~/.claude`)
@@ -45,11 +45,15 @@ command -v jq >/dev/null 2>&1 || die "jq is required but not installed"
45
45
  # jq.exe on MSYS may use CRT text mode unless --binary is supported.
46
46
  case "${OSTYPE:-}:${MSYSTEM:-}" in
47
47
  msys*:*|*:MINGW*)
48
+ _hq_jq_binary_supported=""
48
49
  jq() {
49
50
  local stdout_file stderr_file status binary_supported
50
51
  stdout_file="$(mktemp)" || return $?
51
52
  stderr_file="$(mktemp)" || { status=$?; rm -f "$stdout_file"; return "$status"; }
52
- if command jq --binary -n 'null' >/dev/null 2>&1; then binary_supported=1; else binary_supported=0; fi
53
+ if [[ -z "$_hq_jq_binary_supported" ]]; then
54
+ if command jq --binary -n 'null' >/dev/null 2>&1; then _hq_jq_binary_supported=1; else _hq_jq_binary_supported=0; fi
55
+ fi
56
+ binary_supported=$_hq_jq_binary_supported
53
57
  if [[ "$binary_supported" == 1 ]]; then
54
58
  if command jq --binary "$@" >"$stdout_file" 2>"$stderr_file"; then status=0; else status=$?; fi
55
59
  command cat "$stdout_file"
@@ -14,7 +14,6 @@
14
14
  * The routes are self-only per agent, so every call uses the BOT's own machine
15
15
  * identity — the same credential path its turns use — not the owner's session.
16
16
  */
17
- import { execFileSync } from "node:child_process";
18
17
  import type { Command } from "commander";
19
18
  import { BotApi, type BotJobRecord, type CreateJobInput } from "../lib/bot/api.js";
20
19
  import { type BotConfig, type BotKind } from "../lib/bot/config.js";
@@ -55,18 +54,9 @@ export interface ParsedSchedule {
55
54
  /** Plain words for the schedule, when the helper supplied them. */
56
55
  human?: string;
57
56
  }
58
- export declare function scheduleHelperPath(hqRoot: string): string;
59
- /**
60
- * Plain English → cron, using HQ's OWN helper (`core/scripts/jobs-schedule-parse.sh`)
61
- * so `hq bot jobs --every` and the `/schedule` command agree on what "every
62
- * weekday morning" means. When the helper is not in this HQ root, the caller is
63
- * told to pass `--cron` instead rather than getting a second, divergent parser.
64
- */
57
+ /** Plain English → cron, using the same native parser as `hq core`. */
65
58
  export declare function parseScheduleText(text: string, opts: {
66
- hqRoot: string;
67
59
  timezone: string;
68
- exec?: typeof execFileSync;
69
- exists?: (p: string) => boolean;
70
60
  }): ParsedSchedule;
71
61
  /** The create body the cloud expects, with the client-side limits enforced first. */
72
62
  export declare function buildCreateJobBody(input: {
@@ -15,14 +15,12 @@
15
15
  * identity — the same credential path its turns use — not the owner's session.
16
16
  */
17
17
  import chalk from "chalk";
18
- import { execFileSync } from "node:child_process";
19
- import * as fs from "node:fs";
20
- import * as path from "node:path";
21
18
  import { localJobsClient } from "../lib/bot/local-jobs.js";
22
19
  import { BotApiError } from "../lib/bot/api.js";
23
20
  import { effectiveBotKind } from "../lib/bot/config.js";
24
21
  import { isQuietJobContext, JOB_RUN_OPTIONS_CONTEXT_KEY } from "../lib/bot/job-run.js";
25
22
  import { BOT_JOB_QUIET_OPTION_FLAG, botJobQuietOptionEnabled } from "../lib/bot/job-quiet-flag.js";
23
+ import { runScheduleParse } from "./core-jobs-schedule.js";
26
24
  // ─── Schedule translation ────────────────────────────────────────────────────
27
25
  /** Mirrors hq-pro-agents `MIN_RECURRING_INTERVAL_SECONDS`. */
28
26
  export const MIN_RECURRING_INTERVAL_SECONDS = 5 * 60;
@@ -190,43 +188,24 @@ export function assertScheduleWithinFloor(expr) {
190
188
  refuse(`recurring jobs may not fire more often than every ${MIN_RECURRING_INTERVAL_SECONDS / 60} minutes`);
191
189
  }
192
190
  }
193
- export function scheduleHelperPath(hqRoot) {
194
- return path.join(hqRoot, "core", "scripts", "jobs-schedule-parse.sh");
195
- }
196
- /**
197
- * Plain English → cron, using HQ's OWN helper (`core/scripts/jobs-schedule-parse.sh`)
198
- * so `hq bot jobs --every` and the `/schedule` command agree on what "every
199
- * weekday morning" means. When the helper is not in this HQ root, the caller is
200
- * told to pass `--cron` instead rather than getting a second, divergent parser.
201
- */
191
+ /** Plain English → cron, using the same native parser as `hq core`. */
202
192
  export function parseScheduleText(text, opts) {
203
- const helper = scheduleHelperPath(opts.hqRoot);
204
- const exists = opts.exists ?? ((p) => fs.existsSync(p));
205
- if (!exists(helper)) {
206
- refuse(`I can't turn "${text}" into a schedule: HQ's schedule helper is not in this HQ folder ` +
207
- `(${helper}). Pass the schedule directly instead, for example --cron "0 9 * * 1-5".`);
208
- }
209
- const exec = opts.exec ?? execFileSync;
210
- let raw;
211
- try {
212
- raw = String(exec("bash", [helper, "--tz", opts.timezone, text], { encoding: "utf8" })).trim();
213
- }
214
- catch (err) {
215
- const out = err.stdout;
216
- raw = typeof out === "string" ? out.trim() : "";
217
- if (!raw) {
218
- refuse(`I couldn't work out a schedule from "${text}". Pass --cron "<expression>" instead.`);
219
- }
220
- }
193
+ let raw = "";
194
+ let stderr = "";
195
+ const status = runScheduleParse(["--tz", opts.timezone, text], {
196
+ stdout: (value) => { raw += value; },
197
+ stderr: (value) => { stderr += value; },
198
+ });
221
199
  let parsed;
222
200
  try {
223
- parsed = JSON.parse(raw);
201
+ parsed = JSON.parse(raw.trim());
224
202
  }
225
203
  catch {
226
204
  refuse(`I couldn't work out a schedule from "${text}". Pass --cron "<expression>" instead.`);
227
205
  }
228
- if (!parsed.ok || !parsed.cron) {
229
- refuse(`I couldn't work out a schedule from "${text}"${parsed.error ? ` (${parsed.error})` : ""}. Pass --cron "<expression>" instead.`);
206
+ if (status !== 0 || !parsed.ok || !parsed.cron) {
207
+ const reason = parsed.error || stderr.trim();
208
+ refuse(`I couldn't work out a schedule from "${text}"${reason ? ` (${reason})` : ""}. Pass --cron "<expression>" instead.`);
230
209
  }
231
210
  return {
232
211
  cron: parsed.cron,
@@ -418,7 +397,7 @@ export async function runBotJobs(nameArg, opts, deps) {
418
397
  const timezone = opts.tz?.trim() || (deps.timezone ?? machineTimezone)();
419
398
  const parse = deps.parseSchedule ?? parseScheduleText;
420
399
  const schedule = opts.every
421
- ? parse(opts.every, { hqRoot: config.hqRoot, timezone })
400
+ ? parse(opts.every, { timezone })
422
401
  : { cron: opts.cron, timezone };
423
402
  const body = buildCreateJobBody({ prompt: opts.add, cron: schedule.cron, timezone: schedule.timezone, quiet: opts.quiet });
424
403
  const api = await jobsApi();
@@ -249,7 +249,7 @@ export function runAuditLog(args, deps) {
249
249
  }
250
250
  export function registerCoreAuditLogNative(core) {
251
251
  registerNativeUtility(core, {
252
- name: "audit-log", asset: AUDIT_LOG_ASSET, root: "live", summary: "Append, query, or summarize the HQ audit log",
252
+ name: "audit-log", root: "live", summary: "Append, query, or summarize the HQ audit log",
253
253
  resolveRoot: (_cwd, hqRoot) => resolveLiveRoot({ hqRoot }),
254
254
  run: (args, context) => runAuditLog(args, context),
255
255
  });
@@ -8,6 +8,12 @@
8
8
  */
9
9
  import * as fs from "node:fs";
10
10
  import { Command } from "commander";
11
+ /**
12
+ * Wall-clock cap on one sibling run. The supervisor kills the sibling and its
13
+ * descendants at this point. It stays under the lock TTL so a capped run
14
+ * always releases the queue before the lock would be presumed abandoned.
15
+ */
16
+ export declare const SIBLING_MAX_RUNTIME_MS: number;
11
17
  type Backend = "claude" | "codex" | "grok" | "none";
12
18
  type SpawnableBackend = Exclude<Backend, "none">;
13
19
  /**
@@ -70,6 +76,27 @@ export declare function autoBackendPreference(preferGrok?: boolean): SpawnableBa
70
76
  */
71
77
  export declare function companyPrefersGrok(liveRoot: string, companySlug: string | null | undefined): Promise<boolean>;
72
78
  export declare function siblingArgs(backend: SpawnableBackend, prompt: string): string[];
79
+ /**
80
+ * Supervisor for a detached sibling on POSIX. It forks the backend, records
81
+ * the backend's descendant tree every 2 seconds, kills that tree after the
82
+ * wall-clock cap, and on exit terminates any recorded descendant that was
83
+ * reparented to init. Without it, a shell command the agent started (an
84
+ * unbounded `find workspace` was the observed case) kept running after the
85
+ * agent exited, with PID 1 as its parent.
86
+ */
87
+ export declare const SIBLING_SUPERVISOR_SCRIPT: string;
88
+ /**
89
+ * The command that launches a sibling: the backend wrapped in the supervisor
90
+ * on POSIX hosts with perl, the bare backend otherwise.
91
+ */
92
+ export declare function siblingLaunch(backend: SpawnableBackend, args: string[], options?: {
93
+ platform?: NodeJS.Platform;
94
+ perlPath?: string | null;
95
+ maxRuntimeMs?: number;
96
+ }): {
97
+ command: string;
98
+ args: string[];
99
+ };
73
100
  /** Attach the native checkpoint command to the hidden `hq core` group. */
74
101
  export declare function registerCoreCheckpointCommand(core: Command, preResolvedHqRoot?: string): void;
75
102
  export {};
@@ -63,6 +63,12 @@ const FALLBACK_BACKEND_ORDER = ["claude", "codex", "grok"];
63
63
  const BACKEND_PROBE_TIMEOUT_MS = 10_000;
64
64
  /** A sibling still holding the lock after this long is treated as abandoned. */
65
65
  const SIBLING_LOCK_TTL_MS = 60 * 60 * 1000;
66
+ /**
67
+ * Wall-clock cap on one sibling run. The supervisor kills the sibling and its
68
+ * descendants at this point. It stays under the lock TTL so a capped run
69
+ * always releases the queue before the lock would be presumed abandoned.
70
+ */
71
+ export const SIBLING_MAX_RUNTIME_MS = 30 * 60 * 1000;
66
72
  /** Newest-wins cap so a wedged sibling cannot grow an unbounded queue. */
67
73
  const MAX_PENDING_PAYLOADS = 50;
68
74
  class CheckpointUsageError extends Error {
@@ -115,6 +121,11 @@ HQ install. Your parent session's state is in <payloadPath>. Work
115
121
  quietly and do not ask questions; if something is ambiguous, record it in the
116
122
  report instead of guessing.
117
123
 
124
+ 0. SEARCH BOUNDS: never run find, grep -r, rg, ls -R, or du over workspace/,
125
+ the HQ root, or repos/. workspace/worktrees holds hundreds of full checkouts
126
+ with node_modules, and an unbounded walk there runs for many minutes. Read
127
+ the exact paths named here or in the payload. If you must search, name one
128
+ narrow directory and use -maxdepth 3, pruning worktrees and node_modules.
118
129
  1. Read the payload. If it lists a transcript path that exists, read its tail (~400 lines)
119
130
  both for session context and to extract additional reusable learnings/insights
120
131
  the parent did not pass explicitly. Never quote secrets
@@ -156,6 +167,8 @@ report instead of guessing.
156
167
  workspace/insights/.
157
168
  4. Close an active session journal fail-soft with
158
169
  bash .claude/skills/_shared/journal.sh close "<project_dir>" "<one-line synthesis>".
170
+ Locate the journal only with bash .claude/skills/_shared/journal.sh path or
171
+ the payload; if neither names one, skip this step. Do not search for it.
159
172
  Write a legacy checkpoint JSON under workspace/checkpoints/<id>.json with
160
173
  id, created_at, summary, files, and next_steps for backward compatibility.
161
174
  5. Update workspace/threads/recent.md and regenerate
@@ -888,6 +901,76 @@ export function siblingArgs(backend, prompt) {
888
901
  ];
889
902
  }
890
903
  }
904
+ /**
905
+ * Supervisor for a detached sibling on POSIX. It forks the backend, records
906
+ * the backend's descendant tree every 2 seconds, kills that tree after the
907
+ * wall-clock cap, and on exit terminates any recorded descendant that was
908
+ * reparented to init. Without it, a shell command the agent started (an
909
+ * unbounded `find workspace` was the observed case) kept running after the
910
+ * agent exited, with PID 1 as its parent.
911
+ */
912
+ export const SIBLING_SUPERVISOR_SCRIPT = [
913
+ "use strict; use warnings; use POSIX qw(:sys_wait_h);",
914
+ "my $secs = shift @ARGV;",
915
+ 'my $pid = fork(); die "checkpoint sibling: fork failed\\n" unless defined $pid;',
916
+ "if ($pid == 0) { exec { $ARGV[0] } @ARGV; exit 127; }",
917
+ "my %seen;",
918
+ "sub tree {",
919
+ " my %kids;",
920
+ " for (qx{ps -axo pid=,ppid=}) { my ($p, $pp) = split ' '; push @{$kids{$pp}}, $p if defined $pp; }",
921
+ " my @out; my @q = ($pid);",
922
+ " while (@q) { my $x = shift @q; for my $c (@{$kids{$x} || []}) { push @out, $c; push @q, $c; } }",
923
+ " return @out;",
924
+ "}",
925
+ "my $deadline = time + $secs; my $rc = 0;",
926
+ "while (1) {",
927
+ " my $w = waitpid($pid, WNOHANG);",
928
+ " if ($w == $pid) { $rc = $?; last; }",
929
+ " $seen{$_} = 1 for tree();",
930
+ " if (time >= $deadline) {",
931
+ " my @d = tree(); $seen{$_} = 1 for @d;",
932
+ ' print STDERR "checkpoint sibling: killed after $secs s wall-clock limit\\n";',
933
+ " kill 'TERM', @d, $pid; sleep 2;",
934
+ " kill 'KILL', grep { kill 0, $_ } (tree(), @d, $pid);",
935
+ " waitpid($pid, 0); $rc = 124 << 8; last;",
936
+ " }",
937
+ " sleep 2;",
938
+ "}",
939
+ "for my $p (keys %seen) {",
940
+ " my $pp = qx{ps -o ppid= -p $p 2>/dev/null}; $pp =~ s/\\s//g;",
941
+ " kill 'TERM', $p if $pp eq '1';",
942
+ "}",
943
+ "exit(($rc & 127) ? 128 + ($rc & 127) : ($rc >> 8));",
944
+ ].join("\n");
945
+ /**
946
+ * The command that launches a sibling: the backend wrapped in the supervisor
947
+ * on POSIX hosts with perl, the bare backend otherwise.
948
+ */
949
+ export function siblingLaunch(backend, args, options = {}) {
950
+ const platform = options.platform ?? process.platform;
951
+ if (platform === "win32")
952
+ return { command: backend, args };
953
+ const perlPath = options.perlPath === undefined ? findPerl() : options.perlPath;
954
+ if (!perlPath)
955
+ return { command: backend, args };
956
+ const seconds = Math.max(1, Math.ceil((options.maxRuntimeMs ?? SIBLING_MAX_RUNTIME_MS) / 1000));
957
+ return {
958
+ command: perlPath,
959
+ args: ["-e", SIBLING_SUPERVISOR_SCRIPT, String(seconds), backend, ...args],
960
+ };
961
+ }
962
+ function findPerl() {
963
+ for (const candidate of ["/usr/bin/perl", "/bin/perl", "/opt/homebrew/bin/perl", "/usr/local/bin/perl"]) {
964
+ try {
965
+ if (fs.statSync(candidate).isFile())
966
+ return candidate;
967
+ }
968
+ catch {
969
+ // Try the next candidate.
970
+ }
971
+ }
972
+ return null;
973
+ }
891
974
  function siblingPayload(input, threadPath, backend) {
892
975
  return {
893
976
  backend,
@@ -1047,10 +1130,10 @@ async function startSibling(liveRoot, input, threadPath, backend) {
1047
1130
  fs.writeFileSync(path.join(runDir, "prompt.md"), prompt);
1048
1131
  const logPath = path.join(runDir, "run.log");
1049
1132
  const logFd = fs.openSync(logPath, "a");
1050
- const args = siblingArgs(backend, prompt);
1133
+ const launch = siblingLaunch(backend, siblingArgs(backend, prompt));
1051
1134
  let child;
1052
1135
  try {
1053
- child = spawn(backend, args, {
1136
+ child = spawn(launch.command, launch.args, {
1054
1137
  detached: true,
1055
1138
  windowsHide: true,
1056
1139
  cwd: liveRoot,
@@ -672,7 +672,7 @@ export async function runDmBind(args, deps) {
672
672
  }
673
673
  export function registerCoreDmBindNative(core) {
674
674
  registerNativeUtility(core, {
675
- name: "dm-bind", asset: DM_BIND_ASSET, root: "live", summary: "Bind this session to one HQ DM channel",
675
+ name: "dm-bind", root: "live", summary: "Bind this session to one HQ DM channel",
676
676
  resolveRoot: (_cwd, hqRoot) => resolveLiveRoot({ hqRoot }),
677
677
  run: (args, context) => runDmBind(args, createDmBindDeps(context.root, context.cwd, context.env, context.now)),
678
678
  });
@@ -178,7 +178,7 @@ export function runGitPackExtensionCheck(args, deps) {
178
178
  }
179
179
  export function registerCoreGitPackExtensionCheckNative(core) {
180
180
  registerNativeUtility(core, {
181
- name: "git-pack-extension-check", asset: GIT_PACK_EXTENSION_CHECK_ASSET, root: "cwd", summary: "Find or repair extensionless Git pack files",
181
+ name: "git-pack-extension-check", root: "cwd", summary: "Find or repair extensionless Git pack files",
182
182
  resolveRoot: (cwd) => cwd,
183
183
  run: (args, context) => runGitPackExtensionCheck(args, {
184
184
  cwd: context.cwd,
@@ -1,6 +1,6 @@
1
1
  import type { Command } from "commander";
2
2
  /** Preserve raw argv (including the option terminator) through Commander. */
3
3
  export declare function rawJobArgumentsForCommand(command: Command, fallback: string[], raw?: string[]): string[];
4
- /** Shared gated registration path for each leaf in the singular job group. */
5
- export declare function registerCoreJobLeaf(group: Command, core: Command, name: string, asset: string, summary: string, run: (args: string[], hqRoot: string) => number, canUseLiveRoot: (args: string[]) => boolean, valueOptions: readonly string[], booleanOptions?: readonly string[]): void;
4
+ /** Shared registration path for each leaf in the singular job group. */
5
+ export declare function registerCoreJobLeaf(group: Command, core: Command, name: string, summary: string, run: (args: string[], hqRoot: string) => number, canUseLiveRoot: (args: string[]) => boolean, valueOptions: readonly string[], booleanOptions?: readonly string[]): void;
6
6
  //# sourceMappingURL=core-job-native-utils.d.ts.map
@@ -1,7 +1,5 @@
1
1
  import { resolveLiveRoot } from "../utils/hq-roots.js";
2
- import { runBundledScript } from "../utils/run-bundled-script.js";
3
2
  import { describeCoreCommand } from "./core-command-catalog.js";
4
- import { coreNativeJobsEnabled } from "./core-native-jobs-flag.js";
5
3
  /** Preserve raw argv (including the option terminator) through Commander. */
6
4
  export function rawJobArgumentsForCommand(command, fallback, raw = process.argv) {
7
5
  const names = [];
@@ -19,8 +17,8 @@ export function rawJobArgumentsForCommand(command, fallback, raw = process.argv)
19
17
  }
20
18
  return start >= 0 ? raw.slice(start) : (command.args.length ? command.args : fallback);
21
19
  }
22
- /** Shared gated registration path for each leaf in the singular job group. */
23
- export function registerCoreJobLeaf(group, core, name, asset, summary, run, canUseLiveRoot, valueOptions, booleanOptions = []) {
20
+ /** Shared registration path for each leaf in the singular job group. */
21
+ export function registerCoreJobLeaf(group, core, name, summary, run, canUseLiveRoot, valueOptions, booleanOptions = []) {
24
22
  const command = group.command(name)
25
23
  .description(summary)
26
24
  .helpOption(false)
@@ -33,17 +31,11 @@ export function registerCoreJobLeaf(group, core, name, asset, summary, run, canU
33
31
  const hqRoot = canUseLiveRoot(argv)
34
32
  ? resolveLiveRoot({ hqRoot: leafRootOverride(argv, valueOptions, booleanOptions) ?? groupHqRoot })
35
33
  : groupHqRoot ?? "";
36
- if (!await coreNativeJobsEnabled()) {
37
- const result = runBundledScript({ asset, args: argv, cwd: process.cwd(), hqRoot: hqRoot || undefined });
38
- if (result.code !== 0)
39
- process.exit(result.code);
40
- return;
41
- }
42
34
  const result = run(argv, hqRoot);
43
35
  if (result !== 0)
44
36
  process.exit(result);
45
37
  });
46
- describeCoreCommand(command, { execution: "native", asset, root: "live" });
38
+ describeCoreCommand(command, { execution: "native", root: "live" });
47
39
  }
48
40
  /**
49
41
  * Find a leaf --hq-root without resolving anything until the leaf parser has
@@ -5,7 +5,6 @@ import { spawnSync } from "node:child_process";
5
5
  import { load } from "js-yaml";
6
6
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
7
  import { registerCoreJobLeaf } from "./core-job-native-utils.js";
8
- const ASSET = "core/scripts/hq-job-notify.sh";
9
8
  const USAGE = [
10
9
  "hq-job-notify.sh — dm-only remediation alerts for Outpost scheduled jobs (US-006).",
11
10
  "",
@@ -351,7 +350,7 @@ export function runJobNotify(argv, options = {}) {
351
350
  return 0;
352
351
  }
353
352
  export function registerCoreJobNotifyNativeCommand(group, core) {
354
- registerCoreJobLeaf(group, core, "notify", ASSET, "Send a scheduled-job status alert to its owner", (argv, hqRoot) => runJobNotify(argv, { hqRoot }), canUseLiveRootForArguments, [
353
+ registerCoreJobLeaf(group, core, "notify", "Send a scheduled-job status alert to its owner", (argv, hqRoot) => runJobNotify(argv, { hqRoot }), canUseLiveRootForArguments, [
355
354
  "--hq-root", "--job-id", "--job-name", "--outcome", "--summary", "--duration", "--log-path",
356
355
  "--failure-class", "--next-action", "--owner", "--notify",
357
356
  ]);
@@ -1,7 +1,6 @@
1
1
  import type { Command } from "commander";
2
- export declare const JOB_PROBE_ASSET = "core/scripts/hq-job-probe.sh";
3
2
  export declare function displayJobFile(value: string, platform?: NodeJS.Platform): string;
4
- export declare function displayShellPath(value: string, platform?: NodeJS.Platform): string;
3
+ export declare function displayShellRegistryError(value: string, platform?: NodeJS.Platform): string;
5
4
  export declare const JOB_PROBE_USAGE: string;
6
5
  export type Checks = {
7
6
  runtime: string;
@@ -32,7 +31,7 @@ export type ProbeDeps = {
32
31
  isDirectory: (file: string) => boolean;
33
32
  realpath: (file: string) => string | undefined;
34
33
  readText: (file: string) => string;
35
- writeState: (file: string, body: string, mode: number) => void;
34
+ writeState: (file: string, body: string, mode: number, lineEnding?: "\n" | "\r\n") => void;
36
35
  writeStderr: (line: string) => void;
37
36
  listSecrets: (company?: string) => Promise<{
38
37
  code: number;
@@ -5,23 +5,18 @@ import * as path from "node:path";
5
5
  import { load, JSON_SCHEMA } from "js-yaml";
6
6
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
7
  import { resolveLiveRoot } from "../utils/hq-roots.js";
8
- import { runBundledScript } from "../utils/run-bundled-script.js";
9
8
  import { describeCoreCommand } from "./core-command-catalog.js";
10
- import { coreNativeJobsEnabled } from "./core-native-jobs-flag.js";
11
9
  import { resolveWindowsExecutable } from "../lib/bot/runtime/windows.js";
12
10
  import { planResolvedCommandSpawn } from "../utils/windows-spawn.js";
13
- export const JOB_PROBE_ASSET = "core/scripts/hq-job-probe.sh";
14
11
  export function displayJobFile(value, platform = process.platform) {
15
12
  if (platform !== "win32")
16
13
  return value;
17
14
  return path.win32.resolve(value).replaceAll("\\", "/");
18
15
  }
19
- export function displayShellPath(value, platform = process.platform) {
16
+ export function displayShellRegistryError(value, platform = process.platform) {
20
17
  if (platform !== "win32")
21
18
  return value;
22
- const normalized = value.replaceAll("\\", "/");
23
- const drivePath = /^([A-Za-z]):\/(.*)$/u.exec(normalized);
24
- return drivePath ? `/${drivePath[1].toLowerCase()}/${drivePath[2]}` : normalized;
19
+ return value.replace(/([A-Za-z]):[\\/]/gu, (_match, drive) => `/${drive.toLowerCase()}/`).replaceAll("\\", "/");
25
20
  }
26
21
  export const JOB_PROBE_USAGE = [
27
22
  "hq-job-probe.sh — Outpost requirements probe → hq-pro jobs status (US-008).", "",
@@ -93,7 +88,7 @@ const realpath = (f) => { try {
93
88
  catch {
94
89
  return undefined;
95
90
  } };
96
- function writeState(file, body, mode) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${body}\n`, { mode }); try {
91
+ function writeState(file, body, mode, lineEnding = "\n") { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, `${body}${lineEnding}`, { mode }); try {
97
92
  fs.chmodSync(file, mode);
98
93
  }
99
94
  catch { /* legacy chmod is best-effort */ } }
@@ -286,7 +281,12 @@ function auth(runtime, d) {
286
281
  return (d.statSize(file) ?? 0) > 0 ? "ok" : "missing";
287
282
  }
288
283
  function writeAttempt(d, id, data) { d.writeState(path.join(d.env.HQ_JOB_PROBE_STATE_DIR || path.join(d.home, ".hq/jobs/probes"), id, "last-attempt.json"), compact(data), 0o600); }
289
- function writeCache(d, id, readiness, source) { d.writeState(path.join(d.env.HQ_JOB_STATUS_CACHE_DIR || path.join(d.home, ".hq/jobs/status-cache"), `${id}.json`), compact({ job_id: id, readiness, updated_at: isoNow(d), source }), 0o600); }
284
+ function writeCache(d, id, readiness, source) {
285
+ // The bundled shell redirects the Windows jq.exe output to this file. The
286
+ // Windows runtime writes CRLF, so preserve those bytes in the native port.
287
+ const lineEnding = process.platform === "win32" ? "\r\n" : "\n";
288
+ d.writeState(path.join(d.env.HQ_JOB_STATUS_CACHE_DIR || path.join(d.home, ".hq/jobs/status-cache"), `${id}.json`), compact({ job_id: id, readiness, updated_at: isoNow(d), source }), 0o600, lineEnding);
289
+ }
290
290
  function resolveCwd(root, cwd, d) {
291
291
  const canon = d.realpath(root) ?? path.resolve(root), candidate = path.resolve(root, cwd), file = d.realpath(candidate) ?? candidate;
292
292
  return { file, allowed: file === canon || file.startsWith(`${canon}${path.sep}`) };
@@ -338,13 +338,13 @@ async function probeFile(file, dry, d) {
338
338
  catch (e) {
339
339
  const rawStderr = legacyYamlParseDiagnostics(file, d);
340
340
  if (rawStderr)
341
- throw new Fatal(`job missing id: ${displayShellPath(file)}`, 2, "hq-job-probe", rawStderr);
341
+ throw new Fatal(`job missing id: ${displayJobFile(file)}`, 2, "hq-job-probe", rawStderr);
342
342
  throw new Fatal(`invalid job YAML: ${file}${e instanceof Error ? `: ${e.message}` : ""}`);
343
343
  }
344
344
  const id = scalar(job.id), name = scalar(job.name), runtime = scalar(nested(job, "requirements", "runtime")) || scalar(job.runtime);
345
345
  const company = scalar(nested(job, "requirements", "company")), cwd = scalar(nested(job, "requirements", "cwd") ?? job.cwd);
346
346
  if (!id)
347
- throw new Fatal(`job missing id: ${displayShellPath(file)}`);
347
+ throw new Fatal(`job missing id: ${displayJobFile(file)}`);
348
348
  if (!runtime)
349
349
  throw new Fatal(`job missing runtime: ${file}`);
350
350
  const at = isoNow(d), actions = [], authState = auth(runtime, d);
@@ -515,7 +515,7 @@ export async function runJobProbe(argv, injected) {
515
515
  return { code: 2, stdout: "", stderr: "hq-job-probe: --all does not take a job argument\n" };
516
516
  const files = allFiles(root);
517
517
  if (!files.length)
518
- return { code: 2, stdout: "", stderr: `hq-job-probe: no job YAML found under personal/jobs or companies/*/jobs in ${displayShellPath(root)}\n` };
518
+ return { code: 2, stdout: "", stderr: `hq-job-probe: no job YAML found under personal/jobs or companies/*/jobs in ${displayJobFile(root)}\n` };
519
519
  let code = 0;
520
520
  try {
521
521
  for (const f of files) {
@@ -545,9 +545,7 @@ export async function runJobProbe(argv, injected) {
545
545
  // then emits the ordinary not-found error after the process substitution
546
546
  // closes. Preserve both stderr lines for byte parity.
547
547
  if (x instanceof Fatal && x.message.startsWith("duplicate job id '")) {
548
- const shellMessage = process.platform === "win32"
549
- ? x.message.replace(/([A-Za-z]):[\\/]/gu, (_match, drive) => "/" + drive.toLowerCase() + "/").replaceAll("\\", "/")
550
- : x.message;
548
+ const shellMessage = displayShellRegistryError(x.message);
551
549
  return { code: x.code, stdout: "", stderr: `${x.prefix}: ${shellMessage}\n${x.prefix}: job id not found in registry: ${opts.target}\n` };
552
550
  }
553
551
  return failure(x);
@@ -593,12 +591,6 @@ export function registerCoreJobProbeCommand(job) {
593
591
  const root = !forwarded.help && (!forwarded.hasRoot || Boolean(forwarded.root))
594
592
  ? resolveLiveRoot({ hqRoot: forwarded.hasRoot ? forwarded.root : core.opts().hqRoot })
595
593
  : undefined;
596
- if (!await coreNativeJobsEnabled()) {
597
- const r = runBundledScript({ asset: JOB_PROBE_ASSET, args: argv, cwd: process.cwd(), ...(root ? { hqRoot: root } : {}) });
598
- if (r.code)
599
- process.exit(r.code);
600
- return;
601
- }
602
594
  const r = await runJobProbe(argv, defaultProbeDeps(root ?? (process.env.HQ_ROOT || process.cwd())));
603
595
  if (r.stdout)
604
596
  process.stdout.write(r.stdout.endsWith("\n") ? r.stdout : `${r.stdout}\n`);
@@ -607,6 +599,6 @@ export function registerCoreJobProbeCommand(job) {
607
599
  if (r.code)
608
600
  process.exit(r.code);
609
601
  });
610
- describeCoreCommand(command, { execution: "native", asset: JOB_PROBE_ASSET, root: "live" });
602
+ describeCoreCommand(command, { execution: "native", root: "live" });
611
603
  }
612
604
  //# sourceMappingURL=core-job-probe-native.js.map
@@ -4,7 +4,6 @@ import * as os from "node:os";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
6
6
  import { registerCoreJobLeaf } from "./core-job-native-utils.js";
7
- const ASSET = "core/scripts/hq-job-remediate.sh";
8
7
  const USAGE = [
9
8
  "hq-job-remediate.sh — bounded self-remediation for Outpost scheduled jobs (US-006).",
10
9
  "",
@@ -367,7 +366,7 @@ export function runJobRemediate(argv, options = {}) {
367
366
  }
368
367
  }
369
368
  export function registerCoreJobRemediateNativeCommand(group, core) {
370
- registerCoreJobLeaf(group, core, "remediate", ASSET, "Run bounded remediation for a scheduled-job failure", (argv, hqRoot) => runJobRemediate(argv, { hqRoot }), canUseLiveRootForArguments, [
369
+ registerCoreJobLeaf(group, core, "remediate", "Run bounded remediation for a scheduled-job failure", (argv, hqRoot) => runJobRemediate(argv, { hqRoot }), canUseLiveRootForArguments, [
371
370
  "--hq-root", "--failure-class", "--job-id", "--company",
372
371
  ], ["--dry-run"]);
373
372
  }