uv-suite 0.29.0 → 0.32.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/LICENSE +21 -0
- package/README.md +72 -46
- package/agents/claude-code/anti-slop-guard.md +14 -1
- package/agents/claude-code/architect.md +30 -4
- package/agents/claude-code/cartographer.md +18 -6
- package/agents/claude-code/eval-writer.md +7 -2
- package/agents/claude-code/reviewer.md +5 -1
- package/agents/claude-code/spec-writer.md +30 -7
- package/agents/generate.py +88 -0
- package/bin/cli.js +146 -73
- package/hooks/auto-checkpoint-helper.sh +2 -2
- package/hooks/auto-checkpoint.sh +3 -3
- package/hooks/auto-restore-on-start.sh +2 -2
- package/hooks/checkpoint-helper.sh +40 -35
- package/hooks/git-context.sh +41 -0
- package/hooks/lite-mode-inject.sh +26 -0
- package/hooks/session-end-helper.sh +2 -2
- package/hooks/session-end.sh +2 -2
- package/hooks/session-label-nag.sh +2 -2
- package/hooks/session-meta.sh +18 -1
- package/hooks/session-review-reminder.sh +2 -2
- package/hooks/session-start.sh +16 -0
- package/hooks/slop-grep.sh +12 -31
- package/hooks/uv-out-best.sh +20 -0
- package/hooks/uv-out-collect.sh +52 -0
- package/hooks/uv-out-notify.sh +35 -0
- package/hooks/uv-out-pointer.sh +16 -0
- package/hooks/uv-out-session.sh +24 -0
- package/hooks/watchtower-end.sh +23 -0
- package/hooks/watchtower-notify.sh +56 -0
- package/hooks/watchtower-send.sh +10 -3
- package/hooks/watchtower-tokens.sh +61 -0
- package/install.sh +93 -42
- package/package.json +6 -3
- package/personas/auto.json +59 -1
- package/personas/professional.json +65 -1
- package/personas/spike.json +51 -2
- package/personas/sport.json +63 -1
- package/settings.json +6 -2
- package/skills/architect/SKILL.md +109 -8
- package/skills/architect/specialists/distributed-systems.md +84 -0
- package/skills/architect/specialists/full-stack.md +92 -0
- package/skills/architect/specialists/llm-ai-engineering.md +86 -0
- package/skills/architect/specialists/ml-systems.md +81 -0
- package/skills/commit/SKILL.md +5 -2
- package/skills/confirm/SKILL.md +3 -3
- package/skills/investigate/SKILL.md +14 -4
- package/skills/lite/SKILL.md +45 -0
- package/skills/qa/SKILL.md +274 -0
- package/skills/review/SKILL.md +187 -8
- package/skills/review/specialists/api-contract.md +122 -0
- package/skills/review/specialists/architecture-trace.md +64 -0
- package/skills/review/specialists/data-migration.md +113 -0
- package/skills/review/specialists/maintainability.md +138 -0
- package/skills/review/specialists/performance.md +115 -0
- package/skills/review/specialists/security.md +132 -0
- package/skills/review/specialists/testing.md +109 -0
- package/skills/session/SKILL.md +87 -0
- package/skills/session/operations/auto.md +22 -0
- package/skills/session/operations/checkpoint.md +43 -0
- package/skills/session/operations/end.md +35 -0
- package/skills/session/operations/init.md +16 -0
- package/skills/session/operations/restore.md +16 -0
- package/skills/spec/SKILL.md +40 -1
- package/skills/test/SKILL.md +89 -0
- package/skills/test/specialists/eval.md +46 -0
- package/skills/test/specialists/integration.md +42 -0
- package/skills/test/specialists/unit.md +39 -0
- package/skills/understand/SKILL.md +118 -0
- package/skills/understand/modes/repo.md +38 -0
- package/skills/understand/modes/stack.md +41 -0
- package/skills/uv-help/SKILL.md +43 -20
- package/uv.sh +36 -3
- package/watchtower/README.md +73 -0
- package/watchtower/app/__init__.py +0 -0
- package/watchtower/app/__pycache__/__init__.cpython-312.pyc +0 -0
- package/watchtower/app/__pycache__/__init__.cpython-314.pyc +0 -0
- package/watchtower/app/__pycache__/db.cpython-312.pyc +0 -0
- package/watchtower/app/__pycache__/db.cpython-314.pyc +0 -0
- package/watchtower/app/__pycache__/main.cpython-312.pyc +0 -0
- package/watchtower/app/__pycache__/main.cpython-314.pyc +0 -0
- package/watchtower/app/__pycache__/models.cpython-312.pyc +0 -0
- package/watchtower/app/__pycache__/models.cpython-314.pyc +0 -0
- package/watchtower/app/db.py +129 -0
- package/watchtower/app/main.py +43 -0
- package/watchtower/app/models.py +54 -0
- package/watchtower/app/routers/__init__.py +0 -0
- package/watchtower/app/routers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/watchtower/app/routers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/control.cpython-312.pyc +0 -0
- package/watchtower/app/routers/__pycache__/control.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/ingest.cpython-312.pyc +0 -0
- package/watchtower/app/routers/__pycache__/ingest.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/query.cpython-312.pyc +0 -0
- package/watchtower/app/routers/__pycache__/query.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/settings.cpython-312.pyc +0 -0
- package/watchtower/app/routers/__pycache__/stream.cpython-312.pyc +0 -0
- package/watchtower/app/routers/__pycache__/stream.cpython-314.pyc +0 -0
- package/watchtower/app/routers/control.py +260 -0
- package/watchtower/app/routers/ingest.py +157 -0
- package/watchtower/app/routers/query.py +133 -0
- package/watchtower/app/routers/settings.py +34 -0
- package/watchtower/app/routers/stream.py +28 -0
- package/watchtower/app/services/__init__.py +0 -0
- package/watchtower/app/services/__pycache__/__init__.cpython-312.pyc +0 -0
- package/watchtower/app/services/__pycache__/__init__.cpython-314.pyc +0 -0
- package/watchtower/app/services/__pycache__/checkpoint.cpython-312.pyc +0 -0
- package/watchtower/app/services/__pycache__/checkpoint.cpython-314.pyc +0 -0
- package/watchtower/app/services/__pycache__/tmux.cpython-312.pyc +0 -0
- package/watchtower/app/services/__pycache__/tmux.cpython-314.pyc +0 -0
- package/watchtower/app/services/checkpoint.py +149 -0
- package/watchtower/app/services/tmux.py +54 -0
- package/watchtower/events.json +10344 -45
- package/watchtower/{auto-checkpoint-runner.js → legacy/auto-checkpoint-runner.js} +29 -2
- package/watchtower/requirements.txt +3 -0
- package/watchtower/static/dashboard.html +577 -0
- package/watchtower/watchtower.db +0 -0
- package/agents/claude-code/devops.md +0 -50
- package/agents/claude-code/security.md +0 -75
- package/agents/codex/anti-slop-guard.toml +0 -12
- package/agents/codex/architect.toml +0 -11
- package/agents/codex/cartographer.toml +0 -16
- package/agents/codex/devops.toml +0 -8
- package/agents/codex/eval-writer.toml +0 -11
- package/agents/codex/prototype-builder.toml +0 -10
- package/agents/codex/reviewer.toml +0 -16
- package/agents/codex/security.toml +0 -14
- package/agents/codex/spec-writer.toml +0 -11
- package/agents/codex/test-writer.toml +0 -13
- package/agents/cursor/anti-slop-guard.mdc +0 -22
- package/agents/cursor/architect.mdc +0 -24
- package/agents/cursor/cartographer.mdc +0 -28
- package/agents/cursor/devops.mdc +0 -16
- package/agents/cursor/eval-writer.mdc +0 -21
- package/agents/cursor/prototype-builder.mdc +0 -25
- package/agents/cursor/reviewer.mdc +0 -26
- package/agents/cursor/security.mdc +0 -20
- package/agents/cursor/spec-writer.mdc +0 -27
- package/agents/cursor/test-writer.mdc +0 -28
- package/agents/portable/anti-slop-guard.md +0 -71
- package/agents/portable/architect.md +0 -83
- package/agents/portable/cartographer.md +0 -64
- package/agents/portable/devops.md +0 -56
- package/agents/portable/eval-writer.md +0 -70
- package/agents/portable/prototype-builder.md +0 -70
- package/agents/portable/reviewer.md +0 -79
- package/agents/portable/security.md +0 -63
- package/agents/portable/spec-writer.md +0 -89
- package/agents/portable/test-writer.md +0 -56
- package/hooks/context-warning.sh +0 -4
- package/skills/auto-checkpoint/SKILL.md +0 -47
- package/skills/checkpoint/SKILL.md +0 -105
- package/skills/map-codebase/SKILL.md +0 -54
- package/skills/map-stack/SKILL.md +0 -121
- package/skills/restore/SKILL.md +0 -55
- package/skills/security-review/SKILL.md +0 -87
- package/skills/session-end/SKILL.md +0 -100
- package/skills/session-init/SKILL.md +0 -45
- package/skills/slop-check/SKILL.md +0 -40
- package/skills/write-evals/SKILL.md +0 -34
- package/skills/write-tests/SKILL.md +0 -54
- /package/watchtower/{auto-checkpoint-prompt.md → legacy/auto-checkpoint-prompt.md} +0 -0
- /package/watchtower/{dashboard.html → legacy/dashboard.html} +0 -0
- /package/watchtower/{server.js → legacy/server.js} +0 -0
- /package/watchtower/{snapshot-manager.js → legacy/snapshot-manager.js} +0 -0
package/bin/cli.js
CHANGED
|
@@ -34,6 +34,7 @@ function usage() {
|
|
|
34
34
|
Monitoring:
|
|
35
35
|
uvs watch Start Watchtower dashboard (open browser)
|
|
36
36
|
uvs watch --bg Start Watchtower in background
|
|
37
|
+
uvs watch --legacy Start the legacy Node Watchtower
|
|
37
38
|
|
|
38
39
|
Personas:
|
|
39
40
|
spike Research & docs (Opus, max effort)
|
|
@@ -182,19 +183,19 @@ function prompt(rl, question) {
|
|
|
182
183
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
186
|
+
// Prompt for one of a fixed set of values (enum). Accepts the number, the full
|
|
187
|
+
// value, or a unique prefix; Enter skips (returns ""). Re-prompts on invalid input.
|
|
188
|
+
async function promptChoice(rl, label, options) {
|
|
189
|
+
const menu = options.map((o, i) => `${i + 1}=${o}`).join(" ");
|
|
190
|
+
for (;;) {
|
|
191
|
+
const raw = (await prompt(rl, `${label} (${menu}, Enter to skip): `)).trim().toLowerCase();
|
|
192
|
+
if (!raw) return "";
|
|
193
|
+
const n = parseInt(raw, 10);
|
|
194
|
+
if (n >= 1 && n <= options.length) return options[n - 1];
|
|
195
|
+
const matches = options.filter((o) => o === raw || o.startsWith(raw));
|
|
196
|
+
if (matches.length === 1) return matches[0];
|
|
197
|
+
console.log(` ? pick one of: ${options.join(", ")} (or its number)`);
|
|
198
|
+
}
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
// Generate a UVS_SESSION_ID, prompt for metadata (name/kind/purpose/priority),
|
|
@@ -220,13 +221,11 @@ async function setupSession(persona) {
|
|
|
220
221
|
});
|
|
221
222
|
console.log("");
|
|
222
223
|
console.log("Label this session (Enter to skip — you'll be reminded):");
|
|
223
|
-
name = (await prompt(rl, " name:
|
|
224
|
-
|
|
225
|
-
purpose = (await prompt(rl, " purpose:
|
|
226
|
-
|
|
224
|
+
name = (await prompt(rl, " name: ")).trim();
|
|
225
|
+
kind = await promptChoice(rl, " kind", ["long-running", "outcome"]);
|
|
226
|
+
purpose = (await prompt(rl, " purpose: ")).trim();
|
|
227
|
+
priority = await promptChoice(rl, " priority", ["low", "med", "high"]);
|
|
227
228
|
rl.close();
|
|
228
|
-
kind = normalizeKind(kindRaw);
|
|
229
|
-
priority = normalizePriority(priorityRaw);
|
|
230
229
|
}
|
|
231
230
|
|
|
232
231
|
const meta = {
|
|
@@ -253,6 +252,47 @@ function ensureInstalled(persona) {
|
|
|
253
252
|
syncPackageFiles(persona);
|
|
254
253
|
}
|
|
255
254
|
|
|
255
|
+
// Launch `tool` so Watchtower can control it: wrap it in a transparent tmux session
|
|
256
|
+
// (dedicated socket `uvs`) and register the handle, so the dashboard can checkpoint /
|
|
257
|
+
// close / approve it. Falls back to a direct spawn when tmux is unavailable, UVS_NO_TMUX
|
|
258
|
+
// is set, or we're already inside the wrapper.
|
|
259
|
+
function launchWrapped(tool, toolArgs, sid) {
|
|
260
|
+
const { spawnSync } = require("child_process");
|
|
261
|
+
const env = { ...process.env, UVS_SESSION_ID: sid };
|
|
262
|
+
const canTmux =
|
|
263
|
+
!process.env.UVS_NO_TMUX &&
|
|
264
|
+
!process.env.UVS_IN_TMUX &&
|
|
265
|
+
spawnSync("tmux", ["-V"], { stdio: "ignore" }).status === 0;
|
|
266
|
+
|
|
267
|
+
if (canTmux) {
|
|
268
|
+
const tname = "uvs_" + sid;
|
|
269
|
+
const wtUrl = process.env.UVS_WATCHTOWER_URL || "http://localhost:4200";
|
|
270
|
+
const shq = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
271
|
+
const inner =
|
|
272
|
+
"UVS_SESSION_ID=" + shq(sid) + " UVS_IN_TMUX=1 exec " + [tool, ...toolArgs].map(shq).join(" ");
|
|
273
|
+
const mk = spawnSync(
|
|
274
|
+
"tmux", ["-L", "uvs", "new-session", "-d", "-s", tname, "-c", process.cwd(), inner],
|
|
275
|
+
{ stdio: "ignore" },
|
|
276
|
+
);
|
|
277
|
+
if (mk.status === 0) {
|
|
278
|
+
spawnSync("tmux", ["-L", "uvs", "set", "-t", tname, "status", "off"], { stdio: "ignore" });
|
|
279
|
+
spawnSync(
|
|
280
|
+
"curl", ["-s", "-m", "2", wtUrl + "/sessions/register",
|
|
281
|
+
"-H", "Content-Type: application/json",
|
|
282
|
+
"-d", JSON.stringify({ id: sid, tmux_target: tname, pid: process.pid, cwd: process.cwd() })],
|
|
283
|
+
{ stdio: "ignore" },
|
|
284
|
+
);
|
|
285
|
+
const att = spawn("tmux", ["-L", "uvs", "attach", "-t", tname], { stdio: "inherit" });
|
|
286
|
+
att.on("exit", (code) => process.exit(code || 0));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// tmux new-session failed — fall through to a direct spawn.
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const child = spawn(tool, toolArgs, { stdio: "inherit", env });
|
|
293
|
+
child.on("exit", (code) => process.exit(code || 0));
|
|
294
|
+
}
|
|
295
|
+
|
|
256
296
|
async function launchClaude(persona, extra) {
|
|
257
297
|
syncPackageFiles(persona);
|
|
258
298
|
const settings = path.resolve(".claude/personas", `${persona}.json`);
|
|
@@ -267,11 +307,7 @@ async function launchClaude(persona, extra) {
|
|
|
267
307
|
console.log(`UV Suite | Claude Code | ${personaLabel(persona)}`);
|
|
268
308
|
console.log(`Session: ${sid.slice(0, 8)}${name ? " — " + name : ""}`);
|
|
269
309
|
console.log("");
|
|
270
|
-
|
|
271
|
-
stdio: "inherit",
|
|
272
|
-
env: { ...process.env, UVS_SESSION_ID: sid },
|
|
273
|
-
});
|
|
274
|
-
child.on("exit", (code) => process.exit(code || 0));
|
|
310
|
+
launchWrapped("claude", ["--settings", settings, ...extra], sid);
|
|
275
311
|
}
|
|
276
312
|
|
|
277
313
|
async function launchCodex(persona, extra) {
|
|
@@ -287,65 +323,102 @@ async function launchCodex(persona, extra) {
|
|
|
287
323
|
console.log(`UV Suite | Codex | ${personaLabel(persona)}`);
|
|
288
324
|
console.log(`Session: ${sid.slice(0, 8)}${name ? " — " + name : ""}`);
|
|
289
325
|
console.log("");
|
|
290
|
-
|
|
291
|
-
stdio: "inherit",
|
|
292
|
-
env: { ...process.env, UVS_SESSION_ID: sid },
|
|
293
|
-
});
|
|
294
|
-
child.on("exit", (code) => process.exit(code || 0));
|
|
326
|
+
launchWrapped("codex", [...codexArgs, ...extra], sid);
|
|
295
327
|
}
|
|
296
328
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
329
|
+
// Resolve how to run the Python app, provisioning deps on first run.
|
|
330
|
+
// Prefers `uv` (astral) if present; otherwise a venv at watchtower/.venv.
|
|
331
|
+
// Returns the argv prefix to which we append `uvicorn` args.
|
|
332
|
+
function ensurePyEnv(wtDir) {
|
|
333
|
+
const { spawnSync } = require("child_process");
|
|
334
|
+
const hasUv = spawnSync("uv", ["--version"], { stdio: "ignore" }).status === 0;
|
|
335
|
+
if (hasUv) {
|
|
336
|
+
// --native-tls: use the OS cert store so corporate SSL-inspection proxies don't break pypi.
|
|
337
|
+
return ["uv", "run", "--native-tls", "--python", "3.12", "--no-project", "--with-requirements", "requirements.txt", "--", "python", "-m"];
|
|
302
338
|
}
|
|
339
|
+
const venv = path.join(wtDir, ".venv");
|
|
340
|
+
const py =
|
|
341
|
+
process.platform === "win32"
|
|
342
|
+
? path.join(venv, "Scripts", "python.exe")
|
|
343
|
+
: path.join(venv, "bin", "python");
|
|
344
|
+
if (!fs.existsSync(py)) {
|
|
345
|
+
const python3 = spawnSync("python3", ["--version"], { stdio: "ignore" }).status === 0 ? "python3" : "python";
|
|
346
|
+
console.log("First run: creating Python env in watchtower/.venv (one-time)...");
|
|
347
|
+
if (spawnSync(python3, ["-m", "venv", ".venv"], { cwd: wtDir, stdio: "inherit" }).status !== 0) {
|
|
348
|
+
console.error("Failed to create venv. Install Python 3 (python3) and retry.");
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
console.log("Installing dependencies (fastapi, uvicorn, aiosqlite)...");
|
|
352
|
+
if (spawnSync(py, ["-m", "pip", "install", "-q", "-r", "requirements.txt"], { cwd: wtDir, stdio: "inherit" }).status !== 0) {
|
|
353
|
+
console.error("pip install failed (see output above).");
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return [py, "-m"];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// If a Watchtower is already running on this port, stop it so we start fresh —
|
|
361
|
+
// uvicorn doesn't hot-reload, so a long-running process serves stale routes while
|
|
362
|
+
// the (disk-served) dashboard updates. Only kills a process that answers /health
|
|
363
|
+
// like a Watchtower, so we never kill an unrelated app on the port.
|
|
364
|
+
function restartIfRunning(port) {
|
|
365
|
+
const { spawnSync } = require("child_process");
|
|
366
|
+
if (process.platform === "win32") return;
|
|
367
|
+
const health = spawnSync("curl", ["-s", "-m", "1", `http://localhost:${port}/health`], { encoding: "utf8" });
|
|
368
|
+
if (!(health.stdout || "").includes("status")) return;
|
|
369
|
+
const pids = (spawnSync("lsof", ["-ti", `tcp:${port}`], { encoding: "utf8" }).stdout || "")
|
|
370
|
+
.trim().split(/\s+/).filter(Boolean);
|
|
371
|
+
if (!pids.length) return;
|
|
372
|
+
spawnSync("kill", pids);
|
|
373
|
+
console.log(`Restarting Watchtower (stopped existing PID ${pids.join(", ")})`);
|
|
374
|
+
spawnSync("sleep", ["1"]); // let the port free up before rebinding
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function watch() {
|
|
378
|
+
const wtDir = path.join(UV_SUITE_DIR, "watchtower");
|
|
303
379
|
|
|
380
|
+
// Legacy fallback: the original Node Watchtower (no Postgres/Docker). `uvs watch --legacy`.
|
|
381
|
+
if (args.includes("--legacy")) {
|
|
382
|
+
const serverScript = path.join(wtDir, "legacy", "server.js");
|
|
383
|
+
if (!fs.existsSync(serverScript)) {
|
|
384
|
+
console.error("Error: legacy watchtower not found at", serverScript);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
const lbg = args.includes("--bg") || args.includes("--background");
|
|
388
|
+
const lurl = "http://localhost:" + (process.env.UVS_WATCHTOWER_PORT || 4200);
|
|
389
|
+
const lopener =
|
|
390
|
+
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
391
|
+
console.log("UV Suite Watchtower (legacy Node) starting...");
|
|
392
|
+
console.log("Dashboard: " + lurl);
|
|
393
|
+
console.log("");
|
|
394
|
+
setTimeout(() => spawn(lopener, [lurl], { stdio: "ignore" }), 1000);
|
|
395
|
+
const lchild = spawn("node", [serverScript], { stdio: lbg ? "ignore" : "inherit", detached: lbg });
|
|
396
|
+
if (lbg) {
|
|
397
|
+
lchild.unref();
|
|
398
|
+
console.log(`Running in background (PID: ${lchild.pid}). Stop with: kill ${lchild.pid}`);
|
|
399
|
+
} else {
|
|
400
|
+
lchild.on("exit", (code) => process.exit(code || 0));
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
304
404
|
const bg = args.includes("--bg") || args.includes("--background");
|
|
405
|
+
const port = process.env.UVS_WATCHTOWER_PORT || 4200;
|
|
406
|
+
const url = "http://localhost:" + port;
|
|
407
|
+
const opener =
|
|
408
|
+
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
409
|
+
|
|
410
|
+
// Python + SQLite, run locally (no Docker, no database to install).
|
|
411
|
+
restartIfRunning(port);
|
|
305
412
|
console.log("UV Suite Watchtower starting...");
|
|
306
|
-
console.log(
|
|
307
|
-
"Dashboard: http://localhost:" + (process.env.UVS_WATCHTOWER_PORT || 4200),
|
|
308
|
-
);
|
|
413
|
+
console.log("Dashboard: " + url);
|
|
309
414
|
console.log("");
|
|
310
|
-
|
|
415
|
+
const argv = [...ensurePyEnv(wtDir), "uvicorn", "app.main:app", "--host", "127.0.0.1", "--port", String(port)];
|
|
416
|
+
setTimeout(() => spawn(opener, [url], { stdio: "ignore" }), 1500);
|
|
417
|
+
const child = spawn(argv[0], argv.slice(1), { cwd: wtDir, stdio: bg ? "ignore" : "inherit", detached: bg });
|
|
311
418
|
if (bg) {
|
|
312
|
-
const child = spawn("node", [serverScript], {
|
|
313
|
-
stdio: "ignore",
|
|
314
|
-
detached: true,
|
|
315
|
-
});
|
|
316
419
|
child.unref();
|
|
317
|
-
console.log(`Running in background (PID: ${child.pid})`);
|
|
318
|
-
console.log("Stop with: kill " + child.pid);
|
|
319
|
-
|
|
320
|
-
// Open browser
|
|
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
|
-
);
|
|
420
|
+
console.log(`Running in background (PID: ${child.pid}). Stop with: kill ${child.pid}`);
|
|
332
421
|
} else {
|
|
333
|
-
// Foreground — open browser after a short delay
|
|
334
|
-
setTimeout(() => {
|
|
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
|
-
);
|
|
346
|
-
}, 1000);
|
|
347
|
-
|
|
348
|
-
const child = spawn("node", [serverScript], { stdio: "inherit" });
|
|
349
422
|
child.on("exit", (code) => process.exit(code || 0));
|
|
350
423
|
}
|
|
351
424
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite helper: read or change auto-checkpoint settings.
|
|
3
|
-
# Used by the /auto
|
|
3
|
+
# Used by the /session auto slash command.
|
|
4
4
|
#
|
|
5
5
|
# Usage:
|
|
6
6
|
# auto-checkpoint-helper.sh status
|
|
@@ -66,7 +66,7 @@ case "$ARG" in
|
|
|
66
66
|
set_field interval_minutes "$ARG"
|
|
67
67
|
echo "Auto-checkpoint interval: $ARG min (mode: $(get_field mode))"
|
|
68
68
|
else
|
|
69
|
-
echo "Usage: /auto
|
|
69
|
+
echo "Usage: /session auto [on | off | <minutes 1-1440> | status]"
|
|
70
70
|
exit 1
|
|
71
71
|
fi
|
|
72
72
|
;;
|
package/hooks/auto-checkpoint.sh
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# Logs every tool call into a per-session activity log. When `interval_minutes`
|
|
6
6
|
# have passed since the last mechanical checkpoint AND there has been activity
|
|
7
7
|
# in the interval, writes a deterministic snapshot to
|
|
8
|
-
# uv-out/
|
|
8
|
+
# uv-out/sessions/<sid>/checkpoints/auto-<ts>-mechanical.md and forwards an
|
|
9
9
|
# AutoCheckpoint event to the watchtower.
|
|
10
10
|
#
|
|
11
11
|
# Tier B (semantic, claude -p) runs separately from the watchtower's timer.
|
|
@@ -96,7 +96,7 @@ fi
|
|
|
96
96
|
|
|
97
97
|
# Resolve checkpoint dir + metadata via the existing helper
|
|
98
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/
|
|
99
|
+
[ -z "$CP_DIR" ] && CP_DIR="$PROJECT_DIR/uv-out/sessions/$SID/checkpoints" && mkdir -p "$CP_DIR"
|
|
100
100
|
|
|
101
101
|
# Build the mechanical checkpoint body
|
|
102
102
|
TS_FILE=$(date +%Y-%m-%d-%H%M)
|
|
@@ -160,7 +160,7 @@ GIT_LOG=$(cd "$PROJECT_DIR" && git log --oneline -5 2>/dev/null)
|
|
|
160
160
|
fi
|
|
161
161
|
} > "$CP_FILE"
|
|
162
162
|
|
|
163
|
-
# Update latest.md so /restore picks it up
|
|
163
|
+
# Update latest.md so /session restore picks it up
|
|
164
164
|
cp "$CP_FILE" "$CP_DIR/latest.md" 2>/dev/null
|
|
165
165
|
echo "$NOW" > "$LAST_CP_FILE"
|
|
166
166
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite Hook: SessionStart — when UVS_RESTORE_FROM is set, inject a
|
|
3
|
-
# system-context instruction telling Claude to /restore that session as
|
|
3
|
+
# system-context instruction telling Claude to /session restore that session as
|
|
4
4
|
# the first action of the new session.
|
|
5
5
|
#
|
|
6
6
|
# This is how the Watchtower's "Open in new terminal" restore button hooks
|
|
@@ -20,7 +20,7 @@ case "$UVS_RESTORE_FROM" in
|
|
|
20
20
|
;;
|
|
21
21
|
esac
|
|
22
22
|
|
|
23
|
-
MSG="[uv-suite auto-restore] This session was opened via the Watchtower restore flow. Your FIRST action must be to run \`/restore $UVS_RESTORE_FROM\` to load the prior session's latest checkpoint. After /restore completes, summarize what you picked up in 1-2 sentences, then wait for the user's next instruction."
|
|
23
|
+
MSG="[uv-suite auto-restore] This session was opened via the Watchtower restore flow. Your FIRST action must be to run \`/session restore $UVS_RESTORE_FROM\` to load the prior session's latest checkpoint. After /session restore completes, summarize what you picked up in 1-2 sentences, then wait for the user's next instruction."
|
|
24
24
|
|
|
25
25
|
if command -v jq >/dev/null 2>&1; then
|
|
26
26
|
jq -nc --arg ctx "$MSG" '{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:$ctx}}'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite helper: locate per-session checkpoint paths and print metadata.
|
|
3
|
-
# Used by the /checkpoint and /restore slash commands.
|
|
3
|
+
# Used by the /session checkpoint and /session restore slash commands.
|
|
4
4
|
#
|
|
5
5
|
# Usage:
|
|
6
6
|
# checkpoint-helper.sh dir # ensure + print the dir for current session
|
|
@@ -16,11 +16,14 @@ resolve_paths() {
|
|
|
16
16
|
if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
|
|
17
17
|
SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
|
|
18
18
|
fi
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
[ -z "$SID" ] && SID="no-session"
|
|
20
|
+
# Unified layout: everything a session produces lives under uv-out/sessions/<sid>/.
|
|
21
|
+
SESSIONS_ROOT="$PROJECT_DIR/uv-out/sessions"
|
|
22
|
+
SESSION_CP_DIR="$SESSIONS_ROOT/$SID/checkpoints"
|
|
23
|
+
# Legacy locations, read for backward compatibility (pre-unify checkpoints):
|
|
24
|
+
LEGACY_CP_ROOT="$PROJECT_DIR/uv-out/checkpoints"
|
|
25
|
+
LEGACY_SESSION_CP_DIR="$LEGACY_CP_ROOT/$SID"
|
|
26
|
+
META_FILE="$STATE_DIR/sessions/$SID.json"
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
print_meta_field() {
|
|
@@ -37,13 +40,8 @@ resolve_paths
|
|
|
37
40
|
|
|
38
41
|
case "$1" in
|
|
39
42
|
dir)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
echo "$SESSION_CP_DIR"
|
|
43
|
-
else
|
|
44
|
-
mkdir -p "$CHECKPOINTS_ROOT"
|
|
45
|
-
echo "$CHECKPOINTS_ROOT"
|
|
46
|
-
fi
|
|
43
|
+
mkdir -p "$SESSION_CP_DIR"
|
|
44
|
+
echo "$SESSION_CP_DIR"
|
|
47
45
|
;;
|
|
48
46
|
meta)
|
|
49
47
|
echo "uvs_session_id=${SID:-}"
|
|
@@ -73,25 +71,32 @@ checkpoint_at: ${NOW}
|
|
|
73
71
|
EOF
|
|
74
72
|
;;
|
|
75
73
|
latest)
|
|
76
|
-
if [ -
|
|
74
|
+
if [ -f "$SESSION_CP_DIR/latest.md" ]; then
|
|
77
75
|
cat "$SESSION_CP_DIR/latest.md"
|
|
78
|
-
elif [ -f "$
|
|
79
|
-
echo "(no
|
|
76
|
+
elif [ -f "$LEGACY_SESSION_CP_DIR/latest.md" ]; then
|
|
77
|
+
echo "(no checkpoint at new path; showing legacy uv-out/checkpoints/${SID}/latest.md)"
|
|
78
|
+
echo
|
|
79
|
+
cat "$LEGACY_SESSION_CP_DIR/latest.md"
|
|
80
|
+
elif [ -f "$LEGACY_CP_ROOT/latest.md" ]; then
|
|
81
|
+
echo "(no per-session checkpoint for ${SID}; showing legacy global latest.md)"
|
|
80
82
|
echo
|
|
81
|
-
cat "$
|
|
83
|
+
cat "$LEGACY_CP_ROOT/latest.md"
|
|
82
84
|
else
|
|
83
|
-
echo "No checkpoint found
|
|
85
|
+
echo "No checkpoint found for session ${SID}. Run /session checkpoint to create one."
|
|
84
86
|
fi
|
|
85
87
|
;;
|
|
86
88
|
list)
|
|
87
|
-
[ ! -d "$CHECKPOINTS_ROOT" ] && { echo "No checkpoints directory at $CHECKPOINTS_ROOT"; exit 0; }
|
|
88
89
|
found=0
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
cp_sid
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
seen=" "
|
|
91
|
+
emit_cp_entry() { # $1 = cp_sid $2 = checkpoint dir $3 = origin tag
|
|
92
|
+
local cp_sid="$1" d="$2" origin="$3"
|
|
93
|
+
[ -d "$d" ] || return 0
|
|
94
|
+
case "$seen" in *" $cp_sid "*) return 0 ;; esac # dedupe sid across new+legacy
|
|
95
|
+
local latest
|
|
96
|
+
latest=$(ls -t "$d"/*.md 2>/dev/null | head -1)
|
|
97
|
+
[ -z "$latest" ] && return 0
|
|
98
|
+
seen="$seen$cp_sid "
|
|
99
|
+
local cp_meta="$STATE_DIR/sessions/$cp_sid.json" cp_name="" cp_priority=""
|
|
95
100
|
if [ -f "$cp_meta" ]; then
|
|
96
101
|
if command -v jq >/dev/null 2>&1; then
|
|
97
102
|
cp_name=$(jq -r '.name // ""' "$cp_meta" 2>/dev/null)
|
|
@@ -100,20 +105,20 @@ EOF
|
|
|
100
105
|
cp_name=$(grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' "$cp_meta" | head -1 | sed 's/.*"name"[[:space:]]*:[[:space:]]*"\(.*\)"/\1/')
|
|
101
106
|
fi
|
|
102
107
|
fi
|
|
103
|
-
|
|
104
|
-
[ -z "$latest" ] && continue
|
|
108
|
+
local ts label mark
|
|
105
109
|
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
110
|
label="${cp_name:-(unlabeled)}"
|
|
107
111
|
[ -n "$cp_priority" ] && label="$label [p:$cp_priority]"
|
|
108
|
-
mark=" "
|
|
109
|
-
|
|
110
|
-
echo "$mark ${cp_sid:0:8} $ts $label"
|
|
112
|
+
mark=" "; [ "$cp_sid" = "$SID" ] && mark="*"
|
|
113
|
+
echo "$mark ${cp_sid:0:8} $ts $label${origin}"
|
|
111
114
|
found=1
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
}
|
|
116
|
+
# New unified layout first, then legacy, deduped by sid.
|
|
117
|
+
for d in "$SESSIONS_ROOT"/*/checkpoints/; do emit_cp_entry "$(basename "$(dirname "$d")")" "$d" ""; done
|
|
118
|
+
for d in "$LEGACY_CP_ROOT"/*/; do emit_cp_entry "$(basename "$d")" "$d" " (legacy path)"; done
|
|
119
|
+
[ "$found" -eq 0 ] && echo "No per-session checkpoints yet (current session: ${SID})"
|
|
120
|
+
if [ -f "$LEGACY_CP_ROOT/latest.md" ]; then
|
|
121
|
+
ts=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' "$LEGACY_CP_ROOT/latest.md" 2>/dev/null || stat -c '%y' "$LEGACY_CP_ROOT/latest.md" 2>/dev/null | cut -c1-16)
|
|
117
122
|
echo " legacy $ts (pre-metadata global latest.md)"
|
|
118
123
|
fi
|
|
119
124
|
;;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite helper: resolve git worktree context for a directory, as a JSON object.
|
|
3
|
+
# Used to stamp session metadata so the Watchtower can tell apart sessions running
|
|
4
|
+
# in different worktrees of the same repo (e.g. a feature worktree vs the main checkout).
|
|
5
|
+
#
|
|
6
|
+
# Usage: git-context.sh [dir] (defaults to current directory)
|
|
7
|
+
# Prints: {} if not a git repo, else:
|
|
8
|
+
# {
|
|
9
|
+
# "git_worktree": "/abs/path/to/worktree-root",
|
|
10
|
+
# "git_branch": "feature-x",
|
|
11
|
+
# "git_main_repo": "/abs/path/to/main/checkout",
|
|
12
|
+
# "git_is_linked_worktree": true|false
|
|
13
|
+
# }
|
|
14
|
+
|
|
15
|
+
DIR="${1:-.}"
|
|
16
|
+
cd "$DIR" 2>/dev/null || { echo '{}'; exit 0; }
|
|
17
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo '{}'; exit 0; }
|
|
18
|
+
|
|
19
|
+
WT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
20
|
+
BR=$(git branch --show-current 2>/dev/null)
|
|
21
|
+
GITDIR=$(git rev-parse --absolute-git-dir 2>/dev/null)
|
|
22
|
+
|
|
23
|
+
# A linked worktree's git dir lives under <main>/.git/worktrees/<name>.
|
|
24
|
+
case "$GITDIR" in
|
|
25
|
+
*/worktrees/*) LINKED=true ;;
|
|
26
|
+
*) LINKED=false ;;
|
|
27
|
+
esac
|
|
28
|
+
|
|
29
|
+
# Common dir points at the shared .git; its parent is the main checkout.
|
|
30
|
+
COMMON=$(git rev-parse --git-common-dir 2>/dev/null)
|
|
31
|
+
case "$COMMON" in /*) ;; *) COMMON="$WT/$COMMON" ;; esac
|
|
32
|
+
MAIN_REPO=$(cd "$(dirname "$COMMON")" 2>/dev/null && pwd)
|
|
33
|
+
[ -z "$MAIN_REPO" ] && MAIN_REPO="$WT"
|
|
34
|
+
|
|
35
|
+
if command -v jq >/dev/null 2>&1; then
|
|
36
|
+
jq -n --arg wt "$WT" --arg br "$BR" --arg main "$MAIN_REPO" --argjson linked "$LINKED" \
|
|
37
|
+
'{git_worktree: $wt, git_branch: $br, git_main_repo: $main, git_is_linked_worktree: $linked}'
|
|
38
|
+
else
|
|
39
|
+
printf '{"git_worktree":"%s","git_branch":"%s","git_main_repo":"%s","git_is_linked_worktree":%s}\n' \
|
|
40
|
+
"$WT" "$BR" "$MAIN_REPO" "$LINKED"
|
|
41
|
+
fi
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: Lite mode — inject terseness instructions
|
|
3
|
+
# Event: UserPromptSubmit
|
|
4
|
+
# Activates when UVS_LITE=1 OR .uv-suite-state/lite-mode.txt contains "on".
|
|
5
|
+
# Reduces output verbosity to save tokens.
|
|
6
|
+
|
|
7
|
+
STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
|
|
8
|
+
STATE_FILE="$STATE_DIR/lite-mode.txt"
|
|
9
|
+
|
|
10
|
+
is_lite="false"
|
|
11
|
+
[ "$UVS_LITE" = "1" ] && is_lite="true"
|
|
12
|
+
[ -f "$STATE_FILE" ] && [ "$(cat "$STATE_FILE" 2>/dev/null | tr -d '[:space:]')" = "on" ] && is_lite="true"
|
|
13
|
+
|
|
14
|
+
if [ "$is_lite" != "true" ]; then
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
cat <<'EOF'
|
|
19
|
+
{
|
|
20
|
+
"continue": true,
|
|
21
|
+
"hookSpecificOutput": {
|
|
22
|
+
"hookEventName": "UserPromptSubmit",
|
|
23
|
+
"additionalContext": "UV Suite lite mode is ACTIVE. Follow these rules for this turn:\n- No preamble. No \"I'll do X\" before doing it. Start with the action.\n- No end-of-turn summaries longer than one sentence.\n- No bullet lists unless the user explicitly asks for one.\n- No code comments unless the user asks.\n- No markdown headers (##, ###) in responses unless the user asks.\n- Inline single-sentence updates between tool calls; never multi-paragraph narration.\n- Cite file paths inline (file.ts:42), not as section headers.\n- Skip pleasantries, acknowledgments, and reassurances.\nThe user is token-constrained. Be useful, not thorough."
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
EOF
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite helper: explicit session termination.
|
|
3
|
-
# Used by the /session
|
|
3
|
+
# Used by the /session end slash command.
|
|
4
4
|
#
|
|
5
5
|
# Writes a "terminated_at" timestamp to the session metadata, fires a
|
|
6
6
|
# SessionEnd event to the Watchtower so the dashboard updates the status
|
|
@@ -53,4 +53,4 @@ print(json.dumps({
|
|
|
53
53
|
fi
|
|
54
54
|
|
|
55
55
|
echo "Session ${SID:0:8} marked terminated at $NOW_ISO."
|
|
56
|
-
echo "Run /checkpoint first if you want a final state snapshot, then exit the terminal."
|
|
56
|
+
echo "Run /session checkpoint first if you want a final state snapshot, then exit the terminal."
|
package/hooks/session-end.sh
CHANGED
|
@@ -33,11 +33,11 @@ UNTRACKED=$(git ls-files --others --exclude-standard 2>/dev/null | head -5)
|
|
|
33
33
|
|
|
34
34
|
REVIEW_MSG=""
|
|
35
35
|
if [ -n "$STAGED" ] || [ -n "$UNSTAGED" ] || [ -n "$UNTRACKED" ]; then
|
|
36
|
-
REVIEW_MSG="Uncommitted changes — consider /review and /slop
|
|
36
|
+
REVIEW_MSG="Uncommitted changes — consider /review and /review --slop before committing. "
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
39
|
# Checkpoint prompt
|
|
40
|
-
CHECKPOINT_MSG="Run /checkpoint to save session state for next time. Run /restore at the start of your next session."
|
|
40
|
+
CHECKPOINT_MSG="Run /session checkpoint to save session state for next time. Run /session restore at the start of your next session."
|
|
41
41
|
|
|
42
42
|
FULL_MSG="${DURATION_MSG}${REVIEW_MSG}${CHECKPOINT_MSG}"
|
|
43
43
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Reads the metadata file at .uv-suite-state/sessions/$UVS_SESSION_ID.json.
|
|
6
6
|
# If the session has no name, injects an additionalContext nudge once every
|
|
7
|
-
# Nth user prompt (default 3) so Claude reminds the user to run /session
|
|
7
|
+
# Nth user prompt (default 3) so Claude reminds the user to run /session init.
|
|
8
8
|
# Skips when the prompt is itself a slash command.
|
|
9
9
|
|
|
10
10
|
INPUT=$(cat)
|
|
@@ -53,7 +53,7 @@ if [ "$COUNT" -ne 1 ] && [ "$REMAINDER" -ne 1 ]; then
|
|
|
53
53
|
exit 0
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
|
-
MSG="[uv-suite] This session has no name yet. Briefly remind the user to run /session
|
|
56
|
+
MSG="[uv-suite] This session has no name yet. Briefly remind the user to run /session init to set name, kind (long-running/outcome), purpose, and priority — these label the session in the watchtower dashboard. One sentence is enough; then proceed with the user's request."
|
|
57
57
|
|
|
58
58
|
if command -v jq >/dev/null 2>&1; then
|
|
59
59
|
jq -nc --arg ctx "$MSG" '{hookSpecificOutput:{hookEventName:"UserPromptSubmit",additionalContext:$ctx}}'
|
package/hooks/session-meta.sh
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite helper: read or write session metadata.
|
|
3
|
-
# Used by the /session
|
|
3
|
+
# Used by the /session init slash command.
|
|
4
4
|
#
|
|
5
5
|
# Usage:
|
|
6
6
|
# session-meta.sh show
|
|
@@ -40,6 +40,18 @@ print(json.dumps({
|
|
|
40
40
|
' > "$META"
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
+
# Refresh git worktree context (worktree path, branch, main repo) so the Watchtower
|
|
44
|
+
# can distinguish sessions running in different worktrees of the same repo.
|
|
45
|
+
GIT_JSON=$("$(dirname "$0")/git-context.sh" "${CLAUDE_PROJECT_DIR:-$(pwd)}" 2>/dev/null)
|
|
46
|
+
if [ -n "$GIT_JSON" ] && [ "$GIT_JSON" != "{}" ] && command -v jq >/dev/null 2>&1; then
|
|
47
|
+
TMP=$(mktemp)
|
|
48
|
+
if jq -s '.[0] * .[1]' "$META" <(printf '%s' "$GIT_JSON") > "$TMP" 2>/dev/null; then
|
|
49
|
+
mv "$TMP" "$META"
|
|
50
|
+
else
|
|
51
|
+
rm -f "$TMP"
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
43
55
|
ACTION="$1"
|
|
44
56
|
shift || true
|
|
45
57
|
|
|
@@ -53,12 +65,17 @@ kind = d.get("kind", "") or "(unset)"
|
|
|
53
65
|
purpose = d.get("purpose", "") or "(unset)"
|
|
54
66
|
priority = d.get("priority", "") or "(unset)"
|
|
55
67
|
persona = d.get("persona", "") or "(unset)"
|
|
68
|
+
branch = d.get("git_branch", "") or "(unset)"
|
|
69
|
+
worktree = d.get("git_worktree", "") or "(unset)"
|
|
70
|
+
linked = d.get("git_is_linked_worktree", False)
|
|
56
71
|
print(f"session: {sid}")
|
|
57
72
|
print(f" name: {name}")
|
|
58
73
|
print(f" kind: {kind}")
|
|
59
74
|
print(f" purpose: {purpose}")
|
|
60
75
|
print(f" priority: {priority}")
|
|
61
76
|
print(f" persona: {persona}")
|
|
77
|
+
print(f" branch: {branch}")
|
|
78
|
+
print(f" worktree: {worktree}" + (" (linked)" if linked else ""))
|
|
62
79
|
PY
|
|
63
80
|
}
|
|
64
81
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite Hook: Remind to review before ending session
|
|
3
3
|
# Event: Stop
|
|
4
|
-
# If there are uncommitted changes, reminds the user to run /review and /slop
|
|
4
|
+
# If there are uncommitted changes, reminds the user to run /review and /review --slop.
|
|
5
5
|
|
|
6
6
|
# Check for uncommitted changes
|
|
7
7
|
STAGED=$(git diff --cached --stat 2>/dev/null)
|
|
@@ -28,7 +28,7 @@ fi
|
|
|
28
28
|
cat <<EOF
|
|
29
29
|
{
|
|
30
30
|
"continue": true,
|
|
31
|
-
"systemMessage": "SESSION END REMINDER: There are uncommitted changes in the working tree.\n\n${SUMMARY}\nConsider running /review and /slop
|
|
31
|
+
"systemMessage": "SESSION END REMINDER: There are uncommitted changes in the working tree.\n\n${SUMMARY}\nConsider running /review and /review --slop before committing."
|
|
32
32
|
}
|
|
33
33
|
EOF
|
|
34
34
|
|