shipwright-cli 2.2.2 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +12 -11
  2. package/dashboard/public/index.html +224 -8
  3. package/dashboard/public/styles.css +1078 -4
  4. package/dashboard/server.ts +1100 -15
  5. package/dashboard/src/canvas/interactions.ts +74 -0
  6. package/dashboard/src/canvas/layout.ts +85 -0
  7. package/dashboard/src/canvas/overlays.ts +117 -0
  8. package/dashboard/src/canvas/particles.ts +105 -0
  9. package/dashboard/src/canvas/renderer.ts +191 -0
  10. package/dashboard/src/components/charts/bar.ts +54 -0
  11. package/dashboard/src/components/charts/donut.ts +25 -0
  12. package/dashboard/src/components/charts/pipeline-rail.ts +105 -0
  13. package/dashboard/src/components/charts/sparkline.ts +82 -0
  14. package/dashboard/src/components/header.ts +616 -0
  15. package/dashboard/src/components/modal.ts +413 -0
  16. package/dashboard/src/components/terminal.ts +144 -0
  17. package/dashboard/src/core/api.test.ts +362 -0
  18. package/dashboard/src/core/api.ts +381 -0
  19. package/dashboard/src/core/helpers.ts +118 -0
  20. package/dashboard/src/core/router.test.ts +266 -0
  21. package/dashboard/src/core/router.ts +190 -0
  22. package/dashboard/src/core/sse.ts +38 -0
  23. package/dashboard/src/core/state.test.ts +235 -0
  24. package/dashboard/src/core/state.ts +150 -0
  25. package/dashboard/src/core/ws.test.ts +216 -0
  26. package/dashboard/src/core/ws.ts +143 -0
  27. package/dashboard/src/design/icons.test.ts +105 -0
  28. package/dashboard/src/design/icons.ts +131 -0
  29. package/dashboard/src/design/tokens.test.ts +204 -0
  30. package/dashboard/src/design/tokens.ts +160 -0
  31. package/dashboard/src/main.ts +68 -0
  32. package/dashboard/src/types/api.ts +337 -0
  33. package/dashboard/src/views/activity.ts +185 -0
  34. package/dashboard/src/views/agent-cockpit.ts +236 -0
  35. package/dashboard/src/views/agents.ts +72 -0
  36. package/dashboard/src/views/fleet-map.ts +299 -0
  37. package/dashboard/src/views/insights.ts +298 -0
  38. package/dashboard/src/views/machines.ts +162 -0
  39. package/dashboard/src/views/metrics.ts +420 -0
  40. package/dashboard/src/views/overview.ts +409 -0
  41. package/dashboard/src/views/pipeline-theater.ts +219 -0
  42. package/dashboard/src/views/pipelines.ts +595 -0
  43. package/dashboard/src/views/team.ts +362 -0
  44. package/dashboard/src/views/timeline.ts +389 -0
  45. package/dashboard/tsconfig.json +21 -0
  46. package/dashboard/vitest.config.ts +27 -0
  47. package/docs/AGI-WHATS-NEXT.md +15 -15
  48. package/package.json +16 -2
  49. package/scripts/lib/helpers.sh +30 -0
  50. package/scripts/lib/pipeline-quality-checks.sh +1 -1
  51. package/scripts/lib/pipeline-stages.sh +59 -0
  52. package/scripts/sw +86 -167
  53. package/scripts/sw-activity.sh +1 -1
  54. package/scripts/sw-adaptive.sh +1 -1
  55. package/scripts/sw-adversarial.sh +1 -1
  56. package/scripts/sw-architecture-enforcer.sh +1 -1
  57. package/scripts/sw-auth.sh +14 -6
  58. package/scripts/sw-autonomous.sh +230 -13
  59. package/scripts/sw-changelog.sh +2 -2
  60. package/scripts/sw-checkpoint.sh +1 -1
  61. package/scripts/sw-ci.sh +1 -1
  62. package/scripts/sw-cleanup.sh +1 -1
  63. package/scripts/sw-code-review.sh +1 -1
  64. package/scripts/sw-connect.sh +1 -1
  65. package/scripts/sw-context.sh +1 -1
  66. package/scripts/sw-cost.sh +1 -1
  67. package/scripts/sw-daemon.sh +2 -2
  68. package/scripts/sw-dashboard.sh +1 -1
  69. package/scripts/sw-db.sh +1 -1
  70. package/scripts/sw-decompose.sh +1 -1
  71. package/scripts/sw-deps.sh +1 -1
  72. package/scripts/sw-developer-simulation.sh +1 -1
  73. package/scripts/sw-discovery.sh +1 -1
  74. package/scripts/sw-doc-fleet.sh +1 -1
  75. package/scripts/sw-docs-agent.sh +1 -1
  76. package/scripts/sw-docs.sh +1 -1
  77. package/scripts/sw-doctor.sh +8 -1
  78. package/scripts/sw-dora.sh +1 -1
  79. package/scripts/sw-durable.sh +1 -1
  80. package/scripts/sw-e2e-orchestrator.sh +1 -1
  81. package/scripts/sw-eventbus.sh +1 -1
  82. package/scripts/sw-feedback.sh +1 -1
  83. package/scripts/sw-fix.sh +6 -5
  84. package/scripts/sw-fleet-discover.sh +1 -1
  85. package/scripts/sw-fleet-viz.sh +1 -1
  86. package/scripts/sw-fleet.sh +1 -1
  87. package/scripts/sw-github-app.sh +5 -2
  88. package/scripts/sw-github-checks.sh +1 -1
  89. package/scripts/sw-github-deploy.sh +1 -1
  90. package/scripts/sw-github-graphql.sh +1 -1
  91. package/scripts/sw-guild.sh +1 -1
  92. package/scripts/sw-heartbeat.sh +1 -1
  93. package/scripts/sw-hygiene.sh +1 -1
  94. package/scripts/sw-incident.sh +1 -1
  95. package/scripts/sw-init.sh +112 -9
  96. package/scripts/sw-instrument.sh +6 -1
  97. package/scripts/sw-intelligence.sh +5 -1
  98. package/scripts/sw-jira.sh +1 -1
  99. package/scripts/sw-launchd.sh +1 -1
  100. package/scripts/sw-linear.sh +20 -9
  101. package/scripts/sw-logs.sh +1 -1
  102. package/scripts/sw-loop.sh +2 -1
  103. package/scripts/sw-memory.sh +10 -1
  104. package/scripts/sw-mission-control.sh +1 -1
  105. package/scripts/sw-model-router.sh +4 -1
  106. package/scripts/sw-otel.sh +1 -1
  107. package/scripts/sw-oversight.sh +1 -1
  108. package/scripts/sw-pipeline-composer.sh +3 -1
  109. package/scripts/sw-pipeline-vitals.sh +4 -6
  110. package/scripts/sw-pipeline.sh +4 -1
  111. package/scripts/sw-pm.sh +5 -2
  112. package/scripts/sw-pr-lifecycle.sh +1 -1
  113. package/scripts/sw-predictive.sh +4 -1
  114. package/scripts/sw-prep.sh +3 -2
  115. package/scripts/sw-ps.sh +1 -1
  116. package/scripts/sw-public-dashboard.sh +10 -4
  117. package/scripts/sw-quality.sh +1 -1
  118. package/scripts/sw-reaper.sh +1 -1
  119. package/scripts/sw-recruit.sh +16 -0
  120. package/scripts/sw-regression.sh +2 -1
  121. package/scripts/sw-release-manager.sh +1 -1
  122. package/scripts/sw-release.sh +7 -5
  123. package/scripts/sw-remote.sh +1 -1
  124. package/scripts/sw-replay.sh +1 -1
  125. package/scripts/sw-retro.sh +4 -1
  126. package/scripts/sw-scale.sh +4 -1
  127. package/scripts/sw-security-audit.sh +1 -1
  128. package/scripts/sw-self-optimize.sh +113 -1
  129. package/scripts/sw-session.sh +1 -1
  130. package/scripts/sw-setup.sh +1 -1
  131. package/scripts/sw-standup.sh +2 -1
  132. package/scripts/sw-status.sh +1 -1
  133. package/scripts/sw-strategic.sh +2 -1
  134. package/scripts/sw-stream.sh +1 -1
  135. package/scripts/sw-swarm.sh +6 -1
  136. package/scripts/sw-team-stages.sh +1 -1
  137. package/scripts/sw-templates.sh +1 -1
  138. package/scripts/sw-testgen.sh +3 -2
  139. package/scripts/sw-tmux-pipeline.sh +2 -1
  140. package/scripts/sw-tmux.sh +1 -1
  141. package/scripts/sw-trace.sh +1 -1
  142. package/scripts/sw-tracker-jira.sh +1 -0
  143. package/scripts/sw-tracker-linear.sh +1 -0
  144. package/scripts/sw-tracker.sh +1 -1
  145. package/scripts/sw-triage.sh +198 -11
  146. package/scripts/sw-upgrade.sh +1 -1
  147. package/scripts/sw-ux.sh +1 -1
  148. package/scripts/sw-webhook.sh +1 -1
  149. package/scripts/sw-widgets.sh +2 -2
  150. package/scripts/sw-worktree.sh +1 -1
  151. package/dashboard/public/app.js +0 -4422
@@ -0,0 +1,389 @@
1
+ // Timeline tab - Canvas2D interactive timeline with scrub, playback, prediction overlays
2
+
3
+ import { escapeHtml, formatDuration, padZero } from "../core/helpers";
4
+ import { icon } from "../design/icons";
5
+ import { colors, STAGES, STAGE_HEX } from "../design/tokens";
6
+ import {
7
+ CanvasRenderer,
8
+ drawText,
9
+ drawRoundRect,
10
+ type CanvasScene,
11
+ } from "../canvas/renderer";
12
+ import * as api from "../core/api";
13
+ import type {
14
+ FleetState,
15
+ View,
16
+ TimelineEntry,
17
+ TimelineSegment,
18
+ } from "../types/api";
19
+
20
+ let timelineRange = "24h";
21
+ let timelineCache: TimelineEntry[] | null = null;
22
+ let renderer: CanvasRenderer | null = null;
23
+ let scene: TimelineScene | null = null;
24
+
25
+ class TimelineScene implements CanvasScene {
26
+ entries: TimelineEntry[] = [];
27
+ earliest = 0;
28
+ latest = 0;
29
+ totalSpan = 1;
30
+ width = 0;
31
+ height = 0;
32
+ scrollY = 0;
33
+ hoverRow = -1;
34
+ hoverX = -1;
35
+ playbackTime = -1;
36
+ isPlaying = false;
37
+ playbackSpeed = 1;
38
+ rowHeight = 44;
39
+ headerHeight = 40;
40
+ labelWidth = 160;
41
+
42
+ setData(entries: TimelineEntry[]): void {
43
+ this.entries = entries;
44
+ this.earliest = Infinity;
45
+ this.latest = -Infinity;
46
+
47
+ for (const entry of entries) {
48
+ for (const seg of entry.segments) {
49
+ const s = new Date(seg.start).getTime();
50
+ if (s < this.earliest) this.earliest = s;
51
+ const e = seg.end ? new Date(seg.end).getTime() : Date.now();
52
+ if (e > this.latest) this.latest = e;
53
+ }
54
+ }
55
+ if (!isFinite(this.earliest)) this.earliest = Date.now() - 3600000;
56
+ if (!isFinite(this.latest)) this.latest = Date.now();
57
+ this.totalSpan = this.latest - this.earliest || 1;
58
+ this.playbackTime = -1;
59
+ }
60
+
61
+ update(dt: number): void {
62
+ if (this.isPlaying && this.entries.length > 0) {
63
+ if (this.playbackTime < 0) this.playbackTime = this.earliest;
64
+ this.playbackTime += dt * this.playbackSpeed * this.totalSpan * 0.1;
65
+ if (this.playbackTime > this.latest) {
66
+ this.playbackTime = this.earliest;
67
+ }
68
+ }
69
+ }
70
+
71
+ draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
72
+ const {
73
+ entries,
74
+ earliest,
75
+ totalSpan,
76
+ rowHeight,
77
+ headerHeight,
78
+ labelWidth,
79
+ scrollY,
80
+ } = this;
81
+
82
+ // Background
83
+ ctx.fillStyle = colors.bg.abyss;
84
+ ctx.fillRect(0, 0, width, height);
85
+
86
+ const trackWidth = width - labelWidth;
87
+ const trackX = labelWidth;
88
+
89
+ // Time axis header
90
+ ctx.fillStyle = colors.bg.deep;
91
+ ctx.fillRect(0, 0, width, headerHeight);
92
+
93
+ // Time ticks
94
+ const tickCount = Math.max(4, Math.floor(trackWidth / 100));
95
+ for (let i = 0; i <= tickCount; i++) {
96
+ const t = earliest + (i / tickCount) * totalSpan;
97
+ const d = new Date(t);
98
+ const label = padZero(d.getHours()) + ":" + padZero(d.getMinutes());
99
+ const x = trackX + (i / tickCount) * trackWidth;
100
+
101
+ // Tick line
102
+ ctx.strokeStyle = colors.bg.foam + "40";
103
+ ctx.lineWidth = 1;
104
+ ctx.beginPath();
105
+ ctx.moveTo(x, headerHeight);
106
+ ctx.lineTo(x, height);
107
+ ctx.stroke();
108
+
109
+ drawText(ctx, label, x, 12, {
110
+ font: "monoSm",
111
+ color: colors.text.muted,
112
+ align: "center",
113
+ });
114
+ }
115
+
116
+ // Playback cursor
117
+ if (this.playbackTime >= 0) {
118
+ const cursorX =
119
+ trackX + ((this.playbackTime - earliest) / totalSpan) * trackWidth;
120
+ ctx.strokeStyle = colors.accent.cyan;
121
+ ctx.lineWidth = 2;
122
+ ctx.beginPath();
123
+ ctx.moveTo(cursorX, headerHeight);
124
+ ctx.lineTo(cursorX, height);
125
+ ctx.stroke();
126
+
127
+ // Cursor time label
128
+ const cursorDate = new Date(this.playbackTime);
129
+ const cursorLabel =
130
+ padZero(cursorDate.getHours()) +
131
+ ":" +
132
+ padZero(cursorDate.getMinutes()) +
133
+ ":" +
134
+ padZero(cursorDate.getSeconds());
135
+ ctx.fillStyle = colors.accent.cyan;
136
+ drawRoundRect(ctx, cursorX - 30, 2, 60, 16, 4);
137
+ ctx.fill();
138
+ drawText(ctx, cursorLabel, cursorX, 4, {
139
+ font: "monoSm",
140
+ color: colors.bg.abyss,
141
+ align: "center",
142
+ });
143
+ }
144
+
145
+ // Entries
146
+ ctx.save();
147
+ ctx.beginPath();
148
+ ctx.rect(0, headerHeight, width, height - headerHeight);
149
+ ctx.clip();
150
+
151
+ for (let i = 0; i < entries.length; i++) {
152
+ const entry = entries[i];
153
+ const y = headerHeight + i * rowHeight - scrollY;
154
+ if (y + rowHeight < headerHeight || y > height) continue;
155
+
156
+ const isHovered = i === this.hoverRow;
157
+
158
+ // Row background
159
+ if (isHovered) {
160
+ ctx.fillStyle = colors.bg.surface + "60";
161
+ ctx.fillRect(0, y, width, rowHeight);
162
+ }
163
+
164
+ // Row separator
165
+ ctx.strokeStyle = colors.bg.foam + "20";
166
+ ctx.lineWidth = 1;
167
+ ctx.beginPath();
168
+ ctx.moveTo(0, y + rowHeight);
169
+ ctx.lineTo(width, y + rowHeight);
170
+ ctx.stroke();
171
+
172
+ // Label
173
+ const issueLabel = `#${entry.issue}`;
174
+ const titleLabel = entry.title ? " " + entry.title.substring(0, 20) : "";
175
+ drawText(ctx, issueLabel + titleLabel, 12, y + 14, {
176
+ font: "caption",
177
+ color: isHovered ? colors.text.primary : colors.text.secondary,
178
+ maxWidth: labelWidth - 20,
179
+ });
180
+
181
+ // Status badge — derive from last segment
182
+ const lastSeg = entry.segments[entry.segments.length - 1];
183
+ const statusColor =
184
+ lastSeg?.status === "failed"
185
+ ? colors.semantic.error
186
+ : lastSeg?.status === "complete"
187
+ ? colors.semantic.success
188
+ : colors.accent.cyan;
189
+ ctx.fillStyle = statusColor;
190
+ ctx.beginPath();
191
+ ctx.arc(labelWidth - 16, y + rowHeight / 2, 4, 0, Math.PI * 2);
192
+ ctx.fill();
193
+
194
+ // Stage bars
195
+ for (const stage of entry.segments) {
196
+ const stageStart = new Date(stage.start).getTime();
197
+ const stageEnd = stage.end ? new Date(stage.end).getTime() : Date.now();
198
+ const left =
199
+ trackX + ((stageStart - earliest) / totalSpan) * trackWidth;
200
+ const barWidth = Math.max(
201
+ 3,
202
+ ((stageEnd - stageStart) / totalSpan) * trackWidth,
203
+ );
204
+ const color =
205
+ (STAGE_HEX as Record<string, string>)[stage.stage] ||
206
+ colors.text.muted;
207
+
208
+ const barY = y + 8;
209
+ const barH = rowHeight - 16;
210
+
211
+ ctx.fillStyle = color;
212
+ ctx.globalAlpha = stage.status === "failed" ? 0.5 : 0.85;
213
+ drawRoundRect(ctx, left, barY, barWidth, barH, 3);
214
+ ctx.fill();
215
+ ctx.globalAlpha = 1;
216
+
217
+ // Stage name if bar is wide enough
218
+ if (barWidth > 40) {
219
+ drawText(ctx, stage.stage, left + 4, barY + 4, {
220
+ font: "tiny",
221
+ color: colors.bg.abyss,
222
+ maxWidth: barWidth - 8,
223
+ });
224
+ }
225
+ }
226
+ }
227
+
228
+ ctx.restore();
229
+
230
+ // Hover tooltip
231
+ if (
232
+ this.hoverRow >= 0 &&
233
+ this.hoverRow < entries.length &&
234
+ this.hoverX >= trackX
235
+ ) {
236
+ const entry = entries[this.hoverRow];
237
+ const hoverTime =
238
+ earliest + ((this.hoverX - trackX) / trackWidth) * totalSpan;
239
+ const hoverStage = entry.segments.find((s: TimelineSegment) => {
240
+ const ss = new Date(s.start).getTime();
241
+ const se = s.end ? new Date(s.end).getTime() : Date.now();
242
+ return hoverTime >= ss && hoverTime <= se;
243
+ });
244
+
245
+ if (hoverStage) {
246
+ const tooltipY =
247
+ headerHeight + this.hoverRow * rowHeight - scrollY - 30;
248
+ const dur =
249
+ (hoverStage.end ? new Date(hoverStage.end).getTime() : Date.now()) -
250
+ new Date(hoverStage.start).getTime();
251
+ const tooltipText = `${hoverStage.stage}: ${formatDuration(dur / 1000)}`;
252
+
253
+ ctx.fillStyle = colors.bg.ocean;
254
+ const tw = ctx.measureText(tooltipText).width + 16;
255
+ drawRoundRect(ctx, this.hoverX - tw / 2, tooltipY, tw, 22, 4);
256
+ ctx.fill();
257
+ ctx.strokeStyle = colors.accent.cyanDim;
258
+ ctx.lineWidth = 1;
259
+ drawRoundRect(ctx, this.hoverX - tw / 2, tooltipY, tw, 22, 4);
260
+ ctx.stroke();
261
+
262
+ drawText(ctx, tooltipText, this.hoverX, tooltipY + 5, {
263
+ font: "monoSm",
264
+ color: colors.text.primary,
265
+ align: "center",
266
+ });
267
+ }
268
+ }
269
+
270
+ // Playback controls indicator
271
+ const controlsY = height - 30;
272
+ const playIcon = this.isPlaying ? "||" : "\u25B6";
273
+ drawText(ctx, playIcon + " " + this.playbackSpeed + "x", 12, controlsY, {
274
+ font: "caption",
275
+ color: colors.text.muted,
276
+ });
277
+ drawText(
278
+ ctx,
279
+ `${entries.length} pipeline${entries.length !== 1 ? "s" : ""}`,
280
+ width - 12,
281
+ controlsY,
282
+ {
283
+ font: "caption",
284
+ color: colors.text.muted,
285
+ align: "right",
286
+ },
287
+ );
288
+ }
289
+
290
+ onResize(width: number, height: number): void {
291
+ this.width = width;
292
+ this.height = height;
293
+ }
294
+
295
+ onMouseMove(x: number, y: number): void {
296
+ const row = Math.floor(
297
+ (y - this.headerHeight + this.scrollY) / this.rowHeight,
298
+ );
299
+ this.hoverRow = row >= 0 && row < this.entries.length ? row : -1;
300
+ this.hoverX = x;
301
+ if (renderer) {
302
+ renderer.getCanvas().style.cursor =
303
+ this.hoverRow >= 0 ? "crosshair" : "default";
304
+ }
305
+ }
306
+
307
+ onMouseClick(x: number, y: number): void {
308
+ if (y < this.headerHeight) {
309
+ // Click on time axis: set playback cursor
310
+ const trackX = this.labelWidth;
311
+ const trackWidth = this.width - this.labelWidth;
312
+ const pct = (x - trackX) / trackWidth;
313
+ if (pct >= 0 && pct <= 1) {
314
+ this.playbackTime = this.earliest + pct * this.totalSpan;
315
+ }
316
+ } else if (x < 60 && y > this.height - 40) {
317
+ // Click on play button
318
+ this.isPlaying = !this.isPlaying;
319
+ }
320
+ }
321
+
322
+ onMouseWheel(delta: number): void {
323
+ const maxScroll = Math.max(
324
+ 0,
325
+ this.entries.length * this.rowHeight - (this.height - this.headerHeight),
326
+ );
327
+ this.scrollY = Math.max(0, Math.min(maxScroll, this.scrollY + delta * 0.5));
328
+ }
329
+ }
330
+
331
+ function setupTimelineControls(): void {
332
+ const select = document.getElementById(
333
+ "timeline-range",
334
+ ) as HTMLSelectElement | null;
335
+ if (select) {
336
+ select.addEventListener("change", () => {
337
+ timelineRange = select.value || "24";
338
+ fetchTimeline();
339
+ });
340
+ }
341
+ }
342
+
343
+ function fetchTimeline(): void {
344
+ api
345
+ .fetchTimeline(timelineRange)
346
+ .then((data) => {
347
+ timelineCache = Array.isArray(data) ? data : [];
348
+ if (scene) scene.setData(timelineCache);
349
+ })
350
+ .catch((err) => {
351
+ const container = document.getElementById("gantt-chart");
352
+ if (container)
353
+ container.innerHTML = `<div class="empty-state"><p>Failed to load: ${escapeHtml(String(err))}</p></div>`;
354
+ });
355
+ }
356
+
357
+ export const timelineView: View = {
358
+ init() {
359
+ setupTimelineControls();
360
+
361
+ // Replace the HTML gantt container with a canvas
362
+ const ganttChart = document.getElementById("gantt-chart");
363
+ if (ganttChart) {
364
+ ganttChart.innerHTML = "";
365
+ ganttChart.style.height = "calc(100vh - 220px)";
366
+ ganttChart.style.minHeight = "400px";
367
+ ganttChart.style.position = "relative";
368
+
369
+ renderer = new CanvasRenderer(ganttChart);
370
+ scene = new TimelineScene();
371
+ renderer.setScene(scene);
372
+ renderer.start();
373
+ }
374
+
375
+ fetchTimeline();
376
+ },
377
+
378
+ render(_data: FleetState) {
379
+ if (!timelineCache) fetchTimeline();
380
+ },
381
+
382
+ destroy() {
383
+ if (renderer) {
384
+ renderer.destroy();
385
+ renderer = null;
386
+ }
387
+ scene = null;
388
+ },
389
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "resolveJsonModule": true,
12
+ "declaration": false,
13
+ "sourceMap": true,
14
+ "outDir": "./public/dist",
15
+ "rootDir": "./src",
16
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
17
+ "types": ["bun-types"]
18
+ },
19
+ "include": ["src/**/*.ts"],
20
+ "exclude": ["node_modules", "public/dist", "src/**/*.test.ts"]
21
+ }
@@ -0,0 +1,27 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "happy-dom",
6
+ root: "./dashboard",
7
+ include: ["src/**/*.test.ts"],
8
+ globals: true,
9
+ coverage: {
10
+ provider: "v8",
11
+ reporter: ["text", "json-summary"],
12
+ include: ["src/**/*.ts"],
13
+ exclude: ["src/**/*.test.ts", "src/types/**"],
14
+ thresholds: {
15
+ statements: 60,
16
+ branches: 50,
17
+ functions: 60,
18
+ lines: 60,
19
+ },
20
+ },
21
+ },
22
+ resolve: {
23
+ alias: {
24
+ "@": "./dashboard/src",
25
+ },
26
+ },
27
+ });
@@ -17,13 +17,13 @@
17
17
 
18
18
  ## 2. Not fully implemented
19
19
 
20
- | Item | What | Next step |
21
- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
22
- | ~~**Phase 3 libs not sourced**~~ | **Done.** `pipeline-quality.sh` sourced by `sw-pipeline.sh` and `sw-quality.sh`; `daemon-health.sh` sourced by `sw-daemon.sh`. | Wired and verified. |
23
- | ~~**Policy JSON Schema validation**~~ | **Done.** `config/policy.schema.json` created; `ajv-cli` validates successfully; optional step in platform-health workflow confirmed working. | Validated locally; trigger workflow_dispatch in CI to confirm. |
24
- | ~~**Sweep workflow still hardcoded**~~ | **Done.** Sweep workflow now checks out repo, reads `config/policy.json`, and exports `STUCK_THRESHOLD_HOURS`, `RETRY_TEMPLATE`, `RETRY_MAX_ITERATIONS`, `STUCK_RETRY_MAX_ITERATIONS` to env. | Wired. |
25
- | ~~**Helpers adoption (Phase 1.4)**~~ | **Done.** All ~98 scripts migrated to `lib/helpers.sh`. Zero duplicated info/success/warn/error blocks remain. | Complete. |
26
- | **Monolith decomposition (Phase 3.1–3.4)** | Pipeline stages, pipeline quality gate, daemon poll loop, daemon health are **not** extracted into separate sourced files. Line counts unchanged (8600+ / 6000+). | Defer or do incrementally: extract one module (e.g. pipeline quality gate block) and source it. |
20
+ | Item | What | Next step |
21
+ | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
22
+ | ~~**Phase 3 libs not sourced**~~ | **Done.** `pipeline-quality.sh` sourced by `sw-pipeline.sh` and `sw-quality.sh`; `daemon-health.sh` sourced by `sw-daemon.sh`. | Wired and verified. |
23
+ | ~~**Policy JSON Schema validation**~~ | **Done.** `config/policy.schema.json` created; `ajv-cli` validates successfully; optional step in platform-health workflow confirmed working. | Validated locally; trigger workflow_dispatch in CI to confirm. |
24
+ | ~~**Sweep workflow still hardcoded**~~ | **Done.** Sweep workflow now checks out repo, reads `config/policy.json`, and exports `STUCK_THRESHOLD_HOURS`, `RETRY_TEMPLATE`, `RETRY_MAX_ITERATIONS`, `STUCK_RETRY_MAX_ITERATIONS` to env. | Wired. |
25
+ | ~~**Helpers adoption (Phase 1.4)**~~ | **Done.** All ~98 scripts migrated to `lib/helpers.sh`. Zero duplicated info/success/warn/error blocks remain. | Complete. |
26
+ | ~~**Monolith decomposition (Phase 3.1–3.4)**~~ | **Done.** Pipeline decomposed: 8,665 2,434 lines (72% reduction). Daemon decomposed: 6,150 1,351 lines (78% reduction). All functions extracted into sourced lib modules. Tests pass. | Complete. |
27
27
 
28
28
  ---
29
29
 
@@ -40,13 +40,13 @@
40
40
 
41
41
  ## 4. Not audited E2E
42
42
 
43
- | Item | What | Next step |
44
- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
45
- | ~~**Pipeline E2E with policy**~~ | **Done.** `sw-policy-e2e-test.sh` (26 tests) verifies pipeline-quality.sh reads coverage/gate thresholds from policy, policy_get with mock and real configs. | Added to npm test suite. |
46
- | ~~**Daemon E2E with policy**~~ | **Done.** `sw-policy-e2e-test.sh` verifies daemon policy_get for poll_interval, heartbeat_timeout, stage_timeouts, auto_scale_interval. | Covered in policy E2E test. |
47
- | **Platform-health workflow E2E** | Workflow validated locally (schema, scan, report steps); not yet triggered via workflow_dispatch in CI. | Trigger workflow (workflow_dispatch) to confirm end-to-end in real CI. |
48
- | ~~**Doctor with no platform-hygiene**~~ | **Done.** Doctor now auto-runs `hygiene platform-refactor` when report is missing; `--skip-platform-scan` flag available for fast mode. | Complete. |
49
- | ~~**Full npm test with policy**~~ | **Done.** `sw-policy-e2e-test.sh` added to npm test; 26 policy-specific assertions covering policy_get, pipeline-quality.sh, daemon thresholds, and sanity checks. | In test suite. |
43
+ | Item | What | Next step |
44
+ | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
45
+ | ~~**Pipeline E2E with policy**~~ | **Done.** `sw-policy-e2e-test.sh` (26 tests) verifies pipeline-quality.sh reads coverage/gate thresholds from policy, policy_get with mock and real configs. | Added to npm test suite. |
46
+ | ~~**Daemon E2E with policy**~~ | **Done.** `sw-policy-e2e-test.sh` verifies daemon policy_get for poll_interval, heartbeat_timeout, stage_timeouts, auto_scale_interval. | Covered in policy E2E test. |
47
+ | ~~**Platform-health workflow E2E**~~ | **Done.** Workflow triggered via workflow_dispatch; runs successfully: policy validation, schema validation (ajv-cli), platform-refactor scan, threshold gate. Reports hardcoded=58 fallback=54 TODO=37. | Complete. |
48
+ | ~~**Doctor with no platform-hygiene**~~ | **Done.** Doctor now auto-runs `hygiene platform-refactor` when report is missing; `--skip-platform-scan` flag available for fast mode. | Complete. |
49
+ | ~~**Full npm test with policy**~~ | **Done.** `sw-policy-e2e-test.sh` added to npm test; 26 policy-specific assertions covering policy_get, pipeline-quality.sh, daemon thresholds, and sanity checks. | In test suite. |
50
50
 
51
51
  ---
52
52
 
@@ -60,7 +60,7 @@
60
60
  - [x] **E2E** — Pipeline + daemon policy assertions in sw-policy-e2e-test.sh; platform-health workflow validated locally.
61
61
  - [x] **TODO/FIXME/HACK** — Phase 4 triage complete: 4 github-issue, 3 accepted-debt, 0 stale. See `docs/PLATFORM-TODO-TRIAGE.md`.
62
62
  - [x] **Strategic + hygiene** — Strategic CI workflow now runs hygiene platform-refactor before analysis.
63
- - [ ] **Platform-health workflow_dispatch** — Trigger once in CI to confirm end-to-end execution.
63
+ - [x] **Platform-health workflow_dispatch** — Triggered and confirmed: policy + schema validation, scan, threshold gate all pass in CI.
64
64
  - [x] **Monolith decomposition (Phase 3.1, 3.3)** — Done. Pipeline 8,665 → 2,434 lines; daemon 6,150 → 1,351 lines. All libs wired and sourced.
65
65
  - [x] **Doctor auto-hygiene** — Doctor auto-runs platform-refactor when report missing; `--skip-platform-scan` flag added.
66
66
  - [x] **Dead code scan** — 1 confirmed dead function (accepted debt); no unused scripts or temp files.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipwright-cli",
3
- "version": "2.2.2",
3
+ "version": "2.3.1",
4
4
  "description": "Orchestrate autonomous Claude Code agent teams in tmux",
5
5
  "bin": {
6
6
  "shipwright": "./scripts/sw",
@@ -30,7 +30,13 @@
30
30
  ],
31
31
  "scripts": {
32
32
  "postinstall": "node scripts/postinstall.mjs",
33
- "test": "bash scripts/sw-agi-roadmap-test.sh && bash scripts/sw-activity-test.sh && bash scripts/sw-adaptive-test.sh && bash scripts/sw-adversarial-test.sh && bash scripts/sw-architecture-enforcer-test.sh && bash scripts/sw-auth-test.sh && bash scripts/sw-autonomous-test.sh && bash scripts/sw-changelog-test.sh && bash scripts/sw-checkpoint-test.sh && bash scripts/sw-ci-test.sh && bash scripts/sw-cleanup-test.sh && bash scripts/sw-code-review-test.sh && bash scripts/sw-connect-test.sh && bash scripts/sw-context-test.sh && bash scripts/sw-cost-test.sh && bash scripts/sw-daemon-test.sh && bash scripts/sw-dashboard-test.sh && bash scripts/sw-db-test.sh && bash scripts/sw-decompose-test.sh && bash scripts/sw-deps-test.sh && bash scripts/sw-developer-simulation-test.sh && bash scripts/sw-discovery-test.sh && bash scripts/sw-doc-fleet-test.sh && bash scripts/sw-docs-agent-test.sh && bash scripts/sw-docs-test.sh && bash scripts/sw-doctor-test.sh && bash scripts/sw-dora-test.sh && bash scripts/sw-durable-test.sh && bash scripts/sw-e2e-orchestrator-test.sh && bash scripts/sw-eventbus-test.sh && bash scripts/sw-feedback-test.sh && bash scripts/sw-fix-test.sh && bash scripts/sw-fleet-discover-test.sh && bash scripts/sw-fleet-test.sh && bash scripts/sw-fleet-viz-test.sh && bash scripts/sw-frontier-test.sh && bash scripts/sw-github-app-test.sh && bash scripts/sw-github-checks-test.sh && bash scripts/sw-github-deploy-test.sh && bash scripts/sw-github-graphql-test.sh && bash scripts/sw-guild-test.sh && bash scripts/sw-heartbeat-test.sh && bash scripts/sw-hygiene-test.sh && bash scripts/sw-incident-test.sh && bash scripts/sw-init-test.sh && bash scripts/sw-instrument-test.sh && bash scripts/sw-intelligence-test.sh && bash scripts/sw-jira-test.sh && bash scripts/sw-launchd-test.sh && bash scripts/sw-linear-test.sh && bash scripts/sw-logs-test.sh && bash scripts/sw-loop-test.sh && bash scripts/sw-memory-test.sh && bash scripts/sw-mission-control-test.sh && bash scripts/sw-model-router-test.sh && bash scripts/sw-otel-test.sh && bash scripts/sw-oversight-test.sh && bash scripts/sw-patrol-meta-test.sh && bash scripts/sw-pipeline-composer-test.sh && bash scripts/sw-pipeline-test.sh && bash scripts/sw-pipeline-vitals-test.sh && bash scripts/sw-pm-test.sh && bash scripts/sw-pr-lifecycle-test.sh && bash scripts/sw-predictive-test.sh && bash scripts/sw-prep-test.sh && bash scripts/sw-ps-test.sh && bash scripts/sw-public-dashboard-test.sh && bash scripts/sw-quality-test.sh && bash scripts/sw-reaper-test.sh && bash scripts/sw-recruit-test.sh && bash scripts/sw-regression-test.sh && bash scripts/sw-release-manager-test.sh && bash scripts/sw-release-test.sh && bash scripts/sw-remote-test.sh && bash scripts/sw-replay-test.sh && bash scripts/sw-retro-test.sh && bash scripts/sw-scale-test.sh && bash scripts/sw-security-audit-test.sh && bash scripts/sw-self-optimize-test.sh && bash scripts/sw-session-test.sh && bash scripts/sw-setup-test.sh && bash scripts/sw-standup-test.sh && bash scripts/sw-status-test.sh && bash scripts/sw-strategic-test.sh && bash scripts/sw-stream-test.sh && bash scripts/sw-swarm-test.sh && bash scripts/sw-team-stages-test.sh && bash scripts/sw-templates-test.sh && bash scripts/sw-testgen-test.sh && bash scripts/sw-tmux-pipeline-test.sh && bash scripts/sw-tmux-test.sh && bash scripts/sw-trace-test.sh && bash scripts/sw-tracker-test.sh && bash scripts/sw-triage-test.sh && bash scripts/sw-upgrade-test.sh && bash scripts/sw-ux-test.sh && bash scripts/sw-webhook-test.sh && bash scripts/sw-widgets-test.sh && bash scripts/sw-worktree-test.sh && bash scripts/sw-policy-e2e-test.sh && bash scripts/sw-e2e-smoke-test.sh",
33
+ "dashboard:build": "bun build dashboard/src/main.ts --target=browser --outdir=dashboard/public/dist --sourcemap=linked",
34
+ "dashboard:watch": "bun build dashboard/src/main.ts --target=browser --outdir=dashboard/public/dist --sourcemap=linked --watch",
35
+ "dashboard:prod": "bun build dashboard/src/main.ts --target=browser --outdir=dashboard/public/dist --minify",
36
+ "dashboard:test": "vitest run --config dashboard/vitest.config.ts",
37
+ "dashboard:test:watch": "vitest --config dashboard/vitest.config.ts",
38
+ "dashboard:test:coverage": "vitest run --config dashboard/vitest.config.ts --coverage",
39
+ "test": "bash scripts/sw-agi-roadmap-test.sh && bash scripts/sw-activity-test.sh && bash scripts/sw-adaptive-test.sh && bash scripts/sw-adversarial-test.sh && bash scripts/sw-architecture-enforcer-test.sh && bash scripts/sw-auth-test.sh && bash scripts/sw-autonomous-test.sh && bash scripts/sw-changelog-test.sh && bash scripts/sw-checkpoint-test.sh && bash scripts/sw-ci-test.sh && bash scripts/sw-cleanup-test.sh && bash scripts/sw-code-review-test.sh && bash scripts/sw-connect-test.sh && bash scripts/sw-context-test.sh && bash scripts/sw-cost-test.sh && bash scripts/sw-daemon-test.sh && bash scripts/sw-dashboard-test.sh && bash scripts/sw-db-test.sh && bash scripts/sw-decompose-test.sh && bash scripts/sw-deps-test.sh && bash scripts/sw-developer-simulation-test.sh && bash scripts/sw-discovery-test.sh && bash scripts/sw-doc-fleet-test.sh && bash scripts/sw-docs-agent-test.sh && bash scripts/sw-docs-test.sh && bash scripts/sw-doctor-test.sh && bash scripts/sw-dora-test.sh && bash scripts/sw-durable-test.sh && bash scripts/sw-e2e-orchestrator-test.sh && bash scripts/sw-eventbus-test.sh && bash scripts/sw-feedback-test.sh && bash scripts/sw-fix-test.sh && bash scripts/sw-fleet-discover-test.sh && bash scripts/sw-fleet-test.sh && bash scripts/sw-fleet-viz-test.sh && bash scripts/sw-frontier-test.sh && bash scripts/sw-github-app-test.sh && bash scripts/sw-github-checks-test.sh && bash scripts/sw-github-deploy-test.sh && bash scripts/sw-github-graphql-test.sh && bash scripts/sw-guild-test.sh && bash scripts/sw-heartbeat-test.sh && bash scripts/sw-hygiene-test.sh && bash scripts/sw-incident-test.sh && bash scripts/sw-init-test.sh && bash scripts/sw-instrument-test.sh && bash scripts/sw-intelligence-test.sh && bash scripts/sw-jira-test.sh && bash scripts/sw-launchd-test.sh && bash scripts/sw-linear-test.sh && bash scripts/sw-logs-test.sh && bash scripts/sw-loop-test.sh && bash scripts/sw-memory-test.sh && bash scripts/sw-mission-control-test.sh && bash scripts/sw-model-router-test.sh && bash scripts/sw-otel-test.sh && bash scripts/sw-oversight-test.sh && bash scripts/sw-patrol-meta-test.sh && bash scripts/sw-pipeline-composer-test.sh && bash scripts/sw-pipeline-test.sh && bash scripts/sw-pipeline-vitals-test.sh && bash scripts/sw-pm-test.sh && bash scripts/sw-pr-lifecycle-test.sh && bash scripts/sw-predictive-test.sh && bash scripts/sw-prep-test.sh && bash scripts/sw-ps-test.sh && bash scripts/sw-public-dashboard-test.sh && bash scripts/sw-quality-test.sh && bash scripts/sw-reaper-test.sh && bash scripts/sw-recruit-test.sh && bash scripts/sw-regression-test.sh && bash scripts/sw-release-manager-test.sh && bash scripts/sw-release-test.sh && bash scripts/sw-remote-test.sh && bash scripts/sw-replay-test.sh && bash scripts/sw-retro-test.sh && bash scripts/sw-scale-test.sh && bash scripts/sw-security-audit-test.sh && bash scripts/sw-self-optimize-test.sh && bash scripts/sw-session-test.sh && bash scripts/sw-setup-test.sh && bash scripts/sw-standup-test.sh && bash scripts/sw-status-test.sh && bash scripts/sw-strategic-test.sh && bash scripts/sw-stream-test.sh && bash scripts/sw-swarm-test.sh && bash scripts/sw-team-stages-test.sh && bash scripts/sw-templates-test.sh && bash scripts/sw-testgen-test.sh && bash scripts/sw-tmux-pipeline-test.sh && bash scripts/sw-tmux-test.sh && bash scripts/sw-trace-test.sh && bash scripts/sw-tracker-test.sh && bash scripts/sw-triage-test.sh && bash scripts/sw-upgrade-test.sh && bash scripts/sw-ux-test.sh && bash scripts/sw-webhook-test.sh && bash scripts/sw-widgets-test.sh && bash scripts/sw-worktree-test.sh && bash scripts/sw-policy-e2e-test.sh && bash scripts/sw-e2e-smoke-test.sh && bash scripts/sw-dashboard-e2e-test.sh",
34
40
  "test:smoke": "bash scripts/sw-e2e-smoke-test.sh",
35
41
  "test:integration": "bash scripts/sw-e2e-integration-test.sh"
36
42
  },
@@ -52,5 +58,13 @@
52
58
  "homepage": "https://sethdford.github.io/shipwright",
53
59
  "engines": {
54
60
  "node": ">=20"
61
+ },
62
+ "devDependencies": {
63
+ "@testing-library/dom": "^10.4.1",
64
+ "bun-types": "^1.3.9",
65
+ "happy-dom": "^20.6.1",
66
+ "jsdom": "^28.1.0",
67
+ "typescript": "^5.9.3",
68
+ "vitest": "^4.0.18"
55
69
  }
56
70
  }
@@ -157,3 +157,33 @@ rotate_jsonl() {
157
157
  tail -n "$max_lines" "$file" > "$tmp_rotate" && mv "$tmp_rotate" "$file" || rm -f "$tmp_rotate"
158
158
  fi
159
159
  }
160
+
161
+ # ─── Project Identity ────────────────────────────────────────────
162
+ # Auto-detect GitHub owner/repo from git remote, with fallbacks
163
+ _sw_github_repo() {
164
+ local remote_url
165
+ remote_url="$(git remote get-url origin 2>/dev/null || echo "")"
166
+ if [[ "$remote_url" =~ github\.com[:/]([^/]+)/([^/.]+) ]]; then
167
+ echo "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
168
+ else
169
+ echo "${SHIPWRIGHT_GITHUB_REPO:-sethdford/shipwright}"
170
+ fi
171
+ }
172
+
173
+ _sw_github_owner() {
174
+ local repo
175
+ repo="$(_sw_github_repo)"
176
+ echo "${repo%%/*}"
177
+ }
178
+
179
+ _sw_docs_url() {
180
+ local owner
181
+ owner="$(_sw_github_owner)"
182
+ echo "${SHIPWRIGHT_DOCS_URL:-https://${owner}.github.io/shipwright}"
183
+ }
184
+
185
+ _sw_github_url() {
186
+ local repo
187
+ repo="$(_sw_github_repo)"
188
+ echo "https://github.com/${repo}"
189
+ }
@@ -927,7 +927,7 @@ run_test_coverage_check() {
927
927
  # Run tests and capture output
928
928
  local test_output
929
929
  local test_rc=0
930
- test_output=$(eval "$test_cmd" 2>&1) || test_rc=$?
930
+ test_output=$(bash -c "$test_cmd" 2>&1) || test_rc=$?
931
931
 
932
932
  if [[ "$test_rc" -ne 0 ]]; then
933
933
  warn "Test command failed (exit code: $test_rc) — cannot extract coverage"