shipwright-cli 2.4.0 → 3.0.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 +16 -11
- package/completions/_shipwright +1 -1
- package/completions/shipwright.bash +3 -8
- package/completions/shipwright.fish +1 -1
- package/config/defaults.json +111 -0
- package/config/event-schema.json +81 -0
- package/config/policy.json +13 -18
- package/dashboard/coverage/coverage-summary.json +14 -0
- package/dashboard/public/index.html +1 -1
- package/dashboard/server.ts +306 -17
- package/dashboard/src/components/charts/bar.test.ts +79 -0
- package/dashboard/src/components/charts/donut.test.ts +68 -0
- package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
- package/dashboard/src/components/charts/sparkline.test.ts +125 -0
- package/dashboard/src/core/api.test.ts +309 -0
- package/dashboard/src/core/helpers.test.ts +301 -0
- package/dashboard/src/core/router.test.ts +307 -0
- package/dashboard/src/core/router.ts +7 -0
- package/dashboard/src/core/sse.test.ts +144 -0
- package/dashboard/src/views/metrics.test.ts +186 -0
- package/dashboard/src/views/overview.test.ts +173 -0
- package/dashboard/src/views/pipelines.test.ts +183 -0
- package/dashboard/src/views/team.test.ts +253 -0
- package/dashboard/vitest.config.ts +14 -5
- package/docs/TIPS.md +1 -1
- package/docs/patterns/README.md +1 -1
- package/package.json +5 -7
- package/scripts/adapters/docker-deploy.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +11 -1
- package/scripts/adapters/wezterm-adapter.sh +1 -1
- package/scripts/check-version-consistency.sh +1 -1
- package/scripts/lib/architecture.sh +126 -0
- package/scripts/lib/bootstrap.sh +75 -0
- package/scripts/lib/compat.sh +89 -6
- package/scripts/lib/config.sh +91 -0
- package/scripts/lib/daemon-adaptive.sh +3 -3
- package/scripts/lib/daemon-dispatch.sh +39 -16
- package/scripts/lib/daemon-health.sh +1 -1
- package/scripts/lib/daemon-patrol.sh +24 -12
- package/scripts/lib/daemon-poll.sh +37 -25
- package/scripts/lib/daemon-state.sh +115 -23
- package/scripts/lib/daemon-triage.sh +30 -8
- package/scripts/lib/fleet-failover.sh +63 -0
- package/scripts/lib/helpers.sh +30 -6
- package/scripts/lib/pipeline-detection.sh +2 -2
- package/scripts/lib/pipeline-github.sh +9 -9
- package/scripts/lib/pipeline-intelligence.sh +85 -35
- package/scripts/lib/pipeline-quality-checks.sh +16 -16
- package/scripts/lib/pipeline-quality.sh +1 -1
- package/scripts/lib/pipeline-stages.sh +242 -28
- package/scripts/lib/pipeline-state.sh +40 -4
- package/scripts/lib/test-helpers.sh +247 -0
- package/scripts/postinstall.mjs +3 -11
- package/scripts/sw +10 -4
- package/scripts/sw-activity.sh +1 -11
- package/scripts/sw-adaptive.sh +109 -85
- package/scripts/sw-adversarial.sh +4 -14
- package/scripts/sw-architecture-enforcer.sh +1 -11
- package/scripts/sw-auth.sh +8 -17
- package/scripts/sw-autonomous.sh +111 -49
- package/scripts/sw-changelog.sh +1 -11
- package/scripts/sw-checkpoint.sh +144 -20
- package/scripts/sw-ci.sh +2 -12
- package/scripts/sw-cleanup.sh +13 -17
- package/scripts/sw-code-review.sh +16 -36
- package/scripts/sw-connect.sh +5 -12
- package/scripts/sw-context.sh +9 -26
- package/scripts/sw-cost.sh +6 -16
- package/scripts/sw-daemon.sh +75 -70
- package/scripts/sw-dashboard.sh +57 -17
- package/scripts/sw-db.sh +506 -15
- package/scripts/sw-decompose.sh +1 -11
- package/scripts/sw-deps.sh +15 -25
- package/scripts/sw-developer-simulation.sh +1 -11
- package/scripts/sw-discovery.sh +112 -30
- package/scripts/sw-doc-fleet.sh +7 -17
- package/scripts/sw-docs-agent.sh +6 -16
- package/scripts/sw-docs.sh +4 -12
- package/scripts/sw-doctor.sh +134 -43
- package/scripts/sw-dora.sh +11 -19
- package/scripts/sw-durable.sh +35 -52
- package/scripts/sw-e2e-orchestrator.sh +11 -27
- package/scripts/sw-eventbus.sh +115 -115
- package/scripts/sw-evidence.sh +114 -30
- package/scripts/sw-feedback.sh +3 -13
- package/scripts/sw-fix.sh +2 -20
- package/scripts/sw-fleet-discover.sh +1 -11
- package/scripts/sw-fleet-viz.sh +10 -18
- package/scripts/sw-fleet.sh +13 -17
- package/scripts/sw-github-app.sh +6 -16
- package/scripts/sw-github-checks.sh +1 -11
- package/scripts/sw-github-deploy.sh +1 -11
- package/scripts/sw-github-graphql.sh +2 -12
- package/scripts/sw-guild.sh +1 -11
- package/scripts/sw-heartbeat.sh +49 -12
- package/scripts/sw-hygiene.sh +45 -43
- package/scripts/sw-incident.sh +48 -74
- package/scripts/sw-init.sh +35 -37
- package/scripts/sw-instrument.sh +1 -11
- package/scripts/sw-intelligence.sh +362 -51
- package/scripts/sw-jira.sh +5 -14
- package/scripts/sw-launchd.sh +2 -12
- package/scripts/sw-linear.sh +8 -17
- package/scripts/sw-logs.sh +4 -12
- package/scripts/sw-loop.sh +641 -90
- package/scripts/sw-memory.sh +243 -17
- package/scripts/sw-mission-control.sh +2 -12
- package/scripts/sw-model-router.sh +73 -34
- package/scripts/sw-otel.sh +11 -21
- package/scripts/sw-oversight.sh +1 -11
- package/scripts/sw-patrol-meta.sh +5 -11
- package/scripts/sw-pipeline-composer.sh +7 -17
- package/scripts/sw-pipeline-vitals.sh +1 -11
- package/scripts/sw-pipeline.sh +478 -122
- package/scripts/sw-pm.sh +2 -12
- package/scripts/sw-pr-lifecycle.sh +27 -25
- package/scripts/sw-predictive.sh +16 -22
- package/scripts/sw-prep.sh +6 -16
- package/scripts/sw-ps.sh +1 -11
- package/scripts/sw-public-dashboard.sh +2 -12
- package/scripts/sw-quality.sh +77 -10
- package/scripts/sw-reaper.sh +1 -11
- package/scripts/sw-recruit.sh +15 -25
- package/scripts/sw-regression.sh +11 -21
- package/scripts/sw-release-manager.sh +19 -28
- package/scripts/sw-release.sh +8 -16
- package/scripts/sw-remote.sh +1 -11
- package/scripts/sw-replay.sh +48 -44
- package/scripts/sw-retro.sh +70 -92
- package/scripts/sw-review-rerun.sh +1 -1
- package/scripts/sw-scale.sh +109 -32
- package/scripts/sw-security-audit.sh +12 -22
- package/scripts/sw-self-optimize.sh +239 -23
- package/scripts/sw-session.sh +3 -13
- package/scripts/sw-setup.sh +8 -18
- package/scripts/sw-standup.sh +5 -15
- package/scripts/sw-status.sh +32 -23
- package/scripts/sw-strategic.sh +129 -13
- package/scripts/sw-stream.sh +1 -11
- package/scripts/sw-swarm.sh +76 -36
- package/scripts/sw-team-stages.sh +10 -20
- package/scripts/sw-templates.sh +4 -14
- package/scripts/sw-testgen.sh +3 -13
- package/scripts/sw-tmux-pipeline.sh +1 -19
- package/scripts/sw-tmux-role-color.sh +0 -10
- package/scripts/sw-tmux-status.sh +3 -11
- package/scripts/sw-tmux.sh +2 -20
- package/scripts/sw-trace.sh +1 -19
- package/scripts/sw-tracker-github.sh +0 -10
- package/scripts/sw-tracker-jira.sh +1 -11
- package/scripts/sw-tracker-linear.sh +1 -11
- package/scripts/sw-tracker.sh +7 -24
- package/scripts/sw-triage.sh +24 -34
- package/scripts/sw-upgrade.sh +5 -23
- package/scripts/sw-ux.sh +1 -19
- package/scripts/sw-webhook.sh +18 -32
- package/scripts/sw-widgets.sh +3 -21
- package/scripts/sw-worktree.sh +11 -27
- package/scripts/update-homebrew-sha.sh +67 -0
- package/templates/pipelines/tdd.json +72 -0
- package/scripts/sw-pipeline.sh.mock +0 -7
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { store } from "../core/state";
|
|
3
|
+
import type { FleetState, TeamData } from "../types/api";
|
|
4
|
+
|
|
5
|
+
vi.mock("../core/api", () => ({
|
|
6
|
+
fetchTeam: vi
|
|
7
|
+
.fn()
|
|
8
|
+
.mockResolvedValue({
|
|
9
|
+
developers: [],
|
|
10
|
+
total_online: 0,
|
|
11
|
+
total_active_pipelines: 0,
|
|
12
|
+
total_queued: 0,
|
|
13
|
+
}),
|
|
14
|
+
fetchTeamActivity: vi.fn().mockResolvedValue([]),
|
|
15
|
+
createTeamInvite: vi
|
|
16
|
+
.fn()
|
|
17
|
+
.mockResolvedValue({ url: "https://invite.example.com/abc" }),
|
|
18
|
+
fetchLinearStatus: vi.fn().mockResolvedValue({ connected: false }),
|
|
19
|
+
fetchDbHealth: vi.fn().mockResolvedValue({ ok: true }),
|
|
20
|
+
fetchDbEvents: vi.fn().mockResolvedValue([]),
|
|
21
|
+
fetchDbJobs: vi.fn().mockResolvedValue([]),
|
|
22
|
+
fetchDbHeartbeats: vi.fn().mockResolvedValue([]),
|
|
23
|
+
fetchDbCostsToday: vi.fn().mockResolvedValue([]),
|
|
24
|
+
claimIssue: vi.fn().mockResolvedValue({ approved: true }),
|
|
25
|
+
releaseIssue: vi.fn().mockResolvedValue({}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
function createTeamDOM(): void {
|
|
29
|
+
const grid = document.createElement("div");
|
|
30
|
+
grid.id = "team-grid";
|
|
31
|
+
document.body.appendChild(grid);
|
|
32
|
+
|
|
33
|
+
const statOnline = document.createElement("span");
|
|
34
|
+
statOnline.id = "team-stat-online";
|
|
35
|
+
document.body.appendChild(statOnline);
|
|
36
|
+
|
|
37
|
+
const statPipelines = document.createElement("span");
|
|
38
|
+
statPipelines.id = "team-stat-pipelines";
|
|
39
|
+
document.body.appendChild(statPipelines);
|
|
40
|
+
|
|
41
|
+
const statQueued = document.createElement("span");
|
|
42
|
+
statQueued.id = "team-stat-queued";
|
|
43
|
+
document.body.appendChild(statQueued);
|
|
44
|
+
|
|
45
|
+
const activity = document.createElement("div");
|
|
46
|
+
activity.id = "team-activity";
|
|
47
|
+
document.body.appendChild(activity);
|
|
48
|
+
|
|
49
|
+
const inviteResult = document.createElement("div");
|
|
50
|
+
inviteResult.id = "team-invite-result";
|
|
51
|
+
inviteResult.style.display = "none";
|
|
52
|
+
document.body.appendChild(inviteResult);
|
|
53
|
+
|
|
54
|
+
const btnInvite = document.createElement("button");
|
|
55
|
+
btnInvite.id = "btn-create-invite";
|
|
56
|
+
document.body.appendChild(btnInvite);
|
|
57
|
+
|
|
58
|
+
const integrations = document.createElement("div");
|
|
59
|
+
integrations.id = "integrations-status";
|
|
60
|
+
document.body.appendChild(integrations);
|
|
61
|
+
|
|
62
|
+
const adminOutput = document.createElement("div");
|
|
63
|
+
adminOutput.id = "admin-debug-output";
|
|
64
|
+
document.body.appendChild(adminOutput);
|
|
65
|
+
|
|
66
|
+
const claimResult = document.createElement("div");
|
|
67
|
+
claimResult.id = "claim-result";
|
|
68
|
+
document.body.appendChild(claimResult);
|
|
69
|
+
|
|
70
|
+
const claimIssue = document.createElement("input");
|
|
71
|
+
claimIssue.id = "claim-issue";
|
|
72
|
+
document.body.appendChild(claimIssue);
|
|
73
|
+
|
|
74
|
+
const claimMachine = document.createElement("input");
|
|
75
|
+
claimMachine.id = "claim-machine";
|
|
76
|
+
document.body.appendChild(claimMachine);
|
|
77
|
+
|
|
78
|
+
const btnClaim = document.createElement("button");
|
|
79
|
+
btnClaim.id = "btn-claim";
|
|
80
|
+
document.body.appendChild(btnClaim);
|
|
81
|
+
|
|
82
|
+
const btnRelease = document.createElement("button");
|
|
83
|
+
btnRelease.id = "btn-release";
|
|
84
|
+
document.body.appendChild(btnRelease);
|
|
85
|
+
|
|
86
|
+
[
|
|
87
|
+
"btn-db-health",
|
|
88
|
+
"btn-db-events",
|
|
89
|
+
"btn-db-jobs",
|
|
90
|
+
"btn-db-heartbeats",
|
|
91
|
+
"btn-db-costs",
|
|
92
|
+
].forEach((id) => {
|
|
93
|
+
const btn = document.createElement("button");
|
|
94
|
+
btn.id = id;
|
|
95
|
+
document.body.appendChild(btn);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function cleanupTeamDOM(): void {
|
|
100
|
+
const ids = [
|
|
101
|
+
"team-grid",
|
|
102
|
+
"team-stat-online",
|
|
103
|
+
"team-stat-pipelines",
|
|
104
|
+
"team-stat-queued",
|
|
105
|
+
"team-activity",
|
|
106
|
+
"team-invite-result",
|
|
107
|
+
"btn-create-invite",
|
|
108
|
+
"integrations-status",
|
|
109
|
+
"admin-debug-output",
|
|
110
|
+
"claim-result",
|
|
111
|
+
"claim-issue",
|
|
112
|
+
"claim-machine",
|
|
113
|
+
"btn-claim",
|
|
114
|
+
"btn-release",
|
|
115
|
+
"btn-db-health",
|
|
116
|
+
"btn-db-events",
|
|
117
|
+
"btn-db-jobs",
|
|
118
|
+
"btn-db-heartbeats",
|
|
119
|
+
"btn-db-costs",
|
|
120
|
+
];
|
|
121
|
+
ids.forEach((id) => document.getElementById(id)?.remove());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function emptyFleetState(): FleetState {
|
|
125
|
+
return {
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
daemon: {
|
|
128
|
+
running: false,
|
|
129
|
+
pid: null,
|
|
130
|
+
uptime_s: 0,
|
|
131
|
+
maxParallel: 0,
|
|
132
|
+
pollInterval: 5,
|
|
133
|
+
},
|
|
134
|
+
pipelines: [],
|
|
135
|
+
queue: [],
|
|
136
|
+
events: [],
|
|
137
|
+
scale: {},
|
|
138
|
+
metrics: {},
|
|
139
|
+
agents: [],
|
|
140
|
+
machines: [],
|
|
141
|
+
cost: { today_spent: 0, daily_budget: 0, pct_used: 0 },
|
|
142
|
+
dora: {} as any,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
describe("TeamView", () => {
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
store.set("teamCache", null);
|
|
149
|
+
createTeamDOM();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
cleanupTeamDOM();
|
|
154
|
+
vi.clearAllMocks();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("renders team members when data provided", async () => {
|
|
158
|
+
const { teamView } = await import("./team");
|
|
159
|
+
const teamData: TeamData = {
|
|
160
|
+
total_online: 2,
|
|
161
|
+
total_active_pipelines: 1,
|
|
162
|
+
total_queued: 0,
|
|
163
|
+
developers: [
|
|
164
|
+
{
|
|
165
|
+
developer_id: "alice",
|
|
166
|
+
machine_name: "macbook-pro",
|
|
167
|
+
daemon_running: true,
|
|
168
|
+
active_jobs: [{ issue: 42, title: "Fix bug", stage: "code" }],
|
|
169
|
+
queued: [],
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
const data = emptyFleetState();
|
|
174
|
+
data.team = teamData;
|
|
175
|
+
teamView.render(data);
|
|
176
|
+
const grid = document.getElementById("team-grid");
|
|
177
|
+
expect(grid).toBeTruthy();
|
|
178
|
+
expect(grid!.innerHTML).toContain("alice");
|
|
179
|
+
expect(grid!.innerHTML).toContain("macbook-pro");
|
|
180
|
+
expect(grid!.innerHTML).toContain("#42");
|
|
181
|
+
expect(grid!.innerHTML).toContain("presence-dot");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("handles empty team", async () => {
|
|
185
|
+
const { teamView } = await import("./team");
|
|
186
|
+
const data = emptyFleetState();
|
|
187
|
+
data.team = {
|
|
188
|
+
developers: [],
|
|
189
|
+
total_online: 0,
|
|
190
|
+
total_active_pipelines: 0,
|
|
191
|
+
total_queued: 0,
|
|
192
|
+
};
|
|
193
|
+
teamView.render(data);
|
|
194
|
+
const grid = document.getElementById("team-grid");
|
|
195
|
+
expect(grid).toBeTruthy();
|
|
196
|
+
expect(grid!.innerHTML).toContain("No developers connected");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("shows activity indicators when team has active jobs", async () => {
|
|
200
|
+
const { teamView } = await import("./team");
|
|
201
|
+
const teamData: TeamData = {
|
|
202
|
+
developers: [
|
|
203
|
+
{
|
|
204
|
+
developer_id: "bob",
|
|
205
|
+
machine_name: "dev-machine",
|
|
206
|
+
daemon_running: true,
|
|
207
|
+
active_jobs: [
|
|
208
|
+
{ issue: 10, stage: "plan" },
|
|
209
|
+
{ issue: 20, stage: "code" },
|
|
210
|
+
],
|
|
211
|
+
queued: [30],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
const data = emptyFleetState();
|
|
216
|
+
data.team = teamData;
|
|
217
|
+
teamView.render(data);
|
|
218
|
+
const grid = document.getElementById("team-grid");
|
|
219
|
+
expect(grid!.innerHTML).toContain("team-card-pipeline-item");
|
|
220
|
+
expect(grid!.innerHTML).toContain("#10");
|
|
221
|
+
expect(grid!.innerHTML).toContain("#20");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("uses teamCache when data.team is absent", async () => {
|
|
225
|
+
const { teamView } = await import("./team");
|
|
226
|
+
const cachedTeam: TeamData = {
|
|
227
|
+
developers: [
|
|
228
|
+
{
|
|
229
|
+
developer_id: "cached",
|
|
230
|
+
machine_name: "cache-machine",
|
|
231
|
+
daemon_running: false,
|
|
232
|
+
active_jobs: [],
|
|
233
|
+
queued: [],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
total_online: 1,
|
|
237
|
+
total_active_pipelines: 0,
|
|
238
|
+
total_queued: 0,
|
|
239
|
+
};
|
|
240
|
+
store.set("teamCache", cachedTeam);
|
|
241
|
+
const data = emptyFleetState();
|
|
242
|
+
data.team = undefined;
|
|
243
|
+
teamView.render(data);
|
|
244
|
+
const grid = document.getElementById("team-grid");
|
|
245
|
+
expect(grid!.innerHTML).toContain("cached");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("init and destroy do not throw", async () => {
|
|
249
|
+
const { teamView } = await import("./team");
|
|
250
|
+
expect(() => teamView.init()).not.toThrow();
|
|
251
|
+
expect(() => teamView.destroy()).not.toThrow();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
@@ -10,12 +10,21 @@ export default defineConfig({
|
|
|
10
10
|
provider: "v8",
|
|
11
11
|
reporter: ["text", "json-summary"],
|
|
12
12
|
include: ["src/**/*.ts"],
|
|
13
|
-
exclude: [
|
|
13
|
+
exclude: [
|
|
14
|
+
"src/**/*.test.ts",
|
|
15
|
+
"src/types/**",
|
|
16
|
+
"src/views/**",
|
|
17
|
+
"src/canvas/**",
|
|
18
|
+
"src/components/header.ts",
|
|
19
|
+
"src/components/modal.ts",
|
|
20
|
+
"src/components/terminal.ts",
|
|
21
|
+
"src/main.ts",
|
|
22
|
+
],
|
|
14
23
|
thresholds: {
|
|
15
|
-
statements:
|
|
16
|
-
branches:
|
|
17
|
-
functions:
|
|
18
|
-
lines:
|
|
24
|
+
statements: 70,
|
|
25
|
+
branches: 60,
|
|
26
|
+
functions: 70,
|
|
27
|
+
lines: 70,
|
|
19
28
|
},
|
|
20
29
|
},
|
|
21
30
|
},
|
package/docs/TIPS.md
CHANGED
|
@@ -131,7 +131,7 @@ This compacts the conversation when it hits 70% of the context window (default i
|
|
|
131
131
|
|
|
132
132
|
### Watch all agents at once
|
|
133
133
|
|
|
134
|
-
Use `shipwright status` (alias: `sw
|
|
134
|
+
Use `shipwright status` (alias: `sw`) to see a dashboard of running team sessions:
|
|
135
135
|
|
|
136
136
|
```bash
|
|
137
137
|
shipwright status
|
package/docs/patterns/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Each agent writes its results to a markdown file in this directory. The team lea
|
|
|
85
85
|
|
|
86
86
|
## Quick Start
|
|
87
87
|
|
|
88
|
-
Pick a pattern, then use `shipwright` (alias: `sw
|
|
88
|
+
Pick a pattern, then use `shipwright` (alias: `sw`) to set up the team:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
91
|
# Start a tmux session
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shipwright-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Orchestrate autonomous Claude Code agent teams in tmux",
|
|
5
5
|
"bin": {
|
|
6
|
-
"shipwright": "
|
|
7
|
-
"sw": "
|
|
8
|
-
"cct": "
|
|
6
|
+
"shipwright": "scripts/sw",
|
|
7
|
+
"sw": "scripts/sw",
|
|
8
|
+
"cct": "scripts/sw"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"config/",
|
|
@@ -48,9 +48,7 @@
|
|
|
48
48
|
"harness:evidence:capture:cli": "bash scripts/sw-evidence.sh capture cli",
|
|
49
49
|
"harness:ui:capture-browser-evidence": "bash scripts/sw-evidence.sh capture browser",
|
|
50
50
|
"harness:ui:verify-browser-evidence": "bash scripts/sw-evidence.sh verify",
|
|
51
|
-
"harness:ui:pre-pr": "bash scripts/sw-evidence.sh pre-pr browser"
|
|
52
|
-
"harness:risk-tier": "bash scripts/sw-pr-lifecycle.sh risk-tier",
|
|
53
|
-
"harness:policy-gate": "bash scripts/sw-pr-lifecycle.sh policy-gate"
|
|
51
|
+
"harness:ui:pre-pr": "bash scripts/sw-evidence.sh pre-pr browser"
|
|
54
52
|
},
|
|
55
53
|
"keywords": [
|
|
56
54
|
"claude",
|
|
@@ -14,9 +14,14 @@
|
|
|
14
14
|
# Track spawned panes by agent name → pane ID (file-based for bash 3.2 compat)
|
|
15
15
|
_TMUX_PANE_MAP="${TMPDIR:-/tmp}/shipwright-tmux-pane-map.$$"
|
|
16
16
|
: > "$_TMUX_PANE_MAP"
|
|
17
|
-
trap '
|
|
17
|
+
trap '
|
|
18
|
+
if [[ -f "$_TMUX_PANE_MAP" ]] && [[ ! -s "$_TMUX_PANE_MAP" ]]; then
|
|
19
|
+
rm -f "$_TMUX_PANE_MAP"
|
|
20
|
+
fi
|
|
21
|
+
' EXIT
|
|
18
22
|
|
|
19
23
|
spawn_agent() {
|
|
24
|
+
[[ -z "${WINDOW_NAME:-}" ]] && { echo "ERROR: WINDOW_NAME not set" >&2; return 1; }
|
|
20
25
|
local name="$1"
|
|
21
26
|
local working_dir="${2:-#{pane_current_path}}"
|
|
22
27
|
local command="${3:-}"
|
|
@@ -32,6 +37,11 @@ spawn_agent() {
|
|
|
32
37
|
new_pane_id=$(tmux split-window -t "$WINDOW_NAME" -c "$working_dir" -P -F '#{pane_id}')
|
|
33
38
|
fi
|
|
34
39
|
|
|
40
|
+
if [[ -z "$new_pane_id" ]]; then
|
|
41
|
+
echo "ERROR: Failed to create tmux pane for agent '$name'" >&2
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
35
45
|
# Record the mapping: name → pane_id
|
|
36
46
|
echo "${name}=${new_pane_id}" >> "$_TMUX_PANE_MAP"
|
|
37
47
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
10
10
|
|
|
11
11
|
# Verify wezterm CLI is available
|
|
12
|
-
if ! command -v wezterm
|
|
12
|
+
if ! command -v wezterm >/dev/null 2>&1; then
|
|
13
13
|
echo -e "\033[38;2;248;113;113m\033[1m✗\033[0m wezterm CLI not found. Install WezTerm first." >&2
|
|
14
14
|
exit 1
|
|
15
15
|
fi
|
|
@@ -11,7 +11,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
11
11
|
|
|
12
12
|
CANONICAL=""
|
|
13
13
|
if [[ -f "$REPO_ROOT/package.json" ]]; then
|
|
14
|
-
if command -v jq
|
|
14
|
+
if command -v jq >/dev/null 2>&1; then
|
|
15
15
|
CANONICAL="$(jq -r .version "$REPO_ROOT/package.json")"
|
|
16
16
|
else
|
|
17
17
|
CANONICAL="$(grep -oE '"version":\s*"[^"]+"' "$REPO_ROOT/package.json" | head -1 | sed 's/.*"\([^"]*\)".*/\1/')"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# architecture.sh — Gather call-graph and dependency context for plan/design stages
|
|
2
|
+
# Source from pipeline-stages. Requires compat (detect_primary_language).
|
|
3
|
+
[[ -n "${_ARCHITECTURE_CONTEXT_LOADED:-}" ]] && return 0
|
|
4
|
+
_ARCHITECTURE_CONTEXT_LOADED=1
|
|
5
|
+
|
|
6
|
+
# Gather rich architecture context: structure, imports, modules, entry points, test map
|
|
7
|
+
gather_architecture_context() {
|
|
8
|
+
local repo_root="${1:-.}"
|
|
9
|
+
local context=""
|
|
10
|
+
|
|
11
|
+
# 1. File structure
|
|
12
|
+
context="## Project Structure
|
|
13
|
+
$(find "$repo_root" -type f \( -name '*.ts' -o -name '*.js' -o -name '*.py' -o -name '*.sh' -o -name '*.go' -o -name '*.rs' \) 2>/dev/null | grep -v node_modules | grep -v .git | head -100 | sort)
|
|
14
|
+
|
|
15
|
+
"
|
|
16
|
+
|
|
17
|
+
# 2. Import/dependency graph (language-specific)
|
|
18
|
+
local lang=""
|
|
19
|
+
if type detect_primary_language >/dev/null 2>&1; then
|
|
20
|
+
lang=$(detect_primary_language "$repo_root" 2>/dev/null || echo "unknown")
|
|
21
|
+
else
|
|
22
|
+
lang="unknown"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
case "$lang" in
|
|
26
|
+
typescript|javascript|nodejs)
|
|
27
|
+
context="${context}## Import Graph (Top Dependencies)
|
|
28
|
+
"
|
|
29
|
+
local imports=""
|
|
30
|
+
for dir in "$repo_root/src" "$repo_root/lib" "$repo_root/app"; do
|
|
31
|
+
[[ -d "$dir" ]] || continue
|
|
32
|
+
imports=$(grep -rh "^import .* from\|require(" "$dir" 2>/dev/null | \
|
|
33
|
+
grep -oE "from ['\"]([^'\"]+)['\"]|require\\(['\"]([^'\"]+)['\"]\\)" | \
|
|
34
|
+
sed "s/from ['\"]//;s/['\"]//g;s/require(//;s/)//g" | \
|
|
35
|
+
sort | uniq -c | sort -rn | head -20)
|
|
36
|
+
[[ -n "$imports" ]] && context="${context}${imports}
|
|
37
|
+
"
|
|
38
|
+
done
|
|
39
|
+
[[ -z "$imports" ]] && context="${context}(none detected)
|
|
40
|
+
"
|
|
41
|
+
|
|
42
|
+
context="${context}## Module Export Counts
|
|
43
|
+
"
|
|
44
|
+
local f
|
|
45
|
+
while IFS= read -r f; do
|
|
46
|
+
[[ -f "$f" ]] || continue
|
|
47
|
+
local exports=0
|
|
48
|
+
exports=$(grep -c "^export " "$f" 2>/dev/null || echo "0")
|
|
49
|
+
[[ "$exports" -gt 2 ]] 2>/dev/null && context="${context} $(basename "$f"): $exports exports
|
|
50
|
+
"
|
|
51
|
+
done < <(find "$repo_root/src" "$repo_root/lib" -name "*.ts" -o -name "*.js" 2>/dev/null | head -30)
|
|
52
|
+
;;
|
|
53
|
+
|
|
54
|
+
python)
|
|
55
|
+
context="${context}## Import Graph (Top Dependencies)
|
|
56
|
+
"
|
|
57
|
+
local py_imports=""
|
|
58
|
+
py_imports=$(find "$repo_root" -name "*.py" -type f 2>/dev/null | \
|
|
59
|
+
xargs grep -h "^from \|^import " 2>/dev/null | \
|
|
60
|
+
grep -v __pycache__ | sort | uniq -c | sort -rn | head -20)
|
|
61
|
+
context="${context}${py_imports}
|
|
62
|
+
"
|
|
63
|
+
;;
|
|
64
|
+
|
|
65
|
+
bash|shell)
|
|
66
|
+
context="${context}## Source Dependencies
|
|
67
|
+
"
|
|
68
|
+
local sh_imports=""
|
|
69
|
+
[[ -d "$repo_root/scripts" ]] && \
|
|
70
|
+
sh_imports=$(grep -rh "^source \|^\. " "$repo_root/scripts" --include="*.sh" 2>/dev/null | \
|
|
71
|
+
sort | uniq -c | sort -rn | head -20)
|
|
72
|
+
context="${context}${sh_imports}
|
|
73
|
+
"
|
|
74
|
+
;;
|
|
75
|
+
*)
|
|
76
|
+
context="${context}## Dependencies
|
|
77
|
+
(Language: $lang — no specific import analysis)
|
|
78
|
+
"
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
|
|
82
|
+
# 3. Module boundaries (directories with >2 files = modules)
|
|
83
|
+
context="${context}## Module Boundaries
|
|
84
|
+
"
|
|
85
|
+
local dir
|
|
86
|
+
while IFS= read -r dir; do
|
|
87
|
+
[[ -d "$dir" ]] || continue
|
|
88
|
+
local count=0
|
|
89
|
+
count=$(find "$dir" -maxdepth 1 -type f \( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.sh" \) 2>/dev/null | wc -l | tr -d ' ')
|
|
90
|
+
[[ "$count" -gt 2 ]] 2>/dev/null && context="${context} $(basename "$dir")/: $count files
|
|
91
|
+
"
|
|
92
|
+
done < <(find "$repo_root/src" "$repo_root/lib" "$repo_root/scripts" -maxdepth 2 -type d 2>/dev/null | head -30)
|
|
93
|
+
|
|
94
|
+
# 4. Entry points
|
|
95
|
+
context="${context}## Entry Points
|
|
96
|
+
"
|
|
97
|
+
if [[ -f "$repo_root/package.json" ]] && command -v jq >/dev/null 2>&1; then
|
|
98
|
+
local main
|
|
99
|
+
main=$(jq -r '.main // .bin // "index.js" | if type == "object" then (. | keys[0]) else . end' "$repo_root/package.json" 2>/dev/null || echo "")
|
|
100
|
+
[[ -n "$main" && "$main" != "null" ]] && context="${context} package.json: $main
|
|
101
|
+
"
|
|
102
|
+
fi
|
|
103
|
+
if [[ -f "$repo_root/Makefile" ]]; then
|
|
104
|
+
local targets
|
|
105
|
+
targets=$(grep '^[a-zA-Z][a-zA-Z0-9_-]*:' "$repo_root/Makefile" 2>/dev/null | cut -d: -f1 | head -10 | tr '\n' ', ')
|
|
106
|
+
[[ -n "$targets" ]] && context="${context} Makefile targets: $targets
|
|
107
|
+
"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# 5. Test-to-source mapping
|
|
111
|
+
context="${context}## Test Coverage Map
|
|
112
|
+
"
|
|
113
|
+
local test_file
|
|
114
|
+
while IFS= read -r test_file; do
|
|
115
|
+
[[ -f "$test_file" ]] || continue
|
|
116
|
+
local base
|
|
117
|
+
base=$(basename "$test_file" | sed 's/[-.]test//;s/[-.]spec//;s/__tests__//;s/\..*$//' | head -c 50)
|
|
118
|
+
[[ -z "$base" ]] && continue
|
|
119
|
+
local source
|
|
120
|
+
source=$(find "$repo_root/src" "$repo_root/lib" "$repo_root/scripts" -name "${base}.*" -type f 2>/dev/null | head -1)
|
|
121
|
+
[[ -n "$source" ]] && context="${context} $test_file -> $source
|
|
122
|
+
"
|
|
123
|
+
done < <(find "$repo_root" -path "*node_modules" -prune -o -path "*/.git" -prune -o \( -name "*test*" -o -name "*spec*" \) -type f -print 2>/dev/null | head -20)
|
|
124
|
+
|
|
125
|
+
echo "$context"
|
|
126
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright bootstrap — Cold-start initialization for optimization data ║
|
|
4
|
+
# ║ Creates sensible defaults when no historical data exists (new installs) ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
|
|
7
|
+
# bootstrap_optimization — create default iteration model, template weights, model routing
|
|
8
|
+
bootstrap_optimization() {
|
|
9
|
+
local opt_dir="$HOME/.shipwright/optimization"
|
|
10
|
+
mkdir -p "$opt_dir"
|
|
11
|
+
|
|
12
|
+
# Default iteration model based on common patterns
|
|
13
|
+
if [[ ! -f "$opt_dir/iteration-model.json" ]]; then
|
|
14
|
+
cat > "$opt_dir/iteration-model.json" << 'JSON'
|
|
15
|
+
{
|
|
16
|
+
"low": {"mean": 5, "stddev": 2, "samples": 0, "source": "bootstrap"},
|
|
17
|
+
"medium": {"mean": 12, "stddev": 4, "samples": 0, "source": "bootstrap"},
|
|
18
|
+
"high": {"mean": 25, "stddev": 8, "samples": 0, "source": "bootstrap"}
|
|
19
|
+
}
|
|
20
|
+
JSON
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Default template weights
|
|
24
|
+
if [[ ! -f "$opt_dir/template-weights.json" ]]; then
|
|
25
|
+
cat > "$opt_dir/template-weights.json" << 'JSON'
|
|
26
|
+
{
|
|
27
|
+
"standard": 1.0,
|
|
28
|
+
"hotfix": 1.0,
|
|
29
|
+
"docs": 1.0,
|
|
30
|
+
"refactor": 1.0,
|
|
31
|
+
"source": "bootstrap"
|
|
32
|
+
}
|
|
33
|
+
JSON
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Default model routing
|
|
37
|
+
if [[ ! -f "$opt_dir/model-routing.json" ]]; then
|
|
38
|
+
cat > "$opt_dir/model-routing.json" << 'JSON'
|
|
39
|
+
{
|
|
40
|
+
"routes": {
|
|
41
|
+
"plan": {"recommended": "opus", "source": "bootstrap"},
|
|
42
|
+
"design": {"recommended": "opus", "source": "bootstrap"},
|
|
43
|
+
"build": {"recommended": "sonnet", "source": "bootstrap"},
|
|
44
|
+
"test": {"recommended": "sonnet", "source": "bootstrap"},
|
|
45
|
+
"review": {"recommended": "sonnet", "source": "bootstrap"}
|
|
46
|
+
},
|
|
47
|
+
"default": "sonnet",
|
|
48
|
+
"source": "bootstrap"
|
|
49
|
+
}
|
|
50
|
+
JSON
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# bootstrap_memory — create initial memory patterns based on project type
|
|
55
|
+
bootstrap_memory() {
|
|
56
|
+
local mem_dir="$HOME/.shipwright/memory"
|
|
57
|
+
mkdir -p "$mem_dir"
|
|
58
|
+
|
|
59
|
+
if [[ ! -f "$mem_dir/patterns.json" ]]; then
|
|
60
|
+
# Detect project type and create initial patterns
|
|
61
|
+
local project_type="unknown"
|
|
62
|
+
[[ -f "package.json" ]] && project_type="nodejs"
|
|
63
|
+
[[ -f "requirements.txt" || -f "pyproject.toml" ]] && project_type="python"
|
|
64
|
+
[[ -f "Cargo.toml" ]] && project_type="rust"
|
|
65
|
+
[[ -f "go.mod" ]] && project_type="go"
|
|
66
|
+
|
|
67
|
+
cat > "$mem_dir/patterns.json" << JSON
|
|
68
|
+
{
|
|
69
|
+
"project_type": "$project_type",
|
|
70
|
+
"detected_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
71
|
+
"source": "bootstrap"
|
|
72
|
+
}
|
|
73
|
+
JSON
|
|
74
|
+
fi
|
|
75
|
+
}
|