1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
#!/usr/bin/env bash
set -Eeuo pipefail
# Setup-hook wrapper around the bin/ implementation managed-files.sh -- runs that
# util (it does not change it) and records the outcome. Wired for the claude-config
# project only, by that project's .claude/settings.json (matcher-less, so it fires
# on both init and maintenance).
# - runs the util in the project cwd; it classifies every file as git-tracked /
# Syncthing-synced / local / both and prints the four counts plus full lists.
# - writes the full text report and a JSON record (the four counts + the report
# path) to the project root as machine-local dotfiles (.managed-files-report.txt
# / .managed-files-result.json) -- gitignored (local)
# - GIT_DIR/GIT_WORK_TREE are stripped before running the util: if they leaked in
# from a dotfiles shell they would retarget the util's `git ls-files` at the
# wrong repo (they override even `git -C`).
# - Setup-hook streams/exit are swallowed by the harness (see the setup-hook-no-
# feedback memory finding), so the JSON record file is the durable channel; stream
# conventions kept anyway: success -> stdout + exit 0, failure -> stderr + exit 2.
self=$(realpath -- "${BASH_SOURCE[0]}")
util="$(dirname -- "$(dirname -- "$self")")/bin/managed-files.sh"
input=$(cat)
cwd=$(jq --raw-output '.cwd // empty' <<<"$input" 2>/dev/null) || cwd=""
[ -n "$cwd" ] || cwd=$PWD
report="$cwd/.managed-files-report.txt"
result="$cwd/.managed-files-result.json"
# Persist this run's outcome as a JSON record (machine-local), written atomically.
# - $1 status (ok|error); on ok the count vars are set, on error $2 is the message.
# - best-effort, so a write failure never aborts the hook.
write_result() {
jq --null-input \
--arg status "$1" --arg message "${2:-}" \
--arg report "$report" --arg cwd "$cwd" \
--arg time "$(date --iso-8601=seconds)" \
--argjson tracked "${tracked:-null}" --argjson synced "${synced:-null}" \
--argjson local "${local:-null}" --argjson both "${both:-null}" \
'{hookEventName: "Setup", status: $status, tracked: $tracked,
synced: $synced, local: $local, both: $both, reportPath: $report,
cwd: $cwd, time: $time}
| if $message == "" then . else . + {message: $message} end' \
>"$result.tmp" 2>/dev/null &&
mv --force -- "$result.tmp" "$result" 2>/dev/null || return 0
}
abort() {
write_result error "$*"
echo "$*" >&2
exit 2
}
# Any unexpected failure becomes this run's recorded result, then exits 2.
trap 'rc=$?; trap - ERR; abort "line $LINENO (rc $rc): $BASH_COMMAND"' ERR
# Run the util in the project dir (with a clean git env), capturing its report.
err_file=$(mktemp)
if ! out=$(cd "$cwd" && env --unset=GIT_DIR --unset=GIT_WORK_TREE bash "$util" 2>"$err_file"); then
msg=$(tr '\n' ' ' <"$err_file")
rm --force -- "$err_file"
abort "report util failed: $msg"
fi
rm --force -- "$err_file"
printf '%s\n' "$out" >"$report.tmp" && mv --force -- "$report.tmp" "$report"
# Parse the four counts from the report header lines ("tracked: N", ...).
count_of() { awk -v k="$1:" '$1 == k { print $2; exit }' <<<"$out"; }
tracked=$(count_of tracked)
synced=$(count_of synced)
local=$(count_of local)
both=$(count_of both)
write_result ok
cat "$result"
exit 0
|