uv-suite 0.28.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.
- package/LICENSE +21 -0
- package/README.md +58 -35
- 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 +51 -48
- package/hooks/auto-checkpoint-helper.sh +2 -2
- package/hooks/auto-checkpoint.sh +3 -3
- package/hooks/auto-restore-on-start.sh +30 -0
- 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 +28 -0
- package/hooks/uv-out-pointer.sh +16 -0
- package/hooks/uv-out-session.sh +24 -0
- package/hooks/watchtower-notify.sh +45 -0
- package/hooks/watchtower-send.sh +4 -0
- package/install.sh +93 -42
- package/package.json +2 -2
- package/personas/auto.json +40 -1
- package/personas/professional.json +46 -1
- package/personas/spike.json +32 -2
- package/personas/sport.json +44 -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/Dockerfile +9 -0
- package/watchtower/README.md +78 -0
- package/watchtower/app/__init__.py +0 -0
- package/watchtower/app/__pycache__/__init__.cpython-314.pyc +0 -0
- package/watchtower/app/__pycache__/db.cpython-314.pyc +0 -0
- package/watchtower/app/__pycache__/main.cpython-314.pyc +0 -0
- package/watchtower/app/__pycache__/models.cpython-314.pyc +0 -0
- package/watchtower/app/db.py +85 -0
- package/watchtower/app/main.py +45 -0
- package/watchtower/app/models.py +49 -0
- package/watchtower/app/routers/__init__.py +0 -0
- package/watchtower/app/routers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/control.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/ingest.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/query.cpython-314.pyc +0 -0
- package/watchtower/app/routers/__pycache__/stream.cpython-314.pyc +0 -0
- package/watchtower/app/routers/control.py +144 -0
- package/watchtower/app/routers/ingest.py +102 -0
- package/watchtower/app/routers/query.py +84 -0
- package/watchtower/app/routers/stream.py +30 -0
- package/watchtower/app/services/__init__.py +0 -0
- package/watchtower/app/services/__pycache__/__init__.cpython-314.pyc +0 -0
- package/watchtower/app/services/__pycache__/checkpoint.cpython-314.pyc +0 -0
- package/watchtower/app/services/__pycache__/tmux.cpython-314.pyc +0 -0
- package/watchtower/app/services/checkpoint.py +107 -0
- package/watchtower/app/services/tmux.py +54 -0
- package/watchtower/docker-compose.yml +22 -0
- package/watchtower/events.json +10373 -1
- package/watchtower/{auto-checkpoint-runner.js → legacy/auto-checkpoint-runner.js} +29 -2
- package/watchtower/{dashboard.html → legacy/dashboard.html} +261 -0
- package/watchtower/{server.js → legacy/server.js} +63 -0
- package/watchtower/legacy/snapshot-manager.js +305 -0
- package/watchtower/requirements.txt +3 -0
- package/watchtower/schema.sql +43 -0
- package/watchtower/static/dashboard.html +449 -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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UV Suite Hook: SessionStart — when UVS_RESTORE_FROM is set, inject a
|
|
3
|
+
# system-context instruction telling Claude to /session restore that session as
|
|
4
|
+
# the first action of the new session.
|
|
5
|
+
#
|
|
6
|
+
# This is how the Watchtower's "Open in new terminal" restore button hooks
|
|
7
|
+
# back into the new uvs session — the env var is set by the spawned terminal
|
|
8
|
+
# command, propagates through uvs/claude, and lands here.
|
|
9
|
+
|
|
10
|
+
if [ -z "$UVS_RESTORE_FROM" ]; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
# Sanity check — sid must look like a UUID prefix or our ad-hoc-<ts>
|
|
15
|
+
case "$UVS_RESTORE_FROM" in
|
|
16
|
+
[a-zA-Z0-9-]*) : ;;
|
|
17
|
+
*)
|
|
18
|
+
echo "[auto-restore] ignoring malformed UVS_RESTORE_FROM" >&2
|
|
19
|
+
exit 0
|
|
20
|
+
;;
|
|
21
|
+
esac
|
|
22
|
+
|
|
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
|
+
|
|
25
|
+
if command -v jq >/dev/null 2>&1; then
|
|
26
|
+
jq -nc --arg ctx "$MSG" '{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:$ctx}}'
|
|
27
|
+
else
|
|
28
|
+
ESCAPED=$(printf '%s' "$MSG" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
29
|
+
printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}' "$ESCAPED"
|
|
30
|
+
fi
|
|
@@ -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
|
|
package/hooks/session-start.sh
CHANGED
|
@@ -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
|
package/hooks/slop-grep.sh
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# UV Suite Hook: Fast deterministic slop check
|
|
3
3
|
# Event: PostToolUse (Edit|Write)
|
|
4
|
-
#
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
41
|
-
cat <<EOF
|
|
24
|
+
cat <<EOF
|
|
42
25
|
{
|
|
43
26
|
"continue": true,
|
|
44
|
-
"systemMessage": "Slop detected: $
|
|
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
|
package/hooks/watchtower-send.sh
CHANGED
|
@@ -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
|