uv-suite 0.26.2 → 0.26.3
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/checkpoint-helper.sh +124 -0
- package/package.json +1 -1
- package/skills/checkpoint/SKILL.md +29 -16
- package/skills/restore/SKILL.md +37 -7
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite helper: locate per-session checkpoint paths and print metadata.
|
|
3
|
+
# Used by the /checkpoint and /restore slash commands.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# checkpoint-helper.sh dir # ensure + print the dir for current session
|
|
7
|
+
# checkpoint-helper.sh meta # print session metadata as shell-eval'able lines
|
|
8
|
+
# checkpoint-helper.sh frontmatter # YAML frontmatter to embed at the top of a checkpoint
|
|
9
|
+
# checkpoint-helper.sh latest # cat the latest checkpoint for current session (with fallback)
|
|
10
|
+
# checkpoint-helper.sh list # list all sessions that have checkpoints, newest first
|
|
11
|
+
|
|
12
|
+
resolve_paths() {
|
|
13
|
+
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
14
|
+
STATE_DIR="$PROJECT_DIR/.uv-suite-state"
|
|
15
|
+
SID="${UVS_SESSION_ID:-}"
|
|
16
|
+
if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
|
|
17
|
+
SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
|
|
18
|
+
fi
|
|
19
|
+
CHECKPOINTS_ROOT="$PROJECT_DIR/uv-out/checkpoints"
|
|
20
|
+
SESSION_CP_DIR=""
|
|
21
|
+
[ -n "$SID" ] && SESSION_CP_DIR="$CHECKPOINTS_ROOT/$SID"
|
|
22
|
+
META_FILE=""
|
|
23
|
+
[ -n "$SID" ] && META_FILE="$STATE_DIR/sessions/$SID.json"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
print_meta_field() {
|
|
27
|
+
# $1 = field name; reads from $META_FILE; empty if missing
|
|
28
|
+
[ -z "$META_FILE" ] || [ ! -f "$META_FILE" ] && { echo ""; return; }
|
|
29
|
+
if command -v jq >/dev/null 2>&1; then
|
|
30
|
+
jq -r --arg k "$1" '.[$k] // ""' "$META_FILE" 2>/dev/null
|
|
31
|
+
else
|
|
32
|
+
grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "$META_FILE" | head -1 | sed "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\(.*\)\"/\1/"
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
resolve_paths
|
|
37
|
+
|
|
38
|
+
case "$1" in
|
|
39
|
+
dir)
|
|
40
|
+
if [ -n "$SESSION_CP_DIR" ]; then
|
|
41
|
+
mkdir -p "$SESSION_CP_DIR"
|
|
42
|
+
echo "$SESSION_CP_DIR"
|
|
43
|
+
else
|
|
44
|
+
mkdir -p "$CHECKPOINTS_ROOT"
|
|
45
|
+
echo "$CHECKPOINTS_ROOT"
|
|
46
|
+
fi
|
|
47
|
+
;;
|
|
48
|
+
meta)
|
|
49
|
+
echo "uvs_session_id=${SID:-}"
|
|
50
|
+
echo "session_name=$(print_meta_field name)"
|
|
51
|
+
echo "session_kind=$(print_meta_field kind)"
|
|
52
|
+
echo "session_purpose=$(print_meta_field purpose)"
|
|
53
|
+
echo "session_priority=$(print_meta_field priority)"
|
|
54
|
+
echo "persona=$(print_meta_field persona)"
|
|
55
|
+
;;
|
|
56
|
+
frontmatter)
|
|
57
|
+
NAME=$(print_meta_field name)
|
|
58
|
+
KIND=$(print_meta_field kind)
|
|
59
|
+
PURPOSE=$(print_meta_field purpose)
|
|
60
|
+
PRIORITY=$(print_meta_field priority)
|
|
61
|
+
PERSONA=$(print_meta_field persona)
|
|
62
|
+
NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
63
|
+
cat <<EOF
|
|
64
|
+
---
|
|
65
|
+
uvs_session_id: ${SID:-}
|
|
66
|
+
session_name: ${NAME}
|
|
67
|
+
session_kind: ${KIND}
|
|
68
|
+
session_purpose: ${PURPOSE}
|
|
69
|
+
session_priority: ${PRIORITY}
|
|
70
|
+
persona: ${PERSONA}
|
|
71
|
+
checkpoint_at: ${NOW}
|
|
72
|
+
---
|
|
73
|
+
EOF
|
|
74
|
+
;;
|
|
75
|
+
latest)
|
|
76
|
+
if [ -n "$SESSION_CP_DIR" ] && [ -f "$SESSION_CP_DIR/latest.md" ]; then
|
|
77
|
+
cat "$SESSION_CP_DIR/latest.md"
|
|
78
|
+
elif [ -f "$CHECKPOINTS_ROOT/latest.md" ]; then
|
|
79
|
+
echo "(no per-session checkpoint for ${SID:-this session}; showing legacy global latest.md)"
|
|
80
|
+
echo
|
|
81
|
+
cat "$CHECKPOINTS_ROOT/latest.md"
|
|
82
|
+
else
|
|
83
|
+
echo "No checkpoint found at $CHECKPOINTS_ROOT. Run /checkpoint to create one."
|
|
84
|
+
fi
|
|
85
|
+
;;
|
|
86
|
+
list)
|
|
87
|
+
[ ! -d "$CHECKPOINTS_ROOT" ] && { echo "No checkpoints directory at $CHECKPOINTS_ROOT"; exit 0; }
|
|
88
|
+
found=0
|
|
89
|
+
for d in "$CHECKPOINTS_ROOT"/*/; do
|
|
90
|
+
[ -d "$d" ] || continue
|
|
91
|
+
cp_sid=$(basename "$d")
|
|
92
|
+
cp_meta="$STATE_DIR/sessions/$cp_sid.json"
|
|
93
|
+
cp_name=""
|
|
94
|
+
cp_priority=""
|
|
95
|
+
if [ -f "$cp_meta" ]; then
|
|
96
|
+
if command -v jq >/dev/null 2>&1; then
|
|
97
|
+
cp_name=$(jq -r '.name // ""' "$cp_meta" 2>/dev/null)
|
|
98
|
+
cp_priority=$(jq -r '.priority // ""' "$cp_meta" 2>/dev/null)
|
|
99
|
+
else
|
|
100
|
+
cp_name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$cp_meta" | head -1 | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\(.*\)"/\1/')
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
latest=$(ls -t "$d"*.md 2>/dev/null | head -1)
|
|
104
|
+
[ -z "$latest" ] && continue
|
|
105
|
+
ts=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' "$latest" 2>/dev/null || stat -c '%y' "$latest" 2>/dev/null | cut -c1-16)
|
|
106
|
+
label="${cp_name:-(unlabeled)}"
|
|
107
|
+
[ -n "$cp_priority" ] && label="$label [p:$cp_priority]"
|
|
108
|
+
mark=" "
|
|
109
|
+
[ "$cp_sid" = "$SID" ] && mark="*"
|
|
110
|
+
echo "$mark ${cp_sid:0:8} $ts $label"
|
|
111
|
+
found=1
|
|
112
|
+
done
|
|
113
|
+
[ "$found" -eq 0 ] && echo "No per-session checkpoints yet (current session: ${SID:-none})"
|
|
114
|
+
# Note legacy global checkpoint if present
|
|
115
|
+
if [ -f "$CHECKPOINTS_ROOT/latest.md" ]; then
|
|
116
|
+
ts=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' "$CHECKPOINTS_ROOT/latest.md" 2>/dev/null || stat -c '%y' "$CHECKPOINTS_ROOT/latest.md" 2>/dev/null | cut -c1-16)
|
|
117
|
+
echo " legacy $ts (pre-metadata global latest.md)"
|
|
118
|
+
fi
|
|
119
|
+
;;
|
|
120
|
+
*)
|
|
121
|
+
echo "Usage: checkpoint-helper.sh [dir|meta|frontmatter|latest|list]"
|
|
122
|
+
exit 1
|
|
123
|
+
;;
|
|
124
|
+
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uv-suite",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.3",
|
|
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",
|
|
@@ -3,7 +3,8 @@ name: checkpoint
|
|
|
3
3
|
description: >
|
|
4
4
|
Save a checkpoint of the current session — what was done, key decisions, current state,
|
|
5
5
|
and what's next. Use before ending a session, before /compact, or at any natural breakpoint.
|
|
6
|
-
|
|
6
|
+
Checkpoints are stored per-session under uv-out/checkpoints/<session-id>/, so concurrent
|
|
7
|
+
terminals don't clobber each other. /restore picks up the latest for the current session.
|
|
7
8
|
argument-hint: "[optional-label]"
|
|
8
9
|
user-invocable: true
|
|
9
10
|
allowed-tools:
|
|
@@ -12,39 +13,51 @@ allowed-tools:
|
|
|
12
13
|
- Bash(git status *)
|
|
13
14
|
- Bash(git diff *)
|
|
14
15
|
- Bash(git log *)
|
|
16
|
+
- Bash(git branch *)
|
|
15
17
|
- Bash(git rev-parse *)
|
|
16
18
|
- Bash(date *)
|
|
17
19
|
- Bash(ls *)
|
|
18
20
|
- Bash(mkdir *)
|
|
21
|
+
- Bash(cat *)
|
|
19
22
|
- Bash(echo *)
|
|
23
|
+
- Bash("$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh *)
|
|
20
24
|
---
|
|
21
25
|
|
|
22
|
-
## Resolve checkpoint directory
|
|
26
|
+
## Resolve session and checkpoint directory
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
working dir as a last resort) so `/checkpoint` and `/restore` always agree, no matter
|
|
26
|
-
which subdirectory the session was launched from.
|
|
27
|
-
|
|
28
|
-
!`DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/uv-out/checkpoints"; mkdir -p "$DIR"; echo "$DIR"`
|
|
28
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh dir`
|
|
29
29
|
|
|
30
30
|
Use the absolute path printed above as `<checkpoint-dir>` for every file path below.
|
|
31
|
+
The directory is per-session — two `uv` launches in the same repo write to
|
|
32
|
+
different folders, so checkpoints don't collide.
|
|
33
|
+
|
|
34
|
+
## Session metadata
|
|
35
|
+
|
|
36
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh meta`
|
|
37
|
+
|
|
38
|
+
## Frontmatter to embed at the top of the checkpoint
|
|
39
|
+
|
|
40
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh frontmatter`
|
|
31
41
|
|
|
32
42
|
## Write a checkpoint
|
|
33
43
|
|
|
34
|
-
Write a file named `<checkpoint-dir>/YYYY-MM-DD-HHMM.md` using the current
|
|
44
|
+
Write a file named `<checkpoint-dir>/YYYY-MM-DD-HHMM.md` using the current
|
|
45
|
+
timestamp. **Begin the file with the YAML frontmatter block printed above
|
|
46
|
+
exactly as shown** — `/restore` parses these fields when picking which
|
|
47
|
+
checkpoint to load.
|
|
35
48
|
|
|
36
|
-
Also write/overwrite `<checkpoint-dir>/latest.md` with the same content,
|
|
37
|
-
session's `/restore` always finds the freshest state
|
|
49
|
+
Also write/overwrite `<checkpoint-dir>/latest.md` with the same content,
|
|
50
|
+
so the next session's `/restore` always finds the freshest state for this
|
|
51
|
+
session.
|
|
38
52
|
|
|
39
53
|
## Label
|
|
40
54
|
|
|
41
55
|
$ARGUMENTS
|
|
42
56
|
|
|
43
|
-
If a label was provided, include it in the filename:
|
|
44
|
-
|
|
45
|
-
## What to capture
|
|
57
|
+
If a label was provided, include it in the filename:
|
|
58
|
+
`<checkpoint-dir>/YYYY-MM-DD-HHMM-[label].md`
|
|
46
59
|
|
|
47
|
-
|
|
60
|
+
## Body structure (after the frontmatter)
|
|
48
61
|
|
|
49
62
|
```markdown
|
|
50
63
|
# Checkpoint: [date] [time] [label if provided]
|
|
@@ -88,5 +101,5 @@ Review the full conversation so far and write a structured checkpoint with these
|
|
|
88
101
|
|
|
89
102
|
- Be specific. "Worked on auth" is useless. "Added JWT refresh token rotation with 7-day expiry" is useful.
|
|
90
103
|
- Capture WHY decisions were made, not just what. The next session needs the rationale.
|
|
91
|
-
- Keep
|
|
92
|
-
-
|
|
104
|
+
- Keep the body under 80 lines. The frontmatter is required and not counted.
|
|
105
|
+
- Always include the YAML frontmatter — `/restore` reads it to pick the right checkpoint and to display session context.
|
package/skills/restore/SKILL.md
CHANGED
|
@@ -1,25 +1,55 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: restore
|
|
3
3
|
description: >
|
|
4
|
-
Restore the latest checkpoint
|
|
5
|
-
key decisions, current state, and what's next.
|
|
4
|
+
Restore the latest checkpoint for the current session — shows what was done,
|
|
5
|
+
key decisions, current state, and what's next. With no arguments, picks the
|
|
6
|
+
current `UVS_SESSION_ID`'s most recent checkpoint. Pass a session id prefix
|
|
7
|
+
or name to restore from a different session.
|
|
8
|
+
argument-hint: "[<session-id-prefix> | <session-name> | list]"
|
|
6
9
|
user-invocable: true
|
|
7
10
|
allowed-tools:
|
|
8
11
|
- Read(*)
|
|
9
12
|
- Bash(ls *)
|
|
10
13
|
- Bash(cat *)
|
|
11
14
|
- Bash(grep *)
|
|
15
|
+
- Bash(find *)
|
|
12
16
|
- Bash(git rev-parse *)
|
|
17
|
+
- Bash("$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh *)
|
|
13
18
|
---
|
|
14
19
|
|
|
15
|
-
##
|
|
20
|
+
## Available sessions with checkpoints
|
|
16
21
|
|
|
17
|
-
!`
|
|
22
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh list`
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
(`*` marks the current session.)
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
## Latest checkpoint for the current session
|
|
27
|
+
|
|
28
|
+
!`"$CLAUDE_PROJECT_DIR"/.claude/hooks/checkpoint-helper.sh latest`
|
|
29
|
+
|
|
30
|
+
## Argument
|
|
31
|
+
|
|
32
|
+
$ARGUMENTS
|
|
22
33
|
|
|
23
34
|
## Instructions
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
1. **If `$ARGUMENTS` is empty or "latest"**: read the checkpoint shown above (the
|
|
37
|
+
current session's `latest.md`). Summarize it in 3-4 sentences: what was
|
|
38
|
+
done, current state, what's next. Then ask: "Ready to pick up from here, or
|
|
39
|
+
do you want to take a different direction?"
|
|
40
|
+
|
|
41
|
+
2. **If `$ARGUMENTS` is "list"**: just show the user the available-sessions
|
|
42
|
+
list above and ask which one they want to restore.
|
|
43
|
+
|
|
44
|
+
3. **If `$ARGUMENTS` looks like a session id prefix** (8-char hex / UUID-ish)
|
|
45
|
+
**or a session name**: match it against the list above. Read the
|
|
46
|
+
matching session's `latest.md` from
|
|
47
|
+
`<project>/uv-out/checkpoints/<full-session-id>/latest.md` using the Read
|
|
48
|
+
tool, then summarize as in (1).
|
|
49
|
+
|
|
50
|
+
4. If no match is found, list the available sessions and ask the user to
|
|
51
|
+
pick one.
|
|
52
|
+
|
|
53
|
+
When summarizing, include the session's name and purpose from the
|
|
54
|
+
frontmatter at the top of the checkpoint — that's the context the next
|
|
55
|
+
session needs to know what it's picking up.
|