@indigoai-us/hq-cli 5.316.0 → 5.318.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 +9 -0
- package/assets/scaffold/core/scripts/hq-session.sh +3 -3
- package/assets/scaffold/core/scripts/session-journal.sh +301 -0
- package/assets/scaffold/core/scripts/session-project.sh +800 -0
- package/assets/scaffold/core/scripts/session-title-config.sh +76 -0
- package/assets/scaffold/core/scripts/session-title.sh +305 -0
- package/dist/command-catalog.generated.d.ts +52 -0
- package/dist/command-catalog.generated.js +64 -0
- package/dist/commands/core-hq-session-native.js +3 -2
- package/dist/commands/core-session-helper-runtime.d.ts +10 -0
- package/dist/commands/core-session-helper-runtime.js +7 -0
- package/dist/commands/core-session-journal-native.d.ts +4 -0
- package/dist/commands/core-session-journal-native.js +327 -0
- package/dist/commands/core-session-project-native.d.ts +3 -0
- package/dist/commands/core-session-project-native.js +855 -0
- package/dist/commands/core-session-title-config-native.d.ts +4 -0
- package/dist/commands/core-session-title-config-native.js +77 -0
- package/dist/commands/core-session-title-native.d.ts +3 -0
- package/dist/commands/core-session-title-native.js +306 -0
- package/dist/commands/core.js +61 -0
- package/dist/fast-scaffold.js +16 -0
- package/dist/lib/lanes/autopilot.d.ts +7 -0
- package/dist/lib/lanes/autopilot.js +22 -2
- package/dist/utils/version-gate.js +8 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.318.0] — 2026-10-02
|
|
6
|
+
- `hq lanes autopilot run` now marks missing lead registry entries as stale and includes a command to remove each stale row.
|
|
7
|
+
- Keep retired policies retired regardless of YAML field order in the session helper.
|
|
8
|
+
- `publish.yml` refuses a manual run with `dry_run=false` and always passes dry-run to the publish job unless the run comes from a tag push.
|
|
9
|
+
|
|
10
|
+
## [5.317.0] — 2026-10-02
|
|
11
|
+
- Add native `hq core session-title`, `session-title-config`, `session-journal` and `session-project` on the fast route behind the default-off `core-native-hook-helpers` flag.
|
|
12
|
+
- Build required npm self-updates outside the active prefix, then activate only after the staged package is ready, so an interrupted download or unpack leaves the installed CLI runnable.
|
|
13
|
+
|
|
5
14
|
## [5.316.0] — 2026-10-02
|
|
6
15
|
- Add a default-off autopilot notice for registered lead headlines that remain stale for more than 55 minutes, using `headline_at` or the timestamp prefix in `now`.
|
|
7
16
|
- Add a flag-gated per-job option to complete successful scheduled bot runs without an owner DM; failures still report normally.
|
|
@@ -368,12 +368,12 @@ emit_company_hard_policies() {
|
|
|
368
368
|
lines="$(awk -v co="$co" '
|
|
369
369
|
function bn(p, n,a,b){ n=split(p,a,"/"); b=a[n]; sub(/\.md$/,"",b); return b }
|
|
370
370
|
function policy_file(p, n,a){ n=split(p,a,"/"); return a[n] }
|
|
371
|
-
function flush(){ if(enf=="hard" && rule!=""){ if(id=="")id=bn(fn); printf "- [hard] **%s**: %s Full text: `companies/%s/policies/%s`.\n", id, rule, co, policy_file(fn) } }
|
|
372
|
-
FNR==1 { if(seen) flush(); d=0;id="";enf="";rule="";rsec=0;rcap=0;fn=FILENAME;seen=1 }
|
|
371
|
+
function flush(){ if(enf=="hard" && !retired && rule!=""){ if(id=="")id=bn(fn); printf "- [hard] **%s**: %s Full text: `companies/%s/policies/%s`.\n", id, rule, co, policy_file(fn) } }
|
|
372
|
+
FNR==1 { if(seen) flush(); d=0;id="";enf="";retired=0;rule="";rsec=0;rcap=0;fn=FILENAME;seen=1 }
|
|
373
373
|
/^---[ \t]*$/ { d++; next }
|
|
374
374
|
d==1 && /^id:/ { s=$0; sub(/^id:[ \t]*/,"",s); gsub(/^["'"'"']|["'"'"']$/,"",s); id=s; next }
|
|
375
375
|
d==1 && /^enforcement:/ { s=$0; sub(/^enforcement:[ \t]*/,"",s); gsub(/[ \t]/,"",s); enf=s; next }
|
|
376
|
-
d==1 && /^status:[ \t]*retired/ {
|
|
376
|
+
d==1 && /^status:[ \t]*retired/ { retired=1; next }
|
|
377
377
|
d>=2 && /^## Rule[ \t]*$/ { rsec=1; next }
|
|
378
378
|
d>=2 && rsec && /^## / { rsec=0 }
|
|
379
379
|
d>=2 && rsec && !rcap && NF { line=$0; gsub(/\*\*/,"",line); if(length(line)>160)line=substr(line,1,157)"..."; rule=line; rcap=1 }
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# session-journal.sh — minimal helper for session-level journal entries.
|
|
3
|
+
# Spec: core/knowledge/public/hq-core/session-journal-spec.md
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# session-journal.sh write "<title>" [--files f1,f2,...] [--body-file PATH] [--session KEY]
|
|
7
|
+
# session-journal.sh list [--date YYYY-MM-DD]
|
|
8
|
+
# session-journal.sh read <NNN> [--date YYYY-MM-DD]
|
|
9
|
+
# session-journal.sh index-path [--date YYYY-MM-DD] # prints the INDEX.md path
|
|
10
|
+
# session-journal.sh dir-path [--date YYYY-MM-DD] # prints the journal dir
|
|
11
|
+
# session-journal.sh tool-counter increment [--session KEY] # used by PostToolUse hook
|
|
12
|
+
# session-journal.sh tool-counter read [--session KEY] # current count
|
|
13
|
+
# session-journal.sh tool-counter reset [--session KEY]
|
|
14
|
+
#
|
|
15
|
+
# The tool counter is per-session: state lives at
|
|
16
|
+
# workspace/threads/journal/<date>/.tool-count-<session-key>. The key comes from
|
|
17
|
+
# --session, else $HQ_JOURNAL_SESSION, else $HQ_HOOK_SESSION_ID, else
|
|
18
|
+
# "unscoped". A date-global counter let concurrent Claude and Codex tasks
|
|
19
|
+
# trigger and reset each other's journal reminders.
|
|
20
|
+
#
|
|
21
|
+
# All commands fail-soft: warn to stderr, exit 0 — never block the caller.
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
HQ_ROOT="${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}}"
|
|
26
|
+
|
|
27
|
+
today() { date -u +%Y-%m-%d; }
|
|
28
|
+
now_iso_z() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
|
29
|
+
now_hhmm_z() { date -u +%H:%MZ; }
|
|
30
|
+
|
|
31
|
+
journal_dir_for() {
|
|
32
|
+
local d="${1:-$(today)}"
|
|
33
|
+
printf '%s/workspace/threads/journal/%s' "$HQ_ROOT" "$d"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
warn() { echo "session-journal: $*" >&2; }
|
|
37
|
+
|
|
38
|
+
slugify() {
|
|
39
|
+
# Lowercase, replace non-alnum with hyphen, collapse hyphens, trim.
|
|
40
|
+
printf '%s' "$1" \
|
|
41
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
42
|
+
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' \
|
|
43
|
+
| cut -c1-40
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Counters are per-session. A date-global counter lets concurrent Claude and
|
|
47
|
+
# Codex tasks trip (and reset) each other's journal reminders.
|
|
48
|
+
counter_key() {
|
|
49
|
+
local raw="${1:-}"
|
|
50
|
+
raw="$(printf '%s' "$raw" | tr -c 'A-Za-z0-9._-' '_')"
|
|
51
|
+
raw="${raw#_}"; raw="${raw%_}"
|
|
52
|
+
[ -z "$raw" ] && raw="unscoped"
|
|
53
|
+
[ "${#raw}" -gt 96 ] && raw="${raw:0:96}"
|
|
54
|
+
printf '%s' "$raw"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
resolve_session_key() {
|
|
58
|
+
counter_key "${1:-${HQ_JOURNAL_SESSION:-${HQ_HOOK_SESSION_ID:-}}}"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Read-modify-write on the counter must not interleave across concurrent tasks.
|
|
62
|
+
with_counter_lock() {
|
|
63
|
+
local lockdir="$1"; shift
|
|
64
|
+
local i=0
|
|
65
|
+
while ! mkdir "$lockdir" 2>/dev/null; do
|
|
66
|
+
i=$((i + 1))
|
|
67
|
+
[ "$i" -ge 50 ] && break
|
|
68
|
+
sleep 0.02 2>/dev/null || sleep 1
|
|
69
|
+
done
|
|
70
|
+
"$@"
|
|
71
|
+
rmdir "$lockdir" 2>/dev/null || true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
counter_read_raw() {
|
|
75
|
+
local counter="$1" n=0
|
|
76
|
+
[ -f "$counter" ] && n=$(tr -dc '0-9' < "$counter" 2>/dev/null)
|
|
77
|
+
[ -z "$n" ] && n=0
|
|
78
|
+
printf '%d' "$n"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
next_seq() {
|
|
82
|
+
local dir="$1"
|
|
83
|
+
# Highest NNN in the dir, +1, zero-padded to 3.
|
|
84
|
+
local hi
|
|
85
|
+
hi=$(ls "$dir" 2>/dev/null \
|
|
86
|
+
| grep -E '^[0-9]{3}-' \
|
|
87
|
+
| sed -E 's/^([0-9]+)-.*/\1/' \
|
|
88
|
+
| sort -n \
|
|
89
|
+
| tail -1)
|
|
90
|
+
if [ -z "$hi" ]; then printf '001'; else printf '%03d' $((10#$hi + 1)); fi
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
acquire_journal_write_lock() {
|
|
94
|
+
local lock="$1/.session-journal-write.lock" owner=0 i=0
|
|
95
|
+
while [ "$i" -lt 250 ]; do
|
|
96
|
+
if mkdir "$lock" 2>/dev/null; then
|
|
97
|
+
printf '%s\n' "$$" > "$lock/pid" || { rm -rf "$lock"; return 1; }
|
|
98
|
+
JOURNAL_WRITE_LOCK="$lock"
|
|
99
|
+
return 0
|
|
100
|
+
fi
|
|
101
|
+
owner="$(cat "$lock/pid" 2>/dev/null || true)"
|
|
102
|
+
if [[ "$owner" =~ ^[0-9]+$ ]] && [ "$owner" -gt 0 ] && ! kill -0 "$owner" 2>/dev/null; then
|
|
103
|
+
if [ "$(cat "$lock/pid" 2>/dev/null || true)" = "$owner" ]; then rm -rf "$lock" 2>/dev/null || true; fi
|
|
104
|
+
fi
|
|
105
|
+
i=$((i + 1))
|
|
106
|
+
sleep 0.02 2>/dev/null || sleep 1
|
|
107
|
+
done
|
|
108
|
+
return 1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
release_journal_write_lock() {
|
|
112
|
+
[ -n "${JOURNAL_WRITE_LOCK:-}" ] || return 0
|
|
113
|
+
if [ "$(cat "$JOURNAL_WRITE_LOCK/pid" 2>/dev/null || true)" = "$$" ]; then
|
|
114
|
+
rm -rf "$JOURNAL_WRITE_LOCK" 2>/dev/null || true
|
|
115
|
+
fi
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
cmd="${1:-}"; shift || true
|
|
119
|
+
|
|
120
|
+
case "$cmd" in
|
|
121
|
+
write)
|
|
122
|
+
title="${1:-}"; shift || true
|
|
123
|
+
files_csv=""
|
|
124
|
+
body_file=""
|
|
125
|
+
session_arg=""
|
|
126
|
+
while [ $# -gt 0 ]; do
|
|
127
|
+
case "$1" in
|
|
128
|
+
--files) files_csv="${2:-}"; shift 2 || break ;;
|
|
129
|
+
--body-file) body_file="${2:-}"; shift 2 || break ;;
|
|
130
|
+
--session) session_arg="${2:-}"; shift 2 || break ;;
|
|
131
|
+
*) shift ;;
|
|
132
|
+
esac
|
|
133
|
+
done
|
|
134
|
+
|
|
135
|
+
[ -z "$title" ] && { warn "write: title required"; exit 0; }
|
|
136
|
+
|
|
137
|
+
d="$(today)"
|
|
138
|
+
dir="$(journal_dir_for "$d")"
|
|
139
|
+
mkdir -p "$dir" 2>/dev/null || { warn "write: mkdir failed: $dir"; exit 0; }
|
|
140
|
+
|
|
141
|
+
slug="$(slugify "$title")"
|
|
142
|
+
[ -z "$slug" ] && slug="entry"
|
|
143
|
+
JOURNAL_WRITE_LOCK=""
|
|
144
|
+
acquire_journal_write_lock "$dir" || { warn "write: failed to acquire journal lock"; exit 0; }
|
|
145
|
+
trap release_journal_write_lock EXIT
|
|
146
|
+
seq=""
|
|
147
|
+
out=""
|
|
148
|
+
created=false
|
|
149
|
+
attempt=0
|
|
150
|
+
while [ "$attempt" -lt 10000 ]; do
|
|
151
|
+
attempt=$((attempt + 1))
|
|
152
|
+
seq="$(next_seq "$dir")"
|
|
153
|
+
out="$dir/${seq}-${slug}.md"
|
|
154
|
+
write_status=0
|
|
155
|
+
(
|
|
156
|
+
set -o noclobber
|
|
157
|
+
if ! exec 3>"$out" 2>/dev/null; then
|
|
158
|
+
if [ -e "$out" ] || [ -L "$out" ]; then exit 17; fi
|
|
159
|
+
exit 16
|
|
160
|
+
fi
|
|
161
|
+
{
|
|
162
|
+
printf -- '---\n'
|
|
163
|
+
printf 'ts: %s\n' "$(now_iso_z)"
|
|
164
|
+
printf 'title: %s\n' "$title"
|
|
165
|
+
printf 'slug: %s\n' "$slug"
|
|
166
|
+
if [ -n "$files_csv" ]; then
|
|
167
|
+
printf 'files:\n'
|
|
168
|
+
old_ifs="$IFS"
|
|
169
|
+
IFS=','
|
|
170
|
+
set -- $files_csv
|
|
171
|
+
IFS="$old_ifs"
|
|
172
|
+
for f in "$@"; do
|
|
173
|
+
[ -n "$f" ] && printf ' - %s\n' "$f"
|
|
174
|
+
done
|
|
175
|
+
fi
|
|
176
|
+
printf 'status: closed\n'
|
|
177
|
+
printf -- '---\n\n'
|
|
178
|
+
printf '# %s — %s\n\n' "$seq" "$title"
|
|
179
|
+
if [ -n "$body_file" ] && [ -f "$body_file" ]; then
|
|
180
|
+
cat "$body_file"
|
|
181
|
+
else
|
|
182
|
+
printf '## Goal\n\n## Findings\n\n## Decisions\n\n## Next\n'
|
|
183
|
+
fi
|
|
184
|
+
} >&3 || exit 18
|
|
185
|
+
)
|
|
186
|
+
write_status=$?
|
|
187
|
+
if [ "$write_status" -eq 0 ]; then
|
|
188
|
+
created=true
|
|
189
|
+
break
|
|
190
|
+
elif [ "$write_status" -eq 17 ]; then
|
|
191
|
+
continue
|
|
192
|
+
else
|
|
193
|
+
warn "write: failed to write $out"
|
|
194
|
+
exit 0
|
|
195
|
+
fi
|
|
196
|
+
done
|
|
197
|
+
[ "$created" = true ] || { warn "write: failed to write $out"; exit 0; }
|
|
198
|
+
|
|
199
|
+
# Update INDEX.md
|
|
200
|
+
idx="$dir/INDEX.md"
|
|
201
|
+
if [ ! -f "$idx" ]; then
|
|
202
|
+
printf '# Session journal — %s\n\n' "$d" > "$idx"
|
|
203
|
+
fi
|
|
204
|
+
printf -- '- `%s` %s — %s\n' "$seq" "$(now_hhmm_z)" "$title" >> "$idx" \
|
|
205
|
+
|| warn "write: INDEX update failed"
|
|
206
|
+
|
|
207
|
+
# Reset the tool counter for THIS session only — journaling one task's
|
|
208
|
+
# milestone must not clear a concurrent task's progress toward its own.
|
|
209
|
+
"$0" tool-counter reset --session "$(resolve_session_key "$session_arg")" >/dev/null 2>&1 || true
|
|
210
|
+
|
|
211
|
+
printf '%s\n' "$out"
|
|
212
|
+
;;
|
|
213
|
+
|
|
214
|
+
list)
|
|
215
|
+
date_arg="$(today)"
|
|
216
|
+
while [ $# -gt 0 ]; do
|
|
217
|
+
case "$1" in --date) date_arg="${2:-$(today)}"; shift 2 ;; *) shift ;; esac
|
|
218
|
+
done
|
|
219
|
+
dir="$(journal_dir_for "$date_arg")"
|
|
220
|
+
idx="$dir/INDEX.md"
|
|
221
|
+
if [ -f "$idx" ]; then
|
|
222
|
+
cat "$idx"
|
|
223
|
+
else
|
|
224
|
+
echo "(no journal for $date_arg)"
|
|
225
|
+
fi
|
|
226
|
+
;;
|
|
227
|
+
|
|
228
|
+
read)
|
|
229
|
+
seq="${1:-}"; shift || true
|
|
230
|
+
date_arg="$(today)"
|
|
231
|
+
while [ $# -gt 0 ]; do
|
|
232
|
+
case "$1" in --date) date_arg="${2:-$(today)}"; shift 2 ;; *) shift ;; esac
|
|
233
|
+
done
|
|
234
|
+
[ -z "$seq" ] && { warn "read: NNN required"; exit 0; }
|
|
235
|
+
[[ "$seq" =~ ^[0-9]{1,18}$ ]] || { warn "read: NNN required"; exit 0; }
|
|
236
|
+
seq=$(printf '%03d' $((10#$seq)) 2>/dev/null) || seq="$seq"
|
|
237
|
+
dir="$(journal_dir_for "$date_arg")"
|
|
238
|
+
match=$(ls "$dir" 2>/dev/null | grep -E "^${seq}-" | head -1)
|
|
239
|
+
if [ -n "$match" ]; then cat "$dir/$match"; else warn "read: no entry $seq for $date_arg"; fi
|
|
240
|
+
;;
|
|
241
|
+
|
|
242
|
+
index-path)
|
|
243
|
+
date_arg="$(today)"
|
|
244
|
+
while [ $# -gt 0 ]; do
|
|
245
|
+
case "$1" in --date) date_arg="${2:-$(today)}"; shift 2 ;; *) shift ;; esac
|
|
246
|
+
done
|
|
247
|
+
printf '%s/INDEX.md\n' "$(journal_dir_for "$date_arg")"
|
|
248
|
+
;;
|
|
249
|
+
|
|
250
|
+
dir-path)
|
|
251
|
+
date_arg="$(today)"
|
|
252
|
+
while [ $# -gt 0 ]; do
|
|
253
|
+
case "$1" in --date) date_arg="${2:-$(today)}"; shift 2 ;; *) shift ;; esac
|
|
254
|
+
done
|
|
255
|
+
journal_dir_for "$date_arg"
|
|
256
|
+
;;
|
|
257
|
+
|
|
258
|
+
tool-counter)
|
|
259
|
+
sub="${1:-}"; shift || true
|
|
260
|
+
session_arg=""
|
|
261
|
+
while [ $# -gt 0 ]; do
|
|
262
|
+
case "$1" in --session) session_arg="${2:-}"; shift 2 || break ;; *) shift ;; esac
|
|
263
|
+
done
|
|
264
|
+
session_key="$(resolve_session_key "$session_arg")"
|
|
265
|
+
dir="$(journal_dir_for "$(today)")"
|
|
266
|
+
mkdir -p "$dir" 2>/dev/null || true
|
|
267
|
+
counter="$dir/.tool-count-$session_key"
|
|
268
|
+
lockdir="$counter.lock"
|
|
269
|
+
case "$sub" in
|
|
270
|
+
increment)
|
|
271
|
+
do_increment() {
|
|
272
|
+
local n
|
|
273
|
+
n=$(counter_read_raw "$counter")
|
|
274
|
+
echo $((n + 1)) > "$counter" 2>/dev/null || true
|
|
275
|
+
}
|
|
276
|
+
with_counter_lock "$lockdir" do_increment
|
|
277
|
+
;;
|
|
278
|
+
read)
|
|
279
|
+
printf '%d\n' "$(counter_read_raw "$counter")"
|
|
280
|
+
;;
|
|
281
|
+
reset)
|
|
282
|
+
do_reset() { echo 0 > "$counter" 2>/dev/null || true; }
|
|
283
|
+
with_counter_lock "$lockdir" do_reset
|
|
284
|
+
;;
|
|
285
|
+
*) warn "tool-counter: subcommand required (increment|read|reset)" ;;
|
|
286
|
+
esac
|
|
287
|
+
;;
|
|
288
|
+
|
|
289
|
+
*)
|
|
290
|
+
cat >&2 <<'USAGE'
|
|
291
|
+
session-journal.sh: usage:
|
|
292
|
+
session-journal.sh write "<title>" [--files f1,f2,...] [--body-file PATH] [--session KEY]
|
|
293
|
+
session-journal.sh list [--date YYYY-MM-DD]
|
|
294
|
+
session-journal.sh read <NNN> [--date YYYY-MM-DD]
|
|
295
|
+
session-journal.sh index-path [--date YYYY-MM-DD]
|
|
296
|
+
session-journal.sh dir-path [--date YYYY-MM-DD]
|
|
297
|
+
session-journal.sh tool-counter (increment|read|reset) [--session KEY]
|
|
298
|
+
USAGE
|
|
299
|
+
exit 1
|
|
300
|
+
;;
|
|
301
|
+
esac
|