bigpowers 1.3.1 → 1.4.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/CHANGELOG.md +14 -0
- package/SKILL-INDEX.md +9 -9
- package/audit-code/SKILL.md +12 -0
- package/build-epic/SKILL.md +7 -4
- package/commit-message/SKILL.md +12 -0
- package/compose-workflow/SKILL.md +2 -0
- package/dashboard/bin/dashboard.js +93 -0
- package/dashboard/package-lock.json +1035 -0
- package/dashboard/package.json +18 -0
- package/dashboard/src/data/gate-status.js +32 -0
- package/dashboard/src/data/metrics.js +89 -0
- package/dashboard/src/data/pipeline-map.js +32 -0
- package/dashboard/src/data/reader.js +122 -0
- package/dashboard/src/data/watcher.js +108 -0
- package/dashboard/src/loaders/gate-status.js +32 -0
- package/dashboard/src/loaders/metrics.js +89 -0
- package/dashboard/src/loaders/pipeline-map.js +32 -0
- package/dashboard/src/loaders/reader.js +122 -0
- package/dashboard/src/loaders/watcher.js +108 -0
- package/dashboard/src/tui/epic-queue.js +36 -0
- package/dashboard/src/tui/filesystem.js +95 -0
- package/dashboard/src/tui/index.js +161 -0
- package/dashboard/src/tui/ledger.js +66 -0
- package/dashboard/src/tui/metrics-bar.js +30 -0
- package/dashboard/src/tui/pipeline.js +46 -0
- package/dashboard/src/tui/state-yaml.js +49 -0
- package/dashboard/src/web/client.html +477 -0
- package/dashboard/src/web/server.js +112 -0
- package/dashboard/test/fixtures/state.yaml +2 -0
- package/deepen-architecture/SKILL.md +2 -0
- package/define-language/SKILL.md +2 -0
- package/define-success/SKILL.md +4 -0
- package/delegate-task/SKILL.md +2 -0
- package/design-interface/SKILL.md +2 -0
- package/develop-tdd/SKILL.md +32 -0
- package/dispatch-agents/SKILL.md +2 -0
- package/edit-document/SKILL.md +6 -0
- package/elaborate-spec/SKILL.md +2 -0
- package/enforce-first/SKILL.md +2 -0
- package/grill-me/SKILL.md +2 -0
- package/guard-git/SKILL.md +2 -0
- package/hook-commits/SKILL.md +2 -0
- package/inspect-quality/SKILL.md +2 -0
- package/kickoff-branch/SKILL.md +5 -0
- package/map-codebase/SKILL.md +2 -0
- package/model-domain/SKILL.md +4 -0
- package/orchestrate-project/REFERENCE.md +1 -1
- package/orchestrate-project/SKILL.md +3 -1
- package/organize-workspace/SKILL.md +2 -0
- package/package.json +4 -2
- package/plan-refactor/SKILL.md +2 -0
- package/plan-work/SKILL.md +44 -0
- package/release-branch/SKILL.md +28 -0
- package/respond-review/SKILL.md +2 -0
- package/run-planning/SKILL.md +2 -0
- package/scripts/sync-bugs-registry.sh +16 -10
- package/scripts/sync-skills.sh +19 -0
- package/search-skills/SKILL.md +2 -0
- package/seed-conventions/SKILL.md +2 -0
- package/session-state/SKILL.md +16 -0
- package/setup-environment/SKILL.md +2 -0
- package/simulate-agents/SKILL.md +2 -0
- package/spike-prototype/SKILL.md +2 -0
- package/stocktake-skills/SKILL.md +2 -0
- package/survey-context/SKILL.md +23 -0
- package/terse-mode/SKILL.md +2 -0
- package/using-bigpowers/SKILL.md +4 -0
- package/validate-fix/SKILL.md +2 -0
- package/verify-work/SKILL.md +33 -0
- package/visual-dashboard/SKILL.md +2 -0
- package/wire-observability/SKILL.md +2 -0
|
@@ -0,0 +1,477 @@
|
|
|
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>BigPowers Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
background: #0d1117;
|
|
16
|
+
color: #c9d1d9;
|
|
17
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
18
|
+
font-size: 12px;
|
|
19
|
+
line-height: 1.6;
|
|
20
|
+
padding: 16px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.container {
|
|
24
|
+
max-width: 1400px;
|
|
25
|
+
margin: 0 auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
h1 {
|
|
29
|
+
font-size: 20px;
|
|
30
|
+
margin-bottom: 24px;
|
|
31
|
+
color: #58a6ff;
|
|
32
|
+
border-bottom: 2px solid #30363d;
|
|
33
|
+
padding-bottom: 8px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.grid {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
39
|
+
gap: 16px;
|
|
40
|
+
margin-bottom: 16px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.panel {
|
|
44
|
+
background: #161b22;
|
|
45
|
+
border: 1px solid #30363d;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
padding: 16px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.panel h2 {
|
|
51
|
+
font-size: 14px;
|
|
52
|
+
margin-bottom: 12px;
|
|
53
|
+
color: #79c0ff;
|
|
54
|
+
border-bottom: 1px solid #30363d;
|
|
55
|
+
padding-bottom: 8px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.metric-row {
|
|
59
|
+
display: flex;
|
|
60
|
+
justify-content: space-between;
|
|
61
|
+
padding: 6px 0;
|
|
62
|
+
border-bottom: 1px solid #21262d;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.metric-row:last-child {
|
|
66
|
+
border-bottom: none;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.metric-label {
|
|
70
|
+
color: #8b949e;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.metric-value {
|
|
74
|
+
color: #79c0ff;
|
|
75
|
+
font-weight: bold;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.step-container {
|
|
79
|
+
display: flex;
|
|
80
|
+
gap: 8px;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.step {
|
|
85
|
+
flex: 1;
|
|
86
|
+
min-width: 80px;
|
|
87
|
+
padding: 8px;
|
|
88
|
+
background: #21262d;
|
|
89
|
+
border: 1px solid #30363d;
|
|
90
|
+
border-radius: 4px;
|
|
91
|
+
text-align: center;
|
|
92
|
+
font-size: 10px;
|
|
93
|
+
color: #8b949e;
|
|
94
|
+
transition: all 0.2s ease;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.step.active {
|
|
98
|
+
background: #238636;
|
|
99
|
+
border-color: #2ea043;
|
|
100
|
+
color: #aff5b4;
|
|
101
|
+
font-weight: bold;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.step-label {
|
|
105
|
+
display: block;
|
|
106
|
+
margin-bottom: 4px;
|
|
107
|
+
word-break: break-word;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.state-pairs {
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.state-pair {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.state-key {
|
|
122
|
+
color: #8b949e;
|
|
123
|
+
font-size: 11px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.state-value {
|
|
127
|
+
color: #79c0ff;
|
|
128
|
+
word-break: break-all;
|
|
129
|
+
margin-top: 2px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.ledger-row {
|
|
133
|
+
padding: 6px 0;
|
|
134
|
+
border-bottom: 1px solid #21262d;
|
|
135
|
+
display: flex;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ledger-row:last-child {
|
|
140
|
+
border-bottom: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.ledger-id {
|
|
144
|
+
color: #8b949e;
|
|
145
|
+
min-width: 60px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.ledger-value {
|
|
149
|
+
color: #79c0ff;
|
|
150
|
+
font-weight: bold;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.ledger-meta {
|
|
154
|
+
color: #6e7681;
|
|
155
|
+
font-size: 10px;
|
|
156
|
+
margin-top: 2px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.epic-item {
|
|
160
|
+
padding: 8px;
|
|
161
|
+
background: #21262d;
|
|
162
|
+
border-left: 3px solid #30363d;
|
|
163
|
+
margin-bottom: 6px;
|
|
164
|
+
border-radius: 2px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.epic-title {
|
|
168
|
+
color: #79c0ff;
|
|
169
|
+
font-weight: bold;
|
|
170
|
+
margin-bottom: 4px;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.epic-stories {
|
|
174
|
+
margin-left: 8px;
|
|
175
|
+
color: #8b949e;
|
|
176
|
+
font-size: 11px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.status-badge {
|
|
180
|
+
display: inline-block;
|
|
181
|
+
padding: 2px 6px;
|
|
182
|
+
background: #21262d;
|
|
183
|
+
border: 1px solid #30363d;
|
|
184
|
+
border-radius: 3px;
|
|
185
|
+
font-size: 10px;
|
|
186
|
+
color: #8b949e;
|
|
187
|
+
margin-right: 4px;
|
|
188
|
+
margin-bottom: 2px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.status-active {
|
|
192
|
+
background: #238636;
|
|
193
|
+
border-color: #2ea043;
|
|
194
|
+
color: #aff5b4;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.status-complete {
|
|
198
|
+
background: #1a414b;
|
|
199
|
+
border-color: #238636;
|
|
200
|
+
color: #7ee787;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.empty {
|
|
204
|
+
color: #6e7681;
|
|
205
|
+
font-style: italic;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.connection-status {
|
|
209
|
+
position: fixed;
|
|
210
|
+
bottom: 16px;
|
|
211
|
+
right: 16px;
|
|
212
|
+
padding: 8px 12px;
|
|
213
|
+
background: #238636;
|
|
214
|
+
color: #aff5b4;
|
|
215
|
+
border-radius: 4px;
|
|
216
|
+
font-size: 11px;
|
|
217
|
+
border: 1px solid #2ea043;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.connection-status.disconnected {
|
|
221
|
+
background: #da3633;
|
|
222
|
+
border-color: #f85149;
|
|
223
|
+
color: #f5a8a8;
|
|
224
|
+
}
|
|
225
|
+
</style>
|
|
226
|
+
</head>
|
|
227
|
+
<body>
|
|
228
|
+
<div class="container">
|
|
229
|
+
<h1>BigPowers Dashboard</h1>
|
|
230
|
+
|
|
231
|
+
<div class="grid">
|
|
232
|
+
<!-- Pipeline Panel -->
|
|
233
|
+
<div class="panel">
|
|
234
|
+
<h2>Pipeline</h2>
|
|
235
|
+
<div class="step-container" id="pipeline"></div>
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
<!-- Metrics Panel -->
|
|
239
|
+
<div class="panel">
|
|
240
|
+
<h2>Metrics</h2>
|
|
241
|
+
<div id="metrics"></div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<!-- State Panel -->
|
|
245
|
+
<div class="panel">
|
|
246
|
+
<h2>State</h2>
|
|
247
|
+
<div class="state-pairs" id="state"></div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<!-- Epic Queue Panel -->
|
|
251
|
+
<div class="panel">
|
|
252
|
+
<h2>Epic Queue</h2>
|
|
253
|
+
<div id="epics"></div>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<!-- Cycle-Time Ledger Panel -->
|
|
257
|
+
<div class="panel">
|
|
258
|
+
<h2>Cycle-Time Ledger</h2>
|
|
259
|
+
<div id="ledger"></div>
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<div class="connection-status" id="status">Live</div>
|
|
265
|
+
|
|
266
|
+
<script>
|
|
267
|
+
const PIPELINE_STEPS = [
|
|
268
|
+
'survey-context',
|
|
269
|
+
'plan-work',
|
|
270
|
+
'kickoff-branch',
|
|
271
|
+
'develop-tdd',
|
|
272
|
+
'verify-work',
|
|
273
|
+
'audit-code',
|
|
274
|
+
'commit-message',
|
|
275
|
+
'release-branch'
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
let eventSource = null;
|
|
279
|
+
let pollInterval = null;
|
|
280
|
+
|
|
281
|
+
function render(data) {
|
|
282
|
+
if (!data) return;
|
|
283
|
+
|
|
284
|
+
renderPipeline(data);
|
|
285
|
+
renderMetrics(data);
|
|
286
|
+
renderState(data);
|
|
287
|
+
renderEpics(data);
|
|
288
|
+
renderLedger(data);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function renderPipeline(data) {
|
|
292
|
+
const container = document.getElementById('pipeline');
|
|
293
|
+
const activeFlow = data.state?.activeFlow;
|
|
294
|
+
const activeIndex = activeFlow ? PIPELINE_STEPS.indexOf(activeFlow) : -1;
|
|
295
|
+
|
|
296
|
+
container.innerHTML = PIPELINE_STEPS.map((step, idx) => {
|
|
297
|
+
const isActive = idx === activeIndex;
|
|
298
|
+
const className = isActive ? 'step active' : 'step';
|
|
299
|
+
return `<div class="${className}"><span class="step-label">${step}</span></div>`;
|
|
300
|
+
}).join('');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function renderMetrics(data) {
|
|
304
|
+
const container = document.getElementById('metrics');
|
|
305
|
+
const metrics = data.projectMetrics;
|
|
306
|
+
const velocity = data.currentVelocity;
|
|
307
|
+
const timestamp = data.timestamp;
|
|
308
|
+
|
|
309
|
+
let html = '';
|
|
310
|
+
if (metrics) {
|
|
311
|
+
html += `<div class="metric-row">
|
|
312
|
+
<span class="metric-label">Total BCPs</span>
|
|
313
|
+
<span class="metric-value">${metrics.totalBcps}</span>
|
|
314
|
+
</div>`;
|
|
315
|
+
html += `<div class="metric-row">
|
|
316
|
+
<span class="metric-label">Avg BCP/Hour</span>
|
|
317
|
+
<span class="metric-value">${metrics.avgBcpPerHour.toFixed(2)}</span>
|
|
318
|
+
</div>`;
|
|
319
|
+
}
|
|
320
|
+
if (velocity) {
|
|
321
|
+
html += `<div class="metric-row">
|
|
322
|
+
<span class="metric-label">Current Velocity</span>
|
|
323
|
+
<span class="metric-value">${velocity.avgBcpPerHour.toFixed(2)} BCP/h</span>
|
|
324
|
+
</div>`;
|
|
325
|
+
}
|
|
326
|
+
if (data.state?.release?.version) {
|
|
327
|
+
html += `<div class="metric-row">
|
|
328
|
+
<span class="metric-label">Version</span>
|
|
329
|
+
<span class="metric-value">${data.state.release.version}</span>
|
|
330
|
+
</div>`;
|
|
331
|
+
}
|
|
332
|
+
if (timestamp) {
|
|
333
|
+
html += `<div class="metric-row">
|
|
334
|
+
<span class="metric-label">Updated</span>
|
|
335
|
+
<span class="metric-value" style="font-size: 10px;">${new Date(timestamp).toLocaleTimeString()}</span>
|
|
336
|
+
</div>`;
|
|
337
|
+
}
|
|
338
|
+
container.innerHTML = html || '<p class="empty">No metrics available</p>';
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renderState(data) {
|
|
342
|
+
const container = document.getElementById('state');
|
|
343
|
+
const state = data.state || {};
|
|
344
|
+
|
|
345
|
+
const pairs = [
|
|
346
|
+
{ key: 'Active Flow', value: state.activeFlow },
|
|
347
|
+
{ key: 'Active Epic', value: state.activeEpic },
|
|
348
|
+
{ key: 'Active Story', value: state.activeStory },
|
|
349
|
+
{ key: 'Git Branch', value: state.gitBranch }
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
container.innerHTML = pairs.map(pair => {
|
|
353
|
+
const val = pair.value || '(none)';
|
|
354
|
+
return `
|
|
355
|
+
<div class="state-pair">
|
|
356
|
+
<span class="state-key">${pair.key}</span>
|
|
357
|
+
<span class="state-value">${val}</span>
|
|
358
|
+
</div>
|
|
359
|
+
`;
|
|
360
|
+
}).join('');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function renderEpics(data) {
|
|
364
|
+
const container = document.getElementById('epics');
|
|
365
|
+
const epics = data.epics || [];
|
|
366
|
+
|
|
367
|
+
if (epics.length === 0) {
|
|
368
|
+
container.innerHTML = '<p class="empty">No epics available</p>';
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
container.innerHTML = epics.map(epic => {
|
|
373
|
+
const stories = epic.stories || [];
|
|
374
|
+
const storyCount = stories.length;
|
|
375
|
+
return `
|
|
376
|
+
<div class="epic-item">
|
|
377
|
+
<div class="epic-title">${epic.id || 'Unknown'}</div>
|
|
378
|
+
<div style="color: #8b949e; font-size: 11px; margin-bottom: 4px;">${epic.title || 'Untitled'}</div>
|
|
379
|
+
<div class="epic-stories">${storyCount} stories</div>
|
|
380
|
+
</div>
|
|
381
|
+
`;
|
|
382
|
+
}).join('');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function renderLedger(data) {
|
|
386
|
+
const container = document.getElementById('ledger');
|
|
387
|
+
const cycleTimes = data.cycleTimes || [];
|
|
388
|
+
|
|
389
|
+
if (cycleTimes.length === 0) {
|
|
390
|
+
container.innerHTML = '<p class="empty">No cycle-time data available</p>';
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const recentRows = cycleTimes.slice(-8);
|
|
395
|
+
container.innerHTML = recentRows.map(row => {
|
|
396
|
+
const end = row.end ? new Date(row.end).toLocaleDateString() : '(active)';
|
|
397
|
+
return `
|
|
398
|
+
<div class="ledger-row">
|
|
399
|
+
<div class="ledger-id">${row.id}</div>
|
|
400
|
+
<div style="text-align: right;">
|
|
401
|
+
<div class="ledger-value">${row.bcps} BCP</div>
|
|
402
|
+
<div class="ledger-meta">${row.cycleMin}min | ${row.bcpPerHour.toFixed(2)}/h</div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
`;
|
|
406
|
+
}).join('');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function setConnectionStatus(connected) {
|
|
410
|
+
const status = document.getElementById('status');
|
|
411
|
+
if (connected) {
|
|
412
|
+
status.textContent = 'Live';
|
|
413
|
+
status.classList.remove('disconnected');
|
|
414
|
+
} else {
|
|
415
|
+
status.textContent = 'Polling...';
|
|
416
|
+
status.classList.add('disconnected');
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function startEventSource() {
|
|
421
|
+
eventSource = new EventSource('/events');
|
|
422
|
+
setConnectionStatus(true);
|
|
423
|
+
|
|
424
|
+
eventSource.onmessage = (event) => {
|
|
425
|
+
try {
|
|
426
|
+
const data = JSON.parse(event.data);
|
|
427
|
+
render(data);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
console.error('Failed to parse SSE data:', err);
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
eventSource.onerror = (err) => {
|
|
434
|
+
console.warn('SSE connection lost, falling back to polling');
|
|
435
|
+
eventSource.close();
|
|
436
|
+
setConnectionStatus(false);
|
|
437
|
+
startPolling();
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function startPolling() {
|
|
442
|
+
if (pollInterval) return;
|
|
443
|
+
|
|
444
|
+
pollInterval = setInterval(() => {
|
|
445
|
+
fetch('/api/state')
|
|
446
|
+
.then(res => res.json())
|
|
447
|
+
.then(data => {
|
|
448
|
+
render(data);
|
|
449
|
+
setConnectionStatus(false);
|
|
450
|
+
})
|
|
451
|
+
.catch(err => console.error('Poll failed:', err));
|
|
452
|
+
}, 5000);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function stopPolling() {
|
|
456
|
+
if (pollInterval) {
|
|
457
|
+
clearInterval(pollInterval);
|
|
458
|
+
pollInterval = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Initial load
|
|
463
|
+
window.addEventListener('load', () => {
|
|
464
|
+
fetch('/api/state')
|
|
465
|
+
.then(res => res.json())
|
|
466
|
+
.then(data => {
|
|
467
|
+
render(data);
|
|
468
|
+
startEventSource();
|
|
469
|
+
})
|
|
470
|
+
.catch(err => {
|
|
471
|
+
console.error('Initial load failed:', err);
|
|
472
|
+
startPolling();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
</script>
|
|
476
|
+
</body>
|
|
477
|
+
</html>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { watch } = require('../loaders/watcher');
|
|
3
|
+
const reader = require('../loaders/reader');
|
|
4
|
+
const metrics = require('../loaders/metrics');
|
|
5
|
+
|
|
6
|
+
let express;
|
|
7
|
+
try {
|
|
8
|
+
express = require('express');
|
|
9
|
+
} catch (err) {
|
|
10
|
+
express = null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function createServer(projectRoot, port = 7742) {
|
|
14
|
+
const watcher = watch(projectRoot);
|
|
15
|
+
const clients = new Set();
|
|
16
|
+
let debounceTimer = null;
|
|
17
|
+
|
|
18
|
+
function buildSnapshot() {
|
|
19
|
+
const state = reader.readStateYaml(projectRoot);
|
|
20
|
+
const execStatus = reader.readExecutionStatus(projectRoot);
|
|
21
|
+
const epics = reader.readEpicShards(projectRoot);
|
|
22
|
+
const cycleTimes = reader.readCycleTimes(projectRoot);
|
|
23
|
+
|
|
24
|
+
const projectMetrics = metrics.computeProjectMetrics(cycleTimes);
|
|
25
|
+
const currentVelocity = metrics.computeCurrentVelocity(cycleTimes);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
state,
|
|
29
|
+
execStatus,
|
|
30
|
+
epics,
|
|
31
|
+
cycleTimes,
|
|
32
|
+
projectMetrics,
|
|
33
|
+
currentVelocity,
|
|
34
|
+
timestamp: new Date().toISOString()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function broadcastSnapshot() {
|
|
39
|
+
if (debounceTimer) {
|
|
40
|
+
clearTimeout(debounceTimer);
|
|
41
|
+
}
|
|
42
|
+
debounceTimer = setTimeout(() => {
|
|
43
|
+
const snapshot = buildSnapshot();
|
|
44
|
+
const message = `data: ${JSON.stringify(snapshot)}\n\n`;
|
|
45
|
+
for (const client of clients) {
|
|
46
|
+
client.write(message);
|
|
47
|
+
}
|
|
48
|
+
}, 300);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
watcher.on('change', broadcastSnapshot);
|
|
52
|
+
|
|
53
|
+
if (!express) {
|
|
54
|
+
// Fallback: return a minimal server object that doesn't do much
|
|
55
|
+
return {
|
|
56
|
+
listen(portNum, cb) {
|
|
57
|
+
console.warn('Express not installed. Server will not start.');
|
|
58
|
+
if (cb) cb();
|
|
59
|
+
return {
|
|
60
|
+
close() {}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const app = express();
|
|
67
|
+
|
|
68
|
+
// GET / - serve client.html
|
|
69
|
+
app.get('/', (req, res) => {
|
|
70
|
+
const clientPath = path.join(__dirname, 'client.html');
|
|
71
|
+
res.sendFile(clientPath);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// GET /api/state - return current snapshot as JSON
|
|
75
|
+
app.get('/api/state', (req, res) => {
|
|
76
|
+
const snapshot = buildSnapshot();
|
|
77
|
+
res.json(snapshot);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// GET /events - Server-Sent Events endpoint
|
|
81
|
+
app.get('/events', (req, res) => {
|
|
82
|
+
res.writeHead(200, {
|
|
83
|
+
'Content-Type': 'text/event-stream',
|
|
84
|
+
'Cache-Control': 'no-cache',
|
|
85
|
+
'Connection': 'keep-alive'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Send initial snapshot
|
|
89
|
+
const snapshot = buildSnapshot();
|
|
90
|
+
res.write(`data: ${JSON.stringify(snapshot)}\n\n`);
|
|
91
|
+
|
|
92
|
+
// Add client to set
|
|
93
|
+
clients.add(res);
|
|
94
|
+
|
|
95
|
+
// Clean up on disconnect
|
|
96
|
+
req.on('close', () => {
|
|
97
|
+
clients.delete(res);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
res.on('error', () => {
|
|
101
|
+
clients.delete(res);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
listen(portNum = port, cb) {
|
|
107
|
+
return app.listen(portNum, cb);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { createServer };
|
|
@@ -8,6 +8,8 @@ description: Find deepening opportunities in a codebase, informed by the domain
|
|
|
8
8
|
|
|
9
9
|
Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.
|
|
10
10
|
|
|
11
|
+
> **HARD GATE** — Deep modules must solve a forcing function, not just be "nice abstractions." If you cannot articulate why the abstraction exists, it is premature.
|
|
12
|
+
|
|
11
13
|
## Glossary
|
|
12
14
|
|
|
13
15
|
Use these terms exactly in every suggestion. Consistent language is the point — don't drift into "component," "service," "API," or "boundary." Full definitions in [LANGUAGE.md](LANGUAGE.md).
|
package/define-language/SKILL.md
CHANGED
|
@@ -8,6 +8,8 @@ description: Extract a DDD-style ubiquitous language glossary from the current c
|
|
|
8
8
|
|
|
9
9
|
Extract and formalize domain terminology from the current conversation into a consistent glossary, saved to `specs/UBIQUITOUS_LANGUAGE.md`.
|
|
10
10
|
|
|
11
|
+
> **HARD GATE** — Ubiquitous language is NOT optional. Every term in the domain that could be misunderstood must be glossed. Ambiguity = rework.
|
|
12
|
+
|
|
11
13
|
## Process
|
|
12
14
|
|
|
13
15
|
1. **Scan the conversation** for domain-relevant nouns, verbs, and concepts
|
package/define-success/SKILL.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
ARCHIVED: content absorbed into plan-work
|
|
2
|
+
|
|
1
3
|
---
|
|
2
4
|
name: define-success
|
|
3
5
|
model: sonnet
|
|
@@ -8,6 +10,8 @@ description: Convert an imperative task statement into explicit "step → verify
|
|
|
8
10
|
|
|
9
11
|
Transform "do X" into "step → verify: <cmd>" pairs. This is the pre-flight check before `plan-work` or `develop-tdd` — it makes success observable and removes ambiguity about when you're done.
|
|
10
12
|
|
|
13
|
+
> **HARD GATE** — Success criteria must be testable and user-observable. "Code should be fast" is not testable. "Pageload latency < 2s" is testable.
|
|
14
|
+
|
|
11
15
|
## Why this matters
|
|
12
16
|
|
|
13
17
|
"Implement user authentication" is not a plan. It has no checkpoints, no evidence requirement, and no way to know if you're done. The Karpathy principle: every step must be independently verifiable with a runnable command. If you can't verify it, you can't prove it works.
|
package/delegate-task/SKILL.md
CHANGED
|
@@ -5,6 +5,8 @@ description: Delegate one complex task to a single subagent, review its work in
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Delegate Task
|
|
8
|
+
> **HARD GATE** — **HARD GATE** — Delegated work must have clear success criteria and verification commands. The delegate must be able to verify completion independently.
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
Delegate a single complex task to a subagent with a two-stage review gate before accepting the result. Use when oversight of a single task matters more than speed.
|
|
10
12
|
|
|
@@ -8,6 +8,8 @@ description: Generate multiple radically different interface designs for a modul
|
|
|
8
8
|
|
|
9
9
|
Based on "Design It Twice" from "A Philosophy of Software Design": your first idea is unlikely to be the best. Generate multiple radically different designs, then compare.
|
|
10
10
|
|
|
11
|
+
> **HARD GATE** — Multiple design options must be explored. Do NOT settle on first idea. Compare trade-offs (UX, complexity, extensibility, performance) before committing.
|
|
12
|
+
|
|
11
13
|
## Workflow
|
|
12
14
|
|
|
13
15
|
### 1. Gather Requirements
|
package/develop-tdd/SKILL.md
CHANGED
|
@@ -145,6 +145,33 @@ Once the story is complete and all tests pass:
|
|
|
145
145
|
2. Present the script to the user as a step-by-step guide.
|
|
146
146
|
3. Wait for the user to confirm the behavioral correctness before moving to the next story or declaring the task done.
|
|
147
147
|
|
|
148
|
+
## TDD phases
|
|
149
|
+
|
|
150
|
+
### Red Phase
|
|
151
|
+
|
|
152
|
+
Write a failing test first that confirms the behavior you want to implement:
|
|
153
|
+
|
|
154
|
+
- Test describes the desired observable behavior through the public interface
|
|
155
|
+
- Run the test to confirm it fails for the right reason (not a syntax error, not a typo)
|
|
156
|
+
- Commit the failing test: `git commit -m "test(<scope>): <description>"`
|
|
157
|
+
|
|
158
|
+
### Green Phase
|
|
159
|
+
|
|
160
|
+
Write the minimum amount of code to make the test pass:
|
|
161
|
+
|
|
162
|
+
- No extra logic, no anticipated future cases, no premature optimization
|
|
163
|
+
- Focus only on making the current test pass
|
|
164
|
+
- Commit the passing code: `git commit -m "feat(<scope>): <description>" or "fix(<scope>): <description>"`
|
|
165
|
+
|
|
166
|
+
### Refactor Phase
|
|
167
|
+
|
|
168
|
+
Improve the code structure, naming, and clarity without changing behavior:
|
|
169
|
+
|
|
170
|
+
- Extract duplication, apply SOLID principles where natural, deepen modules
|
|
171
|
+
- Run tests after each refactor step to ensure behavior is preserved
|
|
172
|
+
- Commit refactoring: `git commit -m "refactor(<scope>): <description>"`
|
|
173
|
+
- Apply the Boy Scout Rule: leave the code cleaner than you found it
|
|
174
|
+
|
|
148
175
|
## Checklist Per Cycle
|
|
149
176
|
|
|
150
177
|
```
|
|
@@ -159,3 +186,8 @@ Once the story is complete and all tests pass:
|
|
|
159
186
|
[ ] Progress committed (Conventional Commits)
|
|
160
187
|
[ ] verify: command passes
|
|
161
188
|
```
|
|
189
|
+
|
|
190
|
+
## Handoff
|
|
191
|
+
|
|
192
|
+
Gate: READY -> next: verify-work
|
|
193
|
+
Writes: state.yaml handoff.next_skill = verify-work
|
package/dispatch-agents/SKILL.md
CHANGED
|
@@ -5,6 +5,8 @@ description: Dispatch multiple subagents in parallel on independent tasks. No wa
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Dispatch Agents
|
|
8
|
+
> **HARD GATE** — **HARD GATE** — Agent work must be parallelizable and have explicit synchronization points. Do NOT dispatch work that has hidden dependencies between agents.
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
Run multiple subagents in parallel on independent tasks. Use when tasks are genuinely decoupled — no agent needs the output of another to start.
|
|
10
12
|
|