@indigoai-us/hq-cli 5.289.0 → 5.291.0
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 +61 -0
- package/assets/scaffold/core/scripts/handoff-finalize.sh +670 -0
- package/assets/scaffold/core/scripts/handoff-open-steps.sh +81 -0
- package/assets/scaffold/core/scripts/handoff-post.sh +187 -0
- package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
- package/assets/scaffold/core/scripts/hq-session.sh +182 -21
- package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
- package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
- package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
- package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
- package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +40 -0
- package/dist/command-catalog.generated.d.ts +110 -0
- package/dist/command-catalog.generated.js +136 -0
- package/dist/commands/cloud.d.ts +26 -14
- package/dist/commands/cloud.js +207 -56
- package/dist/commands/core-handoff.d.ts +22 -0
- package/dist/commands/core-handoff.js +1240 -0
- package/dist/commands/core-jobs-native.d.ts +8 -0
- package/dist/commands/core-jobs-native.js +624 -0
- package/dist/commands/core-native-jobs-flag.d.ts +4 -0
- package/dist/commands/core-native-jobs-flag.js +7 -0
- package/dist/commands/core-ontology.d.ts +6 -1
- package/dist/commands/core-ontology.js +42 -7
- package/dist/commands/core-policy.js +24 -5
- package/dist/commands/core.js +15 -1
- package/dist/commands/integrations-core.js +7 -0
- package/dist/commands/lanes.js +111 -35
- package/dist/commands/members.js +5 -0
- package/dist/commands/mesh.js +46 -25
- package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
- package/dist/commands/personal-targeted-push-flag.js +7 -0
- package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
- package/dist/commands/personal-targeted-push-paths.js +67 -0
- package/dist/commands/secrets.js +9 -0
- package/dist/commands/skill.d.ts +3 -0
- package/dist/commands/skill.js +28 -0
- package/dist/fast-core.js +1 -0
- package/dist/lib/billing/free-month.d.ts +18 -0
- package/dist/lib/billing/free-month.js +31 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
- package/dist/lib/billing/plan-limit-refusals.js +187 -0
- package/dist/lib/billing/plan-lock.d.ts +2 -6
- package/dist/lib/billing/plan-lock.js +5 -16
- package/dist/lib/checkpoint-ontology/entity-file.d.ts +21 -0
- package/dist/lib/checkpoint-ontology/entity-file.js +100 -0
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +34 -2
- package/dist/lib/checkpoint-ontology/merge-copies.js +541 -27
- package/dist/lib/core-utils/secret-patterns.d.ts +3 -1
- package/dist/lib/core-utils/secret-patterns.js +3 -2
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/autopilot.d.ts +69 -0
- package/dist/lib/lanes/autopilot.js +418 -0
- package/dist/lib/lanes/mesh-events.js +2 -0
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +18 -9
- package/dist/lib/mesh/api.js +4 -0
- package/dist/lib/mesh/live/daemon/state.d.ts +7 -1
- package/dist/lib/mesh/live/daemon/state.js +34 -2
- package/dist/main.js +23 -12
- package/dist/utils/plan-gate-error.d.ts +24 -0
- package/dist/utils/plan-gate-error.js +219 -9
- package/dist/utils/self-update.d.ts +3 -0
- package/dist/utils/self-update.js +232 -1
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# handoff-open-steps.sh — list and close next steps across handoff threads
|
|
4
|
+
# (2026-09-07).
|
|
5
|
+
#
|
|
6
|
+
# Handoff threads record next_steps; since 2026-09-07 each carries an `id`
|
|
7
|
+
# (`<thread_id>#<n>`) and a `status` (open|done|dropped). This script is the
|
|
8
|
+
# closure side: /startwork and /resumework list what is still open instead of
|
|
9
|
+
# re-copying it forward, and a session closes a step when it is actually done.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# handoff-open-steps.sh list [--limit N] [--company <slug>] [--json]
|
|
13
|
+
# handoff-open-steps.sh close <step-id> [--as done|dropped] [--note "<why>"]
|
|
14
|
+
# handoff-open-steps.sh reopen <step-id>
|
|
15
|
+
#
|
|
16
|
+
# Threads live in workspace/threads/T-*.json (and archive/**). Closing edits
|
|
17
|
+
# the step in place inside its thread file (only `status`, `closed_at`,
|
|
18
|
+
# `closed_by`, `note`) and bumps `updated_at`; nothing else in the thread is
|
|
19
|
+
# touched. Legacy steps without an id are listed with a synthetic id and can be
|
|
20
|
+
# closed the same way (the id is written on first close).
|
|
21
|
+
set -uo pipefail
|
|
22
|
+
HQ_ROOT="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}}"
|
|
23
|
+
THREADS="$HQ_ROOT/workspace/threads"
|
|
24
|
+
command -v jq >/dev/null 2>&1 || { echo "handoff-open-steps: jq required" >&2; exit 2; }
|
|
25
|
+
CMD="${1:-list}"; shift || true
|
|
26
|
+
|
|
27
|
+
thread_files() { # newest first, handoffs and checkpoints only
|
|
28
|
+
find "$THREADS" -name 'T-*.json' ! -name '*.changeset.json' ! -path '*/resume-locks/*' 2>/dev/null \
|
|
29
|
+
| while IFS= read -r f; do printf '%s\t%s\n' "$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f")" "$f"; done \
|
|
30
|
+
| sort -rn | cut -f2
|
|
31
|
+
}
|
|
32
|
+
normalize_thread() { # <file> -> ensure every step has id/status (in memory)
|
|
33
|
+
jq -c '.next_steps = ((.next_steps // []) | to_entries | map(.key as $k |
|
|
34
|
+
(if (.value|type)=="string" then {step:.value} else .value end)
|
|
35
|
+
| .id = (.id // (input_filename|split("/")|last|sub("\\.json$";"")) + "#" + (($k+1)|tostring))
|
|
36
|
+
| .status = (.status // "open")) | map(del(.key)))' "$1"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "$CMD" in
|
|
40
|
+
list)
|
|
41
|
+
LIMIT=20; CO=""; JSON=0
|
|
42
|
+
while [ $# -gt 0 ]; do case "$1" in --limit) LIMIT="$2"; shift 2;; --company) CO="$2"; shift 2;; --json) JSON=1; shift;; *) shift;; esac; done
|
|
43
|
+
n=0; rows=""
|
|
44
|
+
while IFS= read -r f; do
|
|
45
|
+
[ -n "$f" ] || continue
|
|
46
|
+
[ "$n" -ge "$LIMIT" ] && break
|
|
47
|
+
n=$((n+1))
|
|
48
|
+
t="$(normalize_thread "$f" 2>/dev/null)" || continue
|
|
49
|
+
if [ -n "$CO" ]; then
|
|
50
|
+
printf '%s' "$t" | jq -e --arg co "$CO" '(.metadata.company // []) | index($co) != null' >/dev/null 2>&1 || continue
|
|
51
|
+
fi
|
|
52
|
+
rows="$rows$(printf '%s' "$t" | jq -c '{thread: .thread_id, title: (.metadata.title // ""), created: .created_at} as $h | .next_steps[] | select(.status == "open") | $h + {id, step}')
|
|
53
|
+
"
|
|
54
|
+
done < <(thread_files)
|
|
55
|
+
if [ "$JSON" = 1 ]; then printf '%s' "$rows" | jq -s 'map(select(. != null))'; exit 0; fi
|
|
56
|
+
if [ -z "$(printf '%s' "$rows" | tr -d '[:space:]')" ]; then echo "No open handoff steps in the last $n thread(s)."; exit 0; fi
|
|
57
|
+
printf '%s' "$rows" | jq -r '"\(.id) \(.step) [\(.title)]"'
|
|
58
|
+
;;
|
|
59
|
+
close|reopen)
|
|
60
|
+
ID="${1:-}"; shift || true
|
|
61
|
+
[ -n "$ID" ] || { echo "usage: $CMD <step-id>" >&2; exit 2; }
|
|
62
|
+
AS="done"; NOTE=""
|
|
63
|
+
while [ $# -gt 0 ]; do case "$1" in --as) AS="$2"; shift 2;; --note) NOTE="$2"; shift 2;; *) shift;; esac; done
|
|
64
|
+
[ "$CMD" = reopen ] && AS="open"
|
|
65
|
+
case "$AS" in done|dropped|open) ;; *) echo "--as must be done|dropped" >&2; exit 2;; esac
|
|
66
|
+
tid="${ID%%#*}"
|
|
67
|
+
f="$(find "$THREADS" -name "$tid.json" ! -path '*/resume-locks/*' 2>/dev/null | head -1)"
|
|
68
|
+
[ -n "$f" ] || { echo "handoff-open-steps: thread $tid not found under $THREADS" >&2; exit 3; }
|
|
69
|
+
now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"; by="${HQ_SESSION_ID:-${CLAUDE_SESSION_ID:-unknown-session}}"
|
|
70
|
+
normalize_thread "$f" | jq --arg id "$ID" --arg as "$AS" --arg now "$now" --arg by "$by" --arg note "$NOTE" '
|
|
71
|
+
if ([.next_steps[] | select(.id == $id)] | length) == 0 then error("step \($id) not in thread") else . end
|
|
72
|
+
| .next_steps = (.next_steps | map(if .id == $id then
|
|
73
|
+
(if $as == "open" then del(.closed_at, .closed_by) | .status = "open"
|
|
74
|
+
else .status = $as | .closed_at = $now | .closed_by = $by | (if $note != "" then .note = $note else . end) end)
|
|
75
|
+
else . end))
|
|
76
|
+
| .updated_at = $now' > "$f.tmp" 2>"$f.err" || { cat "$f.err" >&2; rm -f "$f.tmp" "$f.err"; exit 3; }
|
|
77
|
+
rm -f "$f.err"; mv "$f.tmp" "$f"
|
|
78
|
+
echo "handoff-open-steps: $ID -> $AS"
|
|
79
|
+
;;
|
|
80
|
+
*) sed -n 2,22p "$0"; exit 2 ;;
|
|
81
|
+
esac
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# handoff-post.sh — detached post-handoff orchestrator.
|
|
3
|
+
#
|
|
4
|
+
# Runs AFTER handoff-finalize.sh emits handoff.json. Does the mechanical work
|
|
5
|
+
# that used to burn foreground-session tokens:
|
|
6
|
+
# 1. Archive old threads (60d), then regen thread INDEX.md + recent.md (bash)
|
|
7
|
+
# 2. Regen orchestrator INDEX.md (bash)
|
|
8
|
+
# 3. qmd reindex via qmd-reindex-bg.sh (single-flight; agent boxes skip)
|
|
9
|
+
#
|
|
10
|
+
# Model work is intentionally not launched from this detached shell. /learn and
|
|
11
|
+
# /document-release follow-ups run from the handoff skill itself, so auth
|
|
12
|
+
# failures cannot disappear into /tmp logs.
|
|
13
|
+
#
|
|
14
|
+
# Usage (called by handoff skill as `nohup handoff-post.sh ... &`):
|
|
15
|
+
# core/scripts/handoff-post.sh <thread_path> [learnings_json_file]
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
20
|
+
cd "$HQ_ROOT"
|
|
21
|
+
|
|
22
|
+
THREAD_PATH="${1:-}"
|
|
23
|
+
LEARNINGS_FILE="${2:-}"
|
|
24
|
+
|
|
25
|
+
LOG_DIR="${HANDOFF_LOG_DIR:-/tmp}"
|
|
26
|
+
LOG_MAIN="${LOG_DIR}/handoff-post.log"
|
|
27
|
+
LOG_QMD="${LOG_DIR}/qmd-handoff.log"
|
|
28
|
+
|
|
29
|
+
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
30
|
+
|
|
31
|
+
log() { echo "[${TS}] $*" >> "$LOG_MAIN"; }
|
|
32
|
+
|
|
33
|
+
: > "$LOG_MAIN"
|
|
34
|
+
log "handoff-post starting (thread=${THREAD_PATH:-none}, learnings=${LEARNINGS_FILE:-none})"
|
|
35
|
+
|
|
36
|
+
# --- 1. Archive old threads (gated once per 24h) ---
|
|
37
|
+
if hq core archive-old-threads --gated >>"$LOG_MAIN" 2>&1; then
|
|
38
|
+
log "archive: ok"
|
|
39
|
+
else
|
|
40
|
+
log "archive: error (continuing)"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# --- 2. Regen thread INDEX + recent (bash, no Claude) ---
|
|
44
|
+
if hq core rebuild-index threads --both >>"$LOG_MAIN" 2>&1; then
|
|
45
|
+
log "threads-index: ok"
|
|
46
|
+
else
|
|
47
|
+
log "threads-index: error (continuing)"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# --- 3. Regen orchestrator INDEX (bash, no Claude) ---
|
|
51
|
+
if hq core rebuild-index orchestrator >>"$LOG_MAIN" 2>&1; then
|
|
52
|
+
log "orchestrator-index: ok"
|
|
53
|
+
else
|
|
54
|
+
log "orchestrator-index: error (continuing)"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if [[ -n "$LEARNINGS_FILE" && -s "$LEARNINGS_FILE" ]]; then
|
|
58
|
+
learning_count=$(jq 'if type == "array" then length else 0 end' "$LEARNINGS_FILE" 2>/dev/null || echo 0)
|
|
59
|
+
if [[ "${learning_count:-0}" -gt 0 ]]; then
|
|
60
|
+
log "learn: eligible and pending runtime dispatch by handoff skill (${learning_count} learning(s); no dispatch proof)"
|
|
61
|
+
else
|
|
62
|
+
log "learn: no learnings to dispatch"
|
|
63
|
+
fi
|
|
64
|
+
else
|
|
65
|
+
log "learn: no learnings file provided"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if [[ -n "$THREAD_PATH" && -f "$THREAD_PATH" ]]; then
|
|
69
|
+
if scope_counts=$(jq -r '
|
|
70
|
+
(.files_touched // []) as $files
|
|
71
|
+
| (if ($files | type) == "array" then
|
|
72
|
+
[$files[] |
|
|
73
|
+
if type == "string" then .
|
|
74
|
+
elif type == "object" and (.path | type) == "string" then .path
|
|
75
|
+
else null
|
|
76
|
+
end]
|
|
77
|
+
else [] end) as $paths
|
|
78
|
+
| [
|
|
79
|
+
($paths | map(select(if type == "string" then test("^(companies|repos)/") else false end)) | length),
|
|
80
|
+
($paths | map(select(. == null)) | length)
|
|
81
|
+
]
|
|
82
|
+
| @tsv
|
|
83
|
+
' "$THREAD_PATH" 2>/dev/null); then
|
|
84
|
+
IFS=$'\t' read -r scope_match skipped_count <<< "$scope_counts"
|
|
85
|
+
else
|
|
86
|
+
scope_match=0
|
|
87
|
+
skipped_count=0
|
|
88
|
+
log "document-release: skipped (invalid thread JSON)"
|
|
89
|
+
fi
|
|
90
|
+
for ((skipped_index = 0; skipped_index < skipped_count; skipped_index++)); do
|
|
91
|
+
log "document-release: skipped unsupported files_touched entry"
|
|
92
|
+
done
|
|
93
|
+
if [[ "$scope_match" -gt 0 ]]; then
|
|
94
|
+
if bash "$HQ_ROOT/core/scripts/skill-installed.sh" document-release "${HQ_ACTIVE_COMPANY:-}" >/dev/null 2>>"$LOG_MAIN"; then
|
|
95
|
+
log "document-release: eligible and pending runtime dispatch by handoff skill ($scope_match scoped files; no dispatch proof)"
|
|
96
|
+
else
|
|
97
|
+
log "document-release: skipped (skill not installed)"
|
|
98
|
+
fi
|
|
99
|
+
else
|
|
100
|
+
log "document-release: skipped (no company/repo files in files_touched)"
|
|
101
|
+
fi
|
|
102
|
+
else
|
|
103
|
+
log "document-release: skipped (no thread path)"
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# --- 3b. Push company handoff mirrors promptly (best effort) ---
|
|
107
|
+
# Session snapshots are intentionally gitignored, so a completed mirror needs
|
|
108
|
+
# the sync client to make the handoff available to a second device. Trust only
|
|
109
|
+
# the finalizer's lowercase company-slug metadata before constructing a path.
|
|
110
|
+
if [[ -n "$THREAD_PATH" && -f "$THREAD_PATH" ]] && command -v hq >/dev/null 2>&1; then
|
|
111
|
+
while IFS= read -r company; do
|
|
112
|
+
[[ "$company" =~ ^[a-z][a-z0-9_-]*$ ]] || continue
|
|
113
|
+
[[ -d "$HQ_ROOT/companies/$company/workspace" ]] || continue
|
|
114
|
+
if hq sync push --company "$company" "companies/$company/workspace" >>"$LOG_MAIN" 2>&1; then
|
|
115
|
+
log "workspace-sync: pushed companies/$company/workspace"
|
|
116
|
+
else
|
|
117
|
+
log "workspace-sync: failed companies/$company/workspace"
|
|
118
|
+
fi
|
|
119
|
+
done < <(jq -r '.metadata.company // empty | if type == "array" then .[] else . end' "$THREAD_PATH" 2>/dev/null || true)
|
|
120
|
+
elif [[ -n "$THREAD_PATH" && -f "$THREAD_PATH" ]]; then
|
|
121
|
+
log "workspace-sync: skipped (hq CLI unavailable)"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# --- 4. qmd reindex (agent: skip; laptop: single-flight) ---
|
|
125
|
+
# handoff-finalize may already have run the helper; agent boxes no-op
|
|
126
|
+
# (skipped-agent). Never dual-nohup raw cleanup+update+embed on agents.
|
|
127
|
+
# Never invoke managed index wrappers — handoff owns no agent freshness.
|
|
128
|
+
# Prefer Core US-001 helper; fleet binary only if Core file is missing.
|
|
129
|
+
# Lock acquisition runs BEFORE the US-010 session_end spawn so a detached
|
|
130
|
+
# session-end hook cannot delay or interleave with finalize+post single-flight.
|
|
131
|
+
_QMD_BG="$HQ_ROOT/core/scripts/qmd-reindex-bg.sh"
|
|
132
|
+
if [[ ! -f "$_QMD_BG" ]] && [[ -x /usr/local/bin/hq-agent-qmd-reindex-bg ]]; then
|
|
133
|
+
_QMD_BG=/usr/local/bin/hq-agent-qmd-reindex-bg
|
|
134
|
+
fi
|
|
135
|
+
if [[ -x "$_QMD_BG" ]] || [[ -f "$_QMD_BG" ]]; then
|
|
136
|
+
_qmd_out="$(bash "$_QMD_BG" --log "$LOG_QMD" 2>/dev/null || true)"
|
|
137
|
+
# Empty stdout is busy/dedupe quiet — never claim "ok".
|
|
138
|
+
log "qmd: reindex-bg → ${_qmd_out:-busy-or-quiet}"
|
|
139
|
+
elif [[ -d /var/lib/hq-agent ]]; then
|
|
140
|
+
log "qmd: skipped-agent (no helper; indexer owns freshness)"
|
|
141
|
+
else
|
|
142
|
+
log "qmd: skipped (helper missing)"
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# --- 4b. Work Mesh Live: enqueue session_end (US-010) ---
|
|
146
|
+
# Detached + non-blocking. Presence ends via spool; daemon flushes.
|
|
147
|
+
# Same launch shape as the former work-mesh-close hook (nohup + disown).
|
|
148
|
+
# Runs AFTER qmd reindex so this path cannot perturb single-flight lock
|
|
149
|
+
# acquisition order or spawn count. Guarded: older checkouts skip quietly.
|
|
150
|
+
WM_END_HOOK="$HQ_ROOT/core/hooks/SessionEnd/35-work-mesh-session-end.sh"
|
|
151
|
+
if [[ -f "$WM_END_HOOK" ]]; then
|
|
152
|
+
sid=""
|
|
153
|
+
if [[ -f "$HQ_ROOT/workspace/sessions/.current" ]]; then
|
|
154
|
+
sid="$(tr -d '[:space:]' <"$HQ_ROOT/workspace/sessions/.current" 2>/dev/null || true)"
|
|
155
|
+
fi
|
|
156
|
+
if [[ -n "$sid" ]]; then
|
|
157
|
+
payload=$(printf '{"session_id":"%s"}' "$sid")
|
|
158
|
+
else
|
|
159
|
+
payload='{}'
|
|
160
|
+
fi
|
|
161
|
+
HQ_ROOT="$HQ_ROOT" CLAUDE_CODE_SESSION_ID="${sid:-}" \
|
|
162
|
+
nohup bash -c 'printf "%s\n" "$1" | bash "$2" SessionEnd' \
|
|
163
|
+
_ "$payload" "$WM_END_HOOK" \
|
|
164
|
+
>>"${LOG_DIR}/work-mesh-session-end.log" 2>&1 </dev/null &
|
|
165
|
+
disown 2>/dev/null || true
|
|
166
|
+
log "work-mesh-session-end: launched PID $!"
|
|
167
|
+
else
|
|
168
|
+
log "work-mesh-session-end: skipped (hook absent)"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# --- 5. Worktree GC (gated once-per-24h, detached, fully fail-soft) ---
|
|
172
|
+
# HQ worktrees accumulate forever and can reach hundreds of GB. Opportunistically
|
|
173
|
+
# GC the provably-safe stale ones on every handoff. --gated caps this to once per
|
|
174
|
+
# 24h; --apply lets it actually remove (the script's own guards keep it to clean,
|
|
175
|
+
# old, branch-preserved, unreferenced worktrees only). Launched DETACHED because
|
|
176
|
+
# the safety check fetches from origin per repo — it must never block or fail the
|
|
177
|
+
# handoff. Guarded so an older checkout without the script simply skips it.
|
|
178
|
+
WT_GC="$HQ_ROOT/core/scripts/worktree-gc.sh"
|
|
179
|
+
if [[ -f "$WT_GC" ]]; then
|
|
180
|
+
HQ_ROOT="$HQ_ROOT" nohup bash "$WT_GC" --apply --gated >>"${LOG_DIR}/worktree-gc.log" 2>&1 </dev/null &
|
|
181
|
+
disown 2>/dev/null || true
|
|
182
|
+
log "worktree-gc: launched PID $! (--apply --gated)"
|
|
183
|
+
else
|
|
184
|
+
log "worktree-gc: skipped (script absent)"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
log "handoff-post complete"
|