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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
#!/usr/bin/env bash
# Stop hook: tear down the ai-term session (or power off) AFTER a wrap-up.
#
# wrap-it-up.org drops a sentinel only at the very end of a teardown- or
# shutdown-mode wrap, once commit+push is verified and the valediction is
# delivered. This hook fires when Claude stops responding; on every NORMAL
# stop there is no sentinel, so it is a silent no-op. Only after a wrap that
# requested teardown does the matching sentinel exist, and only then does this
# hook act — which is what keeps a routine end-of-turn from killing the
# session.
#
# Decoupling via the Stop hook (rather than an inline workflow step) is what
# lets the valediction flush before the session dies: teardown kills the very
# tmux session Claude runs in, so it cannot happen inline.
#
# Two sentinels, keyed by the project basename (one ai-term session per
# project, named aiv-<basename>):
# /tmp/ai-wrap-teardown-<basename> -> cj/ai-term-quit "<basename>"
# kill the aiv-<basename> tmux session (takes claude with it), kill the
# vterm buffer, restore geometry. Defined in .emacs.d/modules/ai-term.el.
# /tmp/ai-wrap-shutdown-<basename> -> cj/ai-term-shutdown-countdown
# abort-able 10->1 echo-area countdown, then sudo shutdown now. Shutdown
# supersedes teardown (killing the buffer is moot if powering off).
#
# The sentinel is removed BEFORE the emacsclient call: cj/ai-term-quit kills
# the tmux session this hook runs inside, so the hook process may not survive
# the call. Clearing first guarantees the sentinel never lingers to re-fire on
# a later session in the same project. The emacs daemon is a separate process,
# so it completes the teardown even when the hook is cut off mid-call.
#
# emacsclient absent or daemon unreachable: clear the sentinel and exit 0. The
# session simply stays up — graceful degradation, never a wedge.
#
# Wire in ~/.claude/settings.json (see hooks/settings-snippet.json):
#
# "Stop": [
# { "hooks": [
# { "type": "command",
# "command": "~/.claude/hooks/ai-wrap-teardown.sh" } ] } ]
set -u
payload="$(cat)"
# Stop-hook stdin JSON carries cwd; basename it to the project / aiv- session.
cwd="$(printf '%s' "$payload" | jq -r '.cwd // empty' 2>/dev/null)"
[ -z "$cwd" ] && cwd="$PWD"
proj="$(basename "$cwd")"
teardown_sentinel="/tmp/ai-wrap-teardown-${proj}"
shutdown_sentinel="/tmp/ai-wrap-shutdown-${proj}"
fire() {
# $1 = elisp form. Best-effort: only when emacsclient resolves.
command -v emacsclient >/dev/null 2>&1 || return 0
emacsclient -e "$1" >/dev/null 2>&1 || true
}
# A sentinel means wrap-up claimed completion. Re-prove that claim immediately
# before consuming it: the certificate binds a prior strict check to HEAD, and
# verify also performs a fresh strict check to catch late writes.
verify_wrap() {
local gate="${GIT_WORKTREE_GATE:-}" detail
if [ -z "$gate" ]; then
gate="$(command -v git-worktree-gate 2>/dev/null || true)"
fi
if [ -z "$gate" ] || [ ! -x "$gate" ]; then
gate="$HOME/code/rulesets/claude-templates/bin/git-worktree-gate"
fi
if [ ! -x "$gate" ]; then
detail="wrap blocked: git-worktree-gate is unavailable; install rulesets tooling and retry"
else
detail="$("$gate" verify "$cwd" 2>&1)" && return 0
fi
# Codex and Claude consume different Stop-hook response fields. Codex
# command-hook payloads always include model; Claude's do not.
if printf '%s' "$payload" | jq -e '.model? != null' >/dev/null 2>&1; then
jq -n --arg reason "$detail" \
'{continue:false, stopReason:$reason, systemMessage:$reason}'
else
jq -n --arg reason "$detail" \
'{decision:"block", reason:$reason}'
fi
return 1
}
consume_certificate() {
local dir
dir="$(git -C "$cwd" rev-parse --absolute-git-dir 2>/dev/null)" || return 0
rm -f "$dir/ai-wrap-clean"
}
# Shutdown supersedes teardown when both are somehow present.
if [ -f "$shutdown_sentinel" ]; then
verify_wrap || exit 0
rm -f "$shutdown_sentinel" "$teardown_sentinel"
consume_certificate
fire '(cj/ai-term-shutdown-countdown)'
exit 0
fi
if [ -f "$teardown_sentinel" ]; then
verify_wrap || exit 0
rm -f "$teardown_sentinel"
consume_certificate
fire "(cj/ai-term-quit \"${proj}\")"
exit 0
fi
exit 0
|