uv-suite 0.26.3 → 0.26.5

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 CHANGED
@@ -1,34 +1,70 @@
1
1
  # UV Suite
2
2
 
3
- Portable framework for AI-assisted software development. Works with Claude Code, Cursor, and OpenAI Codex.
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
- uv install
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
- This installs 10 agents, 10 skills, 8 hooks, 6 guardrails, and 4 personas into your project.
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
- uv install # Install UV Suite into current project
24
- uv claude pro # Start Claude Code, Professional persona
25
- uv codex auto # Start Codex, Auto persona
26
- uv pro # Shorthand for uv claude pro
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
- ## Modes
31
+ On every `uvs` launch, you'll be prompted to label the session:
30
32
 
31
- Four personas for different contexts. Pick one when you start a session.
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
- ## What You Get
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
- | `/review` | Code review: correctness, security, performance, slop |
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
- | Slop check | File write | Haiku scans for obvious slop patterns |
122
- | Danger zone | File edit | Warns if file is in DANGER-ZONES.md |
123
- | Destructive block | Bash command | Blocks rm -rf, force push, DROP TABLE |
124
- | Session start | Session start | Records start time for duration tracking |
125
- | Session timer | Every 20th tool call | Checkpoint reminders at 45/90/180 min |
126
- | Session end | Session stop | Shows duration, today's total, reflection prompt |
127
- | Status line | Continuous | Shows session time in Claude Code's status bar |
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, Portable):
150
+ 10 agents, each in 4 formats (Claude Code, Cursor, Codex, portable):
132
151
 
133
152
  | Agent | Subsystem | Model | Cycle Budget |
134
- |-------|-----------|-------|-------------|
135
- | Cartographer | UV Index | Opus | 1 |
136
- | Spec Writer | UV Acts | Opus | 1 |
137
- | Architect | UV Acts | Opus | 2 |
138
- | Reviewer | UV Guard | Opus | 1 |
139
- | Test Writer | UV Acts | Sonnet | 3 |
140
- | Eval Writer | UV Acts | Opus | 2 |
141
- | Anti-Slop Guard | UV Guard | Opus | 1 |
142
- | Prototype Builder | UV Acts | Sonnet | 3 |
143
- | DevOps | UV Acts | Opus | 2 |
144
- | Security | UV Guard | Opus | 1 |
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
- | Agent output | Read by |
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 Agent | SAST with 4000+ OWASP-mapped rules |
163
- | [Gitleaks](https://github.com/gitleaks/gitleaks) | Security Agent | Secret detection in git repos |
164
- | [Trivy](https://github.com/aquasecurity/trivy) | Security Agent | Dependency vulnerability scanning |
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 Builder, Test Writer | Browser automation and e2e testing |
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 Permissions, hooks (from persona)
173
- agents/ 10 agent definitions
174
- skills/ 10 slash commands
175
- hooks/ 7 hook scripts
176
- rules/ 6 anti-slop guardrails
177
- personas/ 4 persona configs
178
- .codex/agents/ 10 Codex agent definitions
179
- .cursor/rules/ 10 Cursor rule definitions
180
- AGENTS.md Codex instruction file
181
- DANGER-ZONES.md Risky areas (commit this)
182
- uv-out/ Agent output artifacts (gitignored)
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) |
package/bin/cli.js CHANGED
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { execSync, spawn } = require('child_process');
4
- const path = require('path');
5
- const fs = require('fs');
3
+ const { execSync, spawn } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+ const crypto = require("crypto");
7
+ const readline = require("readline");
6
8
 
7
- const UV_SUITE_DIR = path.resolve(__dirname, '..');
9
+ const UV_SUITE_DIR = path.resolve(__dirname, "..");
8
10
  const args = process.argv.slice(2);
9
11
  const command = args[0];
10
- const pkg = require(path.join(UV_SUITE_DIR, 'package.json'));
12
+ const pkg = require(path.join(UV_SUITE_DIR, "package.json"));
11
13
 
12
- const PERSONAS = ['spike', 'sport', 'pro', 'professional', 'auto'];
13
- const TOOLS = ['claude', 'codex'];
14
+ const PERSONAS = ["spike", "sport", "pro", "professional", "auto"];
15
+ const TOOLS = ["claude", "codex"];
14
16
 
15
17
  function usage() {
16
18
  console.log(`
@@ -57,208 +59,333 @@ function info() {
57
59
  }
58
60
 
59
61
  function install() {
60
- const installScript = path.join(UV_SUITE_DIR, 'install.sh');
62
+ const installScript = path.join(UV_SUITE_DIR, "install.sh");
61
63
  if (!fs.existsSync(installScript)) {
62
- console.error('Error: install.sh not found at', installScript);
64
+ console.error("Error: install.sh not found at", installScript);
63
65
  process.exit(1);
64
66
  }
65
- const installArgs = args.slice(1).join(' ');
67
+ const installArgs = args.slice(1).join(" ");
66
68
  try {
67
- execSync(`bash "${installScript}" ${installArgs}`, { stdio: 'inherit' });
69
+ execSync(`bash "${installScript}" ${installArgs}`, { stdio: "inherit" });
68
70
  } catch (e) {
69
71
  process.exit(e.status || 1);
70
72
  }
71
73
  }
72
74
 
73
75
  function normPersona(p) {
74
- if (p === 'pro' || p === 'professional') return 'professional';
76
+ if (p === "pro" || p === "professional") return "professional";
75
77
  if (PERSONAS.includes(p)) return p;
76
78
  return null;
77
79
  }
78
80
 
79
81
  function personaLabel(p) {
80
82
  const labels = {
81
- spike: 'Spike — research & docs (Opus, max)',
82
- sport: 'Sport — lightweight (Sonnet, high)',
83
- professional: 'Professional — full rigor (all hooks, all guardrails)',
84
- auto: 'Auto — autonomous (max, everything approved)',
83
+ spike: "Spike — research & docs (Opus, max)",
84
+ sport: "Sport — lightweight (Sonnet, high)",
85
+ professional: "Professional — full rigor (all hooks, all guardrails)",
86
+ auto: "Auto — autonomous (max, everything approved)",
85
87
  };
86
88
  return labels[p] || p;
87
89
  }
88
90
 
89
- function ensureInstalled(persona) {
90
- const hooksDir = path.resolve('.claude/hooks');
91
- const personasDir = path.resolve('.claude/personas');
92
- const needsInstall = !fs.existsSync(personasDir) || !fs.existsSync(hooksDir);
93
-
94
- if (needsInstall) {
95
- console.log('UV Suite not installed in this project. Installing core files...');
96
- console.log('');
97
-
98
- // Fast install: copy essential files directly (no pip, no brew, no slow stuff)
99
- const srcDir = UV_SUITE_DIR;
100
- const targetDir = path.resolve('.claude');
101
-
102
- // Create directories
103
- for (const dir of ['agents', 'skills', 'hooks', 'rules', 'personas']) {
104
- fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
105
- }
91
+ // Sync package-owned files (hooks, skills, personas, agents, optional guardrails)
92
+ // from the installed npm package into the project's .claude/. Idempotent — runs
93
+ // every launch so users on older versions pick up new hooks and slash commands
94
+ // after `npm install -g uv-suite@latest` without needing `uvs install` again.
95
+ // settings.json is preserved if it exists (user customizations).
96
+ function syncPackageFiles(persona) {
97
+ const srcDir = UV_SUITE_DIR;
98
+ const targetDir = path.resolve(".claude");
99
+ const hooksDir = path.join(targetDir, "hooks");
100
+ const personasDir = path.join(targetDir, "personas");
101
+ const wasFreshInstall =
102
+ !fs.existsSync(personasDir) || !fs.existsSync(hooksDir);
103
+
104
+ for (const dir of ["agents", "skills", "hooks", "rules", "personas"]) {
105
+ fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
106
+ }
106
107
 
107
- // Copy agents
108
- const agentsSrc = path.join(srcDir, 'agents', 'claude-code');
109
- if (fs.existsSync(agentsSrc)) {
110
- for (const f of fs.readdirSync(agentsSrc)) {
111
- fs.copyFileSync(path.join(agentsSrc, f), path.join(targetDir, 'agents', f));
112
- }
108
+ const agentsSrc = path.join(srcDir, "agents", "claude-code");
109
+ if (fs.existsSync(agentsSrc)) {
110
+ for (const f of fs.readdirSync(agentsSrc)) {
111
+ fs.copyFileSync(
112
+ path.join(agentsSrc, f),
113
+ path.join(targetDir, "agents", f),
114
+ );
113
115
  }
116
+ }
114
117
 
115
- // Copy hooks
116
- const hooksSrc = path.join(srcDir, 'hooks');
117
- if (fs.existsSync(hooksSrc)) {
118
- for (const f of fs.readdirSync(hooksSrc)) {
119
- const dest = path.join(targetDir, 'hooks', f);
120
- fs.copyFileSync(path.join(hooksSrc, f), dest);
121
- fs.chmodSync(dest, 0o755);
122
- }
118
+ const hooksSrc = path.join(srcDir, "hooks");
119
+ if (fs.existsSync(hooksSrc)) {
120
+ for (const f of fs.readdirSync(hooksSrc)) {
121
+ const dest = path.join(targetDir, "hooks", f);
122
+ fs.copyFileSync(path.join(hooksSrc, f), dest);
123
+ fs.chmodSync(dest, 0o755);
123
124
  }
125
+ }
124
126
 
125
- // Copy skills
126
- const skillsSrc = path.join(srcDir, 'skills');
127
- if (fs.existsSync(skillsSrc)) {
128
- for (const d of fs.readdirSync(skillsSrc)) {
129
- const skillFile = path.join(skillsSrc, d, 'SKILL.md');
130
- if (fs.existsSync(skillFile)) {
131
- const destDir = path.join(targetDir, 'skills', d);
132
- fs.mkdirSync(destDir, { recursive: true });
133
- fs.copyFileSync(skillFile, path.join(destDir, 'SKILL.md'));
134
- }
127
+ const skillsSrc = path.join(srcDir, "skills");
128
+ if (fs.existsSync(skillsSrc)) {
129
+ for (const d of fs.readdirSync(skillsSrc)) {
130
+ const skillFile = path.join(skillsSrc, d, "SKILL.md");
131
+ if (fs.existsSync(skillFile)) {
132
+ const destDir = path.join(targetDir, "skills", d);
133
+ fs.mkdirSync(destDir, { recursive: true });
134
+ fs.copyFileSync(skillFile, path.join(destDir, "SKILL.md"));
135
135
  }
136
136
  }
137
+ }
137
138
 
138
- // Copy guardrails (for professional and auto)
139
- if (persona === 'professional' || persona === 'auto') {
140
- const guardSrc = path.join(srcDir, 'guardrails');
141
- if (fs.existsSync(guardSrc)) {
142
- for (const f of fs.readdirSync(guardSrc)) {
143
- fs.copyFileSync(path.join(guardSrc, f), path.join(targetDir, 'rules', f));
144
- }
139
+ if (persona === "professional" || persona === "auto") {
140
+ const guardSrc = path.join(srcDir, "guardrails");
141
+ if (fs.existsSync(guardSrc)) {
142
+ for (const f of fs.readdirSync(guardSrc)) {
143
+ fs.copyFileSync(
144
+ path.join(guardSrc, f),
145
+ path.join(targetDir, "rules", f),
146
+ );
145
147
  }
146
148
  }
149
+ }
147
150
 
148
- // Copy personas
149
- const personasSrc = path.join(srcDir, 'personas');
150
- if (fs.existsSync(personasSrc)) {
151
- for (const f of fs.readdirSync(personasSrc)) {
152
- fs.copyFileSync(path.join(personasSrc, f), path.join(targetDir, 'personas', f));
153
- }
151
+ const personasSrc = path.join(srcDir, "personas");
152
+ if (fs.existsSync(personasSrc)) {
153
+ for (const f of fs.readdirSync(personasSrc)) {
154
+ fs.copyFileSync(
155
+ path.join(personasSrc, f),
156
+ path.join(targetDir, "personas", f),
157
+ );
154
158
  }
159
+ }
155
160
 
156
- // Set settings.json from persona
157
- const personaFile = path.join(targetDir, 'personas', `${persona}.json`);
158
- const settingsFile = path.join(targetDir, 'settings.json');
159
- if (fs.existsSync(personaFile) && !fs.existsSync(settingsFile)) {
160
- fs.copyFileSync(personaFile, settingsFile);
161
- }
161
+ // settings.json is user-owned. Only seed it on fresh install.
162
+ const personaFile = path.join(targetDir, "personas", `${persona}.json`);
163
+ const settingsFile = path.join(targetDir, "settings.json");
164
+ if (
165
+ wasFreshInstall &&
166
+ fs.existsSync(personaFile) &&
167
+ !fs.existsSync(settingsFile)
168
+ ) {
169
+ fs.copyFileSync(personaFile, settingsFile);
170
+ }
162
171
 
172
+ if (wasFreshInstall) {
173
+ console.log(
174
+ "UV Suite not installed in this project. Installing core files...",
175
+ );
163
176
  console.log(` Installed: agents, skills, hooks, guardrails, personas`);
164
- console.log('');
177
+ console.log("");
178
+ }
179
+ }
180
+
181
+ function prompt(rl, question) {
182
+ return new Promise((resolve) => rl.question(question, resolve));
183
+ }
184
+
185
+ function normalizeKind(s) {
186
+ const v = (s || "").toLowerCase().trim();
187
+ if (["l", "long", "long-running"].includes(v)) return "long-running";
188
+ if (["o", "outcome"].includes(v)) return "outcome";
189
+ return "";
190
+ }
191
+
192
+ function normalizePriority(s) {
193
+ const v = (s || "").toLowerCase().trim();
194
+ if (["l", "low"].includes(v)) return "low";
195
+ if (["m", "med", "medium"].includes(v)) return "med";
196
+ if (["h", "high"].includes(v)) return "high";
197
+ return "";
198
+ }
199
+
200
+ // Generate a UVS_SESSION_ID, prompt for metadata (name/kind/purpose/priority),
201
+ // write it to .uv-suite-state/sessions/<sid>.json, and return the id + name.
202
+ // Skipping (Enter) leaves a field empty; the session-label-nag.sh hook will
203
+ // remind the user to run /session-init mid-flight.
204
+ async function setupSession(persona) {
205
+ const projectDir = process.cwd();
206
+ const stateDir = path.join(projectDir, ".uv-suite-state");
207
+ const sessionsDir = path.join(stateDir, "sessions");
208
+ fs.mkdirSync(sessionsDir, { recursive: true });
209
+
210
+ const sid = crypto.randomUUID();
211
+ let name = "";
212
+ let kind = "";
213
+ let purpose = "";
214
+ let priority = "";
215
+
216
+ if (process.stdin.isTTY && !process.env.UVS_NO_PROMPT) {
217
+ const rl = readline.createInterface({
218
+ input: process.stdin,
219
+ output: process.stdout,
220
+ });
221
+ console.log("");
222
+ console.log("Label this session (Enter to skip — you'll be reminded):");
223
+ name = (await prompt(rl, " name: ")).trim();
224
+ const kindRaw = await prompt(rl, " kind [long/outcome]: ");
225
+ purpose = (await prompt(rl, " purpose: ")).trim();
226
+ const priorityRaw = await prompt(rl, " priority [low/med/high]: ");
227
+ rl.close();
228
+ kind = normalizeKind(kindRaw);
229
+ priority = normalizePriority(priorityRaw);
165
230
  }
231
+
232
+ const meta = {
233
+ uvs_session_id: sid,
234
+ name,
235
+ kind,
236
+ purpose,
237
+ priority,
238
+ persona,
239
+ cwd: projectDir,
240
+ started_at: Math.floor(Date.now() / 1000),
241
+ };
242
+ fs.writeFileSync(
243
+ path.join(sessionsDir, `${sid}.json`),
244
+ JSON.stringify(meta, null, 2),
245
+ );
246
+ fs.writeFileSync(path.join(stateDir, "current-session.txt"), sid);
247
+
248
+ return { sid, name };
249
+ }
250
+
251
+ // Backwards-compat shim — older code in this file still references this name.
252
+ function ensureInstalled(persona) {
253
+ syncPackageFiles(persona);
166
254
  }
167
255
 
168
- function launchClaude(persona, extra) {
169
- ensureInstalled(persona);
170
- const settings = path.resolve('.claude/personas', `${persona}.json`);
256
+ async function launchClaude(persona, extra) {
257
+ syncPackageFiles(persona);
258
+ const settings = path.resolve(".claude/personas", `${persona}.json`);
171
259
  if (!fs.existsSync(settings)) {
172
- console.error(`Error: installation failed. Run 'uvs install --persona ${persona}' manually.`);
260
+ console.error(
261
+ `Error: installation failed. Run 'uvs install --persona ${persona}' manually.`,
262
+ );
173
263
  process.exit(1);
174
264
  }
265
+ const { sid, name } = await setupSession(persona);
266
+ console.log("");
175
267
  console.log(`UV Suite | Claude Code | ${personaLabel(persona)}`);
176
- console.log('');
177
- const child = spawn('claude', ['--settings', settings, ...extra], { stdio: 'inherit' });
178
- child.on('exit', (code) => process.exit(code || 0));
268
+ console.log(`Session: ${sid.slice(0, 8)}${name ? " — " + name : ""}`);
269
+ console.log("");
270
+ const child = spawn("claude", ["--settings", settings, ...extra], {
271
+ stdio: "inherit",
272
+ env: { ...process.env, UVS_SESSION_ID: sid },
273
+ });
274
+ child.on("exit", (code) => process.exit(code || 0));
179
275
  }
180
276
 
181
- function launchCodex(persona, extra) {
277
+ async function launchCodex(persona, extra) {
182
278
  const approvalMap = {
183
- spike: ['--model', 'o3', '--approval-mode', 'suggest'],
184
- sport: ['--approval-mode', 'auto-edit'],
185
- professional: ['--approval-mode', 'suggest'],
186
- auto: ['--approval-mode', 'full-auto'],
279
+ spike: ["--model", "o3", "--approval-mode", "suggest"],
280
+ sport: ["--approval-mode", "auto-edit"],
281
+ professional: ["--approval-mode", "suggest"],
282
+ auto: ["--approval-mode", "full-auto"],
187
283
  };
188
- const codexArgs = approvalMap[persona] || ['--approval-mode', 'suggest'];
284
+ const codexArgs = approvalMap[persona] || ["--approval-mode", "suggest"];
285
+ const { sid, name } = await setupSession(persona);
286
+ console.log("");
189
287
  console.log(`UV Suite | Codex | ${personaLabel(persona)}`);
190
- console.log('');
191
- const child = spawn('codex', [...codexArgs, ...extra], { stdio: 'inherit' });
192
- child.on('exit', (code) => process.exit(code || 0));
288
+ console.log(`Session: ${sid.slice(0, 8)}${name ? " — " + name : ""}`);
289
+ console.log("");
290
+ const child = spawn("codex", [...codexArgs, ...extra], {
291
+ stdio: "inherit",
292
+ env: { ...process.env, UVS_SESSION_ID: sid },
293
+ });
294
+ child.on("exit", (code) => process.exit(code || 0));
193
295
  }
194
296
 
195
297
  function watch() {
196
- const serverScript = path.join(UV_SUITE_DIR, 'watchtower', 'server.js');
298
+ const serverScript = path.join(UV_SUITE_DIR, "watchtower", "server.js");
197
299
  if (!fs.existsSync(serverScript)) {
198
- console.error('Error: watchtower server not found at', serverScript);
300
+ console.error("Error: watchtower server not found at", serverScript);
199
301
  process.exit(1);
200
302
  }
201
303
 
202
- const bg = args.includes('--bg') || args.includes('--background');
203
- console.log('UV Suite Watchtower starting...');
204
- console.log('Dashboard: http://localhost:' + (process.env.UVS_WATCHTOWER_PORT || 4200));
205
- console.log('');
304
+ const bg = args.includes("--bg") || args.includes("--background");
305
+ console.log("UV Suite Watchtower starting...");
306
+ console.log(
307
+ "Dashboard: http://localhost:" + (process.env.UVS_WATCHTOWER_PORT || 4200),
308
+ );
309
+ console.log("");
206
310
 
207
311
  if (bg) {
208
- const child = spawn('node', [serverScript], {
209
- stdio: 'ignore',
312
+ const child = spawn("node", [serverScript], {
313
+ stdio: "ignore",
210
314
  detached: true,
211
315
  });
212
316
  child.unref();
213
317
  console.log(`Running in background (PID: ${child.pid})`);
214
- console.log('Stop with: kill ' + child.pid);
318
+ console.log("Stop with: kill " + child.pid);
215
319
 
216
320
  // Open browser
217
- const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
218
- spawn(opener, ['http://localhost:' + (process.env.UVS_WATCHTOWER_PORT || 4200)], { stdio: 'ignore' });
321
+ const opener =
322
+ process.platform === "darwin"
323
+ ? "open"
324
+ : process.platform === "win32"
325
+ ? "start"
326
+ : "xdg-open";
327
+ spawn(
328
+ opener,
329
+ ["http://localhost:" + (process.env.UVS_WATCHTOWER_PORT || 4200)],
330
+ { stdio: "ignore" },
331
+ );
219
332
  } else {
220
333
  // Foreground — open browser after a short delay
221
334
  setTimeout(() => {
222
- const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
223
- spawn(opener, ['http://localhost:' + (process.env.UVS_WATCHTOWER_PORT || 4200)], { stdio: 'ignore' });
335
+ const opener =
336
+ process.platform === "darwin"
337
+ ? "open"
338
+ : process.platform === "win32"
339
+ ? "start"
340
+ : "xdg-open";
341
+ spawn(
342
+ opener,
343
+ ["http://localhost:" + (process.env.UVS_WATCHTOWER_PORT || 4200)],
344
+ { stdio: "ignore" },
345
+ );
224
346
  }, 1000);
225
347
 
226
- const child = spawn('node', [serverScript], { stdio: 'inherit' });
227
- child.on('exit', (code) => process.exit(code || 0));
348
+ const child = spawn("node", [serverScript], { stdio: "inherit" });
349
+ child.on("exit", (code) => process.exit(code || 0));
228
350
  }
229
351
  }
230
352
 
231
353
  // --- Parse and route ---
232
354
 
233
- if (!command || command === '--help' || command === '-h') {
355
+ if (!command || command === "--help" || command === "-h") {
234
356
  usage();
235
357
  process.exit(0);
236
358
  }
237
359
 
238
- if (command === 'watch') {
239
- watch();
240
- } else if (command === 'install') {
241
- install();
242
- } else if (command === 'info') {
243
- info();
244
- } else if (TOOLS.includes(command)) {
245
- // uv claude pro, uv codex auto
246
- const persona = normPersona(args[1] || 'pro');
247
- if (!persona) {
248
- console.error(`Unknown persona: ${args[1]}`);
249
- console.error('Available: spike, sport, pro, auto');
360
+ (async () => {
361
+ if (command === "watch") {
362
+ watch();
363
+ } else if (command === "install") {
364
+ install();
365
+ } else if (command === "info") {
366
+ info();
367
+ } else if (TOOLS.includes(command)) {
368
+ // uvs claude pro, uvs codex auto
369
+ const persona = normPersona(args[1] || "pro");
370
+ if (!persona) {
371
+ console.error(`Unknown persona: ${args[1]}`);
372
+ console.error("Available: spike, sport, pro, auto");
373
+ process.exit(1);
374
+ }
375
+ const extra = args.slice(2);
376
+ if (command === "claude") await launchClaude(persona, extra);
377
+ else await launchCodex(persona, extra);
378
+ } else if (normPersona(command)) {
379
+ // uvs pro (shorthand for uvs claude pro)
380
+ const persona = normPersona(command);
381
+ const extra = args.slice(1);
382
+ await launchClaude(persona, extra);
383
+ } else {
384
+ console.error(`Unknown command: ${command}`);
385
+ usage();
250
386
  process.exit(1);
251
387
  }
252
- const extra = args.slice(2);
253
- if (command === 'claude') launchClaude(persona, extra);
254
- else launchCodex(persona, extra);
255
- } else if (normPersona(command)) {
256
- // uv pro (shorthand for uv claude pro)
257
- const persona = normPersona(command);
258
- const extra = args.slice(1);
259
- launchClaude(persona, extra);
260
- } else {
261
- console.error(`Unknown command: ${command}`);
262
- usage();
388
+ })().catch((err) => {
389
+ console.error(err);
263
390
  process.exit(1);
264
- }
391
+ });
@@ -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. Keep it terse long instructions get tuned out.
44
- ADDITIONAL=$(printf '[uv-suite confirm-mode] The user prompt is %s words (threshold %s). Before doing any work or making tool calls, restate what you understood in 1-2 plain sentences and ask the user to confirm. Only proceed once they confirm. The user can disable this with /confirm off or change the threshold with /confirm <number>.' "$WORDS" "$THRESHOLD")
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.26.3",
3
+ "version": "0.26.5",
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",