uv-suite 0.26.5 → 0.28.0
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/hooks/auto-checkpoint-helper.sh +73 -0
- package/hooks/auto-checkpoint.sh +184 -0
- package/hooks/confirm-helper.sh +45 -0
- package/hooks/session-end-helper.sh +56 -0
- package/package.json +1 -1
- package/personas/auto.json +16 -2
- package/personas/professional.json +18 -2
- package/personas/spike.json +28 -7
- package/personas/sport.json +25 -1
- package/skills/auto-checkpoint/SKILL.md +47 -0
- package/skills/confirm/SKILL.md +4 -7
- package/skills/session-end/SKILL.md +100 -0
- package/watchtower/auto-checkpoint-prompt.md +42 -0
- package/watchtower/auto-checkpoint-runner.js +505 -0
- package/watchtower/dashboard.html +120 -12
- package/watchtower/server.js +21 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite helper: read or change auto-checkpoint settings.
|
|
3
|
+
# Used by the /auto-checkpoint slash command.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# auto-checkpoint-helper.sh status
|
|
7
|
+
# auto-checkpoint-helper.sh on
|
|
8
|
+
# auto-checkpoint-helper.sh off
|
|
9
|
+
# auto-checkpoint-helper.sh <minutes> # set interval
|
|
10
|
+
|
|
11
|
+
STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
|
|
12
|
+
mkdir -p "$STATE_DIR"
|
|
13
|
+
STATE_FILE="$STATE_DIR/auto-checkpoint.json"
|
|
14
|
+
|
|
15
|
+
ensure_state() {
|
|
16
|
+
[ -f "$STATE_FILE" ] && return
|
|
17
|
+
cat > "$STATE_FILE" <<'EOF'
|
|
18
|
+
{
|
|
19
|
+
"mode": "on",
|
|
20
|
+
"interval_minutes": 10
|
|
21
|
+
}
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get_field() {
|
|
26
|
+
ensure_state
|
|
27
|
+
STATE_PATH="$STATE_FILE" KEY="$1" python3 -c '
|
|
28
|
+
import json, os
|
|
29
|
+
d = json.load(open(os.environ["STATE_PATH"]))
|
|
30
|
+
print(d.get(os.environ["KEY"], ""))
|
|
31
|
+
'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set_field() {
|
|
35
|
+
ensure_state
|
|
36
|
+
STATE_PATH="$STATE_FILE" KEY="$1" VAL="$2" python3 -c '
|
|
37
|
+
import json, os
|
|
38
|
+
p = os.environ["STATE_PATH"]
|
|
39
|
+
d = json.load(open(p))
|
|
40
|
+
key = os.environ["KEY"]
|
|
41
|
+
val = os.environ["VAL"]
|
|
42
|
+
if key == "interval_minutes":
|
|
43
|
+
val = int(val)
|
|
44
|
+
d[key] = val
|
|
45
|
+
json.dump(d, open(p, "w"), indent=2)
|
|
46
|
+
'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
ARG=$(printf '%s' "$1" | tr -d '[:space:]')
|
|
50
|
+
|
|
51
|
+
case "$ARG" in
|
|
52
|
+
on)
|
|
53
|
+
set_field mode on
|
|
54
|
+
echo "Auto-checkpoint: ON (every $(get_field interval_minutes) min, mechanical + semantic)"
|
|
55
|
+
;;
|
|
56
|
+
off)
|
|
57
|
+
set_field mode off
|
|
58
|
+
echo "Auto-checkpoint: OFF"
|
|
59
|
+
;;
|
|
60
|
+
""|status)
|
|
61
|
+
ensure_state
|
|
62
|
+
echo "Auto-checkpoint: $(get_field mode) (every $(get_field interval_minutes) min)"
|
|
63
|
+
;;
|
|
64
|
+
*)
|
|
65
|
+
if printf '%s' "$ARG" | grep -qE '^[0-9]+$' && [ "$ARG" -ge 1 ] && [ "$ARG" -le 1440 ]; then
|
|
66
|
+
set_field interval_minutes "$ARG"
|
|
67
|
+
echo "Auto-checkpoint interval: $ARG min (mode: $(get_field mode))"
|
|
68
|
+
else
|
|
69
|
+
echo "Usage: /auto-checkpoint [on | off | <minutes 1-1440> | status]"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: Tier A auto-checkpoint — mechanical state snapshot.
|
|
3
|
+
# Event: PostToolUse
|
|
4
|
+
#
|
|
5
|
+
# Logs every tool call into a per-session activity log. When `interval_minutes`
|
|
6
|
+
# have passed since the last mechanical checkpoint AND there has been activity
|
|
7
|
+
# in the interval, writes a deterministic snapshot to
|
|
8
|
+
# uv-out/checkpoints/<sid>/auto-<ts>-mechanical.md and forwards an
|
|
9
|
+
# AutoCheckpoint event to the watchtower.
|
|
10
|
+
#
|
|
11
|
+
# Tier B (semantic, claude -p) runs separately from the watchtower's timer.
|
|
12
|
+
|
|
13
|
+
INPUT=$(cat 2>/dev/null || true)
|
|
14
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
15
|
+
STATE_DIR="$PROJECT_DIR/.uv-suite-state"
|
|
16
|
+
SESSIONS_DIR="$STATE_DIR/sessions"
|
|
17
|
+
mkdir -p "$SESSIONS_DIR" 2>/dev/null
|
|
18
|
+
|
|
19
|
+
STATE_FILE="$STATE_DIR/auto-checkpoint.json"
|
|
20
|
+
|
|
21
|
+
# Default state if missing
|
|
22
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
23
|
+
cat > "$STATE_FILE" <<'EOF'
|
|
24
|
+
{
|
|
25
|
+
"mode": "on",
|
|
26
|
+
"interval_minutes": 10
|
|
27
|
+
}
|
|
28
|
+
EOF
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Read mode + interval (skip if mode != on)
|
|
32
|
+
read_state() {
|
|
33
|
+
STATE_PATH="$STATE_FILE" python3 -c '
|
|
34
|
+
import json, os, sys
|
|
35
|
+
try:
|
|
36
|
+
d = json.load(open(os.environ["STATE_PATH"]))
|
|
37
|
+
print(d.get("mode", "on"))
|
|
38
|
+
print(d.get("interval_minutes", 10))
|
|
39
|
+
except Exception:
|
|
40
|
+
print("on"); print("10")
|
|
41
|
+
' 2>/dev/null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
STATE_OUT=$(read_state)
|
|
45
|
+
MODE=$(echo "$STATE_OUT" | sed -n 1p)
|
|
46
|
+
INTERVAL_MIN=$(echo "$STATE_OUT" | sed -n 2p)
|
|
47
|
+
[ "$MODE" != "on" ] && exit 0
|
|
48
|
+
[ -z "$INTERVAL_MIN" ] && INTERVAL_MIN=10
|
|
49
|
+
|
|
50
|
+
# Resolve session id
|
|
51
|
+
SID="${UVS_SESSION_ID:-}"
|
|
52
|
+
if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
|
|
53
|
+
SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
|
|
54
|
+
fi
|
|
55
|
+
[ -z "$SID" ] && exit 0
|
|
56
|
+
|
|
57
|
+
ACTIVITY_LOG="$SESSIONS_DIR/$SID.activity.log"
|
|
58
|
+
LAST_CP_FILE="$SESSIONS_DIR/$SID.last-mechanical-checkpoint.txt"
|
|
59
|
+
NOW=$(date +%s)
|
|
60
|
+
|
|
61
|
+
# Append activity entry. Cap log at last 500 lines.
|
|
62
|
+
TOOL=""
|
|
63
|
+
TARGET=""
|
|
64
|
+
if command -v jq >/dev/null 2>&1; then
|
|
65
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
|
|
66
|
+
TARGET=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.command // .tool_input.pattern // .tool_input.url // ""' 2>/dev/null)
|
|
67
|
+
fi
|
|
68
|
+
TARGET=$(printf '%s' "$TARGET" | tr -d '\n' | cut -c1-120)
|
|
69
|
+
[ -n "$TOOL" ] && echo "$NOW $TOOL $TARGET" >> "$ACTIVITY_LOG"
|
|
70
|
+
if [ -f "$ACTIVITY_LOG" ]; then
|
|
71
|
+
LINES=$(wc -l < "$ACTIVITY_LOG" 2>/dev/null | tr -d ' ')
|
|
72
|
+
if [ -n "$LINES" ] && [ "$LINES" -gt 500 ]; then
|
|
73
|
+
tail -n 500 "$ACTIVITY_LOG" > "$ACTIVITY_LOG.tmp" && mv "$ACTIVITY_LOG.tmp" "$ACTIVITY_LOG"
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Has the interval passed since last mechanical checkpoint?
|
|
78
|
+
# On the very first run, seed the timestamp with NOW so we don't fire a
|
|
79
|
+
# checkpoint immediately — the first one fires after the interval has elapsed.
|
|
80
|
+
if [ ! -f "$LAST_CP_FILE" ]; then
|
|
81
|
+
echo "$NOW" > "$LAST_CP_FILE"
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
LAST_CP=$(cat "$LAST_CP_FILE" 2>/dev/null)
|
|
85
|
+
LAST_CP=${LAST_CP:-0}
|
|
86
|
+
ELAPSED=$((NOW - LAST_CP))
|
|
87
|
+
INTERVAL_SEC=$((INTERVAL_MIN * 60))
|
|
88
|
+
[ "$ELAPSED" -lt "$INTERVAL_SEC" ] && exit 0
|
|
89
|
+
|
|
90
|
+
# Activity since last checkpoint? If none, skip.
|
|
91
|
+
SINCE_LINES=0
|
|
92
|
+
if [ -f "$ACTIVITY_LOG" ]; then
|
|
93
|
+
SINCE_LINES=$(awk -v cutoff="$LAST_CP" '$1 > cutoff' "$ACTIVITY_LOG" 2>/dev/null | wc -l | tr -d ' ')
|
|
94
|
+
fi
|
|
95
|
+
[ "${SINCE_LINES:-0}" -lt 1 ] && exit 0
|
|
96
|
+
|
|
97
|
+
# Resolve checkpoint dir + metadata via the existing helper
|
|
98
|
+
CP_DIR=$(CLAUDE_PROJECT_DIR="$PROJECT_DIR" "$PROJECT_DIR/.claude/hooks/checkpoint-helper.sh" dir 2>/dev/null)
|
|
99
|
+
[ -z "$CP_DIR" ] && CP_DIR="$PROJECT_DIR/uv-out/checkpoints/$SID" && mkdir -p "$CP_DIR"
|
|
100
|
+
|
|
101
|
+
# Build the mechanical checkpoint body
|
|
102
|
+
TS_FILE=$(date +%Y-%m-%d-%H%M)
|
|
103
|
+
TS_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
104
|
+
CP_FILE="$CP_DIR/auto-$TS_FILE-mechanical.md"
|
|
105
|
+
|
|
106
|
+
# Frontmatter from helper, augmented with checkpoint_kind
|
|
107
|
+
FRONT=$(CLAUDE_PROJECT_DIR="$PROJECT_DIR" "$PROJECT_DIR/.claude/hooks/checkpoint-helper.sh" frontmatter 2>/dev/null)
|
|
108
|
+
# Insert checkpoint_kind: auto-mechanical before closing ---
|
|
109
|
+
FRONT=$(printf '%s' "$FRONT" | awk '
|
|
110
|
+
/^---$/ { count++ }
|
|
111
|
+
count == 2 && !done { print "checkpoint_kind: auto-mechanical"; done=1 }
|
|
112
|
+
{ print }
|
|
113
|
+
')
|
|
114
|
+
|
|
115
|
+
# Activity summary from the log
|
|
116
|
+
ACTIVITY_SUMMARY=$(awk -v cutoff="$LAST_CP" '$1 > cutoff' "$ACTIVITY_LOG" 2>/dev/null | python3 -c '
|
|
117
|
+
import sys, collections, os
|
|
118
|
+
lines = [l.strip() for l in sys.stdin if l.strip()]
|
|
119
|
+
tools = collections.Counter()
|
|
120
|
+
files = collections.Counter()
|
|
121
|
+
for ln in lines:
|
|
122
|
+
parts = ln.split(" ", 2)
|
|
123
|
+
if len(parts) < 2: continue
|
|
124
|
+
tool = parts[1]
|
|
125
|
+
target = parts[2] if len(parts) > 2 else ""
|
|
126
|
+
tools[tool] += 1
|
|
127
|
+
if target and tool in ("Edit", "Write", "Read"):
|
|
128
|
+
files[target] += 1
|
|
129
|
+
print("### Tool calls")
|
|
130
|
+
for t, n in tools.most_common(8):
|
|
131
|
+
print(f"- {n}× {t}")
|
|
132
|
+
if files:
|
|
133
|
+
print("\n### Files touched")
|
|
134
|
+
for f, n in files.most_common(8):
|
|
135
|
+
print(f"- {f} ({n})")
|
|
136
|
+
print(f"\n_total tool calls in window: {sum(tools.values())}_")
|
|
137
|
+
' 2>/dev/null)
|
|
138
|
+
|
|
139
|
+
# Git state (best-effort, may be absent in non-git dirs)
|
|
140
|
+
GIT_BRANCH=$(cd "$PROJECT_DIR" && git branch --show-current 2>/dev/null)
|
|
141
|
+
GIT_STATUS=$(cd "$PROJECT_DIR" && git status --short 2>/dev/null | head -20)
|
|
142
|
+
GIT_LOG=$(cd "$PROJECT_DIR" && git log --oneline -5 2>/dev/null)
|
|
143
|
+
|
|
144
|
+
{
|
|
145
|
+
printf '%s\n\n' "$FRONT"
|
|
146
|
+
printf '# Auto-checkpoint (mechanical): %s\n\n' "$TS_ISO"
|
|
147
|
+
printf '_window: last %s min, %s tool calls_\n\n' "$INTERVAL_MIN" "$SINCE_LINES"
|
|
148
|
+
printf '## Activity\n\n%s\n\n' "$ACTIVITY_SUMMARY"
|
|
149
|
+
if [ -n "$GIT_BRANCH" ]; then
|
|
150
|
+
printf '## Git\n\n'
|
|
151
|
+
printf '**Branch:** %s\n\n' "$GIT_BRANCH"
|
|
152
|
+
if [ -n "$GIT_STATUS" ]; then
|
|
153
|
+
printf '**Status:**\n```\n%s\n```\n\n' "$GIT_STATUS"
|
|
154
|
+
else
|
|
155
|
+
printf '**Status:** clean\n\n'
|
|
156
|
+
fi
|
|
157
|
+
if [ -n "$GIT_LOG" ]; then
|
|
158
|
+
printf '**Recent commits:**\n```\n%s\n```\n' "$GIT_LOG"
|
|
159
|
+
fi
|
|
160
|
+
fi
|
|
161
|
+
} > "$CP_FILE"
|
|
162
|
+
|
|
163
|
+
# Update latest.md so /restore picks it up
|
|
164
|
+
cp "$CP_FILE" "$CP_DIR/latest.md" 2>/dev/null
|
|
165
|
+
echo "$NOW" > "$LAST_CP_FILE"
|
|
166
|
+
|
|
167
|
+
# Send AutoCheckpoint event to watchtower with inline content
|
|
168
|
+
if [ -x "$PROJECT_DIR/.claude/hooks/watchtower-send.sh" ]; then
|
|
169
|
+
PREVIEW=$(head -c 2000 "$CP_FILE")
|
|
170
|
+
PAYLOAD=$(PREVIEW_PATH="$CP_FILE" PREVIEW="$PREVIEW" INTERVAL_MIN="$INTERVAL_MIN" SINCE_LINES="$SINCE_LINES" CWD_VAR="$PROJECT_DIR" python3 -c '
|
|
171
|
+
import json, os
|
|
172
|
+
print(json.dumps({
|
|
173
|
+
"checkpoint_kind": "auto-mechanical",
|
|
174
|
+
"checkpoint_path": os.environ["PREVIEW_PATH"],
|
|
175
|
+
"checkpoint_preview": os.environ["PREVIEW"],
|
|
176
|
+
"interval_minutes": int(os.environ["INTERVAL_MIN"]),
|
|
177
|
+
"tool_calls_in_window": int(os.environ["SINCE_LINES"]),
|
|
178
|
+
"cwd": os.environ["CWD_VAR"],
|
|
179
|
+
}))
|
|
180
|
+
' 2>/dev/null)
|
|
181
|
+
printf '%s' "$PAYLOAD" | CLAUDE_PROJECT_DIR="$PROJECT_DIR" "$PROJECT_DIR/.claude/hooks/watchtower-send.sh" AutoCheckpoint 2>/dev/null
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
exit 0
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite helper: toggle confirm-mode or change the threshold.
|
|
3
|
+
# Used by the /confirm slash command. Extracted into a script so the
|
|
4
|
+
# slash command can avoid inline ${...} expansions, which Claude Code's
|
|
5
|
+
# permission heuristic flags as obfuscation.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# confirm-helper.sh # show status
|
|
9
|
+
# confirm-helper.sh status
|
|
10
|
+
# confirm-helper.sh on
|
|
11
|
+
# confirm-helper.sh off
|
|
12
|
+
# confirm-helper.sh <number> # set threshold
|
|
13
|
+
|
|
14
|
+
STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
|
|
15
|
+
mkdir -p "$STATE_DIR"
|
|
16
|
+
MODE_FILE="$STATE_DIR/confirm-mode.txt"
|
|
17
|
+
THRESH_FILE="$STATE_DIR/confirm-threshold.txt"
|
|
18
|
+
|
|
19
|
+
current_mode() { cat "$MODE_FILE" 2>/dev/null || echo "on"; }
|
|
20
|
+
current_thresh() { cat "$THRESH_FILE" 2>/dev/null || echo "50"; }
|
|
21
|
+
|
|
22
|
+
ARG=$(printf '%s' "$1" | tr -d '[:space:]')
|
|
23
|
+
|
|
24
|
+
case "$ARG" in
|
|
25
|
+
on)
|
|
26
|
+
echo "on" > "$MODE_FILE"
|
|
27
|
+
echo "Confirm mode: ON (threshold: $(current_thresh) words)"
|
|
28
|
+
;;
|
|
29
|
+
off)
|
|
30
|
+
echo "off" > "$MODE_FILE"
|
|
31
|
+
echo "Confirm mode: OFF"
|
|
32
|
+
;;
|
|
33
|
+
""|status)
|
|
34
|
+
echo "Confirm mode: $(current_mode) (threshold: $(current_thresh) words)"
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
if printf '%s' "$ARG" | grep -qE '^[0-9]+$'; then
|
|
38
|
+
echo "$ARG" > "$THRESH_FILE"
|
|
39
|
+
echo "Threshold set to $ARG words (mode: $(current_mode))"
|
|
40
|
+
else
|
|
41
|
+
echo "Usage: /confirm [on | off | <number> | status]"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite helper: explicit session termination.
|
|
3
|
+
# Used by the /session-end slash command.
|
|
4
|
+
#
|
|
5
|
+
# Writes a "terminated_at" timestamp to the session metadata, fires a
|
|
6
|
+
# SessionEnd event to the Watchtower so the dashboard updates the status
|
|
7
|
+
# badge, and prints a one-line confirmation.
|
|
8
|
+
|
|
9
|
+
STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
|
|
10
|
+
SESSIONS_DIR="$STATE_DIR/sessions"
|
|
11
|
+
mkdir -p "$SESSIONS_DIR"
|
|
12
|
+
|
|
13
|
+
SID="${UVS_SESSION_ID:-}"
|
|
14
|
+
if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
|
|
15
|
+
SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -z "$SID" ]; then
|
|
19
|
+
echo "No active UV Suite session to end."
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
META="$SESSIONS_DIR/$SID.json"
|
|
24
|
+
NOW=$(date +%s)
|
|
25
|
+
NOW_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
26
|
+
|
|
27
|
+
if [ -f "$META" ]; then
|
|
28
|
+
META_PATH="$META" NOW_VAL="$NOW" NOW_ISO_VAL="$NOW_ISO" python3 - <<'PY'
|
|
29
|
+
import json, os
|
|
30
|
+
p = os.environ["META_PATH"]
|
|
31
|
+
d = json.load(open(p))
|
|
32
|
+
d["terminated_at"] = int(os.environ["NOW_VAL"])
|
|
33
|
+
d["terminated_at_iso"] = os.environ["NOW_ISO_VAL"]
|
|
34
|
+
d["lifecycle"] = "terminated"
|
|
35
|
+
json.dump(d, open(p, "w"), indent=2)
|
|
36
|
+
PY
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Fire SessionEnd event so the dashboard flips the status badge to Terminated.
|
|
40
|
+
if [ -x "$CLAUDE_PROJECT_DIR/.claude/hooks/watchtower-send.sh" ]; then
|
|
41
|
+
PAYLOAD=$(SID_VAL="$SID" NOW_VAL="$NOW" CWD_VAL="${CLAUDE_PROJECT_DIR:-$(pwd)}" python3 -c '
|
|
42
|
+
import json, os
|
|
43
|
+
print(json.dumps({
|
|
44
|
+
"uvs_session_id": os.environ["SID_VAL"],
|
|
45
|
+
"session_id": os.environ["SID_VAL"],
|
|
46
|
+
"cwd": os.environ["CWD_VAL"],
|
|
47
|
+
"lifecycle": "terminated",
|
|
48
|
+
"terminated_at": int(os.environ["NOW_VAL"]),
|
|
49
|
+
"terminated_by": "user",
|
|
50
|
+
}))
|
|
51
|
+
')
|
|
52
|
+
printf '%s' "$PAYLOAD" | "$CLAUDE_PROJECT_DIR/.claude/hooks/watchtower-send.sh" SessionEnd 2>/dev/null
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo "Session ${SID:0:8} marked terminated at $NOW_ISO."
|
|
56
|
+
echo "Run /checkpoint first if you want a final state snapshot, then exit the terminal."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uv-suite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Portable framework for AI-assisted software development. 10 agents, 9 skills, 5 hooks, 4 personas. Works with Claude Code, Cursor, and Codex.",
|
|
5
5
|
"author": "Utsav Anand",
|
|
6
6
|
"license": "MIT",
|
package/personas/auto.json
CHANGED
|
@@ -37,14 +37,22 @@
|
|
|
37
37
|
"Bash(wc *)",
|
|
38
38
|
"Bash(head *)",
|
|
39
39
|
"Bash(tail *)",
|
|
40
|
-
"Bash(curl *)"
|
|
40
|
+
"Bash(curl *)",
|
|
41
|
+
"Bash(chmod *)",
|
|
42
|
+
"Bash(rm /tmp/*)",
|
|
43
|
+
"Bash(echo *)",
|
|
44
|
+
"Bash(printf *)",
|
|
45
|
+
"Bash(node --check *)",
|
|
46
|
+
"Bash(bash -n *)"
|
|
41
47
|
],
|
|
42
48
|
"deny": [
|
|
43
49
|
"Bash(rm -rf /)",
|
|
44
50
|
"Bash(rm -rf ~)",
|
|
45
51
|
"Bash(sudo rm -rf *)",
|
|
46
52
|
"Bash(git push --force * main)",
|
|
47
|
-
"Bash(git push --force * master)"
|
|
53
|
+
"Bash(git push --force * master)",
|
|
54
|
+
"Bash(rm -rf .)",
|
|
55
|
+
"Bash(sudo rm *)"
|
|
48
56
|
]
|
|
49
57
|
},
|
|
50
58
|
"hooks": {
|
|
@@ -155,6 +163,12 @@
|
|
|
155
163
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/watchtower-send.sh PostToolUse",
|
|
156
164
|
"timeout": 2,
|
|
157
165
|
"async": true
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"type": "command",
|
|
169
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-checkpoint.sh",
|
|
170
|
+
"timeout": 5,
|
|
171
|
+
"async": true
|
|
158
172
|
}
|
|
159
173
|
]
|
|
160
174
|
},
|
|
@@ -33,7 +33,16 @@
|
|
|
33
33
|
"Bash(npx prettier *)",
|
|
34
34
|
"Bash(pytest *)",
|
|
35
35
|
"Bash(go test *)",
|
|
36
|
-
"Bash(cargo test *)"
|
|
36
|
+
"Bash(cargo test *)",
|
|
37
|
+
"Bash(chmod *)",
|
|
38
|
+
"Bash(mkdir *)",
|
|
39
|
+
"Bash(rm /tmp/*)",
|
|
40
|
+
"Bash(cat *)",
|
|
41
|
+
"Bash(ls *)",
|
|
42
|
+
"Bash(echo *)",
|
|
43
|
+
"Bash(printf *)",
|
|
44
|
+
"Bash(node --check *)",
|
|
45
|
+
"Bash(bash -n *)"
|
|
37
46
|
],
|
|
38
47
|
"deny": [
|
|
39
48
|
"Bash(rm -rf /)",
|
|
@@ -42,7 +51,8 @@
|
|
|
42
51
|
"Bash(sudo rm *)",
|
|
43
52
|
"Bash(git push --force * main)",
|
|
44
53
|
"Bash(git push --force * master)",
|
|
45
|
-
"Bash(git reset --hard origin/*)"
|
|
54
|
+
"Bash(git reset --hard origin/*)",
|
|
55
|
+
"Bash(sudo rm -rf *)"
|
|
46
56
|
]
|
|
47
57
|
},
|
|
48
58
|
"hooks": {
|
|
@@ -164,6 +174,12 @@
|
|
|
164
174
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/watchtower-send.sh PostToolUse",
|
|
165
175
|
"timeout": 2,
|
|
166
176
|
"async": true
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"type": "command",
|
|
180
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-checkpoint.sh",
|
|
181
|
+
"timeout": 5,
|
|
182
|
+
"async": true
|
|
167
183
|
}
|
|
168
184
|
]
|
|
169
185
|
},
|
package/personas/spike.json
CHANGED
|
@@ -22,15 +22,30 @@
|
|
|
22
22
|
"Bash(find *)",
|
|
23
23
|
"Bash(wc *)",
|
|
24
24
|
"Bash(head *)",
|
|
25
|
-
"Bash(tail *)"
|
|
25
|
+
"Bash(tail *)",
|
|
26
|
+
"Edit(*)",
|
|
27
|
+
"Bash(git *)",
|
|
28
|
+
"Bash(npx prettier *)",
|
|
29
|
+
"Bash(npx markdown-* *)",
|
|
30
|
+
"Bash(curl *)",
|
|
31
|
+
"Bash(chmod *)",
|
|
32
|
+
"Bash(mkdir *)",
|
|
33
|
+
"Bash(rm /tmp/*)",
|
|
34
|
+
"Bash(cat *)",
|
|
35
|
+
"Bash(ls *)",
|
|
36
|
+
"Bash(echo *)",
|
|
37
|
+
"Bash(printf *)",
|
|
38
|
+
"Bash(node --check *)",
|
|
39
|
+
"Bash(bash -n *)"
|
|
26
40
|
],
|
|
27
41
|
"deny": [
|
|
28
|
-
"
|
|
29
|
-
"Bash(
|
|
30
|
-
"Bash(
|
|
31
|
-
"Bash(rm *)",
|
|
32
|
-
"Bash(
|
|
33
|
-
"Bash(
|
|
42
|
+
"Bash(rm -rf /)",
|
|
43
|
+
"Bash(rm -rf ~)",
|
|
44
|
+
"Bash(rm -rf .)",
|
|
45
|
+
"Bash(sudo rm *)",
|
|
46
|
+
"Bash(sudo rm -rf *)",
|
|
47
|
+
"Bash(git push --force * main)",
|
|
48
|
+
"Bash(git push --force * master)"
|
|
34
49
|
]
|
|
35
50
|
},
|
|
36
51
|
"hooks": {
|
|
@@ -123,6 +138,12 @@
|
|
|
123
138
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/watchtower-send.sh PostToolUse",
|
|
124
139
|
"timeout": 2,
|
|
125
140
|
"async": true
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"type": "command",
|
|
144
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-checkpoint.sh",
|
|
145
|
+
"timeout": 5,
|
|
146
|
+
"async": true
|
|
126
147
|
}
|
|
127
148
|
]
|
|
128
149
|
},
|
package/personas/sport.json
CHANGED
|
@@ -16,7 +16,25 @@
|
|
|
16
16
|
"Bash(npm *)",
|
|
17
17
|
"Bash(npx *)",
|
|
18
18
|
"Bash(node *)",
|
|
19
|
-
"Bash(git *)"
|
|
19
|
+
"Bash(git *)",
|
|
20
|
+
"Bash(chmod *)",
|
|
21
|
+
"Bash(mkdir *)",
|
|
22
|
+
"Bash(rm /tmp/*)",
|
|
23
|
+
"Bash(cat *)",
|
|
24
|
+
"Bash(ls *)",
|
|
25
|
+
"Bash(echo *)",
|
|
26
|
+
"Bash(printf *)",
|
|
27
|
+
"Bash(node --check *)",
|
|
28
|
+
"Bash(bash -n *)"
|
|
29
|
+
],
|
|
30
|
+
"deny": [
|
|
31
|
+
"Bash(rm -rf /)",
|
|
32
|
+
"Bash(rm -rf ~)",
|
|
33
|
+
"Bash(rm -rf .)",
|
|
34
|
+
"Bash(sudo rm *)",
|
|
35
|
+
"Bash(sudo rm -rf *)",
|
|
36
|
+
"Bash(git push --force * main)",
|
|
37
|
+
"Bash(git push --force * master)"
|
|
20
38
|
]
|
|
21
39
|
},
|
|
22
40
|
"hooks": {
|
|
@@ -109,6 +127,12 @@
|
|
|
109
127
|
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/watchtower-send.sh PostToolUse",
|
|
110
128
|
"timeout": 2,
|
|
111
129
|
"async": true
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"type": "command",
|
|
133
|
+
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-checkpoint.sh",
|
|
134
|
+
"timeout": 5,
|
|
135
|
+
"async": true
|
|
112
136
|
}
|
|
113
137
|
]
|
|
114
138
|
},
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auto-checkpoint
|
|
3
|
+
description: >
|
|
4
|
+
Toggle automatic checkpoints, or change how often they fire. When on,
|
|
5
|
+
UV Suite writes a checkpoint every N minutes (default 10): a mechanical
|
|
6
|
+
state snapshot from the PostToolUse hook, plus a semantic summary written
|
|
7
|
+
by Claude (via `claude -p --bare --model haiku`) from the Watchtower's
|
|
8
|
+
timer. Sessions with no activity in the window are skipped.
|
|
9
|
+
argument-hint: "[on|off|<minutes>|status]"
|
|
10
|
+
user-invocable: true
|
|
11
|
+
allowed-tools:
|
|
12
|
+
- Bash("$CLAUDE_PROJECT_DIR"/.claude/hooks/auto-checkpoint-helper.sh *)
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Apply /auto-checkpoint $ARGUMENTS
|
|
16
|
+
|
|
17
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/auto-checkpoint-helper.sh $ARGUMENTS`
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
|
|
21
|
+
Show the user the line of output above as the response — it confirms what
|
|
22
|
+
changed. Do not add commentary. The change applies to the very next interval;
|
|
23
|
+
no restart needed.
|
|
24
|
+
|
|
25
|
+
## What this controls
|
|
26
|
+
|
|
27
|
+
- `on` / `off` — enable or disable both tiers (mechanical + semantic).
|
|
28
|
+
- `<minutes>` — set the checkpoint interval (1-1440). Default 10.
|
|
29
|
+
- `status` (or no argument) — print the current mode and interval.
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
- **Tier A (mechanical):** the `auto-checkpoint.sh` hook runs after each tool
|
|
34
|
+
call. When the interval has passed and there's been activity since the last
|
|
35
|
+
checkpoint, it writes a deterministic snapshot — git state, recent tool calls,
|
|
36
|
+
files touched — to `uv-out/checkpoints/<sid>/auto-<ts>-mechanical.md`.
|
|
37
|
+
- **Tier B (semantic):** the Watchtower process (`uvs watch`) keeps a timer.
|
|
38
|
+
Every N minutes, for each active session, it shells out to `claude -p --bare
|
|
39
|
+
--model haiku` with a prompt assembled from the recent dashboard events and
|
|
40
|
+
git state. Output is saved next to the mechanical checkpoint as
|
|
41
|
+
`auto-<ts>-semantic.md`. Cap: `--max-budget-usd 0.05` per call.
|
|
42
|
+
- Both tiers fire `AutoCheckpoint` events to the Watchtower so they show up as
|
|
43
|
+
distinct rows on the dashboard.
|
|
44
|
+
- Sessions with zero activity in the interval are skipped — no empty checkpoints.
|
|
45
|
+
|
|
46
|
+
State lives in `.uv-suite-state/auto-checkpoint.json` (mode + interval) and
|
|
47
|
+
`.uv-suite-state/sessions/<sid>.last-{mechanical,semantic}-checkpoint.txt`.
|
package/skills/confirm/SKILL.md
CHANGED
|
@@ -7,15 +7,12 @@ description: >
|
|
|
7
7
|
argument-hint: "[on|off|<number>|status]"
|
|
8
8
|
user-invocable: true
|
|
9
9
|
allowed-tools:
|
|
10
|
-
- Bash(
|
|
11
|
-
- Bash(echo *)
|
|
12
|
-
- Bash(cat *)
|
|
13
|
-
- Bash(printf *)
|
|
10
|
+
- Bash("$CLAUDE_PROJECT_DIR"/.claude/hooks/confirm-helper.sh *)
|
|
14
11
|
---
|
|
15
12
|
|
|
16
13
|
## Apply /confirm $ARGUMENTS
|
|
17
14
|
|
|
18
|
-
!`
|
|
15
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/confirm-helper.sh $ARGUMENTS`
|
|
19
16
|
|
|
20
17
|
## Instructions
|
|
21
18
|
|
|
@@ -31,5 +28,5 @@ next user prompt; no restart needed.
|
|
|
31
28
|
step. Slash commands (`/foo ...`) are always exempt.
|
|
32
29
|
- `status` (or no argument) — print the current mode and threshold.
|
|
33
30
|
|
|
34
|
-
State lives in
|
|
35
|
-
|
|
31
|
+
State lives in `.uv-suite-state/confirm-mode.txt` and `.uv-suite-state/confirm-threshold.txt`
|
|
32
|
+
under `$CLAUDE_PROJECT_DIR`. Defaults if missing: mode `on`, threshold `50`.
|