uv-suite 0.29.0 → 0.30.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.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -35
  3. package/agents/claude-code/anti-slop-guard.md +14 -1
  4. package/agents/claude-code/architect.md +30 -4
  5. package/agents/claude-code/cartographer.md +18 -6
  6. package/agents/claude-code/eval-writer.md +7 -2
  7. package/agents/claude-code/reviewer.md +5 -1
  8. package/agents/claude-code/spec-writer.md +30 -7
  9. package/agents/generate.py +88 -0
  10. package/bin/cli.js +51 -48
  11. package/hooks/auto-checkpoint-helper.sh +2 -2
  12. package/hooks/auto-checkpoint.sh +3 -3
  13. package/hooks/auto-restore-on-start.sh +2 -2
  14. package/hooks/checkpoint-helper.sh +40 -35
  15. package/hooks/git-context.sh +41 -0
  16. package/hooks/lite-mode-inject.sh +26 -0
  17. package/hooks/session-end-helper.sh +2 -2
  18. package/hooks/session-end.sh +2 -2
  19. package/hooks/session-label-nag.sh +2 -2
  20. package/hooks/session-meta.sh +18 -1
  21. package/hooks/session-review-reminder.sh +2 -2
  22. package/hooks/session-start.sh +16 -0
  23. package/hooks/slop-grep.sh +12 -31
  24. package/hooks/uv-out-best.sh +20 -0
  25. package/hooks/uv-out-collect.sh +52 -0
  26. package/hooks/uv-out-notify.sh +28 -0
  27. package/hooks/uv-out-pointer.sh +16 -0
  28. package/hooks/uv-out-session.sh +24 -0
  29. package/hooks/watchtower-notify.sh +45 -0
  30. package/hooks/watchtower-send.sh +4 -0
  31. package/install.sh +93 -42
  32. package/package.json +2 -2
  33. package/personas/auto.json +35 -1
  34. package/personas/professional.json +41 -1
  35. package/personas/spike.json +27 -2
  36. package/personas/sport.json +39 -1
  37. package/settings.json +6 -2
  38. package/skills/architect/SKILL.md +109 -8
  39. package/skills/architect/specialists/distributed-systems.md +84 -0
  40. package/skills/architect/specialists/full-stack.md +92 -0
  41. package/skills/architect/specialists/llm-ai-engineering.md +86 -0
  42. package/skills/architect/specialists/ml-systems.md +81 -0
  43. package/skills/commit/SKILL.md +5 -2
  44. package/skills/confirm/SKILL.md +3 -3
  45. package/skills/investigate/SKILL.md +14 -4
  46. package/skills/lite/SKILL.md +45 -0
  47. package/skills/qa/SKILL.md +274 -0
  48. package/skills/review/SKILL.md +187 -8
  49. package/skills/review/specialists/api-contract.md +122 -0
  50. package/skills/review/specialists/architecture-trace.md +64 -0
  51. package/skills/review/specialists/data-migration.md +113 -0
  52. package/skills/review/specialists/maintainability.md +138 -0
  53. package/skills/review/specialists/performance.md +115 -0
  54. package/skills/review/specialists/security.md +132 -0
  55. package/skills/review/specialists/testing.md +109 -0
  56. package/skills/session/SKILL.md +87 -0
  57. package/skills/session/operations/auto.md +22 -0
  58. package/skills/session/operations/checkpoint.md +43 -0
  59. package/skills/session/operations/end.md +35 -0
  60. package/skills/session/operations/init.md +16 -0
  61. package/skills/session/operations/restore.md +16 -0
  62. package/skills/spec/SKILL.md +40 -1
  63. package/skills/test/SKILL.md +89 -0
  64. package/skills/test/specialists/eval.md +46 -0
  65. package/skills/test/specialists/integration.md +42 -0
  66. package/skills/test/specialists/unit.md +39 -0
  67. package/skills/understand/SKILL.md +118 -0
  68. package/skills/understand/modes/repo.md +38 -0
  69. package/skills/understand/modes/stack.md +41 -0
  70. package/skills/uv-help/SKILL.md +43 -20
  71. package/uv.sh +36 -3
  72. package/watchtower/Dockerfile +9 -0
  73. package/watchtower/README.md +78 -0
  74. package/watchtower/app/__init__.py +0 -0
  75. package/watchtower/app/__pycache__/__init__.cpython-314.pyc +0 -0
  76. package/watchtower/app/__pycache__/db.cpython-314.pyc +0 -0
  77. package/watchtower/app/__pycache__/main.cpython-314.pyc +0 -0
  78. package/watchtower/app/__pycache__/models.cpython-314.pyc +0 -0
  79. package/watchtower/app/db.py +85 -0
  80. package/watchtower/app/main.py +45 -0
  81. package/watchtower/app/models.py +49 -0
  82. package/watchtower/app/routers/__init__.py +0 -0
  83. package/watchtower/app/routers/__pycache__/__init__.cpython-314.pyc +0 -0
  84. package/watchtower/app/routers/__pycache__/control.cpython-314.pyc +0 -0
  85. package/watchtower/app/routers/__pycache__/ingest.cpython-314.pyc +0 -0
  86. package/watchtower/app/routers/__pycache__/query.cpython-314.pyc +0 -0
  87. package/watchtower/app/routers/__pycache__/stream.cpython-314.pyc +0 -0
  88. package/watchtower/app/routers/control.py +144 -0
  89. package/watchtower/app/routers/ingest.py +102 -0
  90. package/watchtower/app/routers/query.py +84 -0
  91. package/watchtower/app/routers/stream.py +30 -0
  92. package/watchtower/app/services/__init__.py +0 -0
  93. package/watchtower/app/services/__pycache__/__init__.cpython-314.pyc +0 -0
  94. package/watchtower/app/services/__pycache__/checkpoint.cpython-314.pyc +0 -0
  95. package/watchtower/app/services/__pycache__/tmux.cpython-314.pyc +0 -0
  96. package/watchtower/app/services/checkpoint.py +107 -0
  97. package/watchtower/app/services/tmux.py +54 -0
  98. package/watchtower/docker-compose.yml +22 -0
  99. package/watchtower/events.json +10344 -45
  100. package/watchtower/{auto-checkpoint-runner.js → legacy/auto-checkpoint-runner.js} +29 -2
  101. package/watchtower/requirements.txt +3 -0
  102. package/watchtower/schema.sql +43 -0
  103. package/watchtower/static/dashboard.html +449 -0
  104. package/agents/claude-code/devops.md +0 -50
  105. package/agents/claude-code/security.md +0 -75
  106. package/agents/codex/anti-slop-guard.toml +0 -12
  107. package/agents/codex/architect.toml +0 -11
  108. package/agents/codex/cartographer.toml +0 -16
  109. package/agents/codex/devops.toml +0 -8
  110. package/agents/codex/eval-writer.toml +0 -11
  111. package/agents/codex/prototype-builder.toml +0 -10
  112. package/agents/codex/reviewer.toml +0 -16
  113. package/agents/codex/security.toml +0 -14
  114. package/agents/codex/spec-writer.toml +0 -11
  115. package/agents/codex/test-writer.toml +0 -13
  116. package/agents/cursor/anti-slop-guard.mdc +0 -22
  117. package/agents/cursor/architect.mdc +0 -24
  118. package/agents/cursor/cartographer.mdc +0 -28
  119. package/agents/cursor/devops.mdc +0 -16
  120. package/agents/cursor/eval-writer.mdc +0 -21
  121. package/agents/cursor/prototype-builder.mdc +0 -25
  122. package/agents/cursor/reviewer.mdc +0 -26
  123. package/agents/cursor/security.mdc +0 -20
  124. package/agents/cursor/spec-writer.mdc +0 -27
  125. package/agents/cursor/test-writer.mdc +0 -28
  126. package/agents/portable/anti-slop-guard.md +0 -71
  127. package/agents/portable/architect.md +0 -83
  128. package/agents/portable/cartographer.md +0 -64
  129. package/agents/portable/devops.md +0 -56
  130. package/agents/portable/eval-writer.md +0 -70
  131. package/agents/portable/prototype-builder.md +0 -70
  132. package/agents/portable/reviewer.md +0 -79
  133. package/agents/portable/security.md +0 -63
  134. package/agents/portable/spec-writer.md +0 -89
  135. package/agents/portable/test-writer.md +0 -56
  136. package/hooks/context-warning.sh +0 -4
  137. package/skills/auto-checkpoint/SKILL.md +0 -47
  138. package/skills/checkpoint/SKILL.md +0 -105
  139. package/skills/map-codebase/SKILL.md +0 -54
  140. package/skills/map-stack/SKILL.md +0 -121
  141. package/skills/restore/SKILL.md +0 -55
  142. package/skills/security-review/SKILL.md +0 -87
  143. package/skills/session-end/SKILL.md +0 -100
  144. package/skills/session-init/SKILL.md +0 -45
  145. package/skills/slop-check/SKILL.md +0 -40
  146. package/skills/write-evals/SKILL.md +0 -34
  147. package/skills/write-tests/SKILL.md +0 -54
  148. /package/watchtower/{auto-checkpoint-prompt.md → legacy/auto-checkpoint-prompt.md} +0 -0
  149. /package/watchtower/{dashboard.html → legacy/dashboard.html} +0 -0
  150. /package/watchtower/{server.js → legacy/server.js} +0 -0
  151. /package/watchtower/{snapshot-manager.js → legacy/snapshot-manager.js} +0 -0
@@ -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
- CHECKPOINTS_ROOT="$PROJECT_DIR/uv-out/checkpoints"
20
- SESSION_CP_DIR=""
21
- [ -n "$SID" ] && SESSION_CP_DIR="$CHECKPOINTS_ROOT/$SID"
22
- META_FILE=""
23
- [ -n "$SID" ] && META_FILE="$STATE_DIR/sessions/$SID.json"
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
- if [ -n "$SESSION_CP_DIR" ]; then
41
- mkdir -p "$SESSION_CP_DIR"
42
- echo "$SESSION_CP_DIR"
43
- else
44
- mkdir -p "$CHECKPOINTS_ROOT"
45
- echo "$CHECKPOINTS_ROOT"
46
- fi
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 [ -n "$SESSION_CP_DIR" ] && [ -f "$SESSION_CP_DIR/latest.md" ]; then
74
+ if [ -f "$SESSION_CP_DIR/latest.md" ]; then
77
75
  cat "$SESSION_CP_DIR/latest.md"
78
- elif [ -f "$CHECKPOINTS_ROOT/latest.md" ]; then
79
- echo "(no per-session checkpoint for ${SID:-this session}; showing legacy global latest.md)"
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 "$CHECKPOINTS_ROOT/latest.md"
83
+ cat "$LEGACY_CP_ROOT/latest.md"
82
84
  else
83
- echo "No checkpoint found at $CHECKPOINTS_ROOT. Run /checkpoint to create one."
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
- for d in "$CHECKPOINTS_ROOT"/*/; do
90
- [ -d "$d" ] || continue
91
- cp_sid=$(basename "$d")
92
- cp_meta="$STATE_DIR/sessions/$cp_sid.json"
93
- cp_name=""
94
- cp_priority=""
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
- latest=$(ls -t "$d"*.md 2>/dev/null | head -1)
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
- [ "$cp_sid" = "$SID" ] && mark="*"
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
- done
113
- [ "$found" -eq 0 ] && echo "No per-session checkpoints yet (current session: ${SID:-none})"
114
- # Note legacy global checkpoint if present
115
- if [ -f "$CHECKPOINTS_ROOT/latest.md" ]; then
116
- ts=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' "$CHECKPOINTS_ROOT/latest.md" 2>/dev/null || stat -c '%y' "$CHECKPOINTS_ROOT/latest.md" 2>/dev/null | cut -c1-16)
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-end slash command.
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."
@@ -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-check before committing. "
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-init.
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-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."
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}}'
@@ -1,6 +1,6 @@
1
1
  #!/bin/bash
2
2
  # UV Suite helper: read or write session metadata.
3
- # Used by the /session-init slash command.
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-check.
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-check before committing."
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
 
@@ -28,6 +28,18 @@ PAYLOAD=""
28
28
  META_FILE=""
29
29
  [ -n "$SID" ] && META_FILE="$SESSIONS_DIR/$SID.json"
30
30
 
31
+ # Refresh git worktree context into the session metadata at each launch (branch can
32
+ # change between sessions), so the Watchtower sees which worktree this session runs in.
33
+ GIT_JSON=$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/git-context.sh" "${CLAUDE_PROJECT_DIR:-.}" 2>/dev/null)
34
+ if [ -n "$META_FILE" ] && [ -f "$META_FILE" ] && [ -n "$GIT_JSON" ] && [ "$GIT_JSON" != "{}" ] && command -v jq >/dev/null 2>&1; then
35
+ TMP=$(mktemp)
36
+ if jq -s '.[0] * .[1]' "$META_FILE" <(printf '%s' "$GIT_JSON") > "$TMP" 2>/dev/null; then
37
+ mv "$TMP" "$META_FILE"
38
+ else
39
+ rm -f "$TMP"
40
+ fi
41
+ fi
42
+
31
43
  if [ -n "$META_FILE" ] && [ -f "$META_FILE" ] && command -v jq >/dev/null 2>&1; then
32
44
  PAYLOAD=$(echo "$INPUT" | jq -c --slurpfile m "$META_FILE" '
33
45
  . + {
@@ -37,6 +49,10 @@ if [ -n "$META_FILE" ] && [ -f "$META_FILE" ] && command -v jq >/dev/null 2>&1;
37
49
  session_purpose: ($m[0].purpose // ""),
38
50
  session_priority: ($m[0].priority // ""),
39
51
  persona: ($m[0].persona // ""),
52
+ git_worktree: ($m[0].git_worktree // ""),
53
+ git_branch: ($m[0].git_branch // ""),
54
+ git_main_repo: ($m[0].git_main_repo // ""),
55
+ git_is_linked_worktree: ($m[0].git_is_linked_worktree // false),
40
56
  cwd: (.cwd // $m[0].cwd // "")
41
57
  }' 2>/dev/null)
42
58
  fi
@@ -1,7 +1,8 @@
1
1
  #!/bin/bash
2
2
  # UV Suite Hook: Fast deterministic slop check
3
3
  # Event: PostToolUse (Edit|Write)
4
- # Greps for mechanical, unambiguous slop patterns. No LLM, no false positives.
4
+ # Catches one mechanical pattern: .toBeTruthy() / .toBeDefined() in JS/TS
5
+ # test files. No LLM. Conservative on purpose — false positives erode trust.
5
6
 
6
7
  INPUT=$(cat)
7
8
  FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
@@ -10,40 +11,20 @@ if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
10
11
  exit 0
11
12
  fi
12
13
 
13
- EXT="${FILE_PATH##*.}"
14
- FINDINGS=""
14
+ # Only check JS/TS test files
15
+ if ! echo "$FILE_PATH" | grep -qE "\.(test|spec)\.(ts|tsx|js|jsx)$"; then
16
+ exit 0
17
+ fi
15
18
 
16
- case "$EXT" in
17
- ts|tsx|js|jsx)
18
- # toBeTruthy/toBeDefined in test files
19
- if echo "$FILE_PATH" | grep -qE "\.(test|spec)\.(ts|tsx|js|jsx)$"; then
20
- MATCH=$(grep -n "\.toBeTruthy()\|\.toBeDefined()" "$FILE_PATH" 2>/dev/null)
21
- if [ -n "$MATCH" ]; then
22
- FINDINGS="${FINDINGS}Weak assertion in $FILE_PATH: $MATCH. Test specific values instead. "
23
- fi
24
- fi
25
- # catch-and-rethrow (catch { ...throw } with nothing meaningful between)
26
- MATCH=$(grep -n "catch.*{" "$FILE_PATH" 2>/dev/null | head -3)
27
- ;;
28
- py)
29
- # bare except: pass
30
- MATCH=$(grep -n "except:$\|except Exception:$" "$FILE_PATH" 2>/dev/null)
31
- if [ -n "$MATCH" ]; then
32
- NEXT=$(grep -A1 "except" "$FILE_PATH" 2>/dev/null | grep -c "pass\|raise")
33
- if [ "$NEXT" -gt 0 ]; then
34
- FINDINGS="${FINDINGS}Bare except with pass/raise in $FILE_PATH. "
35
- fi
36
- fi
37
- ;;
38
- esac
19
+ MATCH=$(grep -n "\.toBeTruthy()\|\.toBeDefined()" "$FILE_PATH" 2>/dev/null)
20
+ if [ -z "$MATCH" ]; then
21
+ exit 0
22
+ fi
39
23
 
40
- if [ -n "$FINDINGS" ]; then
41
- cat <<EOF
24
+ cat <<EOF
42
25
  {
43
26
  "continue": true,
44
- "systemMessage": "Slop detected: ${FINDINGS}"
27
+ "systemMessage": "Slop detected: weak assertion in $FILE_PATH:\n$MATCH\n\nTest specific values, not truthiness. See guardrails/test-slop.md."
45
28
  }
46
29
  EOF
47
- fi
48
-
49
30
  exit 0
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+ # UV Suite helper: print the content of the best-matching artifact across sessions —
3
+ # current session, else newest prior session, else legacy flat. A session-aware
4
+ # drop-in for `cat uv-out/<X>` in skill preloads.
5
+ #
6
+ # Usage: uv-out-best.sh <relative-glob> [headLines]
7
+ # e.g. uv-out-best.sh map-codebase.md 100
8
+
9
+ GLOB="$1"; HEAD="${2:-0}"
10
+ [ -z "$GLOB" ] && exit 1
11
+
12
+ # uv-out-collect.sh ranks current -> prior -> legacy; take the first path (field 4).
13
+ FIRST=$("$(dirname "$0")/uv-out-collect.sh" "$GLOB" 2>/dev/null | head -1 | cut -f4)
14
+ [ -z "$FIRST" ] || [ ! -f "$FIRST" ] && exit 1
15
+
16
+ if [ "$HEAD" -gt 0 ] 2>/dev/null; then
17
+ head -n "$HEAD" "$FIRST"
18
+ else
19
+ cat "$FIRST"
20
+ fi
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # UV Suite helper: list an artifact across sessions for "prefer current, allow prior".
3
+ # Usage: uv-out-collect.sh <relative-glob> e.g. specs/*.md or map-codebase.md
4
+ #
5
+ # Prints tab-separated lines, current session first, then other sessions
6
+ # (newest session first), then legacy flat uv-out/ files:
7
+ # CURRENT <session-name> <YYYY-MM-DD> <path>
8
+ # PRIOR <session-name> <YYYY-MM-DD> <path>
9
+ # LEGACY - <YYYY-MM-DD> <path>
10
+ # A skill shows these and asks the user which to use (default: the CURRENT one,
11
+ # else the newest PRIOR).
12
+
13
+ GLOB="$1"
14
+ [ -z "$GLOB" ] && exit 0
15
+
16
+ ROOT="${CLAUDE_PROJECT_DIR:-.}"
17
+ STATE_DIR="$ROOT/.uv-suite-state"
18
+ SESS_META="$STATE_DIR/sessions"
19
+
20
+ SID="${UVS_SESSION_ID:-}"
21
+ if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
22
+ SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
23
+ fi
24
+
25
+ # Human label for a session id: its name from the meta json, else the short id.
26
+ label_for() {
27
+ local sid="$1" meta="$SESS_META/$1.json" name=""
28
+ if [ -f "$meta" ] && command -v jq >/dev/null 2>&1; then
29
+ name=$(jq -r '.name // ""' "$meta" 2>/dev/null)
30
+ fi
31
+ [ -n "$name" ] && printf '%s' "$name" || printf '%s' "${sid:0:8}"
32
+ }
33
+
34
+ emit() { # kind session-label path
35
+ [ -e "$3" ] || return 0
36
+ local d
37
+ d=$(date -r "$3" +%Y-%m-%d 2>/dev/null || echo "-")
38
+ printf '%s\t%s\t%s\t%s\n' "$1" "$2" "$d" "$3"
39
+ }
40
+
41
+ # Current session
42
+ if [ -n "$SID" ]; then
43
+ for f in uv-out/sessions/"$SID"/$GLOB; do emit CURRENT "$(label_for "$SID")" "$f"; done
44
+ fi
45
+ # Other sessions, newest dir first
46
+ for d in $(ls -dt uv-out/sessions/*/ 2>/dev/null); do
47
+ s=$(basename "$d")
48
+ [ "$s" = "$SID" ] && continue
49
+ for f in ${d}$GLOB; do emit PRIOR "$(label_for "$s")" "$f"; done
50
+ done
51
+ # Legacy flat layout (pre-session-scoping)
52
+ for f in uv-out/$GLOB; do emit LEGACY "-" "$f"; done
@@ -0,0 +1,28 @@
1
+ #!/bin/bash
2
+ # UV Suite Hook: Surface the uv-out/ artifact path after a skill run.
3
+ # Event: Stop
4
+ # When a UV Suite skill writes artifacts to uv-out/, the writing happens inside a
5
+ # forked sub-agent whose transcript the user never sees. This hook runs when control
6
+ # returns to the main loop and prints the path so the user knows where output landed.
7
+
8
+ [ -d uv-out ] || exit 0
9
+
10
+ # Files written in the ~2 min covering the run that just finished.
11
+ RECENT=$(find uv-out -type f -mmin -2 2>/dev/null | sort)
12
+ [ -z "$RECENT" ] && exit 0
13
+
14
+ LIST=$(echo "$RECENT" | sed 's/^/ /' | sed 's/$/\\n/' | tr -d '\n')
15
+
16
+ # Name the session if these artifacts are session-scoped (uv-out/sessions/<sid>/...).
17
+ SID=$(echo "$RECENT" | sed -n 's#^uv-out/sessions/\([^/]*\)/.*#\1#p' | head -1)
18
+ HEADER="UV Suite output written to:"
19
+ [ -n "$SID" ] && HEADER="UV Suite output (session ${SID}) written to:"
20
+
21
+ cat <<EOF
22
+ {
23
+ "continue": true,
24
+ "systemMessage": "${HEADER}\n${LIST}"
25
+ }
26
+ EOF
27
+
28
+ exit 0
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ # UV Suite helper: maintain a stable flat pointer into the current session's output.
3
+ # Fixed-path consumers (/commit, /ship read uv-out/review-state.md and uv-out/qa-state.md)
4
+ # keep working while the real artifact lives under uv-out/sessions/<sid>/.
5
+ #
6
+ # Usage: uv-out-pointer.sh <flat-name> <session-relative-target>
7
+ # e.g. uv-out-pointer.sh review-state.md review/state.md
8
+ # Creates uv-out/<flat-name> -> current/<session-relative-target>
9
+ # (uv-out/current is the symlink to sessions/<sid>/ maintained by uv-out-session.sh).
10
+
11
+ FLAT="$1"; TARGET="$2"
12
+ [ -z "$FLAT" ] || [ -z "$TARGET" ] && exit 0
13
+
14
+ mkdir -p uv-out
15
+ ln -sfn "current/$TARGET" "uv-out/$FLAT" 2>/dev/null || true
16
+ printf 'uv-out/%s -> current/%s\n' "$FLAT" "$TARGET"
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ # UV Suite helper: resolve (and create) the current session's uv-out output dir.
3
+ # Prints the dir path (CWD-relative) on stdout so skills can namespace artifacts
4
+ # by session — you can tell which session produced which artifact.
5
+ #
6
+ # Session id resolution mirrors session-start.sh:
7
+ # UVS_SESSION_ID env (set by uv.sh) → .uv-suite-state/current-session.txt → "no-session"
8
+
9
+ ROOT="${CLAUDE_PROJECT_DIR:-.}"
10
+ STATE_DIR="$ROOT/.uv-suite-state"
11
+
12
+ SID="${UVS_SESSION_ID:-}"
13
+ if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
14
+ SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
15
+ fi
16
+ [ -z "$SID" ] && SID="no-session"
17
+
18
+ OUT="uv-out/sessions/$SID"
19
+ mkdir -p "$OUT"
20
+
21
+ # Convenience pointer to the active session's output (symlink; ignore failure).
22
+ ln -sfn "sessions/$SID" "uv-out/current" 2>/dev/null || true
23
+
24
+ printf '%s\n' "$OUT"
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+ # UV Suite Hook Helper: Notify Watchtower of an approval request.
3
+ # Fires on the Notification hook event (e.g. Claude is waiting for the user
4
+ # to approve a tool call). Non-blocking. Fails silently if server not running.
5
+ #
6
+ # Usage from hook config (Notification event):
7
+ # "command": ".claude/hooks/watchtower-notify.sh"
8
+ # Hook input JSON arrives via stdin from Claude Code.
9
+
10
+ INPUT=$(cat)
11
+ WATCHTOWER_URL="${UVS_WATCHTOWER_URL:-http://localhost:4200}"
12
+
13
+ STATE_DIR="${CLAUDE_PROJECT_DIR:-.}/.uv-suite-state"
14
+
15
+ # Resolve UVS session id: env first, then current-session pointer
16
+ SID="${UVS_SESSION_ID:-}"
17
+ if [ -z "$SID" ] && [ -f "$STATE_DIR/current-session.txt" ]; then
18
+ SID=$(cat "$STATE_DIR/current-session.txt" 2>/dev/null)
19
+ fi
20
+
21
+ PAYLOAD=""
22
+ if command -v jq >/dev/null 2>&1; then
23
+ PAYLOAD=$(echo "$INPUT" | jq -c --arg sid "$SID" '
24
+ {
25
+ session_id: $sid,
26
+ tool_name: (.tool_name // ""),
27
+ command: (.tool_input.command // .message // ""),
28
+ request: .
29
+ }' 2>/dev/null)
30
+ fi
31
+
32
+ # Fallback when jq is missing or produced nothing usable
33
+ if [ -z "$PAYLOAD" ] || [ "$PAYLOAD" = "null" ]; then
34
+ TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4)
35
+ PAYLOAD=$(printf '{"session_id":"%s","tool_name":"%s","command":"","request":%s}' \
36
+ "$SID" "$TOOL_NAME" "$INPUT")
37
+ fi
38
+
39
+ curl -s -X POST "$WATCHTOWER_URL/approvals" \
40
+ -H "Content-Type: application/json" \
41
+ -d "$PAYLOAD" \
42
+ --max-time 2 \
43
+ &>/dev/null &
44
+
45
+ exit 0
@@ -39,6 +39,10 @@ if command -v jq >/dev/null 2>&1; then
39
39
  session_purpose: ($m[0].purpose // ""),
40
40
  session_priority: ($m[0].priority // ""),
41
41
  persona: ($m[0].persona // ""),
42
+ git_worktree: ($m[0].git_worktree // ""),
43
+ git_branch: ($m[0].git_branch // ""),
44
+ git_main_repo: ($m[0].git_main_repo // ""),
45
+ git_is_linked_worktree: ($m[0].git_is_linked_worktree // false),
42
46
  _hook_ts: now
43
47
  }' 2>/dev/null)
44
48
  else