aboutsummaryrefslogtreecommitdiffstats
path: root/hooks/managed-files.hook.sh
blob: 69ed307be68655e25166680154487908cf43d873 (plain)
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