@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.
Files changed (65) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/assets/scaffold/core/scripts/handoff-finalize.sh +670 -0
  3. package/assets/scaffold/core/scripts/handoff-open-steps.sh +81 -0
  4. package/assets/scaffold/core/scripts/handoff-post.sh +187 -0
  5. package/assets/scaffold/core/scripts/hook-lib.sh +345 -50
  6. package/assets/scaffold/core/scripts/hq-session.sh +182 -21
  7. package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +310 -0
  8. package/assets/scaffold/core/scripts/jobs-validate.sh +451 -0
  9. package/assets/scaffold/core/scripts/lib/session-id.sh +3 -2
  10. package/assets/scaffold/core/scripts/lib/work-mesh-enqueue.sh +2 -1
  11. package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +40 -0
  12. package/dist/command-catalog.generated.d.ts +110 -0
  13. package/dist/command-catalog.generated.js +136 -0
  14. package/dist/commands/cloud.d.ts +26 -14
  15. package/dist/commands/cloud.js +207 -56
  16. package/dist/commands/core-handoff.d.ts +22 -0
  17. package/dist/commands/core-handoff.js +1240 -0
  18. package/dist/commands/core-jobs-native.d.ts +8 -0
  19. package/dist/commands/core-jobs-native.js +624 -0
  20. package/dist/commands/core-native-jobs-flag.d.ts +4 -0
  21. package/dist/commands/core-native-jobs-flag.js +7 -0
  22. package/dist/commands/core-ontology.d.ts +6 -1
  23. package/dist/commands/core-ontology.js +42 -7
  24. package/dist/commands/core-policy.js +24 -5
  25. package/dist/commands/core.js +15 -1
  26. package/dist/commands/integrations-core.js +7 -0
  27. package/dist/commands/lanes.js +111 -35
  28. package/dist/commands/members.js +5 -0
  29. package/dist/commands/mesh.js +46 -25
  30. package/dist/commands/personal-targeted-push-flag.d.ts +4 -0
  31. package/dist/commands/personal-targeted-push-flag.js +7 -0
  32. package/dist/commands/personal-targeted-push-paths.d.ts +3 -0
  33. package/dist/commands/personal-targeted-push-paths.js +67 -0
  34. package/dist/commands/secrets.js +9 -0
  35. package/dist/commands/skill.d.ts +3 -0
  36. package/dist/commands/skill.js +28 -0
  37. package/dist/fast-core.js +1 -0
  38. package/dist/lib/billing/free-month.d.ts +18 -0
  39. package/dist/lib/billing/free-month.js +31 -0
  40. package/dist/lib/billing/plan-limit-refusals.d.ts +69 -0
  41. package/dist/lib/billing/plan-limit-refusals.js +187 -0
  42. package/dist/lib/billing/plan-lock.d.ts +2 -6
  43. package/dist/lib/billing/plan-lock.js +5 -16
  44. package/dist/lib/checkpoint-ontology/entity-file.d.ts +21 -0
  45. package/dist/lib/checkpoint-ontology/entity-file.js +100 -0
  46. package/dist/lib/checkpoint-ontology/merge-copies.d.ts +34 -2
  47. package/dist/lib/checkpoint-ontology/merge-copies.js +541 -27
  48. package/dist/lib/core-utils/secret-patterns.d.ts +3 -1
  49. package/dist/lib/core-utils/secret-patterns.js +3 -2
  50. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  51. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  52. package/dist/lib/lanes/autopilot.d.ts +69 -0
  53. package/dist/lib/lanes/autopilot.js +418 -0
  54. package/dist/lib/lanes/mesh-events.js +2 -0
  55. package/dist/lib/lanes/store.d.ts +4 -2
  56. package/dist/lib/lanes/store.js +18 -9
  57. package/dist/lib/mesh/api.js +4 -0
  58. package/dist/lib/mesh/live/daemon/state.d.ts +7 -1
  59. package/dist/lib/mesh/live/daemon/state.js +34 -2
  60. package/dist/main.js +23 -12
  61. package/dist/utils/plan-gate-error.d.ts +24 -0
  62. package/dist/utils/plan-gate-error.js +219 -9
  63. package/dist/utils/self-update.d.ts +3 -0
  64. package/dist/utils/self-update.js +232 -1
  65. 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"