@indigoai-us/hq-cli 5.345.11 → 5.345.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +16 -16
- package/assets/scaffold/core/scripts/register-project.sh +5 -5
- package/assets/scaffold/core/scripts/repo-run-registry.sh +8 -8
- package/assets/scaffold/core/scripts/resume-thread-lock.sh +4 -4
- package/assets/scaffold/core/scripts/verify-story-deliverables.sh +1 -1
- package/assets/scaffold/core/scripts/worktree-gc.sh +1 -1
- package/dist/commands/core-delegate-w1b.js +14 -9
- package/dist/commands/core-delegate.d.ts +1 -0
- package/dist/commands/core-delegate.js +10 -2
- package/dist/commands/core-handoff.js +69 -34
- package/dist/commands/core-hq-session-native.js +26 -5
- package/dist/commands/core-job-notify-fast.d.ts +2 -0
- package/dist/commands/core-job-notify-fast.js +6 -0
- package/dist/commands/core-job-probe-fast.d.ts +2 -0
- package/dist/commands/core-job-probe-fast.js +11 -0
- package/dist/commands/core-job-probe-native.js +1 -1
- package/dist/commands/core-job-remediate-fast.d.ts +2 -0
- package/dist/commands/core-job-remediate-fast.js +6 -0
- package/dist/commands/core-job-run-fast.d.ts +2 -0
- package/dist/commands/core-job-run-fast.js +6 -0
- package/dist/commands/core-pin-fast.d.ts +2 -0
- package/dist/commands/core-pin-fast.js +15 -0
- package/dist/commands/core-rebuild-index-fast.d.ts +6 -0
- package/dist/commands/core-rebuild-index-fast.js +39 -0
- package/dist/commands/core-register-project-native.js +11 -2
- package/dist/commands/core-repo-run-registry-native.js +47 -4
- package/dist/commands/core-session-journal-native.d.ts +1 -1
- package/dist/commands/core-session-journal-native.js +2 -2
- package/dist/commands/core-share-suggestion-state-native.js +6 -2
- package/dist/commands/core-work-mesh-live-rebind-native.js +5 -2
- package/dist/commands/core-worker-lint-fast.d.ts +2 -0
- package/dist/commands/core-worker-lint-fast.js +11 -0
- package/dist/commands/core.js +9 -6
- package/dist/fast-scaffold.js +49 -0
- package/dist/lib/core-utils/worktree-resume.d.ts +1 -0
- package/dist/lib/core-utils/worktree-resume.js +68 -31
- package/dist/lib/daemon/catalog.d.ts +10 -0
- package/dist/lib/daemon/catalog.js +8 -0
- package/dist/lib/daemon/lanes-reconcile-guard.d.ts +43 -0
- package/dist/lib/daemon/lanes-reconcile-guard.js +115 -0
- package/dist/lib/daemon/run.js +2 -0
- package/dist/lib/daemon/supervisor.d.ts +2 -0
- package/dist/lib/daemon/supervisor.js +28 -0
- package/dist/lib/lanes/autopilot-registry.d.ts +2 -5
- package/dist/lib/lanes/autopilot-registry.js +4 -4
- package/dist/lib/lanes/compaction.d.ts +2 -2
- package/dist/lib/lanes/compaction.js +221 -54
- package/dist/lib/lanes/move-account.js +24 -1
- package/dist/lib/lanes/reconcile.d.ts +0 -5
- package/dist/lib/lanes/reconcile.js +4 -4
- package/dist/lib/lanes/types.d.ts +6 -5
- package/dist/utils/run-bundled-script.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.13] — 2026-10-06
|
|
6
|
+
- Stream provider transcript scans and account-move comparisons so large session files do not require whole-file reads.
|
|
7
|
+
|
|
8
|
+
## [5.345.12] — 2026-10-06
|
|
9
|
+
- Route rebuild-index, pin, scheduled-job commands, and worker-skill lint through the fast CLI entry path to reduce command startup time.
|
|
10
|
+
- `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.
|
|
11
|
+
- Fixed Windows shell/native parity for jq output and Git Bash path display in hook-state and worktree helpers.
|
|
12
|
+
- Fixed Windows parity for clipboard command shims and CRLF output from bundled scaffold scripts.
|
|
13
|
+
- Made the repo-run-registry clock fixture deterministic and stripped jq.exe carriage returns before writing registry state files.
|
|
14
|
+
|
|
5
15
|
## [5.345.11] — 2026-10-05
|
|
6
16
|
- Fix Windows parity for delegate, scheduled job, and session helpers.
|
|
7
17
|
|
|
@@ -65,11 +65,11 @@ done
|
|
|
65
65
|
[ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST"
|
|
66
66
|
jq -e . "$MANIFEST" >/dev/null 2>&1 || die "manifest is not valid JSON: $MANIFEST"
|
|
67
67
|
|
|
68
|
-
COMPANY="$(jq -r '.company // empty' "$MANIFEST")"
|
|
69
|
-
PROJECT="$(jq -r '.project.name // empty' "$MANIFEST")"
|
|
70
|
-
PRINCIPAL="$(jq -r '.to.principal // empty' "$MANIFEST")"
|
|
71
|
-
DISPLAY="$(jq -r '.to.displayName // .to.principal // empty' "$MANIFEST")"
|
|
72
|
-
STATUS="$(jq -r '.status // empty' "$MANIFEST")"
|
|
68
|
+
COMPANY="$(jq -r '.company // empty' "$MANIFEST" | tr -d '\r')"
|
|
69
|
+
PROJECT="$(jq -r '.project.name // empty' "$MANIFEST" | tr -d '\r')"
|
|
70
|
+
PRINCIPAL="$(jq -r '.to.principal // empty' "$MANIFEST" | tr -d '\r')"
|
|
71
|
+
DISPLAY="$(jq -r '.to.displayName // .to.principal // empty' "$MANIFEST" | tr -d '\r')"
|
|
72
|
+
STATUS="$(jq -r '.status // empty' "$MANIFEST" | tr -d '\r')"
|
|
73
73
|
[ -n "$COMPANY" ] || die "manifest has no company"
|
|
74
74
|
[ -n "$PROJECT" ] || die "manifest has no project.name"
|
|
75
75
|
[ -n "$PRINCIPAL" ] || die "manifest has no to.principal"
|
|
@@ -96,7 +96,7 @@ case "$PRINCIPAL" in
|
|
|
96
96
|
TAIL="$(printf '%s' "$PRINCIPAL" | tail -c 9 | tr '[:upper:]' '[:lower:]')"
|
|
97
97
|
LEGACY_GROUP="grp_dlg-$TAIL"
|
|
98
98
|
NEXT_GROUP="$(agent_group_id "$PRINCIPAL")"
|
|
99
|
-
RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST")"
|
|
99
|
+
RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST" | tr -d '\r')"
|
|
100
100
|
case "$RECORDED_GROUP" in
|
|
101
101
|
"$LEGACY_GROUP"|"$NEXT_GROUP") GRANT_PRINCIPAL="$RECORDED_GROUP" ;;
|
|
102
102
|
*) GRANT_PRINCIPAL="$NEXT_GROUP" ;;
|
|
@@ -109,7 +109,7 @@ case "$STATUS" in
|
|
|
109
109
|
*) die "manifest status is '$STATUS' — grants run from 'building' (or re-run from 'granted'), not from there" ;;
|
|
110
110
|
esac
|
|
111
111
|
|
|
112
|
-
PREFIX_COUNT="$(jq '.vaultPrefixes | length' "$MANIFEST")"
|
|
112
|
+
PREFIX_COUNT="$(jq '.vaultPrefixes | length' "$MANIFEST" | tr -d '\r')"
|
|
113
113
|
[ "$PREFIX_COUNT" -gt 0 ] || die "manifest has no vaultPrefixes"
|
|
114
114
|
|
|
115
115
|
# --- validate every prefix BEFORE touching anything --------------------------
|
|
@@ -118,7 +118,7 @@ PREFIX_COUNT="$(jq '.vaultPrefixes | length' "$MANIFEST")"
|
|
|
118
118
|
# not exist in the bucket. Either is a hard error, never a silent grant.
|
|
119
119
|
|
|
120
120
|
BAD="$(jq -r '.vaultPrefixes[] | .prefix
|
|
121
|
-
| select((endswith("/") | not) or startswith("companies/") or startswith("/"))' "$MANIFEST")"
|
|
121
|
+
| select((endswith("/") | not) or startswith("companies/") or startswith("/"))' "$MANIFEST" | tr -d '\r')"
|
|
122
122
|
if [ -n "$BAD" ]; then
|
|
123
123
|
die "prefix violates the hq-files prefix conventions (must be company-relative folder form ending in '/'): $BAD"
|
|
124
124
|
fi
|
|
@@ -130,8 +130,8 @@ if [ "$GRANT_PRINCIPAL" != "$PRINCIPAL" ]; then
|
|
|
130
130
|
echo " 0. Agent recipient: grants flow through group $GRANT_PRINCIPAL (created if absent, containing exactly $DISPLAY)"
|
|
131
131
|
fi
|
|
132
132
|
echo " 1. Push companies/$COMPANY/projects/$PROJECT/ to the vault (on-conflict keep)"
|
|
133
|
-
jq -r '.vaultPrefixes[] | " 2. Grant \(.permission) on \(.prefix) — \(.reason // "")"' "$MANIFEST"
|
|
134
|
-
WRITE_PREFIXES="$(jq -r '.vaultPrefixes[] | select(.permission == "write") | .prefix' "$MANIFEST")"
|
|
133
|
+
jq -r '.vaultPrefixes[] | " 2. Grant \(.permission) on \(.prefix) — \(.reason // "")"' "$MANIFEST" | tr -d '\r'
|
|
134
|
+
WRITE_PREFIXES="$(jq -r '.vaultPrefixes[] | select(.permission == "write") | .prefix' "$MANIFEST" | tr -d '\r')"
|
|
135
135
|
if [ -n "$WRITE_PREFIXES" ]; then
|
|
136
136
|
echo
|
|
137
137
|
echo "NOTE: granting 'write' is a privilege escalation. The recipient will be able"
|
|
@@ -155,7 +155,7 @@ hq sync push "companies/$COMPANY/projects/$PROJECT/" --company "$COMPANY" --on-c
|
|
|
155
155
|
|
|
156
156
|
jq -r --arg co "$COMPANY" \
|
|
157
157
|
'((.knowledge // []) + (.policies // []))[] | select(startswith("companies/" + $co + "/"))' \
|
|
158
|
-
"$MANIFEST" | while IFS= read -r kpath; do
|
|
158
|
+
"$MANIFEST" | tr -d '\r' | while IFS= read -r kpath; do
|
|
159
159
|
if [ -e "$HQ_ROOT/$kpath" ]; then
|
|
160
160
|
hq sync push "$kpath" --company "$COMPANY" --on-conflict keep \
|
|
161
161
|
|| die "vault push failed for referenced knowledge: $kpath"
|
|
@@ -195,8 +195,8 @@ case "$GRANT_PRINCIPAL" in
|
|
|
195
195
|
if printf '%s' "$RESOLVED" | jq -e '.status == "found" and (.person.personUid | type == "string") and (.person.companyUid | type == "string")' >/dev/null; then
|
|
196
196
|
[ "$RESOLVE_RC" -eq 0 ] || die "recipient resolution failed"
|
|
197
197
|
EXPECTED_TYPE=person
|
|
198
|
-
EXPECTED_ID="$(printf '%s' "$RESOLVED" | jq -r '.person.personUid')"
|
|
199
|
-
EXPECTED_COMPANY="$(printf '%s' "$RESOLVED" | jq -r '.person.companyUid')"
|
|
198
|
+
EXPECTED_ID="$(printf '%s' "$RESOLVED" | jq -r '.person.personUid' | tr -d '\r')"
|
|
199
|
+
EXPECTED_COMPANY="$(printf '%s' "$RESOLVED" | jq -r '.person.companyUid' | tr -d '\r')"
|
|
200
200
|
elif printf '%s' "$RESOLVED" | jq -e '.status == "not_found"' >/dev/null; then
|
|
201
201
|
case "$PRINCIPAL" in *@*) EXPECTED_TYPE=email; EXPECTED_ID="$(printf '%s' "$PRINCIPAL" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" ;; *) die "unresolved recipient" ;; esac
|
|
202
202
|
else
|
|
@@ -223,15 +223,15 @@ read_acl() {
|
|
|
223
223
|
NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
224
224
|
i=0
|
|
225
225
|
while [ "$i" -lt "$PREFIX_COUNT" ]; do
|
|
226
|
-
PFX="$(jq -r ".vaultPrefixes[$i].prefix" "$MANIFEST")"
|
|
227
|
-
PERM="$(jq -r ".vaultPrefixes[$i].permission" "$MANIFEST")"
|
|
226
|
+
PFX="$(jq -r ".vaultPrefixes[$i].prefix" "$MANIFEST" | tr -d '\r')"
|
|
227
|
+
PERM="$(jq -r ".vaultPrefixes[$i].permission" "$MANIFEST" | tr -d '\r')"
|
|
228
228
|
# Always address the recursive "folder/*" pattern explicitly. Since hq-pro
|
|
229
229
|
# #3662 (2026-09-23) the server echoes the raw pattern back on read, and a
|
|
230
230
|
# bare trailing "/" can mean a PRIVATE folder pattern when that flag is on.
|
|
231
231
|
# A delegation grant is always the recursive share, never a private folder.
|
|
232
232
|
ACL_PATTERN="${PFX}*"
|
|
233
233
|
BEFORE="$(read_acl "$ACL_PATTERN")" || die "ACL preflight failed; no share attempted for $PFX"
|
|
234
|
-
EXPECTED_COMPANY="$(printf '%s' "$BEFORE" | jq -r '.companyUid')"
|
|
234
|
+
EXPECTED_COMPANY="$(printf '%s' "$BEFORE" | jq -r '.companyUid' | tr -d '\r')"
|
|
235
235
|
SHARE_RC=0
|
|
236
236
|
hq files share "$ACL_PATTERN" --with "$GRANT_PRINCIPAL" --permission "$PERM" --company "$COMPANY" || SHARE_RC=$?
|
|
237
237
|
[ "$SHARE_RC" -eq 0 ] || echo "hq-delegate-grant: share reported an error on $PFX; checking exact grant" >&2
|
|
@@ -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
|
-
|
|
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)"
|
|
@@ -7,7 +7,7 @@ import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
|
7
7
|
import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
|
|
8
8
|
import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
|
|
9
9
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
10
|
-
import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
10
|
+
import { commandExists, delegateDiagnosticPath, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
11
11
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
12
12
|
import { ACL_SOURCE_DISPLAY_LABELS } from "../utils/files-browse-acl.js";
|
|
13
13
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
@@ -179,11 +179,16 @@ function bashPath(value) {
|
|
|
179
179
|
const drivePath = /^([A-Za-z]):\/(.*)$/u.exec(normalized);
|
|
180
180
|
return drivePath ? `/${drivePath[1].toLowerCase()}/${drivePath[2]}` : normalized;
|
|
181
181
|
}
|
|
182
|
+
function shellFallbackArgs(args) {
|
|
183
|
+
if (process.platform !== "win32")
|
|
184
|
+
return args;
|
|
185
|
+
return args.map((arg, index) => args[index - 1] === "--manifest" && /^[a-z]:[\\/]/i.test(arg) ? bashPath(arg) : arg);
|
|
186
|
+
}
|
|
182
187
|
function runShellFallback(name, args, context) {
|
|
183
188
|
const root = context.root ?? context.cwd;
|
|
184
189
|
const result = runBundledScript({
|
|
185
190
|
asset: ASSETS[name],
|
|
186
|
-
args,
|
|
191
|
+
args: shellFallbackArgs(args),
|
|
187
192
|
cwd: context.cwd,
|
|
188
193
|
hqRoot: bashPath(root),
|
|
189
194
|
captureOutput: true,
|
|
@@ -404,16 +409,16 @@ function runGrant(args, context) {
|
|
|
404
409
|
if (!manifestPath)
|
|
405
410
|
return fail("grant", "--manifest is required");
|
|
406
411
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
407
|
-
return fail("grant", `manifest not found: ${
|
|
412
|
+
return fail("grant", `manifest not found: ${delegateDiagnosticPath(manifestPath)}`);
|
|
408
413
|
let manifest;
|
|
409
414
|
try {
|
|
410
415
|
manifest = readJsonFile(manifestPath);
|
|
411
416
|
}
|
|
412
417
|
catch {
|
|
413
|
-
return fail("grant", `manifest is not valid JSON: ${
|
|
418
|
+
return fail("grant", `manifest is not valid JSON: ${delegateDiagnosticPath(manifestPath)}`);
|
|
414
419
|
}
|
|
415
420
|
if (manifest === null || manifest === false)
|
|
416
|
-
return fail("grant", `manifest is not valid JSON: ${
|
|
421
|
+
return fail("grant", `manifest is not valid JSON: ${delegateDiagnosticPath(manifestPath)}`);
|
|
417
422
|
if (!isRecord(manifest))
|
|
418
423
|
return fail("grant", "manifest has no company");
|
|
419
424
|
const company = jqRawPath(manifest, "company");
|
|
@@ -790,10 +795,10 @@ function runSend(args, context) {
|
|
|
790
795
|
if (!manifestPath)
|
|
791
796
|
return fail("send", "--manifest is required");
|
|
792
797
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
793
|
-
return fail("send", `manifest not found: ${
|
|
798
|
+
return fail("send", `manifest not found: ${delegateDiagnosticPath(manifestPath)}`);
|
|
794
799
|
const manifest = jsonFile(manifestPath);
|
|
795
800
|
if (!manifest)
|
|
796
|
-
return fail("send", `manifest is not valid JSON: ${
|
|
801
|
+
return fail("send", `manifest is not valid JSON: ${delegateDiagnosticPath(manifestPath)}`);
|
|
797
802
|
if (send) {
|
|
798
803
|
const publishedPath = path.join(context.root ?? context.cwd, jqRawPath(manifest, "publication.manifestPath"));
|
|
799
804
|
if (malformedJsonFile(publishedPath)) {
|
|
@@ -1047,12 +1052,12 @@ function runVerify(args, context) {
|
|
|
1047
1052
|
if (!manifestPath)
|
|
1048
1053
|
return fail("verify", "--manifest is required (or --dry-run with --company/--project/--to)");
|
|
1049
1054
|
if (!fs.existsSync(manifestPath) || !fs.statSync(manifestPath).isFile())
|
|
1050
|
-
return fail("verify", `manifest not found: ${
|
|
1055
|
+
return fail("verify", `manifest not found: ${delegateDiagnosticPath(manifestPath)}`);
|
|
1051
1056
|
if (malformedJsonFile(manifestPath))
|
|
1052
1057
|
return runShellFallback("verify", args, context);
|
|
1053
1058
|
const manifest = jsonFile(manifestPath);
|
|
1054
1059
|
if (!manifest)
|
|
1055
|
-
return fail("verify", `jq: error: ${
|
|
1060
|
+
return fail("verify", `jq: error: ${delegateDiagnosticPath(manifestPath)}: Invalid JSON`);
|
|
1056
1061
|
const lock = `${manifestPath}.send-lock`;
|
|
1057
1062
|
try {
|
|
1058
1063
|
fs.mkdirSync(lock);
|
|
@@ -40,6 +40,7 @@ export declare function jqRawPaths(value: unknown, dottedPaths: string[], fallba
|
|
|
40
40
|
export declare function requireRecord(value: unknown, property: string): JsonRecord;
|
|
41
41
|
export declare function moveFile(source: string, target: string): void;
|
|
42
42
|
export declare function writeJsonAtomically(target: string, value: unknown, siblingTemp?: boolean): void;
|
|
43
|
+
export declare function delegateDiagnosticPath(argument: string): string;
|
|
43
44
|
/** Fast-entry adapter; family gating and shell fallback happen before import. */
|
|
44
45
|
export declare function runDelegateFast(args: readonly string[], _stdin: string, cwd: string, hqRoot: string | undefined, commandName: string): number;
|
|
45
46
|
/** Register the Wave 1a delegate commands and preserve the shell rollback path. */
|
|
@@ -188,6 +188,7 @@ export function moveFile(source, target) {
|
|
|
188
188
|
}
|
|
189
189
|
export function writeJsonAtomically(target, value, siblingTemp = false) {
|
|
190
190
|
const serialized = serializeJqJson(value, "pretty");
|
|
191
|
+
// Native delegate JSON writers use LF on every platform.
|
|
191
192
|
if (siblingTemp) {
|
|
192
193
|
const temporary = `${target}.tmp`;
|
|
193
194
|
fs.writeFileSync(temporary, serialized);
|
|
@@ -557,6 +558,13 @@ function parseStandard(name, args) {
|
|
|
557
558
|
}
|
|
558
559
|
return { values };
|
|
559
560
|
}
|
|
561
|
+
export function delegateDiagnosticPath(argument) {
|
|
562
|
+
// Git Bash receives drive-qualified native paths in MSYS spelling. Preserve
|
|
563
|
+
// relative and already-MSYS path arguments exactly as supplied.
|
|
564
|
+
if (process.platform === "win32" && /^[a-z]:[\\/]/i.test(argument))
|
|
565
|
+
return shellDisplayPath(argument);
|
|
566
|
+
return argument;
|
|
567
|
+
}
|
|
560
568
|
function existingManifest(name, args, context) {
|
|
561
569
|
const prefix = `hq-delegate-${name}:`;
|
|
562
570
|
const parsed = parseStandard(name, args);
|
|
@@ -575,11 +583,11 @@ function existingManifest(name, args, context) {
|
|
|
575
583
|
}
|
|
576
584
|
const file = path.resolve(context.cwd, manifestArg);
|
|
577
585
|
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) {
|
|
578
|
-
writeErr(`${prefix} manifest not found: ${
|
|
586
|
+
writeErr(`${prefix} manifest not found: ${delegateDiagnosticPath(manifestArg)}\n`);
|
|
579
587
|
return { code: 1, parsed };
|
|
580
588
|
}
|
|
581
589
|
if (!jqValid(file)) {
|
|
582
|
-
writeErr(`${prefix} manifest is not valid JSON: ${
|
|
590
|
+
writeErr(`${prefix} manifest is not valid JSON: ${delegateDiagnosticPath(manifestArg)}\n`);
|
|
583
591
|
return { code: 1, parsed };
|
|
584
592
|
}
|
|
585
593
|
return { file, parsed, document: readJsonFile(file) };
|
|
@@ -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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
811
|
-
|
|
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:
|
|
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
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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" "$
|
|
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
|
-
|
|
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, "_",
|
|
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
|
-
|
|
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` };
|