@indigoai-us/hq-cli 5.345.113 → 5.345.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/assets/scaffold/core/scripts/hq-delegate-send.sh +5 -1
  3. package/dist/commands/bot-jobs.d.ts +1 -11
  4. package/dist/commands/bot-jobs.js +13 -34
  5. package/dist/commands/capacity.js +2 -0
  6. package/dist/commands/core-audit-log-native.js +1 -1
  7. package/dist/commands/core-checkpoint-native.d.ts +27 -0
  8. package/dist/commands/core-checkpoint-native.js +85 -2
  9. package/dist/commands/core-dm-bind-native.js +1 -1
  10. package/dist/commands/core-git-pack-extension-check-native.js +1 -1
  11. package/dist/commands/core-job-native-utils.d.ts +2 -2
  12. package/dist/commands/core-job-native-utils.js +3 -11
  13. package/dist/commands/core-job-notify-native.js +1 -2
  14. package/dist/commands/core-job-probe-native.d.ts +2 -3
  15. package/dist/commands/core-job-probe-native.js +14 -22
  16. package/dist/commands/core-job-remediate-native.js +1 -2
  17. package/dist/commands/core-job-run-native.js +58 -54
  18. package/dist/commands/core-jobs-native.d.ts +1 -3
  19. package/dist/commands/core-jobs-native.js +16 -271
  20. package/dist/commands/core-jobs-schedule.d.ts +7 -0
  21. package/dist/commands/core-jobs-schedule.js +243 -0
  22. package/dist/commands/core-native-utilities.d.ts +2 -3
  23. package/dist/commands/core-native-utilities.js +2 -19
  24. package/dist/commands/core-pin-native.js +1 -1
  25. package/dist/commands/core-refresh-vault-access-native.js +1 -1
  26. package/dist/commands/core-resolve-company-command.js +2 -17
  27. package/dist/commands/core-work-mesh-bind-trusted-native.js +1 -1
  28. package/dist/commands/core.js +1 -17
  29. package/dist/commands/lanes.js +1 -0
  30. package/dist/fast-scaffold.d.ts +1 -1
  31. package/dist/fast-scaffold.js +0 -38
  32. package/dist/lib/capacity/lane-login.js +73 -6
  33. package/dist/lib/capacity/route.d.ts +1 -1
  34. package/dist/lib/capacity/route.js +5 -1
  35. package/dist/lib/capacity/sign-in.d.ts +12 -0
  36. package/dist/lib/capacity/sign-in.js +65 -16
  37. package/dist/lib/daemon/catalog.d.ts +0 -2
  38. package/dist/lib/daemon/catalog.js +3 -6
  39. package/dist/lib/daemon/run.js +0 -7
  40. package/dist/utils/resident-package-preload.js +2 -7
  41. package/package.json +1 -1
  42. package/assets/scaffold/core/scripts/audit-log.sh +0 -384
  43. package/assets/scaffold/core/scripts/git-pack-extension-check.sh +0 -164
  44. package/assets/scaffold/core/scripts/hq-dm-bind.sh +0 -590
  45. package/assets/scaffold/core/scripts/hq-job-notify.sh +0 -389
  46. package/assets/scaffold/core/scripts/hq-job-probe.sh +0 -865
  47. package/assets/scaffold/core/scripts/hq-job-remediate.sh +0 -359
  48. package/assets/scaffold/core/scripts/hq-job-run.sh +0 -1025
  49. package/assets/scaffold/core/scripts/hq-pin.sh +0 -118
  50. package/assets/scaffold/core/scripts/jobs-schedule-parse.sh +0 -310
  51. package/assets/scaffold/core/scripts/jobs-validate.sh +0 -482
  52. package/assets/scaffold/core/scripts/outpost-jobs-reconcile.sh +0 -836
  53. package/assets/scaffold/core/scripts/refresh-vault-access.sh +0 -188
  54. package/assets/scaffold/core/scripts/resolve-company.sh +0 -331
  55. package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +0 -184
  56. package/dist/commands/core-job-post-exit-assets.d.ts +0 -8
  57. package/dist/commands/core-job-post-exit-assets.js +0 -37
  58. package/dist/commands/core-native-jobs-flag.d.ts +0 -4
  59. package/dist/commands/core-native-jobs-flag.js +0 -7
  60. package/dist/commands/core-native-utilities-flag.d.ts +0 -4
  61. package/dist/commands/core-native-utilities-flag.js +0 -7
@@ -1,384 +0,0 @@
1
- #!/usr/bin/env bash
2
- # =============================================================================
3
- # audit-log.sh — Structured audit log utility for HQ
4
- #
5
- # Schema fields: timestamp, event, company, project, story_id, worker, model,
6
- # action, files_touched, result, duration_ms, error, session_id
7
- #
8
- # Usage:
9
- # core/scripts/audit-log.sh append --event task_started --project my-project [fields...]
10
- # core/scripts/audit-log.sh query [--project X] [--company X] [--worker X] [--since X] [--event X]
11
- # core/scripts/audit-log.sh summary
12
- #
13
- # Events: task_started | phase_completed | task_completed | task_failed |
14
- # project_started | project_completed |
15
- # story_dispatched | story_completed | story_failed |
16
- # pipeline_started | pipeline_completed | pipeline_paused | pipeline_failed |
17
- # project_pr_created | project_reviewed | project_merged | project_deployed |
18
- # project_canary_pass | project_canary_fail |
19
- # gate_requested | gate_resolved
20
- # Results: success | fail | skipped
21
- # =============================================================================
22
-
23
- set -euo pipefail
24
-
25
- HQ_ROOT="${HQ_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
26
- # shellcheck source=core/scripts/lib/portable.sh
27
- . "$HQ_ROOT/core/scripts/lib/portable.sh"
28
- AUDIT_LOG="$HQ_ROOT/workspace/metrics/audit-log.jsonl"
29
-
30
- VALID_EVENTS="task_started phase_completed task_completed task_failed project_started project_completed story_dispatched story_completed story_failed pipeline_started pipeline_completed pipeline_paused pipeline_failed project_pr_created project_reviewed project_merged project_deployed project_canary_pass project_canary_fail gate_requested gate_resolved"
31
- VALID_RESULTS="success fail skipped"
32
-
33
- # ---------------------------------------------------------------------------
34
- # helpers
35
- # ---------------------------------------------------------------------------
36
-
37
- die() { echo "ERROR: $*" >&2; exit 1; }
38
-
39
- # require_jq comes from portable.sh (multi-OS install guidance).
40
-
41
- iso_now() {
42
- date -u +"%Y-%m-%dT%H:%M:%SZ"
43
- }
44
-
45
- contains() {
46
- local needle="$1" haystack="$2"
47
- [[ " $haystack " == *" $needle "* ]]
48
- }
49
-
50
- # ---------------------------------------------------------------------------
51
- # append subcommand
52
- # ---------------------------------------------------------------------------
53
- # Usage:
54
- # audit-log.sh append \
55
- # --event task_started \
56
- # --project hq-observability \
57
- # [--company {company}] \
58
- # [--story-id US-001] \
59
- # [--worker backend-dev] \
60
- # [--model sonnet] \
61
- # [--action "Scaffold schema"] \
62
- # [--files "core/scripts/audit-log.sh,workspace/metrics/audit-log.jsonl"] \
63
- # [--result success] \
64
- # [--duration-ms 1234] \
65
- # [--error "message"] \
66
- # [--session-id abc123]
67
- # ---------------------------------------------------------------------------
68
- cmd_append() {
69
- require_jq
70
-
71
- local timestamp="" event="" company="" project="" story_id="" worker=""
72
- local model="" action="" files_touched="" result="" duration_ms="" error="" session_id=""
73
-
74
- while [[ $# -gt 0 ]]; do
75
- case "$1" in
76
- --timestamp) timestamp="$2"; shift 2 ;;
77
- --event) event="$2"; shift 2 ;;
78
- --company) company="$2"; shift 2 ;;
79
- --project) project="$2"; shift 2 ;;
80
- --story-id) story_id="$2"; shift 2 ;;
81
- --worker) worker="$2"; shift 2 ;;
82
- --model) model="$2"; shift 2 ;;
83
- --action) action="$2"; shift 2 ;;
84
- --files) files_touched="$2"; shift 2 ;;
85
- --result) result="$2"; shift 2 ;;
86
- --duration-ms) duration_ms="$2"; shift 2 ;;
87
- --error) error="$2"; shift 2 ;;
88
- --session-id) session_id="$2"; shift 2 ;;
89
- *) die "Unknown flag: $1" ;;
90
- esac
91
- done
92
-
93
- # --- required field validation ---
94
- [[ -n "$event" ]] || die "Missing required field: --event"
95
- [[ -n "$project" ]] || die "Missing required field: --project"
96
-
97
- # validate event value
98
- contains "$event" "$VALID_EVENTS" || die "Invalid event '$event'. Must be one of: $VALID_EVENTS"
99
-
100
- # validate result if provided
101
- if [[ -n "$result" ]]; then
102
- contains "$result" "$VALID_RESULTS" || die "Invalid result '$result'. Must be one of: $VALID_RESULTS"
103
- fi
104
-
105
- # validate duration_ms is numeric if provided
106
- if [[ -n "$duration_ms" ]]; then
107
- [[ "$duration_ms" =~ ^[0-9]+$ ]] || die "--duration-ms must be a non-negative integer"
108
- fi
109
-
110
- # auto-fill timestamp
111
- [[ -n "$timestamp" ]] || timestamp="$(iso_now)"
112
-
113
- # --- build files_touched array ---
114
- # Input: comma-separated string → JSON array
115
- local files_json="[]"
116
- if [[ -n "$files_touched" ]]; then
117
- files_json="$(echo "$files_touched" | jq -Rc 'split(",")')"
118
- fi
119
-
120
- # --- build JSON line ---
121
- local entry
122
- entry="$(jq -cn \
123
- --arg ts "$timestamp" \
124
- --arg event "$event" \
125
- --arg company "$company" \
126
- --arg project "$project" \
127
- --arg story_id "$story_id" \
128
- --arg worker "$worker" \
129
- --arg model "$model" \
130
- --arg action "$action" \
131
- --argjson files "$files_json" \
132
- --arg result "$result" \
133
- --arg dur "$duration_ms" \
134
- --arg error "$error" \
135
- --arg session "$session_id" \
136
- '{
137
- timestamp: $ts,
138
- event: $event,
139
- company: (if $company != "" then $company else null end),
140
- project: $project,
141
- story_id: (if $story_id != "" then $story_id else null end),
142
- worker: (if $worker != "" then $worker else null end),
143
- model: (if $model != "" then $model else null end),
144
- action: (if $action != "" then $action else null end),
145
- files_touched: $files,
146
- result: (if $result != "" then $result else null end),
147
- duration_ms: (if $dur != "" then ($dur | tonumber) else null end),
148
- error: (if $error != "" then $error else null end),
149
- session_id: (if $session != "" then $session else null end)
150
- } | with_entries(select(.value != null and .value != []))'
151
- )"
152
-
153
- mkdir -p "$(dirname "$AUDIT_LOG")"
154
- echo "$entry" >> "$AUDIT_LOG"
155
- echo "Appended to $AUDIT_LOG"
156
- }
157
-
158
- # ---------------------------------------------------------------------------
159
- # query subcommand
160
- # ---------------------------------------------------------------------------
161
- # Usage:
162
- # audit-log.sh query [--project X] [--company X] [--worker X] [--since YYYY-MM-DD] [--event X]
163
- # ---------------------------------------------------------------------------
164
- cmd_query() {
165
- require_jq
166
-
167
- local filter_project="" filter_company="" filter_worker="" filter_since="" filter_event=""
168
-
169
- while [[ $# -gt 0 ]]; do
170
- case "$1" in
171
- --project) filter_project="$2"; shift 2 ;;
172
- --company) filter_company="$2"; shift 2 ;;
173
- --worker) filter_worker="$2"; shift 2 ;;
174
- --since) filter_since="$2"; shift 2 ;;
175
- --event) filter_event="$2"; shift 2 ;;
176
- *) die "Unknown flag: $1" ;;
177
- esac
178
- done
179
-
180
- if [[ ! -f "$AUDIT_LOG" ]]; then
181
- echo "No audit log found at $AUDIT_LOG" >&2
182
- exit 0
183
- fi
184
-
185
- # Build jq args + filter chain (parameterized to prevent filter injection)
186
- local jq_args=()
187
- local jq_filter="."
188
-
189
- if [[ -n "$filter_project" ]]; then
190
- jq_args+=(--arg fp "$filter_project")
191
- jq_filter+=' | select(.project == $fp)'
192
- fi
193
- if [[ -n "$filter_company" ]]; then
194
- jq_args+=(--arg fc "$filter_company")
195
- jq_filter+=' | select(.company == $fc)'
196
- fi
197
- if [[ -n "$filter_worker" ]]; then
198
- jq_args+=(--arg fw "$filter_worker")
199
- jq_filter+=' | select(.worker == $fw)'
200
- fi
201
- if [[ -n "$filter_event" ]]; then
202
- jq_args+=(--arg fe "$filter_event")
203
- jq_filter+=' | select(.event == $fe)'
204
- fi
205
- if [[ -n "$filter_since" ]]; then
206
- jq_args+=(--arg fs "$filter_since")
207
- jq_filter+=' | select(.timestamp >= $fs)'
208
- fi
209
-
210
- local count
211
- count=$(jq -r "${jq_args[@]}" "$jq_filter" "$AUDIT_LOG" 2>/dev/null | jq -s 'length')
212
-
213
- jq -r "${jq_args[@]}" "$jq_filter" "$AUDIT_LOG" 2>/dev/null | jq -s '.'
214
- echo "--- $count record(s) matched ---" >&2
215
- }
216
-
217
- # ---------------------------------------------------------------------------
218
- # summary subcommand
219
- # ---------------------------------------------------------------------------
220
- # Outputs table: project | stories | phases | failures | last_activity
221
- # ---------------------------------------------------------------------------
222
- cmd_summary() {
223
- require_jq
224
-
225
- if [[ ! -f "$AUDIT_LOG" ]]; then
226
- echo "No audit log found at $AUDIT_LOG" >&2
227
- exit 0
228
- fi
229
-
230
- local line_count
231
- line_count=$(wc -l < "$AUDIT_LOG" | tr -d ' ')
232
-
233
- if [[ "$line_count" -eq 0 ]]; then
234
- echo "Audit log is empty — no records to summarize."
235
- exit 0
236
- fi
237
-
238
- echo ""
239
- echo "=== Audit Log Summary ==="
240
- echo ""
241
-
242
- # Per-project breakdown
243
- echo "--- By Project ---"
244
- printf "%-40s %8s %8s %8s %8s %-26s\n" \
245
- "PROJECT" "STORIES" "PHASES" "FAILURES" "SKIPPED" "LAST_ACTIVITY"
246
- printf "%-40s %8s %8s %8s %8s %-26s\n" \
247
- "$(printf '%0.s-' {1..40})" "--------" "--------" "--------" "--------" "$(printf '%0.s-' {1..26})"
248
-
249
- jq -rs '
250
- group_by(.project) |
251
- .[] |
252
- {
253
- project: .[0].project,
254
- stories: (map(select(.event == "task_completed" or .event == "task_failed")) | length),
255
- phases: (map(select(.event == "phase_completed")) | length),
256
- failures: (map(select(.result == "fail" or .event == "task_failed")) | length),
257
- skipped: (map(select(.result == "skipped")) | length),
258
- last_activity: (sort_by(.timestamp) | last | .timestamp)
259
- }
260
- ' "$AUDIT_LOG" | jq -r '
261
- "\(.project // "unknown")\t\(.stories)\t\(.phases)\t\(.failures)\t\(.skipped)\t\(.last_activity // "-")"
262
- ' | while IFS=$'\t' read -r proj stories phases failures skipped last; do
263
- printf "%-40s %8s %8s %8s %8s %-26s\n" \
264
- "$proj" "$stories" "$phases" "$failures" "$skipped" "$last"
265
- done
266
-
267
- echo ""
268
-
269
- # Per-worker breakdown
270
- echo "--- By Worker ---"
271
- printf "%-30s %8s %8s %-10s\n" "WORKER" "TASKS" "FAILURES" "SUCCESS_RATE"
272
- printf "%-30s %8s %8s %-10s\n" \
273
- "$(printf '%0.s-' {1..30})" "--------" "--------" "----------"
274
-
275
- jq -rs '
276
- map(select(.worker != null)) |
277
- group_by(.worker) |
278
- .[] |
279
- {
280
- worker: .[0].worker,
281
- tasks: (map(select(.event == "task_completed" or .event == "task_failed")) | length),
282
- failures: (map(select(.result == "fail" or .event == "task_failed")) | length)
283
- } |
284
- . + {
285
- rate: (if .tasks > 0 then (((.tasks - .failures) / .tasks * 100) | round | tostring) + "%" else "n/a" end)
286
- }
287
- ' "$AUDIT_LOG" | jq -r '
288
- "\(.worker)\t\(.tasks)\t\(.failures)\t\(.rate)"
289
- ' | while IFS=$'\t' read -r worker tasks failures rate; do
290
- printf "%-30s %8s %8s %-10s\n" "$worker" "$tasks" "$failures" "$rate"
291
- done
292
-
293
- echo ""
294
-
295
- # Overall stats
296
- local total_events total_tasks total_failures
297
- total_events=$(wc -l < "$AUDIT_LOG" | tr -d ' ')
298
- total_tasks=$(jq -rs 'map(select(.event == "task_completed" or .event == "task_failed")) | length' "$AUDIT_LOG")
299
- total_failures=$(jq -rs 'map(select(.result == "fail" or .event == "task_failed")) | length' "$AUDIT_LOG")
300
-
301
- echo "--- Totals ---"
302
- echo " Total events: $total_events"
303
- echo " Total tasks: $total_tasks"
304
- echo " Total failures: $total_failures"
305
- echo ""
306
- }
307
-
308
- # ---------------------------------------------------------------------------
309
- # help
310
- # ---------------------------------------------------------------------------
311
- cmd_help() {
312
- cat <<'EOF'
313
- audit-log.sh — HQ audit log utility
314
-
315
- SUBCOMMANDS:
316
- append Write one JSONL record to the audit log
317
- query Filter and display records
318
- summary Show aggregated table (project | worker | status)
319
-
320
- APPEND FLAGS (--event and --project are required):
321
- --timestamp ISO8601 (auto-filled if omitted)
322
- --event task_started | phase_completed | task_completed | task_failed |
323
- project_started | project_completed |
324
- story_dispatched | story_completed | story_failed |
325
- pipeline_started | pipeline_completed | pipeline_paused |
326
- pipeline_failed | project_pr_created | project_reviewed |
327
- project_merged | project_deployed | project_canary_pass |
328
- project_canary_fail | gate_requested | gate_resolved
329
- --company Company slug (e.g. {company})
330
- --project Project slug (e.g. hq-observability)
331
- --story-id Story ID (e.g. US-001)
332
- --worker Worker name (e.g. backend-dev)
333
- --model Model name (e.g. sonnet)
334
- --action Short description of what happened
335
- --files Comma-separated file paths touched
336
- --result success | fail | skipped
337
- --duration-ms Wall-clock time in milliseconds (integer)
338
- --error Error message if result=fail
339
- --session-id Claude session/thread ID
340
-
341
- QUERY FLAGS:
342
- --project Filter by project slug
343
- --company Filter by company slug
344
- --worker Filter by worker name
345
- --since Filter by timestamp >= YYYY-MM-DD (or full ISO8601)
346
- --event Filter by event type
347
-
348
- EXAMPLES:
349
- # Append a task_started event
350
- core/scripts/audit-log.sh append \
351
- --event task_started \
352
- --project hq-observability \
353
- --company {company} \
354
- --story-id US-001 \
355
- --worker backend-dev \
356
- --model sonnet
357
-
358
- # Query all failures for a project
359
- core/scripts/audit-log.sh query --project hq-observability --event task_failed
360
-
361
- # Query since a date
362
- core/scripts/audit-log.sh query --since 2026-03-01
363
-
364
- # Show summary table
365
- core/scripts/audit-log.sh summary
366
-
367
- LOG FILE:
368
- workspace/metrics/audit-log.jsonl
369
- EOF
370
- }
371
-
372
- # ---------------------------------------------------------------------------
373
- # dispatch
374
- # ---------------------------------------------------------------------------
375
- SUBCOMMAND="${1:-help}"
376
- shift || true
377
-
378
- case "$SUBCOMMAND" in
379
- append) cmd_append "$@" ;;
380
- query) cmd_query "$@" ;;
381
- summary) cmd_summary "$@" ;;
382
- help|--help|-h) cmd_help ;;
383
- *) die "Unknown subcommand '$SUBCOMMAND'. Use: append | query | summary | help" ;;
384
- esac
@@ -1,164 +0,0 @@
1
- #!/usr/bin/env bash
2
- # hq-core: public
3
- # git-pack-extension-check.sh — find pack files that have lost their extension.
4
- #
5
- # A git packfile set is three files sharing one basename:
6
- #
7
- # pack-<sha>.pack the objects (magic "PACK")
8
- # pack-<sha>.idx the index into them (magic "\377tOc")
9
- # pack-<sha>.rev reverse index (magic "RIDX")
10
- #
11
- # Git pairs them BY EXTENSION. Strip the extension off any one of them and git
12
- # stops seeing the set entirely: the objects are still on disk, byte for byte,
13
- # but they become unreachable. The repo then fails in a way that reads like
14
- # catastrophic history loss rather than a filename problem:
15
- #
16
- # fatal: unable to read tree (<sha>)
17
- # error: bad tree object HEAD
18
- # error: refs/heads/<branch>: invalid sha1 pointer <sha>
19
- #
20
- # `git fsck` reports the objects as missing and `git gc` cannot help, because
21
- # nothing is actually corrupt — the bytes are intact and the repair is a
22
- # rename. That mismatch between how it presents and what it is makes this
23
- # failure expensive to diagnose from the error text alone, which is why this
24
- # check exists as a deterministic probe.
25
- #
26
- # Git never writes an extensionless `pack-*` file, so finding one is
27
- # unambiguous. Observed in the wild on a macOS HQ root where a folder-level
28
- # sync/backup agent had been walking the tree; the mechanism that drops the
29
- # extension is not established.
30
- #
31
- # Usage:
32
- # git-pack-extension-check.sh [--root <path>] [--fix] [--quiet]
33
- #
34
- # --root <path> Repository to check (default: current directory).
35
- # --fix Rename each orphan back to the extension its magic bytes
36
- # prove it should carry. Without this the script only
37
- # reports.
38
- # --quiet Suppress the healthy-case line; findings still print.
39
- #
40
- # Exit codes:
41
- # 0 healthy, or every orphan was repaired under --fix
42
- # 1 usage error, or the root is not a git repository
43
- # 2 orphans found and left in place (report-only, or unrepairable)
44
- #
45
- # The repair is a rename and nothing else. It never deletes, never rewrites
46
- # file contents, never touches refs or the index, and refuses to overwrite an
47
- # existing target. A file whose magic bytes are not a known pack signature is
48
- # reported and left alone — this script will not guess.
49
-
50
- set -uo pipefail
51
-
52
- ROOT="."
53
- FIX=0
54
- QUIET=0
55
-
56
- while [[ $# -gt 0 ]]; do
57
- case "$1" in
58
- --root)
59
- [[ $# -ge 2 ]] || { echo "git-pack-extension-check: --root needs a path" >&2; exit 1; }
60
- ROOT="$2"; shift 2 ;;
61
- --fix) FIX=1; shift ;;
62
- --quiet) QUIET=1; shift ;;
63
- -h|--help)
64
- sed -n '2,50p' "$0" | sed 's/^# \{0,1\}//'
65
- exit 0 ;;
66
- *)
67
- echo "git-pack-extension-check: unknown argument '$1'" >&2
68
- exit 1 ;;
69
- esac
70
- done
71
-
72
- if [[ ! -d "$ROOT" ]]; then
73
- echo "git-pack-extension-check: not a directory: $ROOT" >&2
74
- exit 1
75
- fi
76
-
77
- # Packs live in the COMMON git dir, not the per-worktree one — a linked
78
- # worktree shares the main object store, so checking either resolves to the
79
- # same place. `--path-format=absolute` needs git 2.31+; the fallback keeps
80
- # this working on older hosts by resolving the (possibly relative) path
81
- # against the repo root itself.
82
- GIT_COMMON="$(git -C "$ROOT" rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)"
83
- if [[ -z "$GIT_COMMON" ]]; then
84
- GIT_COMMON="$(git -C "$ROOT" rev-parse --git-common-dir 2>/dev/null || true)"
85
- if [[ -z "$GIT_COMMON" ]]; then
86
- echo "git-pack-extension-check: not a git repository: $ROOT" >&2
87
- exit 1
88
- fi
89
- if [[ "$GIT_COMMON" != /* ]]; then
90
- GIT_COMMON="$(cd "$ROOT" && cd "$GIT_COMMON" 2>/dev/null && pwd || true)"
91
- fi
92
- fi
93
-
94
- PACK_DIR="$GIT_COMMON/objects/pack"
95
- if [[ ! -d "$PACK_DIR" ]]; then
96
- [[ "$QUIET" == "1" ]] || echo "git-pack-extension-check: no pack directory, nothing to check"
97
- exit 0
98
- fi
99
-
100
- # The magic bytes are read with `od` rather than compared as shell strings:
101
- # a packfile is binary and its first bytes can include NUL, which a shell
102
- # variable cannot hold.
103
- magic_of() {
104
- od -A n -t x1 -N 4 "$1" 2>/dev/null | tr -d ' \n'
105
- }
106
-
107
- FOUND=0
108
- REPAIRED=0
109
- UNREPAIRED=0
110
-
111
- # NUL-delimited: a mangled filename is exactly the case where a newline in a
112
- # name would split the list and corrupt the scan.
113
- while IFS= read -r -d '' orphan; do
114
- base="$(basename "$orphan")"
115
- hex="$(magic_of "$orphan")"
116
- case "$hex" in
117
- 5041434b) ext="pack" ;; # "PACK"
118
- ff744f63) ext="idx" ;; # "\377tOc"
119
- 52494458) ext="rev" ;; # "RIDX"
120
- *) ext="" ;;
121
- esac
122
-
123
- FOUND=$((FOUND + 1))
124
-
125
- if [[ -z "$ext" ]]; then
126
- echo "git-pack-extension-check: FOUND $base — extensionless, but its magic bytes (${hex:-empty}) are not a pack/idx/rev signature; leaving it alone" >&2
127
- UNREPAIRED=$((UNREPAIRED + 1))
128
- continue
129
- fi
130
-
131
- target="$orphan.$ext"
132
- if [[ -e "$target" ]]; then
133
- echo "git-pack-extension-check: FOUND $base — should be $base.$ext, but that file already exists; resolve by hand" >&2
134
- UNREPAIRED=$((UNREPAIRED + 1))
135
- continue
136
- fi
137
-
138
- if [[ "$FIX" != "1" ]]; then
139
- echo "git-pack-extension-check: FOUND $base — lost its .$ext extension; git cannot see this pack set. Re-run with --fix to rename it." >&2
140
- UNREPAIRED=$((UNREPAIRED + 1))
141
- continue
142
- fi
143
-
144
- if mv -n "$orphan" "$target" 2>/dev/null && [[ -e "$target" && ! -e "$orphan" ]]; then
145
- echo "git-pack-extension-check: REPAIRED $base -> $base.$ext"
146
- REPAIRED=$((REPAIRED + 1))
147
- else
148
- echo "git-pack-extension-check: FAILED to rename $base -> $base.$ext" >&2
149
- UNREPAIRED=$((UNREPAIRED + 1))
150
- fi
151
- done < <(find "$PACK_DIR" -maxdepth 1 -type f -name 'pack-*' ! -name '*.*' -print0 2>/dev/null)
152
-
153
- if [[ "$FOUND" -eq 0 ]]; then
154
- [[ "$QUIET" == "1" ]] || echo "git-pack-extension-check: ok — no extensionless pack files in $PACK_DIR"
155
- exit 0
156
- fi
157
-
158
- if [[ "$UNREPAIRED" -gt 0 ]]; then
159
- echo "git-pack-extension-check: ${FOUND} orphan(s), ${REPAIRED} repaired, ${UNREPAIRED} still broken" >&2
160
- exit 2
161
- fi
162
-
163
- echo "git-pack-extension-check: ${REPAIRED} pack file(s) repaired — re-run 'git fsck' to confirm"
164
- exit 0