@indigoai-us/hq-cli 5.345.16 → 5.345.17
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 +7 -1
- package/assets/scaffold/core/scripts/generate-workers-registry.sh +130 -6
- package/assets/scaffold/core/scripts/hq-session.sh +1 -1
- package/assets/scaffold/core/scripts/lib/hq-cli-floor.sh +342 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +122 -5
- package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +21 -1
- package/assets/scaffold/core/scripts/scan-packages.sh +130 -5
- package/dist/commands/core.js +1 -0
- package/dist/lib/core-utils/sentry-report.d.ts +2 -2
- package/dist/lib/core-utils/sentry-report.js +47 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.17] — 2026-10-06
|
|
6
|
+
- Git Bash forwarders now keep slash commands intact and convert file path arguments that hq needs on Windows.
|
|
7
|
+
|
|
5
8
|
## [5.345.16] — 2026-10-06
|
|
9
|
+
- Claude account model settings are defaults, so explicit lane models remain eligible; explicit account selections on create, resume, and message are recorded as CLI choices.
|
|
10
|
+
- When `hq-anywhere-runtime` is enabled, `hq install --global --runtime claude --via plugin` installs the `hq` plugin from the `indigoai-us/hq-packages` Claude marketplace, and `hq install @indigoai-us/hq-anywhere` installs the Codex pack from that repository. The flag-off path refuses the HQ Anywhere alias without fetching a package source.
|
|
11
|
+
|
|
12
|
+
## [5.345.15] — 2026-10-06
|
|
6
13
|
- Route live hook Sentry reports through the fast CLI path.
|
|
7
14
|
|
|
8
|
-
- Claude account model settings are defaults, so explicit lane models remain eligible; explicit account selections on create, resume, and message are recorded as CLI choices.
|
|
9
15
|
- When `hq-anywhere-runtime` is enabled, `hq install --global --runtime claude --via plugin` installs the `hq` plugin from the `indigoai-us/hq-packages` Claude marketplace, and `hq install @indigoai-us/hq-anywhere` installs the Codex pack from that repository. The flag-off path refuses the HQ Anywhere alias without fetching a package source.
|
|
10
16
|
|
|
11
17
|
## [5.345.14] — 2026-10-06
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# FORWARDER —
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# as the
|
|
6
|
-
#
|
|
4
|
+
# It ships at assets/scaffold/core/scripts/generate-workers-registry.sh inside @indigoai-us/hq-cli and
|
|
5
|
+
# runs as the hidden command `hq core generate-workers-registry`. This file stays behind so every
|
|
6
|
+
# existing caller — skills, other scripts, CI, and muscle memory — keeps working
|
|
7
|
+
# against the path it already knows.
|
|
8
|
+
#
|
|
9
|
+
# Why the implementation moved: it is a derived-index generator invoked explicitly and by reindex, so
|
|
10
|
+
# process startup is immaterial and the CLI already owns the index primitives.
|
|
11
|
+
#
|
|
12
|
+
# Equivalence is not assumed. The CLI's native implementation is pinned against a
|
|
13
|
+
# byte-frozen copy of this script's pre-migration source by a differential suite
|
|
14
|
+
# (hq-cli e2e/registry-scan-differential.test.ts), which compares exit status,
|
|
15
|
+
# stdout, stderr and a sha256 manifest of every file touched, across repeated runs.
|
|
16
|
+
#
|
|
17
|
+
# The ABI is preserved exactly: arguments are forwarded unchanged, stdin is never
|
|
18
|
+
# read by this file, stdout and stderr are inherited untouched, and the child
|
|
19
|
+
# replaces this process so its exit code and signal disposition become the
|
|
20
|
+
# caller's.
|
|
7
21
|
|
|
8
22
|
set -euo pipefail
|
|
9
23
|
|
|
10
|
-
|
|
24
|
+
FORWARDER_PATH="${BASH_SOURCE[0]}"
|
|
25
|
+
FORWARDER_DIR="${FORWARDER_PATH%/*}"
|
|
26
|
+
[ "$FORWARDER_DIR" != "$FORWARDER_PATH" ] || FORWARDER_DIR=.
|
|
27
|
+
SCRIPT_DIR="$(cd "$FORWARDER_DIR" && pwd)"
|
|
28
|
+
|
|
29
|
+
# This forwarder sits in the tree it targets, so its own location IS the root.
|
|
30
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
11
31
|
|
|
12
32
|
if ! command -v hq >/dev/null 2>&1; then
|
|
13
33
|
echo "generate-workers-registry.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
@@ -15,4 +35,108 @@ if ! command -v hq >/dev/null 2>&1; then
|
|
|
15
35
|
exit 127
|
|
16
36
|
fi
|
|
17
37
|
|
|
18
|
-
|
|
38
|
+
if [ -f "$SCRIPT_DIR/lib/hq-cli-floor.sh" ]; then
|
|
39
|
+
# shellcheck source=lib/hq-cli-floor.sh
|
|
40
|
+
. "$SCRIPT_DIR/lib/hq-cli-floor.sh"
|
|
41
|
+
hq_cli_floor_check "generate-workers-registry.sh" "5.78.0"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# MSYS path conversion is disabled for the hq process so slash commands arrive unchanged.
|
|
45
|
+
# Convert only this generated root path to Windows form for the native CLI.
|
|
46
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
47
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
forwarded_args=("$@")
|
|
51
|
+
forwarded_arg_count=$#
|
|
52
|
+
PATH_OPERANDS=none
|
|
53
|
+
convert_forwarded_path() {
|
|
54
|
+
local arg_index="$1"
|
|
55
|
+
local value="${forwarded_args[$arg_index]}"
|
|
56
|
+
case "$value" in
|
|
57
|
+
/*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
|
|
58
|
+
esac
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
62
|
+
IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
|
|
63
|
+
for path_rule in "${path_rules[@]}"; do
|
|
64
|
+
case "$path_rule" in
|
|
65
|
+
none) ;;
|
|
66
|
+
position:*)
|
|
67
|
+
index="${path_rule#position:}"
|
|
68
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
69
|
+
;;
|
|
70
|
+
first-nonoption)
|
|
71
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
72
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
|
|
73
|
+
done
|
|
74
|
+
;;
|
|
75
|
+
all-nonoptions)
|
|
76
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
77
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
|
|
78
|
+
done
|
|
79
|
+
;;
|
|
80
|
+
options:*)
|
|
81
|
+
IFS=, read -r -a path_options <<< "${path_rule#options:}"
|
|
82
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
83
|
+
for path_option in "${path_options[@]}"; do
|
|
84
|
+
case "${forwarded_args[index]}" in
|
|
85
|
+
"$path_option")
|
|
86
|
+
index=$((index + 1))
|
|
87
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
88
|
+
break
|
|
89
|
+
;;
|
|
90
|
+
"$path_option"=*)
|
|
91
|
+
value="${forwarded_args[index]#*=}"
|
|
92
|
+
case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
|
|
93
|
+
break
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
done
|
|
98
|
+
;;
|
|
99
|
+
comma:*)
|
|
100
|
+
path_option="${path_rule#comma:}"
|
|
101
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
102
|
+
case "${forwarded_args[index]}" in
|
|
103
|
+
"$path_option")
|
|
104
|
+
index=$((index + 1))
|
|
105
|
+
if (( index < forwarded_arg_count )); then
|
|
106
|
+
IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
|
|
107
|
+
converted_values=()
|
|
108
|
+
for path_value in "${path_values[@]}"; do
|
|
109
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
110
|
+
done
|
|
111
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
112
|
+
forwarded_args[index]="$value"
|
|
113
|
+
fi
|
|
114
|
+
break
|
|
115
|
+
;;
|
|
116
|
+
"$path_option"=*)
|
|
117
|
+
value="${forwarded_args[index]#*=}"
|
|
118
|
+
case "$value" in
|
|
119
|
+
/*,*|/*)
|
|
120
|
+
IFS=, read -r -a path_values <<< "$value"
|
|
121
|
+
converted_values=()
|
|
122
|
+
for path_value in "${path_values[@]}"; do
|
|
123
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
124
|
+
done
|
|
125
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
126
|
+
forwarded_args[index]="${path_option}=$value"
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
break
|
|
130
|
+
;;
|
|
131
|
+
esac
|
|
132
|
+
done
|
|
133
|
+
;;
|
|
134
|
+
*) echo "unknown path_operands rule: $path_rule" >&2; exit 70 ;;
|
|
135
|
+
esac
|
|
136
|
+
done
|
|
137
|
+
fi
|
|
138
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
139
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" generate-workers-registry "${forwarded_args[@]}"
|
|
140
|
+
else
|
|
141
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" generate-workers-registry
|
|
142
|
+
fi
|
|
@@ -60,7 +60,7 @@ if [ "${HQ_HQ_SESSION_NO_CLI:-}" != "1" ]; then
|
|
|
60
60
|
case "$__hs_caps" in
|
|
61
61
|
*hq-session*)
|
|
62
62
|
case "$__hs_caps" in
|
|
63
|
-
*exact-company-directory-match-v1*) exec hq core hq-session "$@" ;;
|
|
63
|
+
*exact-company-directory-match-v1*) MSYS2_ARG_CONV_EXCL='*' exec hq core hq-session "$@" ;;
|
|
64
64
|
esac
|
|
65
65
|
;;
|
|
66
66
|
esac
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared hq-cli version-floor helpers for setup and the standalone hook doctor.
|
|
3
|
+
# Keep this compatible with macOS Bash 3.2 and Linux Bash 5.
|
|
4
|
+
|
|
5
|
+
HQ_CLI_FLOOR_UPGRADE_COMMAND='npm install -g @indigoai-us/hq-cli@latest'
|
|
6
|
+
|
|
7
|
+
# Print the top-level requiresHqCli value from core/core.yaml without yq.
|
|
8
|
+
# Return 1 when the key/file is absent and 2 when the key is duplicated.
|
|
9
|
+
hq_cli_floor_required() {
|
|
10
|
+
local hq_root="$1"
|
|
11
|
+
local core_yaml="${hq_root%/}/core/core.yaml"
|
|
12
|
+
[ -r "$core_yaml" ] || return 1
|
|
13
|
+
|
|
14
|
+
awk '
|
|
15
|
+
BEGIN { found = 0; invalid = 0 }
|
|
16
|
+
/^requiresHqCli[[:space:]]*:/ {
|
|
17
|
+
if (found) { invalid = 1; next }
|
|
18
|
+
found = 1
|
|
19
|
+
value = $0
|
|
20
|
+
sub(/^requiresHqCli[[:space:]]*:[[:space:]]*/, "", value)
|
|
21
|
+
sub(/[[:space:]]+#.*$/, "", value)
|
|
22
|
+
sub(/^[[:space:]]*/, "", value)
|
|
23
|
+
sub(/[[:space:]]*$/, "", value)
|
|
24
|
+
quote = sprintf("%c", 39)
|
|
25
|
+
first = substr(value, 1, 1)
|
|
26
|
+
last = substr(value, length(value), 1)
|
|
27
|
+
if ((first == "\"" && last == "\"") || (first == quote && last == quote)) {
|
|
28
|
+
value = substr(value, 2, length(value) - 2)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
END {
|
|
32
|
+
if (invalid) exit 2
|
|
33
|
+
if (!found) exit 1
|
|
34
|
+
print value
|
|
35
|
+
}
|
|
36
|
+
' "$core_yaml"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Resolve an executable through symlinks using portable readlink calls.
|
|
40
|
+
_hq_cli_floor_resolve_executable() {
|
|
41
|
+
local executable="$1" link_dir link_target hops=0
|
|
42
|
+
case "$executable" in
|
|
43
|
+
/*) ;;
|
|
44
|
+
*) executable="$PWD/$executable" ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
while [ -L "$executable" ]; do
|
|
48
|
+
hops=$((hops + 1))
|
|
49
|
+
[ "$hops" -le 40 ] || return 1
|
|
50
|
+
link_dir="${executable%/*}"
|
|
51
|
+
[ -n "$link_dir" ] || link_dir="/"
|
|
52
|
+
link_dir="$(cd -P "$link_dir" 2>/dev/null && pwd)" || return 1
|
|
53
|
+
link_target="$(readlink "$executable" 2>/dev/null)" || return 1
|
|
54
|
+
case "$link_target" in
|
|
55
|
+
/*) executable="$link_target" ;;
|
|
56
|
+
*) executable="$link_dir/$link_target" ;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
[ -f "$executable" ] || return 1
|
|
61
|
+
link_dir="${executable%/*}"
|
|
62
|
+
[ -n "$link_dir" ] || link_dir="/"
|
|
63
|
+
link_dir="$(cd -P "$link_dir" 2>/dev/null && pwd)" || return 1
|
|
64
|
+
printf '%s/%s\n' "${link_dir%/}" "${executable##*/}"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_hq_cli_floor_package_version() {
|
|
68
|
+
local package_json="$1"
|
|
69
|
+
awk -F '"' '
|
|
70
|
+
{
|
|
71
|
+
for (i = 2; i + 2 <= NF; i += 4) {
|
|
72
|
+
key = $i
|
|
73
|
+
value = $(i + 2)
|
|
74
|
+
if (key == "name" && package_name == "") package_name = value
|
|
75
|
+
else if (key == "version" && package_version == "") package_version = value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
END {
|
|
79
|
+
if (package_name == "@indigoai-us/hq-cli" && package_version != "") {
|
|
80
|
+
print package_version
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
' "$package_json"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Read the package version using shell builtins on the warm path. The npm
|
|
87
|
+
# package's top-level version is the first version key in its package.json.
|
|
88
|
+
_hq_cli_floor_package_version_builtin() {
|
|
89
|
+
local package_json="$1" line
|
|
90
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
91
|
+
if [[ "$line" =~ \"version\"[[:space:]]*:[[:space:]]*\"([^\"]+)\" ]]; then
|
|
92
|
+
printf '%s\n' "${BASH_REMATCH[1]}"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
done < "$package_json"
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Return the installed CLI version without starting another Node process on the
|
|
100
|
+
# hook path. A package's version field is the same version reported by its
|
|
101
|
+
# generated hq entry point. Unrecognized or malformed installations retain the
|
|
102
|
+
# historical --version probe as a fallback.
|
|
103
|
+
_hq_cli_floor_installed_version() {
|
|
104
|
+
local hq_command cached_schema cached_command cached_target cached_package_json cached_version current_package_version
|
|
105
|
+
local cache_dir cache_file resolved dir package_json package_version parent raw_version parsed_version
|
|
106
|
+
|
|
107
|
+
hq_command="$(command -v hq 2>/dev/null)" || return 127
|
|
108
|
+
[ -n "$hq_command" ] || return 127
|
|
109
|
+
|
|
110
|
+
# The cache hit path is intentionally shell-only. Compare the selected hq
|
|
111
|
+
# entry point and its current target with the entry recorded on the cold
|
|
112
|
+
# lookup; -nt also catches an in-place replacement of that target.
|
|
113
|
+
cache_dir="${XDG_CACHE_HOME:-${HOME:-}/.cache}/hq-cli-floor"
|
|
114
|
+
cache_file="$cache_dir/version"
|
|
115
|
+
if [ -r "$cache_file" ]; then
|
|
116
|
+
{
|
|
117
|
+
IFS= read -r cached_schema || cached_schema=""
|
|
118
|
+
IFS= read -r cached_command || cached_command=""
|
|
119
|
+
IFS= read -r cached_target || cached_target=""
|
|
120
|
+
IFS= read -r cached_package_json || cached_package_json=""
|
|
121
|
+
IFS= read -r cached_version || cached_version=""
|
|
122
|
+
} < "$cache_file"
|
|
123
|
+
if [ "$cached_schema" = "hq-cli-floor.v2" ] &&
|
|
124
|
+
[ "$cached_command" = "$hq_command" ] && [ -n "$cached_target" ] &&
|
|
125
|
+
[ -n "$cached_package_json" ] && [ -n "$cached_version" ] &&
|
|
126
|
+
_hq_cli_floor_parse_semver "$cached_version" >/dev/null 2>&1 &&
|
|
127
|
+
[ -e "$cached_target" ] && [ -f "$cached_package_json" ] &&
|
|
128
|
+
[ "$hq_command" -ef "$cached_target" ] &&
|
|
129
|
+
! [[ "$cached_target" -nt "$cache_file" ]] &&
|
|
130
|
+
! [[ "$cached_package_json" -nt "$cache_file" ]] &&
|
|
131
|
+
current_package_version="$( _hq_cli_floor_package_version_builtin "$cached_package_json" )" &&
|
|
132
|
+
[ "$current_package_version" = "$cached_version" ]; then
|
|
133
|
+
printf '%s\n' "$cached_version"
|
|
134
|
+
return 0
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
resolved="$( _hq_cli_floor_resolve_executable "$hq_command" 2>/dev/null )" || resolved=""
|
|
139
|
+
if [ -n "$resolved" ]; then
|
|
140
|
+
dir="${resolved%/*}"
|
|
141
|
+
while [ -n "$dir" ] && [ "$dir" != / ]; do
|
|
142
|
+
package_json="$dir/package.json"
|
|
143
|
+
if [ -f "$package_json" ]; then
|
|
144
|
+
package_version="$( _hq_cli_floor_package_version "$package_json" 2>/dev/null )" || package_version=""
|
|
145
|
+
if [ -n "$package_version" ]; then
|
|
146
|
+
if mkdir -p "$cache_dir" 2>/dev/null; then
|
|
147
|
+
local cache_tmp="$cache_file.$$"
|
|
148
|
+
if {
|
|
149
|
+
printf 'hq-cli-floor.v2\n'
|
|
150
|
+
printf '%s\n' "$hq_command"
|
|
151
|
+
printf '%s\n' "$resolved"
|
|
152
|
+
printf '%s\n' "$package_json"
|
|
153
|
+
printf '%s\n' "$package_version"
|
|
154
|
+
} > "$cache_tmp" 2>/dev/null; then
|
|
155
|
+
mv -f "$cache_tmp" "$cache_file" 2>/dev/null || rm -f "$cache_tmp" 2>/dev/null || true
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
printf '%s\n' "$package_version"
|
|
159
|
+
return 0
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
parent="${dir%/*}"
|
|
163
|
+
[ -n "$parent" ] || parent="/"
|
|
164
|
+
[ "$parent" != "$dir" ] || break
|
|
165
|
+
dir="$parent"
|
|
166
|
+
done
|
|
167
|
+
raw_version="$("$resolved" --version 2>/dev/null)" || return 1
|
|
168
|
+
else
|
|
169
|
+
raw_version="$(hq --version 2>/dev/null)" || return 1
|
|
170
|
+
fi
|
|
171
|
+
parsed_version="$(printf '%s\n' "$raw_version" | awk '
|
|
172
|
+
match($0, /[v]?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?/) {
|
|
173
|
+
version = substr($0, RSTART, RLENGTH)
|
|
174
|
+
sub(/^v/, "", version)
|
|
175
|
+
print version
|
|
176
|
+
exit
|
|
177
|
+
}
|
|
178
|
+
')"
|
|
179
|
+
[ -n "$parsed_version" ] || return 1
|
|
180
|
+
printf '%s\n' "$parsed_version"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Parse strict SemVer fields into private globals for the comparator.
|
|
184
|
+
_hq_cli_floor_parse_semver() {
|
|
185
|
+
local version="$1" core prerelease="" build="" identifier
|
|
186
|
+
local -a identifiers=()
|
|
187
|
+
|
|
188
|
+
case "$version" in
|
|
189
|
+
*+*)
|
|
190
|
+
build="${version#*+}"
|
|
191
|
+
[[ "$build" != *+* ]] || return 1
|
|
192
|
+
version="${version%%+*}"
|
|
193
|
+
[[ "$build" =~ ^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]] || return 1
|
|
194
|
+
;;
|
|
195
|
+
esac
|
|
196
|
+
case "$version" in
|
|
197
|
+
*-*)
|
|
198
|
+
prerelease="${version#*-}"
|
|
199
|
+
version="${version%%-*}"
|
|
200
|
+
[[ "$prerelease" =~ ^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]] || return 1
|
|
201
|
+
IFS=. read -r -a identifiers <<< "$prerelease"
|
|
202
|
+
for identifier in "${identifiers[@]}"; do
|
|
203
|
+
if [[ "$identifier" =~ ^[0-9]+$ ]] && [ "${#identifier}" -gt 1 ] && [[ "$identifier" == 0* ]]; then
|
|
204
|
+
return 1
|
|
205
|
+
fi
|
|
206
|
+
done
|
|
207
|
+
;;
|
|
208
|
+
esac
|
|
209
|
+
|
|
210
|
+
core="$version"
|
|
211
|
+
[[ "$core" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || return 1
|
|
212
|
+
HQ_CLI_FLOOR_MAJOR="${BASH_REMATCH[1]}"
|
|
213
|
+
HQ_CLI_FLOOR_MINOR="${BASH_REMATCH[2]}"
|
|
214
|
+
HQ_CLI_FLOOR_PATCH="${BASH_REMATCH[3]}"
|
|
215
|
+
HQ_CLI_FLOOR_PRERELEASE="$prerelease"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_hq_cli_floor_numeric_compare() {
|
|
219
|
+
local left="$1" right="$2"
|
|
220
|
+
if [ "${#left}" -lt "${#right}" ]; then
|
|
221
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
222
|
+
elif [ "${#left}" -gt "${#right}" ]; then
|
|
223
|
+
HQ_CLI_FLOOR_CMP=1
|
|
224
|
+
elif [ "$left" = "$right" ]; then
|
|
225
|
+
HQ_CLI_FLOOR_CMP=0
|
|
226
|
+
elif [[ "$left" > "$right" ]]; then
|
|
227
|
+
HQ_CLI_FLOOR_CMP=1
|
|
228
|
+
else
|
|
229
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
230
|
+
fi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_hq_cli_floor_prerelease_compare() {
|
|
234
|
+
local left="$1" right="$2" i left_id right_id left_num right_num
|
|
235
|
+
local -a left_parts=() right_parts=()
|
|
236
|
+
IFS=. read -r -a left_parts <<< "$left"
|
|
237
|
+
IFS=. read -r -a right_parts <<< "$right"
|
|
238
|
+
|
|
239
|
+
for ((i = 0; i < ${#left_parts[@]} && i < ${#right_parts[@]}; i++)); do
|
|
240
|
+
left_id="${left_parts[$i]}"
|
|
241
|
+
right_id="${right_parts[$i]}"
|
|
242
|
+
left_num=0
|
|
243
|
+
right_num=0
|
|
244
|
+
[[ "$left_id" =~ ^[0-9]+$ ]] && left_num=1
|
|
245
|
+
[[ "$right_id" =~ ^[0-9]+$ ]] && right_num=1
|
|
246
|
+
if [ "$left_num" -eq 1 ] && [ "$right_num" -eq 1 ]; then
|
|
247
|
+
_hq_cli_floor_numeric_compare "$left_id" "$right_id"
|
|
248
|
+
elif [ "$left_num" -eq 1 ]; then
|
|
249
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
250
|
+
elif [ "$right_num" -eq 1 ]; then
|
|
251
|
+
HQ_CLI_FLOOR_CMP=1
|
|
252
|
+
elif [ "$left_id" = "$right_id" ]; then
|
|
253
|
+
HQ_CLI_FLOOR_CMP=0
|
|
254
|
+
elif [[ "$left_id" > "$right_id" ]]; then
|
|
255
|
+
HQ_CLI_FLOOR_CMP=1
|
|
256
|
+
else
|
|
257
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
258
|
+
fi
|
|
259
|
+
[ "$HQ_CLI_FLOOR_CMP" -eq 0 ] || return 0
|
|
260
|
+
done
|
|
261
|
+
|
|
262
|
+
if [ "${#left_parts[@]}" -lt "${#right_parts[@]}" ]; then
|
|
263
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
264
|
+
elif [ "${#left_parts[@]}" -gt "${#right_parts[@]}" ]; then
|
|
265
|
+
HQ_CLI_FLOOR_CMP=1
|
|
266
|
+
else
|
|
267
|
+
HQ_CLI_FLOOR_CMP=0
|
|
268
|
+
fi
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# Pure-Bash SemVer comparison. Returns 0 when have >= want, 1 when older, 2 if invalid.
|
|
272
|
+
hq_cli_version_ge() {
|
|
273
|
+
local LC_ALL=C field left right have_prerelease want_prerelease
|
|
274
|
+
_hq_cli_floor_parse_semver "$1" || return 2
|
|
275
|
+
local have_major="$HQ_CLI_FLOOR_MAJOR" have_minor="$HQ_CLI_FLOOR_MINOR"
|
|
276
|
+
local have_patch="$HQ_CLI_FLOOR_PATCH"
|
|
277
|
+
have_prerelease="$HQ_CLI_FLOOR_PRERELEASE"
|
|
278
|
+
|
|
279
|
+
_hq_cli_floor_parse_semver "$2" || return 2
|
|
280
|
+
want_prerelease="$HQ_CLI_FLOOR_PRERELEASE"
|
|
281
|
+
|
|
282
|
+
for field in major minor patch; do
|
|
283
|
+
case "$field" in
|
|
284
|
+
major) left="$have_major"; right="$HQ_CLI_FLOOR_MAJOR" ;;
|
|
285
|
+
minor) left="$have_minor"; right="$HQ_CLI_FLOOR_MINOR" ;;
|
|
286
|
+
patch) left="$have_patch"; right="$HQ_CLI_FLOOR_PATCH" ;;
|
|
287
|
+
esac
|
|
288
|
+
_hq_cli_floor_numeric_compare "$left" "$right"
|
|
289
|
+
if [ "$HQ_CLI_FLOOR_CMP" -gt 0 ]; then
|
|
290
|
+
return 0
|
|
291
|
+
elif [ "$HQ_CLI_FLOOR_CMP" -lt 0 ]; then
|
|
292
|
+
return 1
|
|
293
|
+
fi
|
|
294
|
+
done
|
|
295
|
+
|
|
296
|
+
if [ -z "$have_prerelease" ]; then
|
|
297
|
+
return 0
|
|
298
|
+
elif [ -z "$want_prerelease" ]; then
|
|
299
|
+
return 1
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
_hq_cli_floor_prerelease_compare "$have_prerelease" "$want_prerelease"
|
|
303
|
+
[ "$HQ_CLI_FLOOR_CMP" -ge 0 ]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Return 0 when hq satisfies the floor; otherwise print the required message and return 127.
|
|
307
|
+
hq_cli_floor_check() {
|
|
308
|
+
local script_name="$1" required="$2" minimum have compare_status
|
|
309
|
+
if ! command -v hq >/dev/null 2>&1; then
|
|
310
|
+
printf '%s: requires the hq CLI — this script\047s implementation now ships with it.\n' "$script_name" >&2
|
|
311
|
+
printf 'Install it with: npm install -g @indigoai-us/hq-cli\n' >&2
|
|
312
|
+
return 127
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
case "$required" in
|
|
316
|
+
'>='*) minimum="${required#>=}" ;;
|
|
317
|
+
*) minimum="$required" ;;
|
|
318
|
+
esac
|
|
319
|
+
if ! _hq_cli_floor_parse_semver "$minimum"; then
|
|
320
|
+
printf '%s: requiresHqCli has unsupported value %s; expected >=X.Y.Z.\n' "$script_name" "$required" >&2
|
|
321
|
+
return 64
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
have="$( _hq_cli_floor_installed_version )" || have=""
|
|
325
|
+
if [ -z "$have" ]; then
|
|
326
|
+
printf '%s: could not read hq --version; required hq-cli >= %s.\n' "$script_name" "$minimum" >&2
|
|
327
|
+
return 126
|
|
328
|
+
fi
|
|
329
|
+
if hq_cli_version_ge "$have" "$minimum"; then
|
|
330
|
+
return 0
|
|
331
|
+
else
|
|
332
|
+
compare_status=$?
|
|
333
|
+
fi
|
|
334
|
+
if [ "$compare_status" -gt 1 ]; then
|
|
335
|
+
printf '%s: hq --version returned invalid SemVer %s; required hq-cli >= %s.\n' "$script_name" "$have" "$minimum" >&2
|
|
336
|
+
return 126
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
printf '%s: this script needs hq-cli >= %s (found %s); upgrade with: %s\n' \
|
|
340
|
+
"$script_name" "$minimum" "$have" "$HQ_CLI_FLOOR_UPGRADE_COMMAND" >&2
|
|
341
|
+
return 127
|
|
342
|
+
}
|
|
@@ -1,16 +1,133 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# FORWARDER — the implementation of this script lives in the hq CLI.
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# `hq core qmd-reindex-after-sync
|
|
6
|
-
#
|
|
4
|
+
# It ships at assets/scaffold/core/scripts/qmd-reindex-after-sync.sh inside @indigoai-us/hq-cli and
|
|
5
|
+
# runs as the hidden command `hq core qmd-reindex-after-sync`. This file stays behind so every
|
|
6
|
+
# existing caller — skills, other scripts, CI, and muscle memory — keeps working
|
|
7
|
+
# against the path it already knows.
|
|
8
|
+
#
|
|
9
|
+
# Why the implementation moved: it is a cold, explicitly-invoked operation where
|
|
10
|
+
# process startup is immaterial, so hosting it in the CLI costs nothing and
|
|
11
|
+
# keeps the scaffold to configuration rather than code.
|
|
12
|
+
#
|
|
13
|
+
# The ABI is preserved exactly: arguments are forwarded unchanged, stdin is never
|
|
14
|
+
# read by this file, stdout and stderr are inherited untouched, and the child
|
|
15
|
+
# replaces this process so its exit code and signal disposition become the
|
|
16
|
+
# caller's.
|
|
7
17
|
|
|
8
18
|
set -euo pipefail
|
|
9
19
|
|
|
20
|
+
FORWARDER_PATH="${BASH_SOURCE[0]}"
|
|
21
|
+
FORWARDER_DIR="${FORWARDER_PATH%/*}"
|
|
22
|
+
[ "$FORWARDER_DIR" != "$FORWARDER_PATH" ] || FORWARDER_DIR=.
|
|
23
|
+
SCRIPT_DIR="$(cd "$FORWARDER_DIR" && pwd)"
|
|
24
|
+
|
|
25
|
+
# No root is injected: this script derived its root from the CALLER's cwd
|
|
26
|
+
# before it moved (git top level, a cwd walk, or a positional argument), and the
|
|
27
|
+
# CLI preserves that. Anything the caller already exported still applies.
|
|
28
|
+
|
|
10
29
|
if ! command -v hq >/dev/null 2>&1; then
|
|
11
30
|
echo "qmd-reindex-after-sync.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
12
31
|
echo "Install it with: npm install -g @indigoai-us/hq-cli" >&2
|
|
13
32
|
exit 127
|
|
14
33
|
fi
|
|
15
34
|
|
|
16
|
-
|
|
35
|
+
if [ -f "$SCRIPT_DIR/lib/hq-cli-floor.sh" ]; then
|
|
36
|
+
# shellcheck source=lib/hq-cli-floor.sh
|
|
37
|
+
. "$SCRIPT_DIR/lib/hq-cli-floor.sh"
|
|
38
|
+
hq_cli_floor_check "qmd-reindex-after-sync.sh" "5.78.0"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
forwarded_args=("$@")
|
|
42
|
+
forwarded_arg_count=$#
|
|
43
|
+
PATH_OPERANDS=first-nonoption
|
|
44
|
+
convert_forwarded_path() {
|
|
45
|
+
local arg_index="$1"
|
|
46
|
+
local value="${forwarded_args[$arg_index]}"
|
|
47
|
+
case "$value" in
|
|
48
|
+
/*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
|
|
49
|
+
esac
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
53
|
+
IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
|
|
54
|
+
for path_rule in "${path_rules[@]}"; do
|
|
55
|
+
case "$path_rule" in
|
|
56
|
+
none) ;;
|
|
57
|
+
position:*)
|
|
58
|
+
index="${path_rule#position:}"
|
|
59
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
60
|
+
;;
|
|
61
|
+
first-nonoption)
|
|
62
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
63
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
|
|
64
|
+
done
|
|
65
|
+
;;
|
|
66
|
+
all-nonoptions)
|
|
67
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
68
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
|
|
69
|
+
done
|
|
70
|
+
;;
|
|
71
|
+
options:*)
|
|
72
|
+
IFS=, read -r -a path_options <<< "${path_rule#options:}"
|
|
73
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
74
|
+
for path_option in "${path_options[@]}"; do
|
|
75
|
+
case "${forwarded_args[index]}" in
|
|
76
|
+
"$path_option")
|
|
77
|
+
index=$((index + 1))
|
|
78
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
79
|
+
break
|
|
80
|
+
;;
|
|
81
|
+
"$path_option"=*)
|
|
82
|
+
value="${forwarded_args[index]#*=}"
|
|
83
|
+
case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
|
|
84
|
+
break
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
done
|
|
89
|
+
;;
|
|
90
|
+
comma:*)
|
|
91
|
+
path_option="${path_rule#comma:}"
|
|
92
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
93
|
+
case "${forwarded_args[index]}" in
|
|
94
|
+
"$path_option")
|
|
95
|
+
index=$((index + 1))
|
|
96
|
+
if (( index < forwarded_arg_count )); then
|
|
97
|
+
IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
|
|
98
|
+
converted_values=()
|
|
99
|
+
for path_value in "${path_values[@]}"; do
|
|
100
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
101
|
+
done
|
|
102
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
103
|
+
forwarded_args[index]="$value"
|
|
104
|
+
fi
|
|
105
|
+
break
|
|
106
|
+
;;
|
|
107
|
+
"$path_option"=*)
|
|
108
|
+
value="${forwarded_args[index]#*=}"
|
|
109
|
+
case "$value" in
|
|
110
|
+
/*,*|/*)
|
|
111
|
+
IFS=, read -r -a path_values <<< "$value"
|
|
112
|
+
converted_values=()
|
|
113
|
+
for path_value in "${path_values[@]}"; do
|
|
114
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
115
|
+
done
|
|
116
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
117
|
+
forwarded_args[index]="${path_option}=$value"
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
break
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
done
|
|
124
|
+
;;
|
|
125
|
+
*) echo "unknown path_operands rule: $path_rule" >&2; exit 70 ;;
|
|
126
|
+
esac
|
|
127
|
+
done
|
|
128
|
+
fi
|
|
129
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
130
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core qmd-reindex-after-sync "${forwarded_args[@]}"
|
|
131
|
+
else
|
|
132
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core qmd-reindex-after-sync
|
|
133
|
+
fi
|
|
@@ -37,4 +37,24 @@ if ! command -v hq >/dev/null 2>&1; then
|
|
|
37
37
|
exit 127
|
|
38
38
|
fi
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
41
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
42
|
+
fi
|
|
43
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
44
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
45
|
+
fi
|
|
46
|
+
forwarded_args=("$@")
|
|
47
|
+
forwarded_arg_count=$#
|
|
48
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
49
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
50
|
+
case "${forwarded_args[index]}" in
|
|
51
|
+
--log) index=$((index + 1)); if ((index < forwarded_arg_count)); then forwarded_args[index]="$(cygpath -m "${forwarded_args[index]}")"; fi ;;
|
|
52
|
+
--log=*) forwarded_args[index]="--log=$(cygpath -m "${forwarded_args[index]#--log=}")" ;;
|
|
53
|
+
esac
|
|
54
|
+
done
|
|
55
|
+
fi
|
|
56
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
57
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq index background --hq-root "$HQ_ROOT" "${forwarded_args[@]}"
|
|
58
|
+
else
|
|
59
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq index background --hq-root "$HQ_ROOT"
|
|
60
|
+
fi
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# FORWARDER —
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# It ships at assets/scaffold/core/scripts/scan-packages.sh inside @indigoai-us/hq-cli and
|
|
5
|
+
# runs as the hidden command `hq core scan-packages`. This file stays behind so every
|
|
6
|
+
# existing caller — skills, other scripts, CI, and muscle memory — keeps working
|
|
7
|
+
# against the path it already knows.
|
|
8
|
+
#
|
|
9
|
+
# Why the implementation moved: the pack contribution mapping it encoded is single-sourced in the CLI
|
|
10
|
+
# (src/utils/pack-contributions.ts); keeping a second copy here invited drift.
|
|
11
|
+
#
|
|
12
|
+
# Equivalence is not assumed. The CLI's native implementation is pinned against a
|
|
13
|
+
# byte-frozen copy of this script's pre-migration source by a differential suite
|
|
14
|
+
# (hq-cli e2e/registry-scan-differential.test.ts), which compares exit status,
|
|
15
|
+
# stdout, stderr and a sha256 manifest of every file touched, across repeated runs.
|
|
16
|
+
#
|
|
17
|
+
# The ABI is preserved exactly: arguments are forwarded unchanged, stdin is never
|
|
18
|
+
# read by this file, stdout and stderr are inherited untouched, and the child
|
|
19
|
+
# replaces this process so its exit code and signal disposition become the
|
|
20
|
+
# caller's.
|
|
6
21
|
|
|
7
22
|
set -euo pipefail
|
|
8
23
|
|
|
9
|
-
|
|
24
|
+
FORWARDER_PATH="${BASH_SOURCE[0]}"
|
|
25
|
+
FORWARDER_DIR="${FORWARDER_PATH%/*}"
|
|
26
|
+
[ "$FORWARDER_DIR" != "$FORWARDER_PATH" ] || FORWARDER_DIR=.
|
|
27
|
+
SCRIPT_DIR="$(cd "$FORWARDER_DIR" && pwd)"
|
|
28
|
+
|
|
29
|
+
# This forwarder sits in the tree it targets, so its own location IS the root.
|
|
30
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
10
31
|
|
|
11
32
|
if ! command -v hq >/dev/null 2>&1; then
|
|
12
33
|
echo "scan-packages.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
@@ -14,4 +35,108 @@ if ! command -v hq >/dev/null 2>&1; then
|
|
|
14
35
|
exit 127
|
|
15
36
|
fi
|
|
16
37
|
|
|
17
|
-
|
|
38
|
+
if [ -f "$SCRIPT_DIR/lib/hq-cli-floor.sh" ]; then
|
|
39
|
+
# shellcheck source=lib/hq-cli-floor.sh
|
|
40
|
+
. "$SCRIPT_DIR/lib/hq-cli-floor.sh"
|
|
41
|
+
hq_cli_floor_check "scan-packages.sh" "5.78.0"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# MSYS path conversion is disabled for the hq process so slash commands arrive unchanged.
|
|
45
|
+
# Convert only this generated root path to Windows form for the native CLI.
|
|
46
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
47
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
forwarded_args=("$@")
|
|
51
|
+
forwarded_arg_count=$#
|
|
52
|
+
PATH_OPERANDS=none
|
|
53
|
+
convert_forwarded_path() {
|
|
54
|
+
local arg_index="$1"
|
|
55
|
+
local value="${forwarded_args[$arg_index]}"
|
|
56
|
+
case "$value" in
|
|
57
|
+
/*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
|
|
58
|
+
esac
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
62
|
+
IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
|
|
63
|
+
for path_rule in "${path_rules[@]}"; do
|
|
64
|
+
case "$path_rule" in
|
|
65
|
+
none) ;;
|
|
66
|
+
position:*)
|
|
67
|
+
index="${path_rule#position:}"
|
|
68
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
69
|
+
;;
|
|
70
|
+
first-nonoption)
|
|
71
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
72
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
|
|
73
|
+
done
|
|
74
|
+
;;
|
|
75
|
+
all-nonoptions)
|
|
76
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
77
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
|
|
78
|
+
done
|
|
79
|
+
;;
|
|
80
|
+
options:*)
|
|
81
|
+
IFS=, read -r -a path_options <<< "${path_rule#options:}"
|
|
82
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
83
|
+
for path_option in "${path_options[@]}"; do
|
|
84
|
+
case "${forwarded_args[index]}" in
|
|
85
|
+
"$path_option")
|
|
86
|
+
index=$((index + 1))
|
|
87
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
88
|
+
break
|
|
89
|
+
;;
|
|
90
|
+
"$path_option"=*)
|
|
91
|
+
value="${forwarded_args[index]#*=}"
|
|
92
|
+
case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
|
|
93
|
+
break
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
done
|
|
98
|
+
;;
|
|
99
|
+
comma:*)
|
|
100
|
+
path_option="${path_rule#comma:}"
|
|
101
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
102
|
+
case "${forwarded_args[index]}" in
|
|
103
|
+
"$path_option")
|
|
104
|
+
index=$((index + 1))
|
|
105
|
+
if (( index < forwarded_arg_count )); then
|
|
106
|
+
IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
|
|
107
|
+
converted_values=()
|
|
108
|
+
for path_value in "${path_values[@]}"; do
|
|
109
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
110
|
+
done
|
|
111
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
112
|
+
forwarded_args[index]="$value"
|
|
113
|
+
fi
|
|
114
|
+
break
|
|
115
|
+
;;
|
|
116
|
+
"$path_option"=*)
|
|
117
|
+
value="${forwarded_args[index]#*=}"
|
|
118
|
+
case "$value" in
|
|
119
|
+
/*,*|/*)
|
|
120
|
+
IFS=, read -r -a path_values <<< "$value"
|
|
121
|
+
converted_values=()
|
|
122
|
+
for path_value in "${path_values[@]}"; do
|
|
123
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
124
|
+
done
|
|
125
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
126
|
+
forwarded_args[index]="${path_option}=$value"
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
break
|
|
130
|
+
;;
|
|
131
|
+
esac
|
|
132
|
+
done
|
|
133
|
+
;;
|
|
134
|
+
*) echo "unknown path_operands rule: $path_rule" >&2; exit 70 ;;
|
|
135
|
+
esac
|
|
136
|
+
done
|
|
137
|
+
fi
|
|
138
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
139
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" scan-packages "${forwarded_args[@]}"
|
|
140
|
+
else
|
|
141
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" scan-packages
|
|
142
|
+
fi
|
package/dist/commands/core.js
CHANGED
|
@@ -252,6 +252,7 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
252
252
|
{ asset: "core/scripts/hook-lib.sh", root: "live", summary: "Shared shell primitives sourced by the checkpoint gate" },
|
|
253
253
|
{ asset: "core/scripts/lib/session-scope-capability.sh", root: "live", summary: "Session scope-capability lib sourced by hq-session" },
|
|
254
254
|
{ asset: "core/scripts/lib/session-id.sh", root: "live", summary: "Session-id resolver lib sourced by hq-session" },
|
|
255
|
+
{ asset: "core/scripts/lib/hq-cli-floor.sh", root: "live", summary: "Shared CLI version-floor helper sourced by generated forwarders" },
|
|
255
256
|
{ asset: "core/scripts/lib/work-mesh-enqueue.sh", root: "live", summary: "Work Mesh Live spool enqueue lib sourced by hooks" },
|
|
256
257
|
{ asset: "core/scripts/lib/work-mesh-live-hook.sh", root: "live", summary: "Work Mesh Live hook helpers sourced by rebind" },
|
|
257
258
|
{ asset: "core/scripts/lib/work-mesh-live-rebind.sh", root: "live", summary: "Work Mesh Live session rebind library sourced by hooks and the native rollback command" },
|
|
@@ -26,7 +26,7 @@ export declare const MAX_SENTRY_REPORT_METADATA_VALUE_BYTES = 256;
|
|
|
26
26
|
* is owner-approved telemetry for the exact hook script and may include a
|
|
27
27
|
* company directory; it is never a substitute for hook input or a transcript.
|
|
28
28
|
*/
|
|
29
|
-
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "slow_phase", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "shell", "nproc", "hook_sequence", "hook_timeout_debug_context"];
|
|
29
|
+
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "slow_phase", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "root_storage_class", "shell", "nproc", "hook_sequence", "hook_timeout_debug_context"];
|
|
30
30
|
export declare const MAX_SENTRY_REPORT_METADATA_KEYS: number;
|
|
31
31
|
export type HookSequenceEntry = {
|
|
32
32
|
script: string;
|
|
@@ -47,7 +47,7 @@ type HookTimeoutDebugContext = {
|
|
|
47
47
|
wait_point: string;
|
|
48
48
|
waiting_child_basename: string;
|
|
49
49
|
waiting_child_elapsed_ms: number;
|
|
50
|
-
load_average?: number | "unavailable";
|
|
50
|
+
load_average?: number | "unavailable" | "unsupported_platform";
|
|
51
51
|
spawn_ms?: number | "unavailable";
|
|
52
52
|
process_count?: number | "unavailable";
|
|
53
53
|
};
|
|
@@ -177,6 +177,7 @@ export const ALLOWED_SENTRY_REPORT_METADATA_KEYS = [
|
|
|
177
177
|
"final_elapsed_ms",
|
|
178
178
|
"bash_env_set",
|
|
179
179
|
"cwd_kind",
|
|
180
|
+
"root_storage_class",
|
|
180
181
|
"shell",
|
|
181
182
|
"nproc",
|
|
182
183
|
"hook_sequence",
|
|
@@ -290,6 +291,11 @@ function boundedDebugProbe(value) {
|
|
|
290
291
|
return value;
|
|
291
292
|
return boundedDebugMilliseconds(value) ?? undefined;
|
|
292
293
|
}
|
|
294
|
+
function boundedDebugLoadAverage(value) {
|
|
295
|
+
if (value === "unsupported_platform")
|
|
296
|
+
return value;
|
|
297
|
+
return boundedDebugProbe(value);
|
|
298
|
+
}
|
|
293
299
|
function boundedDebugPhaseTimings(value) {
|
|
294
300
|
if (!Array.isArray(value) || value.length > MAX_HOOK_TIMEOUT_DEBUG_PHASES)
|
|
295
301
|
return null;
|
|
@@ -324,7 +330,7 @@ function boundedHookTimeoutDebugContext(value) {
|
|
|
324
330
|
phaseTimings === null) {
|
|
325
331
|
return null;
|
|
326
332
|
}
|
|
327
|
-
const loadAverage =
|
|
333
|
+
const loadAverage = boundedDebugLoadAverage(value.load_average);
|
|
328
334
|
const spawnMs = boundedDebugProbe(value.spawn_ms);
|
|
329
335
|
const processCount = boundedDebugProbe(value.process_count);
|
|
330
336
|
const hasLoadAverage = typeof loadAverage === "number";
|
|
@@ -439,7 +445,11 @@ function parseSentryReportEvent(stdin) {
|
|
|
439
445
|
? boundedHookSequence(rawMetadata[key])
|
|
440
446
|
: key === "hook_timeout_debug_context"
|
|
441
447
|
? boundedHookTimeoutDebugContext(rawMetadata[key])
|
|
442
|
-
:
|
|
448
|
+
: key === "cwd_kind"
|
|
449
|
+
? closedMetadataValue(rawMetadata[key], SAFE_CWD_KINDS, "unknown")
|
|
450
|
+
: key === "root_storage_class"
|
|
451
|
+
? closedMetadataValue(rawMetadata[key], SAFE_ROOT_STORAGE_CLASSES, "unavailable")
|
|
452
|
+
: rawMetadata[key];
|
|
443
453
|
}
|
|
444
454
|
if (typeof metadata.slow_child === "string") {
|
|
445
455
|
metadata.slow_child = safeSlowChild(metadata);
|
|
@@ -484,6 +494,21 @@ const SAFE_HOOK_EVENT = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
|
484
494
|
const SAFE_HOOK_SCRIPT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
485
495
|
const SAFE_EXIT_CODE = /^(?:-?[0-9]+|running)$/;
|
|
486
496
|
const SAFE_NPROC = /^[0-9]+$/;
|
|
497
|
+
const SAFE_CWD_KINDS = new Set([
|
|
498
|
+
"hq-root",
|
|
499
|
+
"repo",
|
|
500
|
+
"worktree",
|
|
501
|
+
"hq_subdir",
|
|
502
|
+
"root_unset",
|
|
503
|
+
"root_not_directory",
|
|
504
|
+
"cwd_unset",
|
|
505
|
+
"cwd_outside_root",
|
|
506
|
+
]);
|
|
507
|
+
const SAFE_ROOT_STORAGE_CLASSES = new Set([
|
|
508
|
+
"system_drive",
|
|
509
|
+
"non_system_fixed_drive",
|
|
510
|
+
"network_or_mapped_drive",
|
|
511
|
+
]);
|
|
487
512
|
function safeTagValue(value, pattern = SAFE_TAG_TEXT) {
|
|
488
513
|
return pattern.test(value) ? value : "unknown";
|
|
489
514
|
}
|
|
@@ -546,14 +571,27 @@ function parsedLoadAverage(metadata) {
|
|
|
546
571
|
return value;
|
|
547
572
|
if (typeof value !== "string")
|
|
548
573
|
return undefined;
|
|
549
|
-
if (value === "unavailable")
|
|
550
|
-
return
|
|
574
|
+
if (value === "unavailable" || value === "unsupported_platform")
|
|
575
|
+
return value;
|
|
551
576
|
const match = /^\s*\{?\s*([0-9]+(?:\.[0-9]+)?)/.exec(value);
|
|
552
577
|
if (!match)
|
|
553
578
|
return undefined;
|
|
554
579
|
const parsed = Number(match[1]);
|
|
555
580
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
556
581
|
}
|
|
582
|
+
function cwdKindTag(metadata) {
|
|
583
|
+
const value = metadata.cwd_kind;
|
|
584
|
+
return typeof value === "string" && SAFE_CWD_KINDS.has(value) ? value : "unknown";
|
|
585
|
+
}
|
|
586
|
+
function rootStorageClassTag(metadata) {
|
|
587
|
+
const value = metadata.root_storage_class;
|
|
588
|
+
return typeof value === "string" && SAFE_ROOT_STORAGE_CLASSES.has(value)
|
|
589
|
+
? value
|
|
590
|
+
: "unavailable";
|
|
591
|
+
}
|
|
592
|
+
function closedMetadataValue(value, allowed, fallback) {
|
|
593
|
+
return typeof value === "string" && allowed.has(value) ? value : fallback;
|
|
594
|
+
}
|
|
557
595
|
function operatingSystemName(metadata) {
|
|
558
596
|
const platform = stringMetadata(metadata, "platform", "").toLowerCase();
|
|
559
597
|
if (/windows|win32|mingw|msys|cygwin/.test(platform))
|
|
@@ -608,8 +646,8 @@ function hookTimeoutDebugContextTags(context) {
|
|
|
608
646
|
timeout_debug_wait_point: context.wait_point,
|
|
609
647
|
timeout_debug_waiting_child: context.waiting_child_basename,
|
|
610
648
|
timeout_debug_waiting_child_elapsed_bucket: elapsedBucket(context.waiting_child_elapsed_ms),
|
|
611
|
-
timeout_debug_load_bucket: context.load_average === "
|
|
612
|
-
?
|
|
649
|
+
timeout_debug_load_bucket: typeof context.load_average === "string"
|
|
650
|
+
? context.load_average
|
|
613
651
|
: loadBucket(context.load_average),
|
|
614
652
|
timeout_debug_spawn_ms_bucket: debugSpawnMsBucket(context.spawn_ms),
|
|
615
653
|
timeout_debug_process_count_bucket: processCountBucket(context.process_count),
|
|
@@ -695,9 +733,10 @@ function hookTimeoutTags(event) {
|
|
|
695
733
|
hq_core_version: safeMetadataTag(metadata, "hq_version"),
|
|
696
734
|
shell: safeTagValue(shell),
|
|
697
735
|
bash_env: bashEnvTag(metadata),
|
|
698
|
-
cwd_kind:
|
|
736
|
+
cwd_kind: cwdKindTag(metadata),
|
|
699
737
|
os_name: operatingSystemName(metadata),
|
|
700
|
-
load_bucket: loadAverage === "
|
|
738
|
+
load_bucket: typeof loadAverage === "string" ? loadAverage : loadBucket(loadAverage),
|
|
739
|
+
root_storage_class: rootStorageClassTag(metadata),
|
|
701
740
|
spawn_ms_bucket: spawnMsBucket(metadata),
|
|
702
741
|
slow_child: safeSlowChild(metadata),
|
|
703
742
|
slow_phase: safeSlowPhase(metadata.slow_phase),
|