uv-suite 0.5.0 → 0.7.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/bin/cli.js CHANGED
@@ -1,49 +1,52 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync } = require('child_process');
3
+ const { execSync, spawn } = require('child_process');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
6
 
7
7
  const UV_SUITE_DIR = path.resolve(__dirname, '..');
8
8
  const args = process.argv.slice(2);
9
9
  const command = args[0];
10
+ const pkg = require(path.join(UV_SUITE_DIR, 'package.json'));
11
+
12
+ const PERSONAS = ['spike', 'sport', 'pro', 'professional', 'auto'];
13
+ const TOOLS = ['claude', 'codex'];
10
14
 
11
15
  function usage() {
12
16
  console.log(`
13
- uv-suite - AI-assisted development framework
17
+ uv v${pkg.version} AI-assisted development framework
14
18
 
15
- Usage:
16
- npx uv-suite install [--persona sport|professional|auto|spike] [--global]
17
- npx uv-suite info
19
+ Launch a session:
20
+ uv claude pro Claude Code, Professional persona
21
+ uv claude auto Claude Code, Auto persona
22
+ uv codex pro Codex, Professional persona
23
+ uv codex sport Codex, Sport persona
24
+ uv pro Shorthand (defaults to Claude Code)
25
+ uv Claude Code, Professional
18
26
 
19
- Commands:
20
- install Install agents, skills, hooks, guardrails, and personas
21
- info Show what would be installed
27
+ Setup:
28
+ uv install Install UV Suite into current project
29
+ uv install --persona sport
30
+ uv info Show what's installed
22
31
 
23
32
  Personas:
24
- spike Research & documentation (Opus, max effort)
25
- sport New projects, prototyping (Sonnet, high effort)
26
- professional Production code (default, all hooks + guardrails)
27
- auto Fully autonomous (max effort, everything approved)
28
-
29
- Examples:
30
- npx uv-suite install Install with Professional persona
31
- npx uv-suite install --persona sport Install with Sport persona
32
- npx uv-suite install --global Install to ~/.claude/
33
+ spike Research & docs (Opus, max effort)
34
+ sport New projects (Sonnet, high effort)
35
+ pro Production code (all hooks, all guardrails)
36
+ auto Fully autonomous (max effort, everything approved)
33
37
  `);
34
38
  }
35
39
 
36
40
  function info() {
37
41
  console.log(`
38
- UV Suite v0.1.0
42
+ UV Suite v${pkg.version}
39
43
 
40
44
  Contents:
41
45
  10 agents Claude Code (.md), Cursor (.mdc), Codex (.toml)
42
46
  9 skills Slash commands for Claude Code
43
- 4 hooks auto-lint, slop-check, danger-zone, block-destructive
44
- 6 guardrails Anti-slop rules (comment, overengineering, error, test, doc, architecture)
47
+ 5 hooks auto-lint, slop-check, danger-zone, block-destructive, review-reminder
48
+ 6 guardrails Anti-slop rules
45
49
  4 personas Spike, Sport, Professional, Auto
46
- 1 launcher uv.sh (session launcher with persona selection)
47
50
 
48
51
  Source: ${UV_SUITE_DIR}
49
52
  `);
@@ -55,8 +58,6 @@ function install() {
55
58
  console.error('Error: install.sh not found at', installScript);
56
59
  process.exit(1);
57
60
  }
58
-
59
- // Pass through all args after "install"
60
61
  const installArgs = args.slice(1).join(' ');
61
62
  try {
62
63
  execSync(`bash "${installScript}" ${installArgs}`, { stdio: 'inherit' });
@@ -65,20 +66,77 @@ function install() {
65
66
  }
66
67
  }
67
68
 
68
- switch (command) {
69
- case 'install':
70
- install();
71
- break;
72
- case 'info':
73
- info();
74
- break;
75
- case '--help':
76
- case '-h':
77
- case undefined:
78
- usage();
79
- break;
80
- default:
81
- console.error(`Unknown command: ${command}`);
82
- usage();
69
+ function normPersona(p) {
70
+ if (p === 'pro' || p === 'professional') return 'professional';
71
+ if (PERSONAS.includes(p)) return p;
72
+ return null;
73
+ }
74
+
75
+ function personaLabel(p) {
76
+ const labels = {
77
+ spike: 'Spike — research & docs (Opus, max)',
78
+ sport: 'Sport — lightweight (Sonnet, high)',
79
+ professional: 'Professional — full rigor (all hooks, all guardrails)',
80
+ auto: 'Auto — autonomous (max, everything approved)',
81
+ };
82
+ return labels[p] || p;
83
+ }
84
+
85
+ function launchClaude(persona, extra) {
86
+ const settings = path.resolve('.claude/personas', `${persona}.json`);
87
+ if (!fs.existsSync(settings)) {
88
+ console.error(`Error: ${settings} not found. Run 'uv install' first.`);
89
+ process.exit(1);
90
+ }
91
+ console.log(`UV Suite | Claude Code | ${personaLabel(persona)}`);
92
+ console.log('');
93
+ const child = spawn('claude', ['--settings', settings, ...extra], { stdio: 'inherit' });
94
+ child.on('exit', (code) => process.exit(code || 0));
95
+ }
96
+
97
+ function launchCodex(persona, extra) {
98
+ const approvalMap = {
99
+ spike: ['--model', 'o3', '--approval-mode', 'suggest'],
100
+ sport: ['--approval-mode', 'auto-edit'],
101
+ professional: ['--approval-mode', 'suggest'],
102
+ auto: ['--approval-mode', 'full-auto'],
103
+ };
104
+ const codexArgs = approvalMap[persona] || ['--approval-mode', 'suggest'];
105
+ console.log(`UV Suite | Codex | ${personaLabel(persona)}`);
106
+ console.log('');
107
+ const child = spawn('codex', [...codexArgs, ...extra], { stdio: 'inherit' });
108
+ child.on('exit', (code) => process.exit(code || 0));
109
+ }
110
+
111
+ // --- Parse and route ---
112
+
113
+ if (!command || command === '--help' || command === '-h') {
114
+ usage();
115
+ process.exit(0);
116
+ }
117
+
118
+ if (command === 'install') {
119
+ install();
120
+ } else if (command === 'info') {
121
+ info();
122
+ } else if (TOOLS.includes(command)) {
123
+ // uv claude pro, uv codex auto
124
+ const persona = normPersona(args[1] || 'pro');
125
+ if (!persona) {
126
+ console.error(`Unknown persona: ${args[1]}`);
127
+ console.error('Available: spike, sport, pro, auto');
83
128
  process.exit(1);
129
+ }
130
+ const extra = args.slice(2);
131
+ if (command === 'claude') launchClaude(persona, extra);
132
+ else launchCodex(persona, extra);
133
+ } else if (normPersona(command)) {
134
+ // uv pro (shorthand for uv claude pro)
135
+ const persona = normPersona(command);
136
+ const extra = args.slice(1);
137
+ launchClaude(persona, extra);
138
+ } else {
139
+ console.error(`Unknown command: ${command}`);
140
+ usage();
141
+ process.exit(1);
84
142
  }
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # UV Suite Hook: Session end — reflection and total time
3
+ # Event: Stop (replaces session-review-reminder.sh with richer behavior)
4
+
5
+ STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
6
+ START_FILE="$STATE_DIR/session-start.txt"
7
+
8
+ # Compute session duration
9
+ DURATION_MSG=""
10
+ if [ -f "$START_FILE" ]; then
11
+ START=$(cat "$START_FILE")
12
+ NOW=$(date +%s)
13
+ ELAPSED=$((NOW - START))
14
+ MINUTES=$((ELAPSED / 60))
15
+
16
+ # Add to today's total
17
+ TODAY=$(date +%Y-%m-%d)
18
+ TODAY_FILE="$STATE_DIR/active-$TODAY.txt"
19
+ TODAY_TOTAL=$(cat "$TODAY_FILE" 2>/dev/null || echo "0")
20
+ TODAY_TOTAL=$((TODAY_TOTAL + MINUTES))
21
+ echo "$TODAY_TOTAL" > "$TODAY_FILE"
22
+
23
+ DURATION_MSG="Session: ${MINUTES} min. Today: ${TODAY_TOTAL} min. "
24
+
25
+ # Reset session state
26
+ rm -f "$START_FILE" "$STATE_DIR/tool-count.txt" "$STATE_DIR/last-warning.txt"
27
+ fi
28
+
29
+ # Check for uncommitted changes
30
+ STAGED=$(git diff --cached --stat 2>/dev/null)
31
+ UNSTAGED=$(git diff --stat 2>/dev/null)
32
+ UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | head -5)
33
+
34
+ REVIEW_MSG=""
35
+ if [ -n "$STAGED" ] || [ -n "$UNSTAGED" ] || [ -n "$UNTRACKED" ]; then
36
+ REVIEW_MSG="Uncommitted changes — consider /review and /slop-check before committing. "
37
+ fi
38
+
39
+ # Reflection prompt
40
+ REFLECTION_MSG="Before closing: What shipped? What did you learn? What would you teach the agent for next time (add to CLAUDE.md or DANGER-ZONES.md)?"
41
+
42
+ FULL_MSG="${DURATION_MSG}${REVIEW_MSG}${REFLECTION_MSG}"
43
+
44
+ cat <<EOF
45
+ {
46
+ "continue": true,
47
+ "systemMessage": "$FULL_MSG"
48
+ }
49
+ EOF
50
+
51
+ exit 0
@@ -0,0 +1,18 @@
1
+ #!/bin/bash
2
+ # UV Suite Hook: Session start — record start time
3
+ # Event: SessionStart
4
+
5
+ STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
6
+ mkdir -p "$STATE_DIR"
7
+
8
+ # Record session start time
9
+ date +%s > "$STATE_DIR/session-start.txt"
10
+
11
+ # Track today's total active time
12
+ TODAY=$(date +%Y-%m-%d)
13
+ TODAY_FILE="$STATE_DIR/active-$TODAY.txt"
14
+ if [ ! -f "$TODAY_FILE" ]; then
15
+ echo "0" > "$TODAY_FILE"
16
+ fi
17
+
18
+ exit 0
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # UV Suite Hook: Session timer — warn about session duration
3
+ # Event: PostToolUse (runs every Nth tool call)
4
+ # Escalates: 45min → gentle, 90min → firm, 180min → strong
5
+
6
+ STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
7
+ START_FILE="$STATE_DIR/session-start.txt"
8
+
9
+ # Only check every 20th tool call to avoid noise
10
+ COUNT_FILE="$STATE_DIR/tool-count.txt"
11
+ COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo "0")
12
+ COUNT=$((COUNT + 1))
13
+ echo "$COUNT" > "$COUNT_FILE"
14
+
15
+ if [ $((COUNT % 20)) -ne 0 ]; then
16
+ exit 0
17
+ fi
18
+
19
+ if [ ! -f "$START_FILE" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ START=$(cat "$START_FILE")
24
+ NOW=$(date +%s)
25
+ ELAPSED=$((NOW - START))
26
+ MINUTES=$((ELAPSED / 60))
27
+
28
+ # Check if we've already shown a warning for this threshold
29
+ LAST_WARN_FILE="$STATE_DIR/last-warning.txt"
30
+ LAST_WARN=$(cat "$LAST_WARN_FILE" 2>/dev/null || echo "0")
31
+
32
+ MESSAGE=""
33
+ WARN_LEVEL=0
34
+
35
+ if [ "$MINUTES" -ge 180 ] && [ "$LAST_WARN" -lt 3 ]; then
36
+ MESSAGE="SESSION HEALTH (3+ hours): You've been in this session for ${MINUTES} minutes. This is a long stretch. Strongly consider: (1) commit what you have, (2) close this session, (3) take a real break away from the screen. Long uninterrupted agent sessions lead to goal drift, lower judgment quality, and burnout. /compact if you must continue, but a break is better."
37
+ WARN_LEVEL=3
38
+ elif [ "$MINUTES" -ge 90 ] && [ "$LAST_WARN" -lt 2 ]; then
39
+ MESSAGE="SESSION HEALTH (90+ min): You've been in this session for ${MINUTES} minutes. Consider taking a 10-minute break — stretch, water, walk. Come back with fresh eyes. Also a good time to commit progress."
40
+ WARN_LEVEL=2
41
+ elif [ "$MINUTES" -ge 45 ] && [ "$LAST_WARN" -lt 1 ]; then
42
+ MESSAGE="SESSION HEALTH (45 min): You've been in this session for ${MINUTES} minutes. Good time for a quick break and to commit any progress."
43
+ WARN_LEVEL=1
44
+ fi
45
+
46
+ if [ -n "$MESSAGE" ]; then
47
+ echo "$WARN_LEVEL" > "$LAST_WARN_FILE"
48
+ cat <<EOF
49
+ {
50
+ "continue": true,
51
+ "systemMessage": "$MESSAGE"
52
+ }
53
+ EOF
54
+ fi
55
+
56
+ exit 0
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # UV Suite Hook: Status line — show session duration and persona
3
+ # Event: statusLine (rendered continuously)
4
+
5
+ STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
6
+ START_FILE="$STATE_DIR/session-start.txt"
7
+
8
+ # Session duration
9
+ if [ -f "$START_FILE" ]; then
10
+ START=$(cat "$START_FILE")
11
+ NOW=$(date +%s)
12
+ ELAPSED=$((NOW - START))
13
+ MINS=$((ELAPSED / 60))
14
+
15
+ if [ "$MINS" -ge 180 ]; then
16
+ SESSION="session ${MINS}m (!! take a break)"
17
+ elif [ "$MINS" -ge 90 ]; then
18
+ SESSION="session ${MINS}m (break soon)"
19
+ elif [ "$MINS" -ge 45 ]; then
20
+ SESSION="session ${MINS}m"
21
+ else
22
+ SESSION="session ${MINS}m"
23
+ fi
24
+ else
25
+ SESSION="session 0m"
26
+ fi
27
+
28
+ # Today's total active time
29
+ TODAY=$(date +%Y-%m-%d)
30
+ TODAY_FILE="$STATE_DIR/active-$TODAY.txt"
31
+ if [ -f "$TODAY_FILE" ]; then
32
+ TODAY_TOTAL=$(cat "$TODAY_FILE")
33
+ CURRENT_MINS=${MINS:-0}
34
+ TODAY_NOW=$((TODAY_TOTAL + CURRENT_MINS))
35
+ TODAY_STR=" · today ${TODAY_NOW}m"
36
+ else
37
+ TODAY_STR=""
38
+ fi
39
+
40
+ # Persona from settings path (best-effort detection)
41
+ PERSONA=""
42
+ if [ -f "$CLAUDE_PROJECT_DIR/.claude/settings.local.json" ]; then
43
+ PERSONA=$(grep -o '"effort"[^,]*' "$CLAUDE_PROJECT_DIR/.claude/settings.local.json" 2>/dev/null | head -1)
44
+ fi
45
+
46
+ echo "UV Suite · $SESSION$TODAY_STR"
package/install.sh CHANGED
@@ -112,13 +112,16 @@ echo " ✓ /map-codebase, /spec, /architect, /review, /write-tests"
112
112
  echo " ✓ /write-evals, /slop-check, /prototype, /security-review"
113
113
 
114
114
  # --- Install hooks ---
115
- echo "Installing 4 hook scripts..."
115
+ echo "Installing hook scripts..."
116
116
  cp "$UV_SUITE_DIR/hooks/"*.sh "$TARGET_DIR/hooks/"
117
117
  chmod +x "$TARGET_DIR/hooks/"*.sh
118
- echo " ✓ auto-lint.sh (PostToolUse: auto-format on write)"
118
+ echo " ✓ auto-lint.sh (PostToolUse: auto-format)"
119
119
  echo " ✓ danger-zone-check.sh (PreToolUse: warn on danger zone files)"
120
120
  echo " ✓ block-destructive.sh (PreToolUse: block rm -rf, force push, etc.)"
121
- echo " ✓ session-review-reminder.sh (Stop: remind to review uncommitted changes)"
121
+ echo " ✓ session-start.sh (SessionStart: track session start time)"
122
+ echo " ✓ session-timer.sh (PostToolUse: warn at 45/90/180 min)"
123
+ echo " ✓ session-end.sh (Stop: reflection + duration + review reminder)"
124
+ echo " ✓ status-line.sh (statusLine: show session time continuously)"
122
125
  echo " + Real-time slop check (Haiku prompt hook, wired in settings.json)"
123
126
 
124
127
  # --- Install guardrail rules (Sport only) ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uv-suite",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",
@@ -24,6 +24,7 @@
24
24
  "repomix": "^0.3.0"
25
25
  },
26
26
  "bin": {
27
+ "uv": "./bin/cli.js",
27
28
  "uv-suite": "./bin/cli.js"
28
29
  },
29
30
  "files": [
@@ -50,6 +50,14 @@
50
50
  },
51
51
 
52
52
  "hooks": {
53
+ "SessionStart": [
54
+ {
55
+ "matcher": "*",
56
+ "hooks": [
57
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start.sh", "timeout": 5 }
58
+ ]
59
+ }
60
+ ],
53
61
  "PreToolUse": [
54
62
  {
55
63
  "matcher": "Bash",
@@ -64,6 +72,12 @@
64
72
  }
65
73
  ],
66
74
  "PostToolUse": [
75
+ {
76
+ "matcher": "*",
77
+ "hooks": [
78
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-timer.sh", "timeout": 5 }
79
+ ]
80
+ },
67
81
  {
68
82
  "matcher": "Edit|Write",
69
83
  "hooks": [
@@ -75,6 +89,19 @@
75
89
  }
76
90
  ]
77
91
  }
92
+ ],
93
+ "Stop": [
94
+ {
95
+ "matcher": "*",
96
+ "hooks": [
97
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-end.sh", "timeout": 10 }
98
+ ]
99
+ }
78
100
  ]
101
+ },
102
+
103
+ "statusLine": {
104
+ "type": "command",
105
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/status-line.sh"
79
106
  }
80
107
  }
@@ -48,6 +48,18 @@
48
48
  },
49
49
 
50
50
  "hooks": {
51
+ "SessionStart": [
52
+ {
53
+ "matcher": "*",
54
+ "hooks": [
55
+ {
56
+ "type": "command",
57
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start.sh",
58
+ "timeout": 5
59
+ }
60
+ ]
61
+ }
62
+ ],
51
63
  "PreToolUse": [
52
64
  {
53
65
  "matcher": "Bash",
@@ -73,6 +85,16 @@
73
85
  }
74
86
  ],
75
87
  "PostToolUse": [
88
+ {
89
+ "matcher": "*",
90
+ "hooks": [
91
+ {
92
+ "type": "command",
93
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-timer.sh",
94
+ "timeout": 5
95
+ }
96
+ ]
97
+ },
76
98
  {
77
99
  "matcher": "Edit|Write",
78
100
  "hooks": [
@@ -98,12 +120,17 @@
98
120
  "hooks": [
99
121
  {
100
122
  "type": "command",
101
- "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-review-reminder.sh",
123
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-end.sh",
102
124
  "timeout": 10,
103
- "statusMessage": "Checking for uncommitted changes..."
125
+ "statusMessage": "Wrapping up session..."
104
126
  }
105
127
  ]
106
128
  }
107
129
  ]
130
+ },
131
+
132
+ "statusLine": {
133
+ "type": "command",
134
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/status-line.sh"
108
135
  }
109
136
  }
@@ -36,6 +36,14 @@
36
36
  },
37
37
 
38
38
  "hooks": {
39
+ "SessionStart": [
40
+ {
41
+ "matcher": "*",
42
+ "hooks": [
43
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start.sh", "timeout": 5 }
44
+ ]
45
+ }
46
+ ],
39
47
  "PostToolUse": [
40
48
  {
41
49
  "matcher": "Write",
@@ -49,6 +57,19 @@
49
57
  }
50
58
  ]
51
59
  }
60
+ ],
61
+ "Stop": [
62
+ {
63
+ "matcher": "*",
64
+ "hooks": [
65
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-end.sh", "timeout": 10 }
66
+ ]
67
+ }
52
68
  ]
69
+ },
70
+
71
+ "statusLine": {
72
+ "type": "command",
73
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/status-line.sh"
53
74
  }
54
75
  }
@@ -22,6 +22,14 @@
22
22
  },
23
23
 
24
24
  "hooks": {
25
+ "SessionStart": [
26
+ {
27
+ "matcher": "*",
28
+ "hooks": [
29
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-start.sh", "timeout": 5 }
30
+ ]
31
+ }
32
+ ],
25
33
  "PostToolUse": [
26
34
  {
27
35
  "matcher": "Edit|Write",
@@ -34,6 +42,19 @@
34
42
  }
35
43
  ]
36
44
  }
45
+ ],
46
+ "Stop": [
47
+ {
48
+ "matcher": "*",
49
+ "hooks": [
50
+ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-end.sh", "timeout": 10 }
51
+ ]
52
+ }
37
53
  ]
54
+ },
55
+
56
+ "statusLine": {
57
+ "type": "command",
58
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/status-line.sh"
38
59
  }
39
60
  }