@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,451 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# hq-core: public
|
|
3
|
+
# jobs-validate.sh — validate Outpost scheduled job YAML registry files.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# core/scripts/jobs-validate.sh <file-or-dir> [file-or-dir...]
|
|
7
|
+
#
|
|
8
|
+
# Exits 0 when all jobs are valid; non-zero with per-field errors on stderr.
|
|
9
|
+
# Schema: core/knowledge/public/hq-core/outpost-jobs-spec.md
|
|
10
|
+
#
|
|
11
|
+
# Requires: yq (mikefarah), jq. Timezone checked via IANA-looking regex only.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
16
|
+
|
|
17
|
+
# shellcheck source=lib/secret-patterns.sh
|
|
18
|
+
. "$SCRIPT_DIR/lib/secret-patterns.sh"
|
|
19
|
+
|
|
20
|
+
ERRORS=0
|
|
21
|
+
declare -a SEEN_IDS=()
|
|
22
|
+
|
|
23
|
+
die_usage() {
|
|
24
|
+
echo "usage: jobs-validate.sh <file-or-dir> [file-or-dir...]" >&2
|
|
25
|
+
exit 2
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
err() {
|
|
29
|
+
# err <file> <field> <message>
|
|
30
|
+
printf 'jobs-validate: %s: %s: %s\n' "$1" "$2" "$3" >&2
|
|
31
|
+
ERRORS=$((ERRORS + 1))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
command -v yq >/dev/null 2>&1 || {
|
|
35
|
+
echo "jobs-validate: yq is required (mikefarah/yq)" >&2
|
|
36
|
+
exit 2
|
|
37
|
+
}
|
|
38
|
+
command -v jq >/dev/null 2>&1 || {
|
|
39
|
+
echo "jobs-validate: jq is required" >&2
|
|
40
|
+
exit 2
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[ "$#" -ge 1 ] || die_usage
|
|
44
|
+
|
|
45
|
+
collect_files() {
|
|
46
|
+
local arg="$1"
|
|
47
|
+
if [ -f "$arg" ]; then
|
|
48
|
+
case "$arg" in
|
|
49
|
+
*.yaml|*.yml) printf '%s\n' "$arg" ;;
|
|
50
|
+
*) err "$arg" "path" "not a .yaml/.yml job file" ;;
|
|
51
|
+
esac
|
|
52
|
+
return
|
|
53
|
+
fi
|
|
54
|
+
if [ -d "$arg" ]; then
|
|
55
|
+
# Prefer find -print0 / sort for stable duplicate-id detection order
|
|
56
|
+
find "$arg" \( -name '*.yaml' -o -name '*.yml' \) -type f | sort
|
|
57
|
+
return
|
|
58
|
+
fi
|
|
59
|
+
err "$arg" "path" "file or directory not found"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
cron_num_in_range() {
|
|
63
|
+
local n="$1" min="$2" max="$3"
|
|
64
|
+
[[ "$n" =~ ^[0-9]+$ ]] || return 1
|
|
65
|
+
((10#$n >= min && 10#$n <= max))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
is_valid_cron_field() {
|
|
69
|
+
# $1=value $2=min $3=max — *, */n, a-b, a-b/n, lists, dow/month names
|
|
70
|
+
local v="$1" min="$2" max="$3" part base step a b
|
|
71
|
+
local IFS=','
|
|
72
|
+
set -f
|
|
73
|
+
# shellcheck disable=SC2086
|
|
74
|
+
set -- $v
|
|
75
|
+
set +f
|
|
76
|
+
for part in "$@"; do
|
|
77
|
+
case "$part" in
|
|
78
|
+
\*) continue ;;
|
|
79
|
+
\*/*)
|
|
80
|
+
step="${part#*/}"
|
|
81
|
+
[[ "$step" =~ ^[1-9][0-9]*$ ]] || return 1
|
|
82
|
+
continue
|
|
83
|
+
;;
|
|
84
|
+
sun|mon|tue|wed|thu|fri|sat|SUN|MON|TUE|WED|THU|FRI|SAT)
|
|
85
|
+
[ "$max" -eq 7 ] || return 1
|
|
86
|
+
continue
|
|
87
|
+
;;
|
|
88
|
+
jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)
|
|
89
|
+
[ "$max" -eq 12 ] || return 1
|
|
90
|
+
continue
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
if [[ "$part" =~ ^([0-9]+)-([0-9]+)(/([1-9][0-9]*))?$ ]]; then
|
|
94
|
+
a="${BASH_REMATCH[1]}"
|
|
95
|
+
b="${BASH_REMATCH[2]}"
|
|
96
|
+
cron_num_in_range "$a" "$min" "$max" || return 1
|
|
97
|
+
cron_num_in_range "$b" "$min" "$max" || return 1
|
|
98
|
+
((10#$a <= 10#$b)) || return 1
|
|
99
|
+
continue
|
|
100
|
+
fi
|
|
101
|
+
if [[ "$part" =~ ^([0-9]+)(/([1-9][0-9]*))?$ ]]; then
|
|
102
|
+
base="${BASH_REMATCH[1]}"
|
|
103
|
+
cron_num_in_range "$base" "$min" "$max" || return 1
|
|
104
|
+
continue
|
|
105
|
+
fi
|
|
106
|
+
return 1
|
|
107
|
+
done
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
validate_cron() {
|
|
112
|
+
local file="$1" cron="$2"
|
|
113
|
+
# strip surrounding quotes leftovers
|
|
114
|
+
cron="${cron#"${cron%%[![:space:]]*}"}"
|
|
115
|
+
cron="${cron%"${cron##*[![:space:]]}"}"
|
|
116
|
+
local -a fields
|
|
117
|
+
# noglob: cron uses '*' which must not expand against the filesystem
|
|
118
|
+
set -f
|
|
119
|
+
# shellcheck disable=SC2206
|
|
120
|
+
fields=($cron)
|
|
121
|
+
set +f
|
|
122
|
+
if [ "${#fields[@]}" -ne 5 ]; then
|
|
123
|
+
err "$file" "schedule" "invalid cron — expected 5 fields (minute hour dom month dow), got ${#fields[@]}"
|
|
124
|
+
return
|
|
125
|
+
fi
|
|
126
|
+
is_valid_cron_field "${fields[0]}" 0 59 || err "$file" "schedule" "invalid cron minute field '${fields[0]}'"
|
|
127
|
+
is_valid_cron_field "${fields[1]}" 0 23 || err "$file" "schedule" "invalid cron hour field '${fields[1]}'"
|
|
128
|
+
is_valid_cron_field "${fields[2]}" 1 31 || err "$file" "schedule" "invalid cron day-of-month field '${fields[2]}'"
|
|
129
|
+
is_valid_cron_field "${fields[3]}" 1 12 || err "$file" "schedule" "invalid cron month field '${fields[3]}'"
|
|
130
|
+
is_valid_cron_field "${fields[4]}" 0 7 || err "$file" "schedule" "invalid cron day-of-week field '${fields[4]}'"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
looks_like_secret_name_ok() {
|
|
134
|
+
# vault key name: UPPER_SNAKE / mixed alnum._:- without whitespace or credential shapes
|
|
135
|
+
local s="$1"
|
|
136
|
+
[[ "$s" =~ ^[A-Za-z][A-Za-z0-9._:/-]{0,127}$ ]] || return 1
|
|
137
|
+
return 0
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
scan_string_for_secrets() {
|
|
141
|
+
local file="$1" field="$2" value="$3"
|
|
142
|
+
local tmp entry pattern name
|
|
143
|
+
tmp="$(mktemp "${TMPDIR:-/tmp}/jobs-validate-secret.XXXXXX")"
|
|
144
|
+
printf '%s' "$value" >"$tmp"
|
|
145
|
+
for entry in "${SECRET_PATTERNS[@]}"; do
|
|
146
|
+
pattern="${entry%%:*}"
|
|
147
|
+
name="${entry#*:}"
|
|
148
|
+
if grep -Eq "$pattern" "$tmp" 2>/dev/null; then
|
|
149
|
+
err "$file" "$field" "inline credential-shaped value matched pattern '$name' (names only; never values)"
|
|
150
|
+
fi
|
|
151
|
+
done
|
|
152
|
+
rm -f "$tmp"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
validate_timezone() {
|
|
156
|
+
local file="$1" tz="$2"
|
|
157
|
+
if [[ ! "$tz" =~ ^[A-Za-z_]+(/[A-Za-z0-9_+-]+)+$ ]] && [[ "$tz" != "UTC" ]]; then
|
|
158
|
+
err "$file" "timezone" "not an IANA-looking timezone '$tz'"
|
|
159
|
+
return
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
json_type() {
|
|
164
|
+
jq -r "$1 | type" <<<"$2" 2>/dev/null || echo "null"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# cwd must be HQ-root-relative: no absolute paths, no "."/".." components.
|
|
168
|
+
# Runtime resolves under HQ_ROOT; validate shape only (directory need not exist).
|
|
169
|
+
assert_cwd_safe() {
|
|
170
|
+
local file="$1" field="$2" cwd="$3"
|
|
171
|
+
[ -z "$cwd" ] && return 0
|
|
172
|
+
case "$cwd" in
|
|
173
|
+
/*)
|
|
174
|
+
err "$file" "$field" "absolute cwd not allowed — use an HQ-root-relative path"
|
|
175
|
+
return
|
|
176
|
+
;;
|
|
177
|
+
esac
|
|
178
|
+
local part
|
|
179
|
+
local IFS='/'
|
|
180
|
+
# shellcheck disable=SC2086
|
|
181
|
+
for part in $cwd; do
|
|
182
|
+
case "$part" in
|
|
183
|
+
''|.) ;;
|
|
184
|
+
..)
|
|
185
|
+
err "$file" "$field" "cwd must not contain '..' path components"
|
|
186
|
+
return
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
done
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
validate_job_file() {
|
|
193
|
+
local file="$1"
|
|
194
|
+
local json
|
|
195
|
+
if ! json="$(yq -o=json '.' "$file" 2>/dev/null)"; then
|
|
196
|
+
err "$file" "yaml" "failed to parse YAML"
|
|
197
|
+
return
|
|
198
|
+
fi
|
|
199
|
+
if [ "$(jq -r 'type' <<<"$json")" != "object" ]; then
|
|
200
|
+
err "$file" "yaml" "job root must be a mapping"
|
|
201
|
+
return
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
local id name schedule timezone runtime timeout notify enabled owner created_at
|
|
205
|
+
id="$(jq -r '.id // empty' <<<"$json")"
|
|
206
|
+
name="$(jq -r '.name // empty' <<<"$json")"
|
|
207
|
+
schedule="$(jq -r '.schedule // empty' <<<"$json")"
|
|
208
|
+
if jq -e 'has("timezone") and ((.timezone | type) != "string" or .timezone == "")' \
|
|
209
|
+
<<<"$json" >/dev/null; then
|
|
210
|
+
err "$file" "timezone" "must be a non-empty string when present"
|
|
211
|
+
fi
|
|
212
|
+
timezone="$(jq -r '.timezone // empty' <<<"$json")"
|
|
213
|
+
runtime="$(jq -r '.runtime // empty' <<<"$json")"
|
|
214
|
+
timeout="$(jq -r '.timeout_seconds // empty' <<<"$json")"
|
|
215
|
+
notify="$(jq -r '.notify // empty' <<<"$json")"
|
|
216
|
+
# jq's // treats boolean false as missing — use has() for enabled.
|
|
217
|
+
enabled="$(jq -r 'if has("enabled") then (.enabled|tostring) else empty end' <<<"$json")"
|
|
218
|
+
owner="$(jq -r '.owner // empty' <<<"$json")"
|
|
219
|
+
created_at="$(jq -r '.created_at // empty' <<<"$json")"
|
|
220
|
+
|
|
221
|
+
[ -n "$id" ] || err "$file" "id" "missing required field"
|
|
222
|
+
[ -n "$name" ] || err "$file" "name" "missing required field"
|
|
223
|
+
[ -n "$schedule" ] || err "$file" "schedule" "missing required field"
|
|
224
|
+
[ -n "$runtime" ] || err "$file" "runtime" "missing required field"
|
|
225
|
+
[ -n "$timeout" ] || err "$file" "timeout_seconds" "missing required field"
|
|
226
|
+
[ -n "$notify" ] || err "$file" "notify" "missing required field"
|
|
227
|
+
[ -n "$enabled" ] || err "$file" "enabled" "missing required field"
|
|
228
|
+
[ -n "$owner" ] || err "$file" "owner" "missing required field"
|
|
229
|
+
[ -n "$created_at" ] || err "$file" "created_at" "missing required field"
|
|
230
|
+
|
|
231
|
+
if [ -n "$id" ]; then
|
|
232
|
+
if [[ ! "$id" =~ ^[a-z][a-z0-9-]{1,62}$ ]]; then
|
|
233
|
+
err "$file" "id" "must match [a-z][a-z0-9-]{1,62}, got '$id'"
|
|
234
|
+
fi
|
|
235
|
+
local seen
|
|
236
|
+
for seen in "${SEEN_IDS[@]+"${SEEN_IDS[@]}"}"; do
|
|
237
|
+
if [ "$seen" = "$id" ]; then
|
|
238
|
+
err "$file" "id" "duplicate id '$id'"
|
|
239
|
+
break
|
|
240
|
+
fi
|
|
241
|
+
done
|
|
242
|
+
SEEN_IDS+=("$id")
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
# Reject natural-language leftovers — canonical form is 5 cron fields only.
|
|
246
|
+
if [ -n "$schedule" ]; then
|
|
247
|
+
local -a sf
|
|
248
|
+
set -f
|
|
249
|
+
# shellcheck disable=SC2206
|
|
250
|
+
sf=($schedule)
|
|
251
|
+
set +f
|
|
252
|
+
if [ "${#sf[@]}" -ne 5 ]; then
|
|
253
|
+
if [[ "$schedule" =~ [[:alpha:]] ]]; then
|
|
254
|
+
err "$file" "schedule" "natural-language schedule is not allowed — store canonical cron only"
|
|
255
|
+
else
|
|
256
|
+
err "$file" "schedule" "invalid cron — expected 5 fields (minute hour dom month dow), got ${#sf[@]}"
|
|
257
|
+
fi
|
|
258
|
+
else
|
|
259
|
+
validate_cron "$file" "$schedule"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
[ -n "$timezone" ] && validate_timezone "$file" "$timezone"
|
|
264
|
+
|
|
265
|
+
case "$runtime" in
|
|
266
|
+
'' ) ;;
|
|
267
|
+
claude|codex) ;;
|
|
268
|
+
*) err "$file" "runtime" "unknown runtime '$runtime' (allowed: claude|codex)" ;;
|
|
269
|
+
esac
|
|
270
|
+
|
|
271
|
+
# exec
|
|
272
|
+
local exec_type prompt skill
|
|
273
|
+
exec_type="$(json_type '.exec' "$json")"
|
|
274
|
+
if [ "$exec_type" = "null" ]; then
|
|
275
|
+
err "$file" "exec" "missing required field"
|
|
276
|
+
elif [ "$exec_type" != "object" ]; then
|
|
277
|
+
err "$file" "exec" "must be an object with prompt or skill"
|
|
278
|
+
else
|
|
279
|
+
prompt="$(jq -r '.exec.prompt // empty' <<<"$json")"
|
|
280
|
+
skill="$(jq -r '.exec.skill // empty' <<<"$json")"
|
|
281
|
+
if [ -z "$prompt" ] && [ -z "$skill" ]; then
|
|
282
|
+
err "$file" "exec" "must set prompt or skill"
|
|
283
|
+
elif [ -n "$prompt" ] && [ -n "$skill" ]; then
|
|
284
|
+
err "$file" "exec" "set exactly one of prompt or skill, not both"
|
|
285
|
+
fi
|
|
286
|
+
[ -n "$prompt" ] && scan_string_for_secrets "$file" "exec.prompt" "$prompt"
|
|
287
|
+
[ -n "$skill" ] && scan_string_for_secrets "$file" "exec.skill" "$skill"
|
|
288
|
+
# scan args values
|
|
289
|
+
if jq -e '.exec.args != null' <<<"$json" >/dev/null 2>&1; then
|
|
290
|
+
if [ "$(json_type '.exec.args' "$json")" != "object" ]; then
|
|
291
|
+
err "$file" "exec.args" "must be a mapping when present"
|
|
292
|
+
else
|
|
293
|
+
while IFS= read -r aval; do
|
|
294
|
+
[ -z "$aval" ] && continue
|
|
295
|
+
scan_string_for_secrets "$file" "exec.args" "$aval"
|
|
296
|
+
done < <(jq -r '.exec.args | .. | strings' <<<"$json" 2>/dev/null)
|
|
297
|
+
fi
|
|
298
|
+
fi
|
|
299
|
+
surface="$(jq -r '.exec.surface // empty' <<<"$json")"
|
|
300
|
+
case "$surface" in
|
|
301
|
+
''|headless|remote) ;;
|
|
302
|
+
*) err "$file" "exec.surface" "unknown surface '$surface' (allowed: headless|remote)" ;;
|
|
303
|
+
esac
|
|
304
|
+
# reject unknown exec keys
|
|
305
|
+
while IFS= read -r ekey; do
|
|
306
|
+
[ -z "$ekey" ] && continue
|
|
307
|
+
case "$ekey" in
|
|
308
|
+
prompt|skill|args|surface) ;;
|
|
309
|
+
*) err "$file" "exec.$ekey" "unknown exec key (allowed: prompt, skill, args, surface)" ;;
|
|
310
|
+
esac
|
|
311
|
+
done < <(jq -r '.exec | keys[]' <<<"$json")
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
# timeout
|
|
315
|
+
if [ -n "$timeout" ]; then
|
|
316
|
+
if [[ ! "$timeout" =~ ^[0-9]+$ ]]; then
|
|
317
|
+
err "$file" "timeout_seconds" "must be an integer, got '$timeout'"
|
|
318
|
+
else
|
|
319
|
+
if ((timeout < 60 || timeout > 14400)); then
|
|
320
|
+
err "$file" "timeout_seconds" "must be between 60 and 14400, got $timeout"
|
|
321
|
+
fi
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
case "$notify" in
|
|
326
|
+
'' ) ;;
|
|
327
|
+
dm|none|profile) ;;
|
|
328
|
+
*) err "$file" "notify" "unknown notify '$notify' (allowed: dm|none|profile)" ;;
|
|
329
|
+
esac
|
|
330
|
+
|
|
331
|
+
case "$enabled" in
|
|
332
|
+
'' ) ;;
|
|
333
|
+
true|false) ;;
|
|
334
|
+
*)
|
|
335
|
+
# yq may emit boolean; jq -r on bool gives true/false — if someone used string "yes"
|
|
336
|
+
err "$file" "enabled" "must be boolean true|false, got '$enabled'"
|
|
337
|
+
;;
|
|
338
|
+
esac
|
|
339
|
+
|
|
340
|
+
if [ -n "$created_at" ]; then
|
|
341
|
+
if [[ ! "$created_at" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})$ ]]; then
|
|
342
|
+
err "$file" "created_at" "must be ISO-8601 datetime, got '$created_at'"
|
|
343
|
+
fi
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
# requirements — fixed vocabulary
|
|
347
|
+
if jq -e '.requirements != null' <<<"$json" >/dev/null 2>&1; then
|
|
348
|
+
if [ "$(json_type '.requirements' "$json")" != "object" ]; then
|
|
349
|
+
err "$file" "requirements" "must be a mapping"
|
|
350
|
+
else
|
|
351
|
+
local key
|
|
352
|
+
while IFS= read -r key; do
|
|
353
|
+
[ -z "$key" ] && continue
|
|
354
|
+
case "$key" in
|
|
355
|
+
runtime|secrets|company|cwd|tools) ;;
|
|
356
|
+
readiness|last_probe|last_run|failure_class|pending_probe)
|
|
357
|
+
# status-shaped keys under requirements are unknown requirements keys
|
|
358
|
+
err "$file" "requirements.$key" "unknown requirements key (status belongs in hq-pro, not requirements)"
|
|
359
|
+
;;
|
|
360
|
+
*) err "$file" "requirements.$key" "unknown requirements key (allowed: runtime, secrets, company, cwd, tools)" ;;
|
|
361
|
+
esac
|
|
362
|
+
done < <(jq -r '.requirements | keys[]' <<<"$json")
|
|
363
|
+
|
|
364
|
+
local req_runtime
|
|
365
|
+
req_runtime="$(jq -r '.requirements.runtime // empty' <<<"$json")"
|
|
366
|
+
if [ -n "$req_runtime" ]; then
|
|
367
|
+
case "$req_runtime" in
|
|
368
|
+
claude|codex) ;;
|
|
369
|
+
*) err "$file" "requirements.runtime" "unknown runtime '$req_runtime' (allowed: claude|codex)" ;;
|
|
370
|
+
esac
|
|
371
|
+
if [ -n "$runtime" ] && [ "$req_runtime" != "$runtime" ]; then
|
|
372
|
+
err "$file" "requirements.runtime" "mismatches top-level runtime ('$req_runtime' vs '$runtime')"
|
|
373
|
+
fi
|
|
374
|
+
fi
|
|
375
|
+
|
|
376
|
+
if jq -e '.requirements.secrets != null' <<<"$json" >/dev/null 2>&1; then
|
|
377
|
+
if [ "$(json_type '.requirements.secrets' "$json")" != "array" ]; then
|
|
378
|
+
err "$file" "requirements.secrets" "must be an array of vault key names"
|
|
379
|
+
else
|
|
380
|
+
local secret
|
|
381
|
+
while IFS= read -r secret; do
|
|
382
|
+
[ -z "$secret" ] && continue
|
|
383
|
+
scan_string_for_secrets "$file" "requirements.secrets" "$secret"
|
|
384
|
+
if ! looks_like_secret_name_ok "$secret"; then
|
|
385
|
+
# If it already matched a secret pattern, we still want the name-shape error for odd values
|
|
386
|
+
err "$file" "requirements.secrets" "invalid vault key name '$secret' (use names only)"
|
|
387
|
+
fi
|
|
388
|
+
# Heuristic: KEY=value or PEM-ish or long high-entropy blob
|
|
389
|
+
if [[ "$secret" == *"="* ]]; then
|
|
390
|
+
err "$file" "requirements.secrets" "looks like KEY=value inline credential — names only"
|
|
391
|
+
fi
|
|
392
|
+
done < <(jq -r '.requirements.secrets[]?' <<<"$json")
|
|
393
|
+
fi
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
if jq -e '.requirements.tools != null' <<<"$json" >/dev/null 2>&1; then
|
|
397
|
+
if [ "$(json_type '.requirements.tools' "$json")" != "array" ]; then
|
|
398
|
+
err "$file" "requirements.tools" "must be an array of strings"
|
|
399
|
+
fi
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
local req_cwd
|
|
403
|
+
req_cwd="$(jq -r '.requirements.cwd // empty' <<<"$json")"
|
|
404
|
+
if [ -n "$req_cwd" ]; then
|
|
405
|
+
assert_cwd_safe "$file" "requirements.cwd" "$req_cwd"
|
|
406
|
+
fi
|
|
407
|
+
|
|
408
|
+
local req_company
|
|
409
|
+
req_company="$(jq -r '.requirements.company // empty' <<<"$json")"
|
|
410
|
+
if [ -n "$req_company" ]; then
|
|
411
|
+
if [[ ! "$req_company" =~ ^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$ ]]; then
|
|
412
|
+
err "$file" "requirements.company" "invalid company slug '$req_company' (lowercase kebab/underscore)"
|
|
413
|
+
fi
|
|
414
|
+
fi
|
|
415
|
+
fi
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# Top-level cwd (legacy) — same containment rules
|
|
419
|
+
local top_cwd
|
|
420
|
+
top_cwd="$(jq -r '.cwd // empty' <<<"$json")"
|
|
421
|
+
if [ -n "$top_cwd" ]; then
|
|
422
|
+
assert_cwd_safe "$file" "cwd" "$top_cwd"
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
# Top-level readiness is informational only — ignore (no error). Documented SoT is hq-pro.
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Expand args → file list
|
|
429
|
+
FILES=()
|
|
430
|
+
for arg in "$@"; do
|
|
431
|
+
while IFS= read -r f; do
|
|
432
|
+
[ -z "$f" ] && continue
|
|
433
|
+
FILES+=("$f")
|
|
434
|
+
done < <(collect_files "$arg")
|
|
435
|
+
done
|
|
436
|
+
|
|
437
|
+
if [ "${#FILES[@]}" -eq 0 ]; then
|
|
438
|
+
echo "jobs-validate: no job YAML files found" >&2
|
|
439
|
+
exit 1
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
for f in "${FILES[@]}"; do
|
|
443
|
+
validate_job_file "$f"
|
|
444
|
+
done
|
|
445
|
+
|
|
446
|
+
if [ "$ERRORS" -gt 0 ]; then
|
|
447
|
+
echo "jobs-validate: $ERRORS error(s)" >&2
|
|
448
|
+
exit 1
|
|
449
|
+
fi
|
|
450
|
+
|
|
451
|
+
exit 0
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
# 3. CLAUDE_SESSION_ID — Claude Code (alternate/legacy)
|
|
34
34
|
# 4. CODEX_SESSION_ID — Codex
|
|
35
35
|
# 5. CODEX_THREAD_ID — Codex, thread-scoped runs
|
|
36
|
-
# 6.
|
|
36
|
+
# 6. GROK_SESSION_ID — Grok Build (Desktop / CLI)
|
|
37
|
+
# 7. workspace/sessions/.current — no session env; last-writer-wins fallback
|
|
37
38
|
#
|
|
38
39
|
# The env precedence list matches .claude/skills/_shared/journal.sh so every
|
|
39
40
|
# per-session artifact (journal, session meta, scope capability) keys off the
|
|
@@ -59,7 +60,7 @@ session_id_is_valid() {
|
|
|
59
60
|
session_id_from_env() {
|
|
60
61
|
local var val
|
|
61
62
|
for var in HQ_SESSION_ID CLAUDE_CODE_SESSION_ID CLAUDE_SESSION_ID \
|
|
62
|
-
CODEX_SESSION_ID CODEX_THREAD_ID; do
|
|
63
|
+
CODEX_SESSION_ID CODEX_THREAD_ID GROK_SESSION_ID; do
|
|
63
64
|
val="${!var:-}"
|
|
64
65
|
val="$(printf '%s' "$val" | tr -d '[:space:]')"
|
|
65
66
|
if session_id_is_valid "$val"; then
|
|
@@ -100,7 +100,8 @@ work_mesh_ulid() {
|
|
|
100
100
|
bytes=$(dd if=/dev/urandom bs=10 count=1 2>/dev/null | od -An -tu1 | tr -s ' ' '\n' | grep -E '^[0-9]+$' | head -n 10 | tr '\n' ' ')
|
|
101
101
|
# shellcheck disable=SC2206
|
|
102
102
|
local -a bb=($bytes)
|
|
103
|
-
|
|
103
|
+
# Bash 3.2 + set -u: empty bb[@] is unbound; keep the copy portable.
|
|
104
|
+
b=(${bb[@]+"${bb[@]}"})
|
|
104
105
|
while [ "${#b[@]}" -lt 10 ]; do
|
|
105
106
|
b+=($((RANDOM % 256)))
|
|
106
107
|
done
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
|
+
#
|
|
4
|
+
# It runs as `hq index background`. This file stays behind so every existing
|
|
5
|
+
# caller — skills, other scripts, CI, and muscle memory — keeps working against
|
|
6
|
+
# the path it already knows.
|
|
7
|
+
#
|
|
8
|
+
# Why the implementation moved: the single-flight background reindex is a
|
|
9
|
+
# durable state machine (lock ownership, stale reclaim, completion stamps,
|
|
10
|
+
# signal cleanup) that belongs in tested code rather than 700 lines of shell.
|
|
11
|
+
#
|
|
12
|
+
# Equivalence is not assumed. hq-cli's e2e/qmd-background-differential.test.ts
|
|
13
|
+
# runs a byte-frozen copy of this script's pre-migration source against the CLI
|
|
14
|
+
# over identical injected worlds and compares exit status, stdout, the qmd argv
|
|
15
|
+
# sequence, and the resulting lock/stamp state: agent short-circuit before any
|
|
16
|
+
# qmd lookup or lock, empty-HOME silence, cleanup->update->embed ordering with a
|
|
17
|
+
# completion stamp, no stamp after a failed update, and no competing writer
|
|
18
|
+
# behind a held lock.
|
|
19
|
+
#
|
|
20
|
+
# ONE INTENDED DIVERGENCE: this script printed "skipped" when no qmd was on
|
|
21
|
+
# PATH. The CLI bundles qmd (@tobilu/qmd is a dependency, resolved via
|
|
22
|
+
# node_modules/.bin since hq-cli 5.94.1), so it proceeds instead of skipping.
|
|
23
|
+
# That is the point of bundling; see hq-cli#333.
|
|
24
|
+
#
|
|
25
|
+
# The ABI is otherwise preserved: arguments are forwarded unchanged, stdin is
|
|
26
|
+
# never read by this file, stdout and stderr are inherited untouched, and the
|
|
27
|
+
# child replaces this process so its exit code and signal disposition become the
|
|
28
|
+
# caller's.
|
|
29
|
+
|
|
30
|
+
set -euo pipefail
|
|
31
|
+
|
|
32
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
33
|
+
|
|
34
|
+
if ! command -v hq >/dev/null 2>&1; then
|
|
35
|
+
echo "qmd-reindex-bg.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
36
|
+
echo "Install it with: npm install -g @indigoai-us/hq-cli" >&2
|
|
37
|
+
exit 127
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
exec hq index background --hq-root "$HQ_ROOT" "$@"
|
|
@@ -5208,6 +5208,9 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5208
5208
|
}, {
|
|
5209
5209
|
readonly flags: "--only <file>";
|
|
5210
5210
|
readonly description: "handle only the copy -> original pairs listed in this JSON file";
|
|
5211
|
+
}, {
|
|
5212
|
+
readonly flags: "--rewrite-refs";
|
|
5213
|
+
readonly description: "also re-point signal refs, invoice, source and knowledge links to the survivor and carry external ids and web domains (checkpoint.ontology.merge-rewrite-refs)";
|
|
5211
5214
|
}, {
|
|
5212
5215
|
readonly flags: "--json";
|
|
5213
5216
|
readonly description: "print JSON";
|
|
@@ -5402,6 +5405,54 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5402
5405
|
readonly options: readonly [];
|
|
5403
5406
|
readonly subcommands: readonly [];
|
|
5404
5407
|
}];
|
|
5408
|
+
}, {
|
|
5409
|
+
readonly name: "handoff";
|
|
5410
|
+
readonly description: "Handoff session helpers";
|
|
5411
|
+
readonly aliases: readonly [];
|
|
5412
|
+
readonly hidden: false;
|
|
5413
|
+
readonly usage: "[options] [command]";
|
|
5414
|
+
readonly arguments: readonly [];
|
|
5415
|
+
readonly options: readonly [];
|
|
5416
|
+
readonly subcommands: readonly [{
|
|
5417
|
+
readonly name: "finalize";
|
|
5418
|
+
readonly description: "Write and commit the current session handoff";
|
|
5419
|
+
readonly aliases: readonly [];
|
|
5420
|
+
readonly hidden: false;
|
|
5421
|
+
readonly usage: "[args...]";
|
|
5422
|
+
readonly arguments: readonly [{
|
|
5423
|
+
readonly name: "args";
|
|
5424
|
+
readonly required: false;
|
|
5425
|
+
readonly variadic: true;
|
|
5426
|
+
}];
|
|
5427
|
+
readonly options: readonly [];
|
|
5428
|
+
readonly subcommands: readonly [];
|
|
5429
|
+
}, {
|
|
5430
|
+
readonly name: "post";
|
|
5431
|
+
readonly description: "Run best-effort post-handoff maintenance";
|
|
5432
|
+
readonly aliases: readonly [];
|
|
5433
|
+
readonly hidden: false;
|
|
5434
|
+
readonly usage: "[args...]";
|
|
5435
|
+
readonly arguments: readonly [{
|
|
5436
|
+
readonly name: "args";
|
|
5437
|
+
readonly required: false;
|
|
5438
|
+
readonly variadic: true;
|
|
5439
|
+
}];
|
|
5440
|
+
readonly options: readonly [];
|
|
5441
|
+
readonly subcommands: readonly [];
|
|
5442
|
+
}, {
|
|
5443
|
+
readonly name: "open-steps";
|
|
5444
|
+
readonly description: "List and update open handoff steps";
|
|
5445
|
+
readonly aliases: readonly [];
|
|
5446
|
+
readonly hidden: false;
|
|
5447
|
+
readonly usage: "[args...]";
|
|
5448
|
+
readonly arguments: readonly [{
|
|
5449
|
+
readonly name: "args";
|
|
5450
|
+
readonly required: false;
|
|
5451
|
+
readonly variadic: true;
|
|
5452
|
+
}];
|
|
5453
|
+
readonly options: readonly [];
|
|
5454
|
+
readonly subcommands: readonly [];
|
|
5455
|
+
}];
|
|
5405
5456
|
}, {
|
|
5406
5457
|
readonly name: "knowledge-prefs";
|
|
5407
5458
|
readonly description: "Resolve a company knowledge-store preference";
|
|
@@ -5463,6 +5514,41 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
5463
5514
|
readonly options: readonly [];
|
|
5464
5515
|
readonly subcommands: readonly [];
|
|
5465
5516
|
}];
|
|
5517
|
+
}, {
|
|
5518
|
+
readonly name: "jobs";
|
|
5519
|
+
readonly description: "Scheduled jobs helpers";
|
|
5520
|
+
readonly aliases: readonly [];
|
|
5521
|
+
readonly hidden: false;
|
|
5522
|
+
readonly usage: "[options] [command]";
|
|
5523
|
+
readonly arguments: readonly [];
|
|
5524
|
+
readonly options: readonly [];
|
|
5525
|
+
readonly subcommands: readonly [{
|
|
5526
|
+
readonly name: "schedule-parse";
|
|
5527
|
+
readonly description: "Resolve natural-language or raw cron schedules";
|
|
5528
|
+
readonly aliases: readonly [];
|
|
5529
|
+
readonly hidden: false;
|
|
5530
|
+
readonly usage: "[args...]";
|
|
5531
|
+
readonly arguments: readonly [{
|
|
5532
|
+
readonly name: "args";
|
|
5533
|
+
readonly required: false;
|
|
5534
|
+
readonly variadic: true;
|
|
5535
|
+
}];
|
|
5536
|
+
readonly options: readonly [];
|
|
5537
|
+
readonly subcommands: readonly [];
|
|
5538
|
+
}, {
|
|
5539
|
+
readonly name: "validate";
|
|
5540
|
+
readonly description: "Validate scheduled-job YAML files";
|
|
5541
|
+
readonly aliases: readonly [];
|
|
5542
|
+
readonly hidden: false;
|
|
5543
|
+
readonly usage: "[args...]";
|
|
5544
|
+
readonly arguments: readonly [{
|
|
5545
|
+
readonly name: "args";
|
|
5546
|
+
readonly required: false;
|
|
5547
|
+
readonly variadic: true;
|
|
5548
|
+
}];
|
|
5549
|
+
readonly options: readonly [];
|
|
5550
|
+
readonly subcommands: readonly [];
|
|
5551
|
+
}];
|
|
5466
5552
|
}, {
|
|
5467
5553
|
readonly name: "worktree-gc";
|
|
5468
5554
|
readonly description: "Safely collect old, preserved HQ worktrees";
|
|
@@ -8798,6 +8884,30 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
8798
8884
|
readonly description: "JSON on stdout (always)";
|
|
8799
8885
|
}];
|
|
8800
8886
|
readonly subcommands: readonly [];
|
|
8887
|
+
}, {
|
|
8888
|
+
readonly name: "autopilot";
|
|
8889
|
+
readonly description: "Check registered team leads and wake them for ready work";
|
|
8890
|
+
readonly aliases: readonly [];
|
|
8891
|
+
readonly hidden: false;
|
|
8892
|
+
readonly usage: "[options] [command]";
|
|
8893
|
+
readonly arguments: readonly [];
|
|
8894
|
+
readonly options: readonly [];
|
|
8895
|
+
readonly subcommands: readonly [{
|
|
8896
|
+
readonly name: "run";
|
|
8897
|
+
readonly description: "Run one lead autopilot check pass";
|
|
8898
|
+
readonly aliases: readonly [];
|
|
8899
|
+
readonly hidden: false;
|
|
8900
|
+
readonly usage: "[options]";
|
|
8901
|
+
readonly arguments: readonly [];
|
|
8902
|
+
readonly options: readonly [{
|
|
8903
|
+
readonly flags: "--dry-run";
|
|
8904
|
+
readonly description: "Report proposed wakes without writing them";
|
|
8905
|
+
}, {
|
|
8906
|
+
readonly flags: "--json";
|
|
8907
|
+
readonly description: "Emit JSON on stdout (default)";
|
|
8908
|
+
}];
|
|
8909
|
+
readonly subcommands: readonly [];
|
|
8910
|
+
}];
|
|
8801
8911
|
}, {
|
|
8802
8912
|
readonly name: "reconcile";
|
|
8803
8913
|
readonly description: "Run one machine-owned reconcile pass and exit";
|