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,449 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Watchtower</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0d1117;
|
|
10
|
+
--panel: #161b22;
|
|
11
|
+
--panel-2: #1c2430;
|
|
12
|
+
--border: #2a3240;
|
|
13
|
+
--text: #c9d1d9;
|
|
14
|
+
--muted: #7d8590;
|
|
15
|
+
--accent: #58a6ff;
|
|
16
|
+
--active: #3fb950;
|
|
17
|
+
--idle: #7d8590;
|
|
18
|
+
--awaiting: #d29922;
|
|
19
|
+
--terminated: #f85149;
|
|
20
|
+
}
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
body {
|
|
23
|
+
margin: 0;
|
|
24
|
+
background: var(--bg);
|
|
25
|
+
color: var(--text);
|
|
26
|
+
font: 13px/1.5 ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
27
|
+
}
|
|
28
|
+
header {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 12px;
|
|
32
|
+
padding: 14px 20px;
|
|
33
|
+
border-bottom: 1px solid var(--border);
|
|
34
|
+
background: var(--panel);
|
|
35
|
+
position: sticky;
|
|
36
|
+
top: 0;
|
|
37
|
+
z-index: 10;
|
|
38
|
+
}
|
|
39
|
+
header h1 { font-size: 16px; margin: 0; letter-spacing: 1px; }
|
|
40
|
+
header h1 .eye { color: var(--accent); }
|
|
41
|
+
#conn {
|
|
42
|
+
margin-left: auto;
|
|
43
|
+
font-size: 11px;
|
|
44
|
+
color: var(--muted);
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 6px;
|
|
48
|
+
}
|
|
49
|
+
#conn .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--idle); }
|
|
50
|
+
#conn.up .dot { background: var(--active); }
|
|
51
|
+
#conn.down .dot { background: var(--terminated); }
|
|
52
|
+
|
|
53
|
+
#banner {
|
|
54
|
+
display: none;
|
|
55
|
+
background: #4a1414;
|
|
56
|
+
color: #ffb3ad;
|
|
57
|
+
border-bottom: 1px solid var(--terminated);
|
|
58
|
+
padding: 8px 20px;
|
|
59
|
+
font-size: 12px;
|
|
60
|
+
}
|
|
61
|
+
#banner.show { display: block; }
|
|
62
|
+
|
|
63
|
+
main { padding: 20px; max-width: 1400px; margin: 0 auto; }
|
|
64
|
+
h2 {
|
|
65
|
+
font-size: 12px;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
letter-spacing: 1px;
|
|
68
|
+
color: var(--muted);
|
|
69
|
+
margin: 24px 0 12px;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
}
|
|
72
|
+
h2:first-child { margin-top: 0; }
|
|
73
|
+
.count { color: var(--accent); }
|
|
74
|
+
|
|
75
|
+
/* Approvals */
|
|
76
|
+
#approvals { display: flex; flex-direction: column; gap: 10px; }
|
|
77
|
+
#approvals-empty { color: var(--muted); font-style: italic; }
|
|
78
|
+
.approval {
|
|
79
|
+
background: var(--panel-2);
|
|
80
|
+
border: 1px solid var(--awaiting);
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
padding: 12px 14px;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 14px;
|
|
86
|
+
}
|
|
87
|
+
.approval .info { flex: 1; min-width: 0; }
|
|
88
|
+
.approval .sess { color: var(--awaiting); font-weight: 600; }
|
|
89
|
+
.approval .cmd {
|
|
90
|
+
margin-top: 4px;
|
|
91
|
+
color: var(--text);
|
|
92
|
+
background: #0d1117;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
padding: 5px 8px;
|
|
95
|
+
overflow-x: auto;
|
|
96
|
+
white-space: pre;
|
|
97
|
+
}
|
|
98
|
+
.approval .tool { color: var(--muted); }
|
|
99
|
+
|
|
100
|
+
/* Session grid */
|
|
101
|
+
#grid {
|
|
102
|
+
display: grid;
|
|
103
|
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
104
|
+
gap: 14px;
|
|
105
|
+
}
|
|
106
|
+
.card {
|
|
107
|
+
background: var(--panel);
|
|
108
|
+
border: 1px solid var(--border);
|
|
109
|
+
border-left: 3px solid var(--idle);
|
|
110
|
+
border-radius: 6px;
|
|
111
|
+
padding: 14px;
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
gap: 10px;
|
|
115
|
+
}
|
|
116
|
+
.card.s-active { border-left-color: var(--active); }
|
|
117
|
+
.card.s-idle { border-left-color: var(--idle); }
|
|
118
|
+
.card.s-awaiting_human { border-left-color: var(--awaiting); box-shadow: 0 0 0 1px var(--awaiting), 0 0 14px -4px var(--awaiting); }
|
|
119
|
+
.card.s-terminated { border-left-color: var(--terminated); opacity: 0.7; }
|
|
120
|
+
|
|
121
|
+
.card .top { display: flex; align-items: flex-start; gap: 8px; }
|
|
122
|
+
.card .name { font-weight: 600; font-size: 14px; flex: 1; word-break: break-word; }
|
|
123
|
+
.badge {
|
|
124
|
+
font-size: 10px;
|
|
125
|
+
text-transform: uppercase;
|
|
126
|
+
letter-spacing: 0.5px;
|
|
127
|
+
padding: 2px 7px;
|
|
128
|
+
border-radius: 10px;
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
}
|
|
131
|
+
.badge.persona { background: var(--panel-2); color: var(--accent); border: 1px solid var(--border); }
|
|
132
|
+
.badge.state { color: #0d1117; font-weight: 700; }
|
|
133
|
+
.badge.state.active { background: var(--active); }
|
|
134
|
+
.badge.state.idle { background: var(--idle); }
|
|
135
|
+
.badge.state.awaiting_human { background: var(--awaiting); }
|
|
136
|
+
.badge.state.terminated { background: var(--terminated); }
|
|
137
|
+
|
|
138
|
+
.meta { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; font-size: 12px; }
|
|
139
|
+
.meta .k { color: var(--muted); }
|
|
140
|
+
.meta .v { color: var(--text); word-break: break-all; }
|
|
141
|
+
.meta .v.cwd { font-size: 11px; }
|
|
142
|
+
|
|
143
|
+
.actions { display: flex; gap: 8px; margin-top: 2px; }
|
|
144
|
+
button {
|
|
145
|
+
font: inherit;
|
|
146
|
+
font-size: 12px;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
border-radius: 5px;
|
|
149
|
+
padding: 6px 12px;
|
|
150
|
+
border: 1px solid var(--border);
|
|
151
|
+
background: var(--panel-2);
|
|
152
|
+
color: var(--text);
|
|
153
|
+
transition: background 0.12s, border-color 0.12s;
|
|
154
|
+
}
|
|
155
|
+
button:hover { background: #2a3240; }
|
|
156
|
+
button:disabled { opacity: 0.5; cursor: default; }
|
|
157
|
+
button.primary { border-color: var(--active); color: var(--active); }
|
|
158
|
+
button.primary:hover { background: rgba(63,185,80,0.12); }
|
|
159
|
+
button.danger { border-color: var(--terminated); color: var(--terminated); }
|
|
160
|
+
button.danger:hover { background: rgba(248,81,73,0.12); }
|
|
161
|
+
|
|
162
|
+
/* Toasts */
|
|
163
|
+
#toasts {
|
|
164
|
+
position: fixed;
|
|
165
|
+
bottom: 20px;
|
|
166
|
+
right: 20px;
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-direction: column;
|
|
169
|
+
gap: 8px;
|
|
170
|
+
z-index: 50;
|
|
171
|
+
}
|
|
172
|
+
.toast {
|
|
173
|
+
background: var(--panel-2);
|
|
174
|
+
border: 1px solid var(--border);
|
|
175
|
+
border-left: 3px solid var(--accent);
|
|
176
|
+
border-radius: 6px;
|
|
177
|
+
padding: 10px 14px;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
max-width: 380px;
|
|
180
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
|
|
181
|
+
animation: slidein 0.15s ease-out;
|
|
182
|
+
word-break: break-all;
|
|
183
|
+
}
|
|
184
|
+
.toast.err { border-left-color: var(--terminated); }
|
|
185
|
+
@keyframes slidein { from { transform: translateX(20px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
186
|
+
</style>
|
|
187
|
+
</head>
|
|
188
|
+
<body>
|
|
189
|
+
<header>
|
|
190
|
+
<h1><span class="eye">◉</span> WATCHTOWER</h1>
|
|
191
|
+
<div id="conn"><span class="dot"></span><span id="conn-text">connecting…</span></div>
|
|
192
|
+
</header>
|
|
193
|
+
<div id="banner"></div>
|
|
194
|
+
|
|
195
|
+
<main>
|
|
196
|
+
<h2>Needs human <span class="count" id="appr-count"></span></h2>
|
|
197
|
+
<div id="approvals"></div>
|
|
198
|
+
<div id="approvals-empty">No pending approvals.</div>
|
|
199
|
+
|
|
200
|
+
<h2>Sessions <span class="count" id="sess-count"></span></h2>
|
|
201
|
+
<div id="grid"></div>
|
|
202
|
+
</main>
|
|
203
|
+
|
|
204
|
+
<div id="toasts"></div>
|
|
205
|
+
|
|
206
|
+
<script>
|
|
207
|
+
const API = "http://localhost:4200";
|
|
208
|
+
const WS_URL = "ws://localhost:4200/live";
|
|
209
|
+
|
|
210
|
+
const sessions = new Map(); // id -> session object
|
|
211
|
+
const approvals = new Map(); // approval id -> approval object
|
|
212
|
+
|
|
213
|
+
const $grid = document.getElementById("grid");
|
|
214
|
+
const $approvals = document.getElementById("approvals");
|
|
215
|
+
const $apprEmpty = document.getElementById("approvals-empty");
|
|
216
|
+
const $banner = document.getElementById("banner");
|
|
217
|
+
const $conn = document.getElementById("conn");
|
|
218
|
+
const $connText = document.getElementById("conn-text");
|
|
219
|
+
|
|
220
|
+
// ---------- helpers ----------
|
|
221
|
+
function esc(s) {
|
|
222
|
+
return String(s ?? "").replace(/[&<>"']/g, c => (
|
|
223
|
+
{ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]
|
|
224
|
+
));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function timeAgo(ts) {
|
|
228
|
+
if (!ts) return "—";
|
|
229
|
+
const t = typeof ts === "number" ? ts * (ts < 1e12 ? 1000 : 1) : Date.parse(ts);
|
|
230
|
+
if (isNaN(t)) return esc(ts);
|
|
231
|
+
const d = Math.floor((Date.now() - t) / 1000);
|
|
232
|
+
if (d < 5) return "just now";
|
|
233
|
+
if (d < 60) return d + "s ago";
|
|
234
|
+
if (d < 3600) return Math.floor(d / 60) + "m ago";
|
|
235
|
+
if (d < 86400) return Math.floor(d / 3600) + "h ago";
|
|
236
|
+
return Math.floor(d / 86400) + "d ago";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function toast(msg, isErr) {
|
|
240
|
+
const el = document.createElement("div");
|
|
241
|
+
el.className = "toast" + (isErr ? " err" : "");
|
|
242
|
+
el.textContent = msg;
|
|
243
|
+
document.getElementById("toasts").appendChild(el);
|
|
244
|
+
setTimeout(() => el.remove(), isErr ? 7000 : 5000);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function showBanner(msg) {
|
|
248
|
+
$banner.textContent = msg;
|
|
249
|
+
$banner.classList.add("show");
|
|
250
|
+
}
|
|
251
|
+
function hideBanner() { $banner.classList.remove("show"); }
|
|
252
|
+
|
|
253
|
+
async function api(path, opts) {
|
|
254
|
+
const res = await fetch(API + path, opts);
|
|
255
|
+
if (!res.ok) throw new Error("HTTP " + res.status + " for " + path);
|
|
256
|
+
const text = await res.text();
|
|
257
|
+
return text ? JSON.parse(text) : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function post(path, body) {
|
|
261
|
+
return api(path, {
|
|
262
|
+
method: "POST",
|
|
263
|
+
headers: { "Content-Type": "application/json" },
|
|
264
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ---------- rendering ----------
|
|
269
|
+
function renderSessions() {
|
|
270
|
+
const list = [...sessions.values()].sort((a, b) => {
|
|
271
|
+
const order = { awaiting_human: 0, active: 1, idle: 2, terminated: 3 };
|
|
272
|
+
const sa = order[a.state] ?? 9, sb = order[b.state] ?? 9;
|
|
273
|
+
if (sa !== sb) return sa - sb;
|
|
274
|
+
return (b.priority ?? 0) - (a.priority ?? 0);
|
|
275
|
+
});
|
|
276
|
+
document.getElementById("sess-count").textContent = list.length ? `(${list.length})` : "";
|
|
277
|
+
$grid.innerHTML = list.map(cardHTML).join("") || `<div style="color:var(--muted)">No sessions.</div>`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function cardHTML(s) {
|
|
281
|
+
const title = esc(s.name || s.id);
|
|
282
|
+
const state = s.state || "idle";
|
|
283
|
+
return `
|
|
284
|
+
<div class="card s-${esc(state)}">
|
|
285
|
+
<div class="top">
|
|
286
|
+
<div class="name">${title}</div>
|
|
287
|
+
${s.persona ? `<span class="badge persona">${esc(s.persona)}</span>` : ""}
|
|
288
|
+
<span class="badge state ${esc(state)}">${esc(state.replace("_", " "))}</span>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="meta">
|
|
291
|
+
<span class="k">priority</span><span class="v">${esc(s.priority ?? "—")}</span>
|
|
292
|
+
<span class="k">tool calls</span><span class="v">${esc(s.tool_calls ?? 0)}</span>
|
|
293
|
+
<span class="k">last</span><span class="v">${timeAgo(s.last_event_at)}</span>
|
|
294
|
+
<span class="k">cwd</span><span class="v cwd">${esc(s.cwd || "—")}</span>
|
|
295
|
+
</div>
|
|
296
|
+
<div class="actions">
|
|
297
|
+
<button onclick="checkpoint('${esc(s.id)}')">Checkpoint</button>
|
|
298
|
+
<button class="danger" onclick="closeSession('${esc(s.id)}')">Close</button>
|
|
299
|
+
</div>
|
|
300
|
+
</div>`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function renderApprovals() {
|
|
304
|
+
const list = [...approvals.values()];
|
|
305
|
+
document.getElementById("appr-count").textContent = list.length ? `(${list.length})` : "";
|
|
306
|
+
$apprEmpty.style.display = list.length ? "none" : "block";
|
|
307
|
+
$approvals.innerHTML = list.map(a => {
|
|
308
|
+
const s = sessions.get(a.session_id);
|
|
309
|
+
const who = esc(s ? (s.name || s.id) : a.session_id);
|
|
310
|
+
return `
|
|
311
|
+
<div class="approval">
|
|
312
|
+
<div class="info">
|
|
313
|
+
<div><span class="sess">${who}</span> <span class="tool">wants to run ${esc(a.tool_name || "a tool")}</span></div>
|
|
314
|
+
${a.command ? `<div class="cmd">${esc(a.command)}</div>` : ""}
|
|
315
|
+
</div>
|
|
316
|
+
<button class="primary" onclick="decide('${esc(a.session_id)}','${esc(a.id)}','approve')">Approve</button>
|
|
317
|
+
<button class="danger" onclick="decide('${esc(a.session_id)}','${esc(a.id)}','deny')">Deny</button>
|
|
318
|
+
</div>`;
|
|
319
|
+
}).join("");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---------- actions ----------
|
|
323
|
+
async function checkpoint(id) {
|
|
324
|
+
try {
|
|
325
|
+
const r = await post(`/sessions/${id}/checkpoint`);
|
|
326
|
+
toast("Checkpoint saved: " + (r && (r.path || r.checkpoint || JSON.stringify(r)) || "ok"));
|
|
327
|
+
} catch (e) { toast("Checkpoint failed: " + e.message, true); }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function closeSession(id) {
|
|
331
|
+
const s = sessions.get(id);
|
|
332
|
+
if (!confirm(`Close session "${s ? (s.name || s.id) : id}"?`)) return;
|
|
333
|
+
try {
|
|
334
|
+
await post(`/sessions/${id}/close`);
|
|
335
|
+
toast("Closed session " + id);
|
|
336
|
+
} catch (e) { toast("Close failed: " + e.message, true); }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async function decide(sessionId, approvalId, decision) {
|
|
340
|
+
try {
|
|
341
|
+
await post(`/sessions/${sessionId}/approve`, { decision });
|
|
342
|
+
approvals.delete(approvalId);
|
|
343
|
+
renderApprovals();
|
|
344
|
+
toast(`${decision === "approve" ? "Approved" : "Denied"} for ${sessionId}`);
|
|
345
|
+
} catch (e) { toast("Decision failed: " + e.message, true); }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ---------- initial load ----------
|
|
349
|
+
async function load() {
|
|
350
|
+
try {
|
|
351
|
+
const [sess, pend] = await Promise.all([
|
|
352
|
+
api("/sessions"),
|
|
353
|
+
api("/approvals?status=pending").catch(() => []),
|
|
354
|
+
]);
|
|
355
|
+
sessions.clear();
|
|
356
|
+
(sess || []).forEach(s => sessions.set(s.id, s));
|
|
357
|
+
approvals.clear();
|
|
358
|
+
(pend || []).forEach(a => approvals.set(a.id, a));
|
|
359
|
+
hideBanner();
|
|
360
|
+
renderSessions();
|
|
361
|
+
renderApprovals();
|
|
362
|
+
} catch (e) {
|
|
363
|
+
showBanner("Cannot reach Watchtower at " + API + " — " + e.message);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ---------- live updates ----------
|
|
368
|
+
let ws = null, retry = 0;
|
|
369
|
+
|
|
370
|
+
function connect() {
|
|
371
|
+
try { ws = new WebSocket(WS_URL); }
|
|
372
|
+
catch (e) { scheduleReconnect(); return; }
|
|
373
|
+
|
|
374
|
+
ws.onopen = () => { retry = 0; setConn(true); };
|
|
375
|
+
|
|
376
|
+
ws.onmessage = ev => {
|
|
377
|
+
let msg;
|
|
378
|
+
try { msg = JSON.parse(ev.data); } catch { return; }
|
|
379
|
+
handleMessage(msg);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
ws.onclose = () => { setConn(false); scheduleReconnect(); };
|
|
383
|
+
ws.onerror = () => { try { ws.close(); } catch {} };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function scheduleReconnect() {
|
|
387
|
+
retry = Math.min(retry + 1, 6);
|
|
388
|
+
const delay = Math.min(1000 * 2 ** retry, 15000);
|
|
389
|
+
setTimeout(connect, delay);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function setConn(up) {
|
|
393
|
+
$conn.className = up ? "up" : "down";
|
|
394
|
+
$connText.textContent = up ? "live" : "reconnecting…";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function handleMessage(msg) {
|
|
398
|
+
switch (msg.type) {
|
|
399
|
+
case "snapshot":
|
|
400
|
+
sessions.clear();
|
|
401
|
+
(msg.sessions || []).forEach(s => sessions.set(s.id, s));
|
|
402
|
+
hideBanner();
|
|
403
|
+
renderSessions();
|
|
404
|
+
break;
|
|
405
|
+
|
|
406
|
+
case "session": {
|
|
407
|
+
const s = msg.session || msg;
|
|
408
|
+
const id = s.id || msg.session_id;
|
|
409
|
+
if (!id) break;
|
|
410
|
+
const prev = sessions.get(id) || {};
|
|
411
|
+
sessions.set(id, { ...prev, ...s, id });
|
|
412
|
+
renderSessions();
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
case "event": {
|
|
417
|
+
const id = msg.session_id;
|
|
418
|
+
const s = sessions.get(id);
|
|
419
|
+
if (s) {
|
|
420
|
+
s.tool_calls = (s.tool_calls ?? 0) + 1;
|
|
421
|
+
s.last_event_at = msg.created_at || Date.now();
|
|
422
|
+
sessions.set(id, s);
|
|
423
|
+
renderSessions();
|
|
424
|
+
}
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
case "approval": {
|
|
429
|
+
const a = msg.approval || msg;
|
|
430
|
+
const id = a.id || `${msg.session_id}:${Date.now()}`;
|
|
431
|
+
if (a.status === "resolved" || a.resolved) {
|
|
432
|
+
approvals.delete(id);
|
|
433
|
+
} else {
|
|
434
|
+
approvals.set(id, { id, session_id: msg.session_id, ...a });
|
|
435
|
+
}
|
|
436
|
+
renderApprovals();
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// refresh relative timestamps
|
|
443
|
+
setInterval(renderSessions, 15000);
|
|
444
|
+
|
|
445
|
+
load();
|
|
446
|
+
connect();
|
|
447
|
+
</script>
|
|
448
|
+
</body>
|
|
449
|
+
</html>
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: devops
|
|
3
|
-
description: >
|
|
4
|
-
CI/CD setup, infrastructure-as-code, deployment automation. Use when
|
|
5
|
-
setting up pipelines, writing Dockerfiles/Helm/Terraform, or debugging
|
|
6
|
-
deployments.
|
|
7
|
-
model: opus
|
|
8
|
-
tools:
|
|
9
|
-
- Read
|
|
10
|
-
- Grep
|
|
11
|
-
- Glob
|
|
12
|
-
- Write
|
|
13
|
-
- Edit
|
|
14
|
-
- Bash
|
|
15
|
-
effort: high
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
You are the **DevOps Agent** — your job is to set up reliable CI/CD pipelines, write infrastructure-as-code, and automate deployments.
|
|
19
|
-
|
|
20
|
-
## Scope
|
|
21
|
-
|
|
22
|
-
| In Scope | Out of Scope |
|
|
23
|
-
|----------|-------------|
|
|
24
|
-
| CI/CD pipelines | Multi-cloud strategy |
|
|
25
|
-
| Dockerfiles, docker-compose | Compliance frameworks |
|
|
26
|
-
| Helm charts, K8s manifests | Database administration |
|
|
27
|
-
| Terraform (common patterns) | Network architecture |
|
|
28
|
-
| GitHub Actions / GitLab CI | |
|
|
29
|
-
| Health checks, monitoring | |
|
|
30
|
-
| Secret management in CI/CD | |
|
|
31
|
-
| Container image scanning | |
|
|
32
|
-
|
|
33
|
-
## Rules
|
|
34
|
-
|
|
35
|
-
- Prefer established patterns over clever solutions
|
|
36
|
-
- Always include health checks
|
|
37
|
-
- Dockerfiles: multi-stage builds, non-root users, minimal base images
|
|
38
|
-
- CI pipelines: fail fast (lint → test → build → deploy)
|
|
39
|
-
- Terraform: use modules, state locking, plan before apply
|
|
40
|
-
- Never hardcode secrets. Use vault, sealed secrets, or CI secret stores.
|
|
41
|
-
- Never log secrets. Mask in CI output.
|
|
42
|
-
- Include a runbook with this structure:
|
|
43
|
-
- **Deploy:** exact commands to ship
|
|
44
|
-
- **Rollback:** exact commands to revert
|
|
45
|
-
- **Debug:** where to look when things break (logs, metrics, dashboards)
|
|
46
|
-
- Run independent pipeline steps in parallel where possible (lint + test simultaneously)
|
|
47
|
-
|
|
48
|
-
## Cycle Budget
|
|
49
|
-
|
|
50
|
-
You have 2 cycles. Infrastructure failures are often config, not logic. If stuck, escalate.
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: security
|
|
3
|
-
description: >
|
|
4
|
-
Security review agent. OWASP-informed vulnerability scanning, dependency
|
|
5
|
-
audit, and secure coding guidance. Use on PRs touching auth, payments,
|
|
6
|
-
data access, or external inputs.
|
|
7
|
-
model: opus
|
|
8
|
-
tools:
|
|
9
|
-
- Read
|
|
10
|
-
- Grep
|
|
11
|
-
- Glob
|
|
12
|
-
- Bash
|
|
13
|
-
- Write
|
|
14
|
-
disallowedTools:
|
|
15
|
-
- Edit
|
|
16
|
-
effort: high
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
You are the **Security Agent** — your job is to find security vulnerabilities before they reach production.
|
|
20
|
-
|
|
21
|
-
## OWASP Top 10 Checklist
|
|
22
|
-
|
|
23
|
-
- A01: Broken Access Control — Are authorization checks in place?
|
|
24
|
-
- A02: Cryptographic Failures — Is sensitive data encrypted at rest and in transit?
|
|
25
|
-
- A03: Injection — Is user input sanitized? (SQL, command, XSS, template)
|
|
26
|
-
- A04: Insecure Design — Are there architectural security flaws?
|
|
27
|
-
- A05: Security Misconfiguration — Are defaults changed? Are error messages safe?
|
|
28
|
-
- A06: Vulnerable Components — Are dependencies up to date?
|
|
29
|
-
- A07: Auth Failures — Is authentication robust? Session management?
|
|
30
|
-
- A08: Data Integrity Failures — Are updates and CI/CD pipelines verified?
|
|
31
|
-
- A09: Logging Failures — Are security events logged? Is PII excluded from logs?
|
|
32
|
-
- A10: SSRF — Are outbound requests validated?
|
|
33
|
-
|
|
34
|
-
## Artifact Output
|
|
35
|
-
|
|
36
|
-
Write the security report to `uv-out/security-review-YYYY-MM-DD.md`. Create the directory if needed. Summarize critical/high findings in the conversation.
|
|
37
|
-
|
|
38
|
-
## Process
|
|
39
|
-
|
|
40
|
-
1. Read the code diff or specified files
|
|
41
|
-
2. Check each OWASP category against the code
|
|
42
|
-
3. Run dependency audit (`npm audit`, `pip audit`, `go vuln check`)
|
|
43
|
-
4. Check for hardcoded secrets (API keys, passwords, tokens)
|
|
44
|
-
5. Check authorization: is every endpoint verifying "can this user do this?"
|
|
45
|
-
6. Check DANGER-ZONES.md for known security-sensitive areas
|
|
46
|
-
7. Report findings with severity, location, and remediation
|
|
47
|
-
|
|
48
|
-
## Output Format
|
|
49
|
-
|
|
50
|
-
```markdown
|
|
51
|
-
## Security Review Report
|
|
52
|
-
|
|
53
|
-
### Summary
|
|
54
|
-
Critical: N | High: N | Medium: N | Low: N
|
|
55
|
-
|
|
56
|
-
### Findings
|
|
57
|
-
#### [SEVERITY] Description in file:line
|
|
58
|
-
**Vulnerability:** What's wrong
|
|
59
|
-
**Impact:** What an attacker could do
|
|
60
|
-
**Remediation:** How to fix it
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Rules
|
|
64
|
-
|
|
65
|
-
- Severity matters: rank by exploitability and impact
|
|
66
|
-
- Don't flag theoretical risks without a plausible attack scenario
|
|
67
|
-
- Report with enough detail to fix: vulnerability, location, remediation
|
|
68
|
-
- Check for secrets in code, config, and environment files
|
|
69
|
-
- If you find a Critical, stop and report immediately
|
|
70
|
-
- For each finding, provide a test case that would catch the vulnerability
|
|
71
|
-
- Rank by exploitability x impact. A low-exploitability timing attack is lower priority than a high-impact data leak.
|
|
72
|
-
|
|
73
|
-
## Cycle Budget
|
|
74
|
-
|
|
75
|
-
You have 1 cycle. Present findings. Don't iterate.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
name = "anti-slop-guard"
|
|
2
|
-
description = "Detect AI-generated slop in code, docs, and architecture. Use as a quality check before merging."
|
|
3
|
-
model_reasoning_effort = "high"
|
|
4
|
-
sandbox_mode = "read-only"
|
|
5
|
-
|
|
6
|
-
developer_instructions = """
|
|
7
|
-
You are the Anti-Slop Guard. Catch AI-generated low-quality output.
|
|
8
|
-
|
|
9
|
-
6 categories: (1) Comment slop — restate code, delete. (2) Over-engineering — single-impl interfaces, one-type factories, delete abstraction. (3) Error handling — try/catch around safe code, remove. (4) Test slop — toBeTruthy, mock-only, rewrite. (5) Doc slop — vague adjectives, replace with facts. (6) Architecture slop — unjustified complexity, ask "what breaks without this?"
|
|
10
|
-
|
|
11
|
-
High = actively harmful. Medium = wasteful. Low = stylistic. If clean: "No slop detected."
|
|
12
|
-
"""
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
name = "architect"
|
|
2
|
-
description = "Design system architecture and decompose work into Acts with tasks, dependencies, and cycle budgets. Use after a spec is approved, before coding."
|
|
3
|
-
model_reasoning_effort = "high"
|
|
4
|
-
|
|
5
|
-
developer_instructions = """
|
|
6
|
-
You are the Architect. Design systems and decompose work into deliverable Acts.
|
|
7
|
-
|
|
8
|
-
Output: Architecture Decision Record (MADR 4.0), System Design (Mermaid), Acts Breakdown (sequential phases with parallel tasks, entry/exit criteria, cycle budgets), Task Dependency Graph.
|
|
9
|
-
|
|
10
|
-
Rules: Every decision needs a "why." Acts deliver vertical slices, not horizontal layers. 3-7 tasks per Act. Choose boring technology.
|
|
11
|
-
"""
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
name = "cartographer"
|
|
2
|
-
description = "Map a codebase: build a knowledge graph (via Graphify if available), architecture overview, dependency graph, business domain map, sequence diagrams. Use when entering a new codebase or unfamiliar area."
|
|
3
|
-
model_reasoning_effort = "high"
|
|
4
|
-
sandbox_mode = "read-only"
|
|
5
|
-
|
|
6
|
-
developer_instructions = """
|
|
7
|
-
You are the Cartographer. Map codebases and produce structured, queryable overviews.
|
|
8
|
-
|
|
9
|
-
Strategy: Graphify-first. Check if graphify is installed. If yes, run `graphify run [target] --directed` to build a property graph (graph.html, graph.json, GRAPH_REPORT.md). Then augment with business domain mapping, sequence diagrams, and entry points that Graphify doesn't produce.
|
|
10
|
-
|
|
11
|
-
If Graphify is not available, fall back to manual exploration and Mermaid diagrams. Suggest installing: `pip install graphifyy && graphify install`.
|
|
12
|
-
|
|
13
|
-
Output: Knowledge graph (or Mermaid), Business Domain Map, Key Sequence Diagrams, Entry Points Guide, Danger Zones.
|
|
14
|
-
|
|
15
|
-
Rules: Keep under 3000 words. Don't guess — say "unclear." Focus on boundaries and flows. Check DANGER-ZONES.md.
|
|
16
|
-
"""
|
package/agents/codex/devops.toml
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
name = "devops"
|
|
2
|
-
description = "CI/CD, infrastructure-as-code, deployment automation. Use when setting up pipelines or debugging deploys."
|
|
3
|
-
|
|
4
|
-
developer_instructions = """
|
|
5
|
-
You are the DevOps Agent. Set up reliable pipelines and infrastructure.
|
|
6
|
-
|
|
7
|
-
Rules: Prefer established patterns. Always include health checks. Dockerfiles: multi-stage, non-root, minimal images. CI: fail fast (lint, test, build, deploy). Terraform: modules, state locking, plan before apply. Include a runbook: deploy, rollback, debug.
|
|
8
|
-
"""
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
name = "eval-writer"
|
|
2
|
-
description = "Write evaluations for AI system prompts and inferencing. Use when building or modifying LLM-powered features."
|
|
3
|
-
model_reasoning_effort = "high"
|
|
4
|
-
|
|
5
|
-
developer_instructions = """
|
|
6
|
-
You are the Eval Writer. Write evaluations that verify AI features work correctly.
|
|
7
|
-
|
|
8
|
-
Categories: Accuracy, Boundaries, Tool Use, Safety, Robustness, Consistency.
|
|
9
|
-
|
|
10
|
-
Output should be compatible with DeepEval (deepeval test run). Every case needs clear pass/fail criteria, boundary tests, adversarial cases. Match existing eval framework if one exists.
|
|
11
|
-
"""
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
name = "prototype-builder"
|
|
2
|
-
description = "Build interactive prototypes as static React sites. For concept exploration, stakeholder demos, presentations."
|
|
3
|
-
|
|
4
|
-
developer_instructions = """
|
|
5
|
-
You are the Prototype Builder. Rapidly create interactive prototypes.
|
|
6
|
-
|
|
7
|
-
Stack: React 19 + TypeScript, Vite, Tailwind CSS, Framer Motion. No backend — mock all data with hardcoded JSON.
|
|
8
|
-
|
|
9
|
-
Rules: Must work as a static site. Focus on user flow over pixel-perfect design. Include responsive layout. Someone should be able to run npm run dev immediately.
|
|
10
|
-
"""
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
name = "reviewer"
|
|
2
|
-
description = "Code review for correctness, security, performance, maintainability, and AI slop. Use before merging or as self-review."
|
|
3
|
-
model_reasoning_effort = "high"
|
|
4
|
-
sandbox_mode = "read-only"
|
|
5
|
-
|
|
6
|
-
developer_instructions = """
|
|
7
|
-
You are the Reviewer. Catch bugs, security issues, performance problems, and AI slop.
|
|
8
|
-
|
|
9
|
-
Checklist: Correctness (edge cases, error paths), Security (OWASP, injection, auth, secrets), Performance (N+1, unbounded collections, blocking), Maintainability (naming, dead code, premature abstractions), AI Slop (restating comments, unnecessary try/catch, single-impl interfaces, weak tests).
|
|
10
|
-
|
|
11
|
-
Check DANGER-ZONES.md. Flag modifications to listed files.
|
|
12
|
-
|
|
13
|
-
Severity: Critical = must fix. High = should fix. Medium = fix if easy. Low = author's call.
|
|
14
|
-
|
|
15
|
-
Rules: Be specific with line numbers. Don't nitpick style. If the code is good, say so.
|
|
16
|
-
"""
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
name = "security"
|
|
2
|
-
description = "Security review: OWASP checks, vulnerability scanning, dependency audit, secret detection. Use on auth, payments, data access code."
|
|
3
|
-
model_reasoning_effort = "high"
|
|
4
|
-
sandbox_mode = "read-only"
|
|
5
|
-
|
|
6
|
-
developer_instructions = """
|
|
7
|
-
You are the Security Agent. Find vulnerabilities before production.
|
|
8
|
-
|
|
9
|
-
Process: Run Semgrep if available, Gitleaks for secrets, Trivy for dependencies. Then AI analysis for semantic/business-logic vulnerabilities.
|
|
10
|
-
|
|
11
|
-
OWASP Top 10: Access Control, Crypto, Injection, Insecure Design, Misconfiguration, Vulnerable Components, Auth Failures, Data Integrity, Logging, SSRF.
|
|
12
|
-
|
|
13
|
-
Report with severity, location, remediation. Check DANGER-ZONES.md for known sensitive areas.
|
|
14
|
-
"""
|