shipwright-cli 2.2.1 → 2.3.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/README.md +19 -19
- package/dashboard/public/index.html +224 -8
- package/dashboard/public/styles.css +1078 -4
- package/dashboard/server.ts +1100 -15
- package/dashboard/src/canvas/interactions.ts +74 -0
- package/dashboard/src/canvas/layout.ts +85 -0
- package/dashboard/src/canvas/overlays.ts +117 -0
- package/dashboard/src/canvas/particles.ts +105 -0
- package/dashboard/src/canvas/renderer.ts +191 -0
- package/dashboard/src/components/charts/bar.ts +54 -0
- package/dashboard/src/components/charts/donut.ts +25 -0
- package/dashboard/src/components/charts/pipeline-rail.ts +105 -0
- package/dashboard/src/components/charts/sparkline.ts +82 -0
- package/dashboard/src/components/header.ts +616 -0
- package/dashboard/src/components/modal.ts +413 -0
- package/dashboard/src/components/terminal.ts +144 -0
- package/dashboard/src/core/api.ts +381 -0
- package/dashboard/src/core/helpers.ts +118 -0
- package/dashboard/src/core/router.ts +190 -0
- package/dashboard/src/core/sse.ts +38 -0
- package/dashboard/src/core/state.ts +150 -0
- package/dashboard/src/core/ws.ts +143 -0
- package/dashboard/src/design/icons.ts +131 -0
- package/dashboard/src/design/tokens.ts +160 -0
- package/dashboard/src/main.ts +68 -0
- package/dashboard/src/types/api.ts +337 -0
- package/dashboard/src/views/activity.ts +185 -0
- package/dashboard/src/views/agent-cockpit.ts +236 -0
- package/dashboard/src/views/agents.ts +72 -0
- package/dashboard/src/views/fleet-map.ts +299 -0
- package/dashboard/src/views/insights.ts +298 -0
- package/dashboard/src/views/machines.ts +162 -0
- package/dashboard/src/views/metrics.ts +420 -0
- package/dashboard/src/views/overview.ts +409 -0
- package/dashboard/src/views/pipeline-theater.ts +219 -0
- package/dashboard/src/views/pipelines.ts +595 -0
- package/dashboard/src/views/team.ts +362 -0
- package/dashboard/src/views/timeline.ts +389 -0
- package/dashboard/tsconfig.json +21 -0
- package/docs/AGI-PLATFORM-PLAN.md +5 -5
- package/docs/AGI-WHATS-NEXT.md +19 -16
- package/docs/README.md +2 -0
- package/package.json +8 -1
- package/scripts/check-version-consistency.sh +72 -0
- package/scripts/lib/daemon-adaptive.sh +610 -0
- package/scripts/lib/daemon-dispatch.sh +489 -0
- package/scripts/lib/daemon-failure.sh +387 -0
- package/scripts/lib/daemon-patrol.sh +1113 -0
- package/scripts/lib/daemon-poll.sh +1202 -0
- package/scripts/lib/daemon-state.sh +550 -0
- package/scripts/lib/daemon-triage.sh +490 -0
- package/scripts/lib/helpers.sh +81 -0
- package/scripts/lib/pipeline-intelligence.sh +0 -6
- package/scripts/lib/pipeline-quality-checks.sh +3 -1
- package/scripts/lib/pipeline-stages.sh +20 -0
- package/scripts/sw +109 -168
- package/scripts/sw-activity.sh +1 -1
- package/scripts/sw-adaptive.sh +2 -2
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +14 -6
- package/scripts/sw-autonomous.sh +1 -1
- package/scripts/sw-changelog.sh +2 -2
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +1 -1
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +1 -1
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +53 -4817
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +1 -1
- package/scripts/sw-decompose.sh +1 -1
- package/scripts/sw-deps.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +1 -1
- package/scripts/sw-doc-fleet.sh +1 -1
- package/scripts/sw-docs-agent.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +49 -1
- package/scripts/sw-dora.sh +1 -1
- package/scripts/sw-durable.sh +1 -1
- package/scripts/sw-e2e-orchestrator.sh +1 -1
- package/scripts/sw-eventbus.sh +1 -1
- package/scripts/sw-feedback.sh +1 -1
- package/scripts/sw-fix.sh +6 -5
- package/scripts/sw-fleet-discover.sh +1 -1
- package/scripts/sw-fleet-viz.sh +3 -3
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-app.sh +5 -2
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +1 -1
- package/scripts/sw-incident.sh +1 -1
- package/scripts/sw-init.sh +112 -9
- package/scripts/sw-instrument.sh +6 -1
- package/scripts/sw-intelligence.sh +5 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +1 -1
- package/scripts/sw-linear.sh +20 -9
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +2 -1
- package/scripts/sw-memory.sh +10 -1
- package/scripts/sw-mission-control.sh +1 -1
- package/scripts/sw-model-router.sh +4 -1
- package/scripts/sw-otel.sh +4 -4
- package/scripts/sw-oversight.sh +1 -1
- package/scripts/sw-pipeline-composer.sh +3 -1
- package/scripts/sw-pipeline-vitals.sh +4 -6
- package/scripts/sw-pipeline.sh +19 -56
- package/scripts/sw-pipeline.sh.mock +7 -0
- package/scripts/sw-pm.sh +5 -2
- package/scripts/sw-pr-lifecycle.sh +1 -1
- package/scripts/sw-predictive.sh +4 -1
- package/scripts/sw-prep.sh +3 -2
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +10 -4
- package/scripts/sw-quality.sh +1 -1
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +25 -1
- package/scripts/sw-regression.sh +2 -1
- package/scripts/sw-release-manager.sh +1 -1
- package/scripts/sw-release.sh +7 -5
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +1 -1
- package/scripts/sw-retro.sh +1 -1
- package/scripts/sw-scale.sh +11 -5
- package/scripts/sw-security-audit.sh +1 -1
- package/scripts/sw-self-optimize.sh +172 -7
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +4 -3
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +2 -1
- package/scripts/sw-stream.sh +8 -2
- package/scripts/sw-swarm.sh +12 -10
- package/scripts/sw-team-stages.sh +1 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +3 -2
- package/scripts/sw-tmux-pipeline.sh +2 -1
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +1 -1
- package/scripts/sw-tracker-jira.sh +1 -0
- package/scripts/sw-tracker-linear.sh +1 -0
- package/scripts/sw-tracker.sh +24 -6
- package/scripts/sw-triage.sh +1 -1
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +1 -1
- package/scripts/sw-webhook.sh +1 -1
- package/scripts/sw-widgets.sh +2 -2
- package/scripts/sw-worktree.sh +1 -1
- 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"]
|
|
21
|
+
}
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
**Goal:** Pipeline and daemon are split into sourced modules; no single file > 2000 lines for orchestration core.
|
|
53
53
|
|
|
54
|
-
**Status:** 3.2
|
|
54
|
+
**Status:** All done. 3.1 pipeline fully decomposed: sw-pipeline.sh reduced from 8,665 → 2,434 lines (72% reduction) by wiring pipeline-state.sh, pipeline-github.sh, pipeline-detection.sh, pipeline-quality-checks.sh, pipeline-intelligence.sh, pipeline-stages.sh. 3.2 done (pipeline-quality.sh). 3.3 daemon fully decomposed: sw-daemon.sh reduced from 6,150 → 1,351 lines (78% reduction) by wiring daemon-state.sh, daemon-adaptive.sh, daemon-triage.sh, daemon-failure.sh, daemon-dispatch.sh, daemon-patrol.sh, daemon-poll.sh. 3.4 done (daemon-health.sh). All tests pass.
|
|
55
55
|
|
|
56
56
|
| # | Task | Owner | Acceptance |
|
|
57
57
|
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | --------------------------------------------------------------------- |
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
**Goal:** Triage all TODO/FIXME/HACK; remove dead code; reduce fallback count.
|
|
68
68
|
|
|
69
|
-
**Status:** 4.1–4.2
|
|
69
|
+
**Status:** All done. 4.1–4.2 triage complete (PLATFORM-TODO-TRIAGE.md: 4 github-issue, 3 accepted-debt). 4.3 dead code scan complete — 1 confirmed dead function (get_adaptive_heartbeat_timeout in daemon-adaptive.sh, accepted debt; may wire later). No unused scripts. No .bak/temp files. 4.4 fallback count reduced from 71 → 54 via monolith decomposition; remaining fallbacks are legitimate defensive patterns (intelligence heuristics, template fallbacks, grep-based search fallbacks). Pre-existing `now_unix` bug in sw-scale.sh fixed.
|
|
70
70
|
|
|
71
71
|
| # | Task | Owner | Acceptance |
|
|
72
72
|
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | -------------------------------------------------------- |
|
|
@@ -94,10 +94,10 @@
|
|
|
94
94
|
|
|
95
95
|
## Current Snapshot (from platform-refactor scan)
|
|
96
96
|
|
|
97
|
-
- **hardcoded:**
|
|
97
|
+
- **hardcoded:** 66 | **fallback:** 71 | **TODO:** 38 | **FIXME:** 19 | **HACK/KLUDGE:** 18
|
|
98
98
|
- **Triage:** 4 github-issue, 3 accepted-debt, 0 stale, 0 fix-now (see `docs/PLATFORM-TODO-TRIAGE.md`)
|
|
99
|
-
- **Largest scripts:** sw-pipeline.sh (
|
|
100
|
-
- _Last scan: 2026-02-16.
|
|
99
|
+
- **Largest scripts:** sw-pipeline.sh (8665), sw-daemon.sh (6150), sw-loop.sh (2492), sw-recruit.sh (2636), sw-prep.sh (1657), sw-memory.sh (1634). Pipeline/daemon have extracted libs (scripts/lib/pipeline-_.sh, scripts/lib/daemon-_.sh).
|
|
100
|
+
- _Last scan: 2026-02-16. Run `shipwright hygiene platform-refactor` to refresh._
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
package/docs/AGI-WHATS-NEXT.md
CHANGED
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
## 2. Not fully implemented
|
|
19
19
|
|
|
20
|
-
| Item
|
|
21
|
-
|
|
|
22
|
-
| ~~**Phase 3 libs not sourced**~~
|
|
23
|
-
| ~~**Policy JSON Schema validation**~~
|
|
24
|
-
| ~~**Sweep workflow still hardcoded**~~
|
|
25
|
-
| ~~**Helpers adoption (Phase 1.4)**~~
|
|
26
|
-
|
|
|
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
|
|
44
|
-
|
|
|
45
|
-
| ~~**Pipeline E2E with policy**~~
|
|
46
|
-
| ~~**Daemon E2E with policy**~~
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
| ~~**Full npm test with policy**~~
|
|
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,8 +60,11 @@
|
|
|
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
|
-
- [
|
|
64
|
-
- [
|
|
63
|
+
- [x] **Platform-health workflow_dispatch** — Triggered and confirmed: policy + schema validation, scan, threshold gate all pass in CI.
|
|
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
|
+
- [x] **Doctor auto-hygiene** — Doctor auto-runs platform-refactor when report missing; `--skip-platform-scan` flag added.
|
|
66
|
+
- [x] **Dead code scan** — 1 confirmed dead function (accepted debt); no unused scripts or temp files.
|
|
67
|
+
- [x] **Fallback reduction** — Counts reduced 71 → 54 via decomposition; remaining are legitimate patterns.
|
|
65
68
|
|
|
66
69
|
---
|
|
67
70
|
|
package/docs/README.md
CHANGED
|
@@ -78,6 +78,8 @@ Navigation hub for all Shipwright docs. Start here or jump to a section.
|
|
|
78
78
|
|
|
79
79
|
## See Also
|
|
80
80
|
|
|
81
|
+
- [../CHANGELOG.md](../CHANGELOG.md) — Version history and release notes
|
|
82
|
+
- **Release automation** — Prefer CLI: `shipwright version bump <x.y.z>`, `shipwright version check`, `shipwright release build`. Scripts: `scripts/update-version.sh`, `scripts/check-version-consistency.sh`, `scripts/build-release.sh`. Website footer reads version from repo `package.json` at build time.
|
|
81
83
|
- [demo/README.md](../demo/README.md) — Demo app for pipeline testing
|
|
82
84
|
- [claude-code/CLAUDE.md.shipwright](../claude-code/CLAUDE.md.shipwright) — Downstream repo template
|
|
83
85
|
- [.github/pull_request_template.md](../.github/pull_request_template.md) — PR checklist
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipwright-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Orchestrate autonomous Claude Code agent teams in tmux",
|
|
5
5
|
"bin": {
|
|
6
6
|
"shipwright": "./scripts/sw",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
],
|
|
31
31
|
"scripts": {
|
|
32
32
|
"postinstall": "node scripts/postinstall.mjs",
|
|
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",
|
|
33
36
|
"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",
|
|
34
37
|
"test:smoke": "bash scripts/sw-e2e-smoke-test.sh",
|
|
35
38
|
"test:integration": "bash scripts/sw-e2e-integration-test.sh"
|
|
@@ -52,5 +55,9 @@
|
|
|
52
55
|
"homepage": "https://sethdford.github.io/shipwright",
|
|
53
56
|
"engines": {
|
|
54
57
|
"node": ">=20"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"bun-types": "^1.3.9",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
55
62
|
}
|
|
56
63
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ Shipwright — Version consistency check ║
|
|
4
|
+
# ║ Fails if package.json version != README badge / script VERSION= ║
|
|
5
|
+
# ║ Run in CI or before release to catch drift. ║
|
|
6
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
11
|
+
|
|
12
|
+
CANONICAL=""
|
|
13
|
+
if [[ -f "$REPO_ROOT/package.json" ]]; then
|
|
14
|
+
if command -v jq &>/dev/null; then
|
|
15
|
+
CANONICAL="$(jq -r .version "$REPO_ROOT/package.json")"
|
|
16
|
+
else
|
|
17
|
+
CANONICAL="$(grep -oE '"version":\s*"[^"]+"' "$REPO_ROOT/package.json" | head -1 | sed 's/.*"\([^"]*\)".*/\1/')"
|
|
18
|
+
fi
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
if [[ -z "$CANONICAL" ]]; then
|
|
22
|
+
echo "check-version-consistency: could not read version from package.json" >&2
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
ERR=0
|
|
27
|
+
|
|
28
|
+
# README badge: version-X.Y.Z and alt="vX.Y.Z"
|
|
29
|
+
if [[ -f "$REPO_ROOT/README.md" ]]; then
|
|
30
|
+
if ! grep -q "badge/version-$CANONICAL" "$REPO_ROOT/README.md"; then
|
|
31
|
+
echo "check-version-consistency: README badge version does not match package.json ($CANONICAL)" >&2
|
|
32
|
+
ERR=1
|
|
33
|
+
fi
|
|
34
|
+
if ! grep -q "alt=\"v$CANONICAL\"" "$REPO_ROOT/README.md"; then
|
|
35
|
+
echo "check-version-consistency: README alt version does not match package.json ($CANONICAL)" >&2
|
|
36
|
+
ERR=1
|
|
37
|
+
fi
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Sample scripts: VERSION= must match (check a few key ones)
|
|
41
|
+
SAMPLES=(
|
|
42
|
+
"$REPO_ROOT/scripts/sw"
|
|
43
|
+
"$REPO_ROOT/scripts/sw-daemon.sh"
|
|
44
|
+
"$REPO_ROOT/scripts/sw-pipeline.sh"
|
|
45
|
+
"$REPO_ROOT/scripts/install-remote.sh"
|
|
46
|
+
)
|
|
47
|
+
for f in "${SAMPLES[@]}"; do
|
|
48
|
+
if [[ -f "$f" ]]; then
|
|
49
|
+
V="$(grep -m1 '^VERSION="' "$f" 2>/dev/null | sed 's/^VERSION="\([^"]*\)".*/\1/')"
|
|
50
|
+
if [[ -n "$V" && "$V" != "$CANONICAL" ]]; then
|
|
51
|
+
echo "check-version-consistency: $(basename "$f") has VERSION=$V, expected $CANONICAL" >&2
|
|
52
|
+
ERR=1
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
# Any script under scripts/ with ^VERSION=" that differs
|
|
58
|
+
while IFS= read -r file; do
|
|
59
|
+
V="$(grep -m1 '^VERSION="' "$file" 2>/dev/null | sed 's/^VERSION="\([^"]*\)".*/\1/')"
|
|
60
|
+
if [[ -n "$V" && "$V" != "$CANONICAL" ]]; then
|
|
61
|
+
echo "check-version-consistency: $(basename "$file") has VERSION=$V, expected $CANONICAL" >&2
|
|
62
|
+
ERR=1
|
|
63
|
+
fi
|
|
64
|
+
done < <(grep -rl '^VERSION="' "$REPO_ROOT/scripts/" 2>/dev/null || true)
|
|
65
|
+
|
|
66
|
+
if [[ $ERR -eq 1 ]]; then
|
|
67
|
+
echo "Run: bash scripts/update-version.sh $CANONICAL" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
echo "Version consistent: $CANONICAL"
|
|
72
|
+
exit 0
|