@indigoai-us/hq-cli 5.345.11 → 5.345.12

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 (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/assets/scaffold/core/scripts/register-project.sh +5 -5
  3. package/assets/scaffold/core/scripts/repo-run-registry.sh +8 -8
  4. package/assets/scaffold/core/scripts/resume-thread-lock.sh +4 -4
  5. package/assets/scaffold/core/scripts/verify-story-deliverables.sh +1 -1
  6. package/assets/scaffold/core/scripts/worktree-gc.sh +1 -1
  7. package/dist/commands/core-handoff.js +69 -34
  8. package/dist/commands/core-hq-session-native.js +26 -5
  9. package/dist/commands/core-job-notify-fast.d.ts +2 -0
  10. package/dist/commands/core-job-notify-fast.js +6 -0
  11. package/dist/commands/core-job-probe-fast.d.ts +2 -0
  12. package/dist/commands/core-job-probe-fast.js +11 -0
  13. package/dist/commands/core-job-probe-native.js +1 -1
  14. package/dist/commands/core-job-remediate-fast.d.ts +2 -0
  15. package/dist/commands/core-job-remediate-fast.js +6 -0
  16. package/dist/commands/core-job-run-fast.d.ts +2 -0
  17. package/dist/commands/core-job-run-fast.js +6 -0
  18. package/dist/commands/core-pin-fast.d.ts +2 -0
  19. package/dist/commands/core-pin-fast.js +15 -0
  20. package/dist/commands/core-rebuild-index-fast.d.ts +6 -0
  21. package/dist/commands/core-rebuild-index-fast.js +39 -0
  22. package/dist/commands/core-register-project-native.js +11 -2
  23. package/dist/commands/core-repo-run-registry-native.js +47 -4
  24. package/dist/commands/core-session-journal-native.d.ts +1 -1
  25. package/dist/commands/core-session-journal-native.js +2 -2
  26. package/dist/commands/core-share-suggestion-state-native.js +6 -2
  27. package/dist/commands/core-work-mesh-live-rebind-native.js +5 -2
  28. package/dist/commands/core-worker-lint-fast.d.ts +2 -0
  29. package/dist/commands/core-worker-lint-fast.js +11 -0
  30. package/dist/commands/core.js +9 -6
  31. package/dist/fast-scaffold.js +49 -0
  32. package/dist/lib/core-utils/worktree-resume.d.ts +1 -0
  33. package/dist/lib/core-utils/worktree-resume.js +68 -31
  34. package/dist/lib/daemon/catalog.d.ts +10 -0
  35. package/dist/lib/daemon/catalog.js +8 -0
  36. package/dist/lib/daemon/lanes-reconcile-guard.d.ts +43 -0
  37. package/dist/lib/daemon/lanes-reconcile-guard.js +115 -0
  38. package/dist/lib/daemon/run.js +2 -0
  39. package/dist/lib/daemon/supervisor.d.ts +2 -0
  40. package/dist/lib/daemon/supervisor.js +28 -0
  41. package/dist/lib/lanes/autopilot-registry.d.ts +2 -5
  42. package/dist/lib/lanes/autopilot-registry.js +4 -4
  43. package/dist/lib/lanes/reconcile.d.ts +0 -5
  44. package/dist/lib/lanes/reconcile.js +4 -4
  45. package/dist/lib/lanes/types.d.ts +6 -5
  46. package/dist/utils/run-bundled-script.js +1 -1
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.12] — 2026-10-06
6
+ - Route rebuild-index, pin, scheduled-job commands, and worker-skill lint through the fast CLI entry path to reduce command startup time.
7
+ - `hq daemon` on desktop machines no longer starts a lane reconcile process every 15 seconds when no lane needs it. It checks the lane store first: live lanes, keep-alive lanes, pending resumes and pending questions keep the 15-second cadence; a store with only settled lanes is reconciled once a minute, as before 15-second polling; an HQ folder with no lanes, questions, lead registry or lanes config is not reconciled at all. Outposts and self-hosted machines are unchanged.
8
+ - Fixed Windows shell/native parity for jq output and Git Bash path display in hook-state and worktree helpers.
9
+ - Fixed Windows parity for clipboard command shims and CRLF output from bundled scaffold scripts.
10
+ - Made the repo-run-registry clock fixture deterministic and stripped jq.exe carriage returns before writing registry state files.
11
+
5
12
  ## [5.345.11] — 2026-10-05
6
13
  - Fix Windows parity for delegate, scheduled job, and session helpers.
7
14
 
@@ -73,7 +73,7 @@ mark_pending() {
73
73
  [ -n "$title" ] || title="$project"
74
74
  mkdir -p "$(dirname "$board")"
75
75
  if [ ! -f "$board" ]; then
76
- jq -n --arg company "$company" '{company:$company, projects:[]}' > "$board"
76
+ jq -n --arg company "$company" '{company:$company, projects:[]}' | tr -d '\r' > "$board"
77
77
  fi
78
78
  tmp="$(mktemp)"
79
79
  jq \
@@ -101,7 +101,7 @@ mark_pending() {
101
101
  updated_at:$now
102
102
  }]
103
103
  end
104
- ' "$board" > "$tmp"
104
+ ' "$board" | tr -d '\r' > "$tmp"
105
105
  mv "$tmp" "$board"
106
106
  }
107
107
 
@@ -132,7 +132,7 @@ register_one() {
132
132
  [ -n "$title" ] || title="$project"
133
133
  mkdir -p "$(dirname "$board")"
134
134
  if [ ! -f "$board" ]; then
135
- jq -n --arg company "$company" '{company:$company, projects:[]}' > "$board"
135
+ jq -n --arg company "$company" '{company:$company, projects:[]}' | tr -d '\r' > "$board"
136
136
  fi
137
137
  tmp="$(mktemp)"
138
138
  jq \
@@ -164,7 +164,7 @@ register_one() {
164
164
  updated_at:$now
165
165
  }]
166
166
  end
167
- ' "$board" > "$tmp"
167
+ ' "$board" | tr -d '\r' > "$tmp"
168
168
  mv "$tmp" "$board"
169
169
  jq -e \
170
170
  --arg id "$project" \
@@ -245,7 +245,7 @@ retry_pending() {
245
245
  count=$((count + 1))
246
246
  [ "$count" -le 3 ] || break
247
247
  ( register_one "$company" "$id" ) || true
248
- done < <(jq -r '[.projects[]? | select(.pending_registration == true) | .id // empty] | .[]' "$board" 2>/dev/null || true)
248
+ done < <(jq -r '[.projects[]? | select(.pending_registration == true) | .id // empty] | .[]' "$board" 2>/dev/null | tr -d '\r' || true)
249
249
  }
250
250
 
251
251
  if [ "${1:-}" = "--audit" ]; then
@@ -219,7 +219,7 @@ _prune_stale() {
219
219
  kept=$(echo "$kept" | jq --argjson e "$entry" '. + [$e]')
220
220
  done <<< "$runs"
221
221
  fi
222
- jq --argjson runs "$kept" '.runs = $runs' "$REG_FILE" > "$tmp"
222
+ jq --argjson runs "$kept" '.runs = $runs' "$REG_FILE" | tr -d '\r' > "$tmp"
223
223
  mv "$tmp" "$REG_FILE"
224
224
  [[ $pruned -gt 0 ]] && _log "pruned $pruned stale entries"
225
225
  return 0
@@ -277,7 +277,7 @@ _cmd_register() {
277
277
  host: $host,
278
278
  started_at: $now,
279
279
  heartbeat_at: $now
280
- }]' "$REG_FILE" > "$tmp"
280
+ }]' "$REG_FILE" | tr -d '\r' > "$tmp"
281
281
  mv "$tmp" "$REG_FILE"
282
282
  _unlock
283
283
  trap - EXIT
@@ -297,7 +297,7 @@ _cmd_deregister() {
297
297
  trap '_unlock' EXIT
298
298
  _ensure_reg
299
299
  local tmp="$REG_FILE.tmp.$$"
300
- jq --arg run_id "$run_id" '.runs |= map(select(.run_id != $run_id))' "$REG_FILE" > "$tmp"
300
+ jq --arg run_id "$run_id" '.runs |= map(select(.run_id != $run_id))' "$REG_FILE" | tr -d '\r' > "$tmp"
301
301
  mv "$tmp" "$REG_FILE"
302
302
  _unlock
303
303
  trap - EXIT
@@ -320,7 +320,7 @@ _cmd_heartbeat() {
320
320
  local tmp="$REG_FILE.tmp.$$"
321
321
  jq --arg run_id "$run_id" --arg now "$now" \
322
322
  '.runs |= map(if .run_id == $run_id then .heartbeat_at = $now else . end)' \
323
- "$REG_FILE" > "$tmp"
323
+ "$REG_FILE" | tr -d '\r' > "$tmp"
324
324
  mv "$tmp" "$REG_FILE"
325
325
  _unlock
326
326
  trap - EXIT
@@ -329,7 +329,7 @@ _cmd_heartbeat() {
329
329
  _cmd_list() {
330
330
  _ensure_reg
331
331
  _prune_stale_locked
332
- jq '.runs' "$REG_FILE"
332
+ jq '.runs' "$REG_FILE" | tr -d '\r'
333
333
  }
334
334
 
335
335
  _cmd_clean_stale() {
@@ -371,14 +371,14 @@ _cmd_owner_of() {
371
371
  ($entry.scope == "repo" and $entry.repo_path == $repo)
372
372
  or (($entry.scope | startswith("worktree:")) and
373
373
  worktree_covers($target; $entry.scope))
374
- ) | $entry]' "$REG_FILE"
374
+ ) | $entry]' "$REG_FILE" | tr -d '\r'
375
375
  else
376
376
  jq --arg repo "$repo" --arg target "$(_abs_path "$path")" \
377
377
  '[.runs[] | select(
378
378
  (.scope == "repo" and .repo_path == $repo)
379
379
  or (.scope | startswith("worktree:")) and
380
380
  ($target | startswith(.scope | sub("^worktree:"; "")))
381
- )]' "$REG_FILE"
381
+ )]' "$REG_FILE" | tr -d '\r'
382
382
  fi
383
383
  }
384
384
 
@@ -451,7 +451,7 @@ _cmd_check() {
451
451
 
452
452
  # Emit one summary line per owner to stderr for the calling hook
453
453
  echo "$matches" | jq -r '.[] |
454
- "run_id=\(.run_id) pid=\(.pid) command=\(.command) project=\(.project) scope=\(.scope) repo=\(.repo_path) started=\(.started_at) heartbeat=\(.heartbeat_at)"' >&2
454
+ "run_id=\(.run_id) pid=\(.pid) command=\(.command) project=\(.project) scope=\(.scope) repo=\(.repo_path) started=\(.started_at) heartbeat=\(.heartbeat_at)"' | tr -d '\r' >&2
455
455
  return 2
456
456
  }
457
457
 
@@ -138,7 +138,7 @@ emit_unlocked() {
138
138
  jq -cn \
139
139
  --arg thread_id "$thread_id" \
140
140
  --arg lock_path "$relative_lock_path" \
141
- '{status:"unlocked", thread_id:$thread_id, lock_path:$lock_path}'
141
+ '{status:"unlocked", thread_id:$thread_id, lock_path:$lock_path}' | tr -d '\r'
142
142
  }
143
143
 
144
144
  emit_stale() {
@@ -152,7 +152,7 @@ emit_stale() {
152
152
  --arg resumed_at "$resumed_at" \
153
153
  --arg lock_generation "$generation" \
154
154
  --arg prompt "$prompt" \
155
- '{status:"stale", thread_id:$thread_id, lock_path:$lock_path, stale_reason:$stale_reason, session_id:$session_id, resumed_at:$resumed_at, lock_generation:$lock_generation, prompt:$prompt}'
155
+ '{status:"stale", thread_id:$thread_id, lock_path:$lock_path, stale_reason:$stale_reason, session_id:$session_id, resumed_at:$resumed_at, lock_generation:$lock_generation, prompt:$prompt}' | tr -d '\r'
156
156
  }
157
157
 
158
158
  emit_locked() {
@@ -165,7 +165,7 @@ emit_locked() {
165
165
  --arg resumed_at "$resumed_at" \
166
166
  --arg lock_generation "$generation" \
167
167
  --arg prompt "$prompt" \
168
- '{status:"locked", thread_id:$thread_id, lock_path:$lock_path, session_id:$session_id, resumed_at:$resumed_at, lock_generation:$lock_generation, prompt:$prompt}'
168
+ '{status:"locked", thread_id:$thread_id, lock_path:$lock_path, session_id:$session_id, resumed_at:$resumed_at, lock_generation:$lock_generation, prompt:$prompt}' | tr -d '\r'
169
169
  }
170
170
 
171
171
  # Print the existing lock's state. A damaged or expired marker is deliberately
@@ -244,7 +244,7 @@ emit_acquired() {
244
244
  --arg lock_path "$relative_lock_path" \
245
245
  --arg session_id "$session_id" \
246
246
  --arg lock_generation "$generation" \
247
- '{status:"acquired", thread_id:$thread_id, lock_path:$lock_path, session_id:$session_id, lock_generation:$lock_generation}'
247
+ '{status:"acquired", thread_id:$thread_id, lock_path:$lock_path, session_id:$session_id, lock_generation:$lock_generation}' | sed 's/\r$//'
248
248
  }
249
249
 
250
250
  case "$subcommand" in
@@ -123,7 +123,7 @@ while IFS= read -r d; do
123
123
  done < <(jq_capture -r '.[]' <<<"$REFS_JSON")
124
124
 
125
125
  if [ "$JSON" = 1 ]; then
126
- jq -cn --arg s "$STORY" \
126
+ jq_capture -cn --arg s "$STORY" \
127
127
  --argjson m "$(printf '%s\n' "${missing[@]:-}" | jq_capture -R . | jq_capture -s 'map(select(. != ""))')" \
128
128
  --argjson p "$(printf '%s\n' "${present[@]:-}" | jq_capture -R . | jq_capture -s 'map(select(. != ""))')" \
129
129
  '{story:$s, status:(if ($m|length)==0 then "present" else "missing" end), missing:$m, present:$p}'
@@ -350,7 +350,7 @@ if [[ "$JSON" -eq 1 ]]; then
350
350
  skipped_by_reason: ([$records[] | select(.action=="skipped") | .reason]
351
351
  | group_by(.) | map({(.[0]): length}) | add // {}),
352
352
  records: $records
353
- }'
353
+ }' | tr -d '\r'
354
354
  else
355
355
  if [[ "$APPLY" -eq 1 ]]; then
356
356
  echo "worktree-gc [apply]: examined $examined · removed $removed · reclaimed ${reclaimed_gb} GB · skipped ${skip_total} (retention ${DAYS}d)"
@@ -12,6 +12,7 @@ import { defaultBackgroundDependencies, runBackgroundLauncher, } from "../lib/se
12
12
  import { resolveLiveRoot } from "../utils/hq-roots.js";
13
13
  import { runBundledScript } from "../utils/run-bundled-script.js";
14
14
  import { describeCoreCommand } from "./core-command-catalog.js";
15
+ import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
15
16
  import { compactJson, isRecord, jqTruthy, prettyJson, runHandoffOpenSteps, shellDate, } from "./core-handoff-open-steps.js";
16
17
  export { runHandoffOpenSteps };
17
18
  const ASSETS = {
@@ -113,16 +114,20 @@ function git(args, cwd) {
113
114
  }
114
115
  function commandAvailable(name) {
115
116
  for (const entry of (process.env.PATH ?? "").split(path.delimiter)) {
116
- const candidate = path.resolve(entry || ".", name);
117
- try {
118
- fs.accessSync(candidate, fs.constants.X_OK);
119
- if (fs.statSync(candidate).isFile())
120
- return true;
121
- }
122
- catch (error) {
123
- const code = error.code;
124
- if (code !== "ENOENT" && code !== "ENOTDIR" && code !== "EACCES")
125
- throw error;
117
+ const candidates = process.platform === "win32" && path.extname(name) === ""
118
+ ? [`${name}.exe`, `${name}.cmd`, `${name}.bat`, name].map((candidate) => path.resolve(entry || ".", candidate))
119
+ : [path.resolve(entry || ".", name)];
120
+ for (const candidate of candidates) {
121
+ try {
122
+ fs.accessSync(candidate, fs.constants.X_OK);
123
+ if (fs.statSync(candidate).isFile())
124
+ return true;
125
+ }
126
+ catch (error) {
127
+ const code = error.code;
128
+ if (code !== "ENOENT" && code !== "ENOTDIR" && code !== "EACCES" && code !== "UNKNOWN")
129
+ throw error;
130
+ }
126
131
  }
127
132
  }
128
133
  return false;
@@ -143,6 +148,7 @@ function copyClipboardText(value, cwd) {
143
148
  env: process.env,
144
149
  input: value,
145
150
  stdio: ["pipe", "ignore", "ignore"],
151
+ shell: process.platform === "win32",
146
152
  });
147
153
  return result.status === 0;
148
154
  }
@@ -797,18 +803,45 @@ function spawnBashDetached(script, args, cwd, root, logFile) {
797
803
  let fd;
798
804
  try {
799
805
  mkdirParent(logFile);
800
- fd = fs.openSync(logFile, "a");
801
- const child = spawn("bash", [script, ...args], {
802
- cwd,
803
- env: { ...process.env, HQ_ROOT: root },
804
- detached: true,
805
- windowsHide: true,
806
- stdio: ["ignore", fd, fd],
807
- });
806
+ const childEnv = { ...process.env };
807
+ if (process.platform === "win32") {
808
+ // Windows treats environment variable names case-insensitively. Replace
809
+ // every inherited spelling with the Git Bash spelling for this root;
810
+ // if Bash derives PWD from Node's Windows cwd itself, it can truncate the
811
+ // drive prefix when starting a detached child.
812
+ for (const key of Object.keys(childEnv)) {
813
+ if (key.toUpperCase() === "PWD")
814
+ delete childEnv[key];
815
+ }
816
+ childEnv.PWD = shellDisplayPath(root);
817
+ childEnv.HQ_ROOT = shellDisplayPath(root);
818
+ childEnv.HQ_HANDOFF_CHILD_LOG_FILE = shellDisplayPath(logFile);
819
+ }
820
+ else {
821
+ fd = fs.openSync(logFile, "a");
822
+ childEnv.HQ_ROOT = root;
823
+ }
824
+ const child = process.platform === "win32"
825
+ ? spawn("bash", ["-c", 'cd -- "$HQ_ROOT" || exit; exec bash "$@" >> "$HQ_HANDOFF_CHILD_LOG_FILE" 2>&1', "_", shellDisplayPath(script), ...args], {
826
+ cwd,
827
+ env: childEnv,
828
+ detached: true,
829
+ windowsHide: true,
830
+ stdio: ["ignore", "ignore", "ignore"],
831
+ })
832
+ : spawn("bash", [script, ...args], {
833
+ cwd,
834
+ env: childEnv,
835
+ detached: true,
836
+ windowsHide: true,
837
+ stdio: ["ignore", fd, fd],
838
+ });
808
839
  child.once("error", (error) => appendLog(logFile, `${error.message}\n`));
809
840
  child.unref();
810
- fs.closeSync(fd);
811
- fd = undefined;
841
+ if (fd !== undefined) {
842
+ fs.closeSync(fd);
843
+ fd = undefined;
844
+ }
812
845
  return child.pid;
813
846
  }
814
847
  catch {
@@ -836,11 +869,18 @@ function parseLearningCount(file) {
836
869
  }
837
870
  }
838
871
  async function launchHandoffPost(args, root) {
872
+ const childEnv = { ...process.env, HQ_NATIVE_HANDOFF_POST_CHILD: "1" };
873
+ if (process.platform === "win32") {
874
+ for (const key of Object.keys(childEnv))
875
+ if (key.toUpperCase() === "PWD")
876
+ delete childEnv[key];
877
+ childEnv.PWD = shellDisplayPath(root);
878
+ }
839
879
  return await new Promise((resolve) => {
840
880
  try {
841
881
  const child = spawn("hq", ["core", "--hq-root", root, "handoff", "post", ...args], {
842
882
  cwd: root,
843
- env: { ...process.env, HQ_NATIVE_HANDOFF_POST_CHILD: "1" },
883
+ env: childEnv,
844
884
  detached: true,
845
885
  windowsHide: true,
846
886
  stdio: "ignore",
@@ -902,19 +942,14 @@ export function runHandoffPostWorker(args, context) {
902
942
  log("document-release: skipped unsupported files_touched entry");
903
943
  if (counts.scope > 0) {
904
944
  const installedScript = path.join(root, "core/scripts/skill-installed.sh");
905
- const logFd = fs.openSync(logMain, "a");
906
- let installedStatus = null;
907
- try {
908
- const installed = spawnSync("bash", [installedScript, "document-release", process.env.HQ_ACTIVE_COMPANY || ""], {
909
- cwd: root,
910
- env: process.env,
911
- stdio: ["ignore", "ignore", logFd],
912
- });
913
- installedStatus = installed.status;
914
- }
915
- finally {
916
- fs.closeSync(logFd);
917
- }
945
+ const installed = spawnSync("bash", [installedScript, "document-release", process.env.HQ_ACTIVE_COMPANY || ""], {
946
+ cwd: root,
947
+ env: process.env,
948
+ stdio: ["ignore", "ignore", "pipe"],
949
+ });
950
+ if (installed.stderr?.length)
951
+ fs.appendFileSync(logMain, installed.stderr);
952
+ const installedStatus = installed.status;
918
953
  if (installedStatus === 0) {
919
954
  log(`document-release: eligible and pending runtime dispatch by handoff skill (${counts.scope} scoped files; no dispatch proof)`);
920
955
  }
@@ -4,6 +4,7 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { spawn, spawnSync } from "node:child_process";
6
6
  import { awkCannotOpenFileDiagnostic } from "../lib/core-utils/awk-diagnostics.js";
7
+ import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
7
8
  export const HQ_SESSION_ASSET = "core/scripts/hq-session.sh";
8
9
  export const HQ_SESSION_HELP = [
9
10
  "hq-core: public",
@@ -154,14 +155,18 @@ function findMetaValue(contents, key) {
154
155
  return { found: false, value: "" };
155
156
  }
156
157
  function getMetaValue(contents, key) { return findMetaValue(contents, key).value; }
157
- const WORK_MESH_SESSION_START_COMMAND = 'printf "%s" "$1" | bash "$2" SessionStart';
158
+ const WORK_MESH_SESSION_START_COMMAND = 'event=$HQ_CORE_SESSION_EVENT_JSON; unset HQ_CORE_SESSION_EVENT_JSON; printf "%s" "$event" | bash "$1" SessionStart';
158
159
  function recordWorkMeshSessionStart(input) {
159
160
  const hook = path.join(input.root, "core", "hooks", "SessionStart", "35-work-mesh-session-start.sh");
160
161
  // Keep the shell hook as the single owner of SessionStart's Work Mesh behavior.
161
162
  // The bundled hq-session helper skips this path if either dependency is absent.
162
163
  if (!input.isFile(hook) || !input.findExecutable("jq", input.env))
163
164
  return "";
164
- const event = JSON.stringify({ session_id: input.sessionId, cwd: input.env.PWD || input.cwd });
165
+ // hq-session.sh passes its inherited PWD into the Work Mesh event. On
166
+ // Windows that value is drive-letter based even though Git Bash's own pwd
167
+ // builtin uses /c/... for filesystem operations.
168
+ const eventCwd = input.env.PWD || input.cwd;
169
+ const event = JSON.stringify({ session_id: input.sessionId, cwd: eventCwd });
165
170
  if (!event)
166
171
  return "";
167
172
  const logFile = path.join(input.root, "workspace", "logs", "work-mesh-hook.log");
@@ -170,10 +175,15 @@ function recordWorkMeshSessionStart(input) {
170
175
  }
171
176
  catch { /* The shell also suppresses mkdir failure and attempts the redirected launch. */ }
172
177
  try {
173
- input.spawnDetached("nohup", ["bash", "-c", WORK_MESH_SESSION_START_COMMAND, "_", event, hook], input.cwd, {
178
+ input.spawnDetached("nohup", ["bash", "-c", WORK_MESH_SESSION_START_COMMAND, "_", shellDisplayPath(hook)], input.cwd, {
174
179
  ...input.env,
180
+ HQ_CORE_SESSION_EVENT_JSON: event,
175
181
  HQ_ROOT: input.root,
176
182
  CLAUDE_CODE_SESSION_ID: input.sessionId,
183
+ // Git for Windows must not rewrite the JSON payload's backslashes as it
184
+ // converts command arguments. The following hook path remains eligible
185
+ // for normal MSYS path conversion.
186
+ ...(process.platform === "win32" ? { MSYS2_ARG_CONV_EXCL: "*" } : {}),
177
187
  }, logFile);
178
188
  return "";
179
189
  }
@@ -249,6 +259,15 @@ function currentMetaPath(root, sessionOverride, deps) {
249
259
  : path.join(root, "workspace", "sessions", id, "meta.yaml")
250
260
  : "";
251
261
  }
262
+ function shellPathForDiagnostic(value) {
263
+ if (process.platform !== "win32")
264
+ return value;
265
+ const absolute = path.win32.resolve(value);
266
+ const workspaceIndex = absolute.toLowerCase().indexOf("\\workspace\\");
267
+ return workspaceIndex < 0
268
+ ? absolute
269
+ : `${absolute.slice(0, workspaceIndex)}/workspace/${absolute.slice(workspaceIndex + "\\workspace\\".length).replaceAll("\\", "/")}`;
270
+ }
252
271
  function exactCompanyDir(root, slug, deps) {
253
272
  if (!root || !slug)
254
273
  return false;
@@ -412,7 +431,9 @@ function validateSenior(value) {
412
431
  return id !== "" && id !== "." && id !== ".." && /^[A-Za-z0-9._-]+$/.test(id);
413
432
  }
414
433
  function atomicWrite(filePath, contents, deps) {
415
- const temp = deps.makeTempPath();
434
+ // Keep the staging file on the destination volume so atomic rename works
435
+ // when the system temp directory and HQ_ROOT are on different Windows drives.
436
+ const temp = path.join(path.dirname(filePath), path.basename(deps.makeTempPath()));
416
437
  try {
417
438
  deps.writeFile(temp, contents, 0o600);
418
439
  deps.rename(temp, filePath);
@@ -489,7 +510,7 @@ export function executeHqSession(argv, context, overrides = {}) {
489
510
  }
490
511
  catch (error) {
491
512
  if (error?.code === "EACCES" || error?.code === "EPERM") {
492
- return { code: 2, stdout: "", stderr: awkCannotOpenFileDiagnostic(metaPath, "Permission denied", 6, process.platform, deps.env.PATH) };
513
+ return { code: 2, stdout: "", stderr: awkCannotOpenFileDiagnostic(shellPathForDiagnostic(metaPath), "Permission denied", 6, process.platform, deps.env.PATH) };
493
514
  }
494
515
  if (isExpectedFsMiss(error))
495
516
  return { code: 1, stdout: "", stderr: `awk: cannot open ${metaPath} (Permission denied)\n` };
@@ -0,0 +1,2 @@
1
+ export declare function runJobNotifyFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): number;
2
+ //# sourceMappingURL=core-job-notify-fast.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Isolated fast-entry adapter for the native scheduled-job notifier. */
2
+ import { runJobNotify } from "./core-job-notify-native.js";
3
+ export function runJobNotifyFast(args, _stdin, _cwd, hqRoot) {
4
+ return runJobNotify([...args], { hqRoot: hqRoot ?? process.env.HQ_ROOT });
5
+ }
6
+ //# sourceMappingURL=core-job-notify-fast.js.map
@@ -0,0 +1,2 @@
1
+ export declare function runJobProbeFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): Promise<number>;
2
+ //# sourceMappingURL=core-job-probe-fast.d.ts.map
@@ -0,0 +1,11 @@
1
+ /** Isolated fast-entry adapter for the native scheduled-job probe. */
2
+ import { defaultProbeDeps, runJobProbe } from "./core-job-probe-native.js";
3
+ export async function runJobProbeFast(args, _stdin, _cwd, hqRoot) {
4
+ const result = await runJobProbe([...args], defaultProbeDeps(hqRoot ?? process.env.HQ_ROOT ?? process.cwd()));
5
+ if (result.stdout)
6
+ process.stdout.write(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}\n`);
7
+ if (result.stderr)
8
+ process.stderr.write(result.stderr);
9
+ return result.code;
10
+ }
11
+ //# sourceMappingURL=core-job-probe-fast.js.map
@@ -607,6 +607,6 @@ export function registerCoreJobProbeCommand(job) {
607
607
  if (r.code)
608
608
  process.exit(r.code);
609
609
  });
610
- describeCoreCommand(command, { execution: "native", root: "live" });
610
+ describeCoreCommand(command, { execution: "native", asset: JOB_PROBE_ASSET, root: "live" });
611
611
  }
612
612
  //# sourceMappingURL=core-job-probe-native.js.map
@@ -0,0 +1,2 @@
1
+ export declare function runJobRemediateFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): number;
2
+ //# sourceMappingURL=core-job-remediate-fast.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Isolated fast-entry adapter for native bounded job remediation. */
2
+ import { runJobRemediate } from "./core-job-remediate-native.js";
3
+ export function runJobRemediateFast(args, _stdin, _cwd, hqRoot) {
4
+ return runJobRemediate([...args], { hqRoot: hqRoot ?? process.env.HQ_ROOT });
5
+ }
6
+ //# sourceMappingURL=core-job-remediate-fast.js.map
@@ -0,0 +1,2 @@
1
+ export declare function runJobFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): Promise<number>;
2
+ //# sourceMappingURL=core-job-run-fast.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Isolated fast-entry adapter for the native scheduled-job runner. */
2
+ import { runJob } from "./core-job-run-native.js";
3
+ export async function runJobFast(args, _stdin, _cwd, hqRoot) {
4
+ return runJob([...args], hqRoot ?? process.env.HQ_ROOT);
5
+ }
6
+ //# sourceMappingURL=core-job-run-fast.js.map
@@ -0,0 +1,2 @@
1
+ export declare function runPinFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): number;
2
+ //# sourceMappingURL=core-pin-fast.d.ts.map
@@ -0,0 +1,15 @@
1
+ /** Isolated fast-entry adapter for the native pin utility. */
2
+ import { runPin } from "./core-pin-native.js";
3
+ export function runPinFast(args, _stdin, _cwd, hqRoot) {
4
+ const result = runPin([...args], {
5
+ root: hqRoot ?? process.env.HQ_ROOT ?? process.cwd(),
6
+ env: process.env,
7
+ now: () => new Date(),
8
+ });
9
+ if (result.stdout)
10
+ process.stdout.write(result.stdout);
11
+ if (result.stderr)
12
+ process.stderr.write(result.stderr);
13
+ return result.code;
14
+ }
15
+ //# sourceMappingURL=core-pin-fast.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Run a standard rebuild with the same renderer used by the full command.
3
+ * Argument forms that need Commander error formatting remain on the full graph.
4
+ */
5
+ export declare function runRebuildIndexFast(args: readonly string[], _stdin: string, _cwd: string, hqRoot: string | undefined): Promise<number | undefined>;
6
+ //# sourceMappingURL=core-rebuild-index-fast.d.ts.map
@@ -0,0 +1,39 @@
1
+ /** Isolated fast-entry adapter for rebuild-index native renderers. */
2
+ import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
3
+ const targets = new Set([
4
+ "all", "companies", "company-knowledge", "orchestrator", "projects",
5
+ "public-knowledge", "reports", "social-drafts", "threads", "workers",
6
+ ]);
7
+ /**
8
+ * Run a standard rebuild with the same renderer used by the full command.
9
+ * Argument forms that need Commander error formatting remain on the full graph.
10
+ */
11
+ export async function runRebuildIndexFast(args, _stdin, _cwd, hqRoot) {
12
+ const [target, ...rest] = args;
13
+ if (!target || !targets.has(target) || rest.length > 0 || !hqRoot)
14
+ return undefined;
15
+ const logs = [];
16
+ const flushLogs = () => {
17
+ for (const message of logs)
18
+ process.stderr.write(`${message}\n`);
19
+ };
20
+ try {
21
+ const refused = [];
22
+ renderIndexTarget(target, {
23
+ root: hqRoot,
24
+ log: (message) => logs.push(message),
25
+ refused,
26
+ });
27
+ if (refused.length > 0)
28
+ return undefined;
29
+ flushLogs();
30
+ return 0;
31
+ }
32
+ catch (error) {
33
+ if (error instanceof CompanyIndexUserError)
34
+ return undefined;
35
+ flushLogs();
36
+ throw error;
37
+ }
38
+ }
39
+ //# sourceMappingURL=core-rebuild-index-fast.js.map
@@ -5,14 +5,18 @@ import * as path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
7
7
  import { resolveBundledAsset } from "../utils/hq-roots-core.js";
8
+ import { buildSpawnPlan } from "../utils/windows-spawn.js";
8
9
  export const REGISTER_PROJECT_ASSET = "core/scripts/register-project.sh";
9
10
  const USAGE = "usage: register-project.sh <company> <project> | --audit <company> | --backfill <company> --only-active [--yes] | --retry-pending <company>";
10
11
  const MIN_HQ_CLI = "5.139.0";
11
12
  export function createRegisterProjectHqRunner(spawn = spawnSync) {
12
13
  return (args, options) => {
13
- const result = spawn("hq", args, {
14
+ const plan = buildSpawnPlan("hq", args);
15
+ const { cmd, args: spawnArgs, ...spawnOptions } = plan;
16
+ const result = spawn(cmd, spawnArgs, {
14
17
  cwd: options.cwd,
15
18
  env: options.env,
19
+ ...spawnOptions,
16
20
  encoding: "utf8",
17
21
  // Shell hq children inherit fd 0 even when their output is captured or discarded.
18
22
  stdio: options.capture ? ["inherit", "pipe", "pipe"] : ["inherit", "ignore", "ignore"],
@@ -130,7 +134,12 @@ function ensureOne(company, project, deps) {
130
134
  catch { /* legacy jq/write errors remain a failed registration */ }
131
135
  return errorResult(`hq-cli ${MIN_HQ_CLI} or newer is required; ${company}/${project} stays local until then`);
132
136
  }
133
- const ensure = deps.runHq(["mesh", "project", "ensure", project, "--company", company, "--stories-file", prd, "--json"], {
137
+ // The shell builds this argument from its raw HQ_ROOT plus POSIX separators.
138
+ // On Git Bash that preserves a Win32 drive prefix and appends `/companies/...`.
139
+ const hqPrd = process.platform === "win32"
140
+ ? `${deps.root.replace(/[\\/]+$/u, "")}/companies/${company}/projects/${project}/prd.json`
141
+ : prd;
142
+ const ensure = deps.runHq(["mesh", "project", "ensure", project, "--company", company, "--stories-file", hqPrd, "--json"], {
134
143
  cwd: deps.cwd, env: deps.env, capture: true,
135
144
  });
136
145
  if (ensure.code !== 0)