uv-suite 0.26.4 → 0.27.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/README.md +110 -86
- package/hooks/auto-checkpoint-helper.sh +73 -0
- package/hooks/auto-checkpoint.sh +184 -0
- package/hooks/confirm-helper.sh +45 -0
- package/hooks/confirm-prompt.sh +13 -2
- 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/watchtower/auto-checkpoint-prompt.md +51 -0
- package/watchtower/auto-checkpoint-runner.js +285 -0
- package/watchtower/dashboard.html +76 -8
- package/watchtower/server.js +21 -0
package/README.md
CHANGED
|
@@ -1,34 +1,70 @@
|
|
|
1
1
|
# UV Suite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A portable layer that turns Claude Code, Cursor, or Codex into a labeled, observable, anti-slop dev environment — with named sessions, a real-time observability dashboard, anti-slop guardrails, and per-session memory across launches.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g uv-suite
|
|
9
|
-
|
|
9
|
+
uvs claude pro # auto-installs into the current project on first launch
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Or with npx:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npx uv-suite install
|
|
15
|
+
npx uv-suite install # explicit install only
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
> **Note:** The CLI is `uvs`, not `uv`. The name `uv` belongs to Astral's Python package manager and likely already exists on your system.
|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
uvs claude pro # Claude Code, Professional persona
|
|
24
|
+
uvs codex auto # Codex, Auto persona
|
|
25
|
+
uvs pro # Shorthand for uvs claude pro
|
|
26
|
+
uvs install # Explicit install (also runs automatically on launch)
|
|
27
|
+
uvs watch # Open the Watchtower observability dashboard
|
|
28
|
+
uvs info # Show what's installed
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
On every `uvs` launch, you'll be prompted to label the session:
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
```
|
|
34
|
+
Label this session (Enter to skip — you'll be reminded):
|
|
35
|
+
name: payments retry refactor
|
|
36
|
+
kind [long/outcome]: outcome
|
|
37
|
+
purpose: ship retry-on-5xx for the Stripe webhook handler
|
|
38
|
+
priority [low/med/high]: high
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Skip any field with Enter. If you skip the name, `/session-init` will be suggested every few prompts until you label it. Set `UVS_NO_PROMPT=1` to suppress prompts entirely.
|
|
42
|
+
|
|
43
|
+
## Sessions and Watchtower
|
|
44
|
+
|
|
45
|
+
Each `uvs` launch generates a `UVS_SESSION_ID` and writes metadata to `.uv-suite-state/sessions/<id>.json`. This unlocks:
|
|
46
|
+
|
|
47
|
+
- **Concurrent terminals don't collide.** Two `uvs` launches in the same repo run as distinct sessions with separate names, checkpoints, and dashboard rows.
|
|
48
|
+
- **`uvs watch` shows them all.** The Watchtower dashboard at `localhost:4200` streams every tool call across every session in real time — labeled by your name, sorted by priority (high to top, low dimmed), color-coded by persona.
|
|
49
|
+
- **Per-session checkpoints.** `/checkpoint` writes to `uv-out/checkpoints/<sid>/`, and `/restore` auto-picks the current session's latest. Pass a session id prefix or name to restore from a different one.
|
|
50
|
+
- **Status line shows it all.** The Claude Code status bar shows session name, persona, priority, and elapsed time continuously.
|
|
51
|
+
|
|
52
|
+
### Watchtower at a glance
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
Sessions Events Tool calls Errors Need human
|
|
56
|
+
4 1,247 914 2 0
|
|
57
|
+
|
|
58
|
+
[payments retry [auto] [P:high] [outcome] ] (147)
|
|
59
|
+
[infra cleanup [pro] [P:med] [long-running] ] (382)
|
|
60
|
+
[exec deck [spike] [P:low] [outcome] ] (89)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Hooks fire on every Claude Code event (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `Stop`, `PermissionRequest`, ...) and forward to the dashboard with the session metadata merged in. Zero dependencies — vanilla Node + SSE.
|
|
64
|
+
|
|
65
|
+
## Personas
|
|
66
|
+
|
|
67
|
+
Four modes for different contexts. Pick one when you start a session.
|
|
32
68
|
|
|
33
69
|
```
|
|
34
70
|
Spike Sport Professional Auto
|
|
@@ -43,12 +79,7 @@ Writes New files Anything Anything Anything
|
|
|
43
79
|
only (reviewed) (autonomous)
|
|
44
80
|
Edits Blocked Allowed Allowed Allowed
|
|
45
81
|
|
|
46
|
-
Hooks 1 1 8 3
|
|
47
|
-
doc-slop lint all lint, block,
|
|
48
|
-
timer
|
|
49
|
-
|
|
50
82
|
Guardrails Doc slop None All 6 All 6
|
|
51
|
-
|
|
52
83
|
Human gates After each End only Every Act Final output
|
|
53
84
|
map boundary only
|
|
54
85
|
```
|
|
@@ -56,7 +87,7 @@ Human gates After each End only Every Act Final output
|
|
|
56
87
|
### When to use what
|
|
57
88
|
|
|
58
89
|
| Situation | Mode | Why |
|
|
59
|
-
|
|
90
|
+
|---|---|---|
|
|
60
91
|
| Joining a new codebase | **Spike** | Understand before changing. Writes docs, not code. |
|
|
61
92
|
| Architecture review | **Spike** | Map the system, write ADRs, document findings. |
|
|
62
93
|
| Prototyping a demo | **Sport** | Move fast, iterate freely, quality comes later. |
|
|
@@ -71,121 +102,114 @@ Human gates After each End only Every Act Final output
|
|
|
71
102
|
- Spike → Auto (research thoroughly, then let the agent execute)
|
|
72
103
|
- Sport → Professional (prototype fast, switch to rigor when it matters)
|
|
73
104
|
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
| Category | Count | What |
|
|
77
|
-
|----------|-------|------|
|
|
78
|
-
| Agents | 10 | Subagent definitions for Claude Code, Cursor, and Codex |
|
|
79
|
-
| Skills | 10 | Slash commands with dynamic context injection |
|
|
80
|
-
| Hooks | 8 | Auto-lint, slop check, danger zones, session tracking, destructive blocks |
|
|
81
|
-
| Guardrails | 6 | Anti-slop rules (comments, overengineering, tests, docs, architecture, errors) |
|
|
82
|
-
| Personas | 4 | Spike, Sport, Professional, Auto |
|
|
83
|
-
|
|
84
|
-
## Three Subsystems
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
UV Index UV Acts UV Guard
|
|
88
|
-
Understand Build Review
|
|
89
|
-
Learn Deliver Harden
|
|
90
|
-
Remember Present Protect
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**UV Index** maps codebases using [Graphify](https://github.com/safishamsi/graphify) knowledge graphs, captures context, builds persistent memory.
|
|
94
|
-
|
|
95
|
-
**UV Acts** delivers software in sequential phases (Acts) with parallel tasks, human-in-the-loop cycle budgets, and spec-driven development.
|
|
96
|
-
|
|
97
|
-
**UV Guard** catches AI slop in real time, reviews code for security (OWASP, [Semgrep](https://github.com/semgrep/semgrep)), and enforces danger zones.
|
|
98
|
-
|
|
99
|
-
## Skills
|
|
105
|
+
## Skills (slash commands)
|
|
100
106
|
|
|
101
107
|
| Command | What it does |
|
|
102
|
-
|
|
108
|
+
|---|---|
|
|
103
109
|
| `/map-codebase [dir]` | Build a knowledge graph of the codebase |
|
|
104
110
|
| `/map-stack [dir]` | Map multiple services and their connections |
|
|
105
111
|
| `/spec [requirements]` | Write a technical specification |
|
|
106
112
|
| `/architect [spec]` | Design architecture, decompose into Acts |
|
|
107
|
-
| `/
|
|
113
|
+
| `/prototype [concept]` | Build a static React prototype |
|
|
108
114
|
| `/write-tests [file]` | Generate tests matching project conventions |
|
|
109
115
|
| `/write-evals [prompt]` | Write AI/LLM evaluation cases ([DeepEval](https://github.com/confident-ai/deepeval) compatible) |
|
|
116
|
+
| `/review` | Code review: correctness, security, performance, slop |
|
|
110
117
|
| `/slop-check` | Detect 6 categories of AI-generated slop |
|
|
111
|
-
| `/prototype [concept]` | Build a static React prototype |
|
|
112
118
|
| `/security-review` | OWASP audit, dependency scan, secret detection |
|
|
119
|
+
| `/investigate` | Systematic root-cause debugging |
|
|
120
|
+
| `/commit` | Review → test → slop-check → commit (and optionally PR) |
|
|
121
|
+
| `/checkpoint [label]` | Save session state to `uv-out/checkpoints/<sid>/` |
|
|
122
|
+
| `/restore [sid-prefix\|name]` | Load the latest checkpoint for the current (or named) session |
|
|
123
|
+
| `/session-init [name\|--kind\|--purpose\|--priority]` | Label or relabel the current session |
|
|
124
|
+
| `/confirm [on\|off\|<n>]` | Toggle reframe-and-confirm for prompts over `<n>` words |
|
|
125
|
+
| `/uv-help` | List every skill, agent, hook, guardrail, and persona |
|
|
113
126
|
|
|
114
|
-
## Hooks
|
|
127
|
+
## Hooks (lifecycle automation)
|
|
115
128
|
|
|
116
|
-
Fire automatically. You never invoke these.
|
|
129
|
+
Fire automatically on Claude Code events. You never invoke these.
|
|
117
130
|
|
|
118
131
|
| Hook | Fires on | What it does |
|
|
119
|
-
|
|
132
|
+
|---|---|---|
|
|
120
133
|
| auto-lint | File write | Runs prettier, ruff, or gofmt |
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
134
|
+
| slop-grep | File write | Greps for obvious slop patterns (over-commented code, vague docs) |
|
|
135
|
+
| doc-slop-grep | File write | Catches vague adjectives in markdown |
|
|
136
|
+
| danger-zone-check | File edit | Warns if file is in DANGER-ZONES.md |
|
|
137
|
+
| block-destructive | Bash command | Blocks `rm -rf /`, force push to main, `DROP TABLE` |
|
|
138
|
+
| confirm-prompt | UserPromptSubmit | For prompts over the threshold, requires Claude to restate before any work starts |
|
|
139
|
+
| session-label-nag | UserPromptSubmit | Reminds you to run `/session-init` every Nth prompt while the session has no name |
|
|
140
|
+
| context-warning | PostToolUse | Warns when context usage crosses thresholds |
|
|
141
|
+
| watchtower-send | All events | Forwards every event (with session metadata) to `localhost:4200` |
|
|
142
|
+
| session-start | SessionStart | Records start time, fires bootstrap event with session metadata |
|
|
143
|
+
| session-timer | PostToolUse | Reminders at 45 / 90 / 180 minutes |
|
|
144
|
+
| session-end | Stop | Shows duration, today's total, reflection prompt |
|
|
145
|
+
| session-review-reminder | Stop | Nudges you to review uncommitted changes |
|
|
146
|
+
| status-line | Continuous | Renders session label, persona, priority, and timer in the Claude Code status bar |
|
|
128
147
|
|
|
129
148
|
## Agents
|
|
130
149
|
|
|
131
|
-
10 agents, each in 4 formats (Claude Code, Cursor, Codex,
|
|
150
|
+
10 agents, each in 4 formats (Claude Code, Cursor, Codex, portable):
|
|
132
151
|
|
|
133
152
|
| Agent | Subsystem | Model | Cycle Budget |
|
|
134
|
-
|
|
135
|
-
| Cartographer |
|
|
136
|
-
| Spec Writer |
|
|
137
|
-
| Architect |
|
|
138
|
-
| Reviewer |
|
|
139
|
-
| Test Writer |
|
|
140
|
-
| Eval Writer |
|
|
141
|
-
| Anti-Slop Guard |
|
|
142
|
-
| Prototype Builder |
|
|
143
|
-
| DevOps |
|
|
144
|
-
| Security |
|
|
153
|
+
|---|---|---|---|
|
|
154
|
+
| Cartographer | Index | Opus | 1 |
|
|
155
|
+
| Spec Writer | Acts | Opus | 1 |
|
|
156
|
+
| Architect | Acts | Opus | 2 |
|
|
157
|
+
| Reviewer | Guard | Opus | 1 |
|
|
158
|
+
| Test Writer | Acts | Sonnet | 3 |
|
|
159
|
+
| Eval Writer | Acts | Opus | 2 |
|
|
160
|
+
| Anti-Slop Guard | Guard | Opus | 1 |
|
|
161
|
+
| Prototype Builder | Acts | Sonnet | 3 |
|
|
162
|
+
| DevOps | Acts | Opus | 2 |
|
|
163
|
+
| Security | Guard | Opus | 1 |
|
|
145
164
|
|
|
146
165
|
## Artifacts
|
|
147
166
|
|
|
148
167
|
Agents write persistent output to `uv-out/`. Each agent reads prior artifacts automatically.
|
|
149
168
|
|
|
150
|
-
|
|
|
151
|
-
|
|
169
|
+
| Output | Read by |
|
|
170
|
+
|---|---|
|
|
152
171
|
| `uv-out/map-codebase.md` | /architect, /review, /security-review |
|
|
153
172
|
| `uv-out/specs/*.md` | /architect, /write-tests, /write-evals |
|
|
154
173
|
| `uv-out/architecture/*.md` | /review, /write-tests, /slop-check |
|
|
155
174
|
| `uv-out/review-*.md` | /slop-check, /security-review |
|
|
175
|
+
| `uv-out/checkpoints/<sid>/*.md` | /restore |
|
|
156
176
|
|
|
157
177
|
## Integrations
|
|
158
178
|
|
|
159
179
|
| Tool | Used by | Purpose |
|
|
160
|
-
|
|
180
|
+
|---|---|---|
|
|
161
181
|
| [Graphify](https://github.com/safishamsi/graphify) | Cartographer | Knowledge graph from codebase via Tree-sitter |
|
|
162
|
-
| [Semgrep](https://github.com/semgrep/semgrep) | Security
|
|
163
|
-
| [Gitleaks](https://github.com/gitleaks/gitleaks) | Security
|
|
164
|
-
| [Trivy](https://github.com/aquasecurity/trivy) | Security
|
|
182
|
+
| [Semgrep](https://github.com/semgrep/semgrep) | Security | SAST with 4000+ OWASP-mapped rules |
|
|
183
|
+
| [Gitleaks](https://github.com/gitleaks/gitleaks) | Security | Secret detection in git repos |
|
|
184
|
+
| [Trivy](https://github.com/aquasecurity/trivy) | Security | Dependency vulnerability scanning |
|
|
165
185
|
| [DeepEval](https://github.com/confident-ai/deepeval) | Eval Writer | Pytest-compatible LLM evaluation |
|
|
166
|
-
| [Playwright](https://playwright.dev/docs/getting-started-mcp) | Prototype
|
|
186
|
+
| [Playwright](https://playwright.dev/docs/getting-started-mcp) | Prototype, Test Writer | Browser automation and e2e testing |
|
|
167
187
|
|
|
168
188
|
## Project Structure After Install
|
|
169
189
|
|
|
170
190
|
```
|
|
171
191
|
.claude/
|
|
172
|
-
settings.json
|
|
173
|
-
agents/
|
|
174
|
-
skills/
|
|
175
|
-
hooks/
|
|
176
|
-
rules/
|
|
177
|
-
personas/
|
|
178
|
-
.codex/agents/
|
|
179
|
-
.cursor/rules/
|
|
180
|
-
AGENTS.md
|
|
181
|
-
DANGER-ZONES.md
|
|
182
|
-
uv-
|
|
192
|
+
settings.json Permissions and hooks (seeded from your persona on first install)
|
|
193
|
+
agents/ 10 agent definitions
|
|
194
|
+
skills/ 17 slash commands
|
|
195
|
+
hooks/ 14 hook scripts + 2 helpers
|
|
196
|
+
rules/ 6 anti-slop guardrails (Pro / Auto only)
|
|
197
|
+
personas/ 4 persona configs
|
|
198
|
+
.codex/agents/ 10 Codex agent definitions
|
|
199
|
+
.cursor/rules/ 10 Cursor rule definitions
|
|
200
|
+
AGENTS.md Codex instruction file
|
|
201
|
+
DANGER-ZONES.md Risky areas (commit this)
|
|
202
|
+
.uv-suite-state/ Session metadata + counters (gitignored)
|
|
203
|
+
current-session.txt
|
|
204
|
+
sessions/<sid>.json
|
|
205
|
+
uv-out/ Agent output artifacts (gitignored)
|
|
206
|
+
checkpoints/<sid>/ Per-session checkpoints
|
|
183
207
|
```
|
|
184
208
|
|
|
185
209
|
## Documentation
|
|
186
210
|
|
|
187
211
|
| Document | What it covers |
|
|
188
|
-
|
|
212
|
+
|---|---|
|
|
189
213
|
| [usage-guide.md](usage-guide.md) | Full SDLC mapped to exact commands |
|
|
190
214
|
| [personas.md](personas.md) | 4 personas, 7 knobs, when to use each |
|
|
191
215
|
| [practices.md](practices.md) | Working principles (honesty, parallelism, scope, completion) |
|
|
@@ -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
|
package/hooks/confirm-prompt.sh
CHANGED
|
@@ -40,8 +40,19 @@ WORDS=$(echo "$PROMPT" | wc -w | tr -d ' ')
|
|
|
40
40
|
[ "$WORDS" -le "$THRESHOLD" ] && exit 0
|
|
41
41
|
|
|
42
42
|
# Emit Claude Code hook output. additionalContext is appended to the system
|
|
43
|
-
# context for this turn.
|
|
44
|
-
|
|
43
|
+
# context for this turn. The instructions below shape the *style* of the
|
|
44
|
+
# confirmation, not just whether one happens — the response should set
|
|
45
|
+
# context, break the ask into bullets, and end with an explicit invitation
|
|
46
|
+
# for the user to redirect before any work starts.
|
|
47
|
+
ADDITIONAL=$(printf '[uv-suite confirm-mode] The user prompt is %s words (threshold %s). Before any work or tool calls, write a confirmation in this exact shape:
|
|
48
|
+
|
|
49
|
+
1. Open with one short sentence that restates the goal in your own words — set the frame for what you think the user is asking for.
|
|
50
|
+
2. Then a bulleted breakdown (3-7 bullets, one line each) covering: concrete deliverables, the key decisions or assumptions you intend to make, and any open questions or scope choices the user might want to redirect.
|
|
51
|
+
3. End with a single explicit prompt: "Want me to change anything before I start, or should I go ahead?"
|
|
52
|
+
|
|
53
|
+
Do not propose implementation steps, write code, or call tools yet. The point is to confirm understanding so the user can correct course cheaply. Only proceed once the user confirms.
|
|
54
|
+
|
|
55
|
+
The user can disable this with /confirm off or change the threshold with /confirm <number>.' "$WORDS" "$THRESHOLD")
|
|
45
56
|
|
|
46
57
|
if command -v jq >/dev/null 2>&1; then
|
|
47
58
|
jq -nc --arg ctx "$ADDITIONAL" '{hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:$ctx}}'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uv-suite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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
|
},
|