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.
Files changed (161) hide show
  1. package/README.md +16 -11
  2. package/completions/_shipwright +1 -1
  3. package/completions/shipwright.bash +3 -8
  4. package/completions/shipwright.fish +1 -1
  5. package/config/defaults.json +111 -0
  6. package/config/event-schema.json +81 -0
  7. package/config/policy.json +13 -18
  8. package/dashboard/coverage/coverage-summary.json +14 -0
  9. package/dashboard/public/index.html +1 -1
  10. package/dashboard/server.ts +306 -17
  11. package/dashboard/src/components/charts/bar.test.ts +79 -0
  12. package/dashboard/src/components/charts/donut.test.ts +68 -0
  13. package/dashboard/src/components/charts/pipeline-rail.test.ts +117 -0
  14. package/dashboard/src/components/charts/sparkline.test.ts +125 -0
  15. package/dashboard/src/core/api.test.ts +309 -0
  16. package/dashboard/src/core/helpers.test.ts +301 -0
  17. package/dashboard/src/core/router.test.ts +307 -0
  18. package/dashboard/src/core/router.ts +7 -0
  19. package/dashboard/src/core/sse.test.ts +144 -0
  20. package/dashboard/src/views/metrics.test.ts +186 -0
  21. package/dashboard/src/views/overview.test.ts +173 -0
  22. package/dashboard/src/views/pipelines.test.ts +183 -0
  23. package/dashboard/src/views/team.test.ts +253 -0
  24. package/dashboard/vitest.config.ts +14 -5
  25. package/docs/TIPS.md +1 -1
  26. package/docs/patterns/README.md +1 -1
  27. package/package.json +5 -7
  28. package/scripts/adapters/docker-deploy.sh +1 -1
  29. package/scripts/adapters/tmux-adapter.sh +11 -1
  30. package/scripts/adapters/wezterm-adapter.sh +1 -1
  31. package/scripts/check-version-consistency.sh +1 -1
  32. package/scripts/lib/architecture.sh +126 -0
  33. package/scripts/lib/bootstrap.sh +75 -0
  34. package/scripts/lib/compat.sh +89 -6
  35. package/scripts/lib/config.sh +91 -0
  36. package/scripts/lib/daemon-adaptive.sh +3 -3
  37. package/scripts/lib/daemon-dispatch.sh +39 -16
  38. package/scripts/lib/daemon-health.sh +1 -1
  39. package/scripts/lib/daemon-patrol.sh +24 -12
  40. package/scripts/lib/daemon-poll.sh +37 -25
  41. package/scripts/lib/daemon-state.sh +115 -23
  42. package/scripts/lib/daemon-triage.sh +30 -8
  43. package/scripts/lib/fleet-failover.sh +63 -0
  44. package/scripts/lib/helpers.sh +30 -6
  45. package/scripts/lib/pipeline-detection.sh +2 -2
  46. package/scripts/lib/pipeline-github.sh +9 -9
  47. package/scripts/lib/pipeline-intelligence.sh +85 -35
  48. package/scripts/lib/pipeline-quality-checks.sh +16 -16
  49. package/scripts/lib/pipeline-quality.sh +1 -1
  50. package/scripts/lib/pipeline-stages.sh +242 -28
  51. package/scripts/lib/pipeline-state.sh +40 -4
  52. package/scripts/lib/test-helpers.sh +247 -0
  53. package/scripts/postinstall.mjs +3 -11
  54. package/scripts/sw +10 -4
  55. package/scripts/sw-activity.sh +1 -11
  56. package/scripts/sw-adaptive.sh +109 -85
  57. package/scripts/sw-adversarial.sh +4 -14
  58. package/scripts/sw-architecture-enforcer.sh +1 -11
  59. package/scripts/sw-auth.sh +8 -17
  60. package/scripts/sw-autonomous.sh +111 -49
  61. package/scripts/sw-changelog.sh +1 -11
  62. package/scripts/sw-checkpoint.sh +144 -20
  63. package/scripts/sw-ci.sh +2 -12
  64. package/scripts/sw-cleanup.sh +13 -17
  65. package/scripts/sw-code-review.sh +16 -36
  66. package/scripts/sw-connect.sh +5 -12
  67. package/scripts/sw-context.sh +9 -26
  68. package/scripts/sw-cost.sh +6 -16
  69. package/scripts/sw-daemon.sh +75 -70
  70. package/scripts/sw-dashboard.sh +57 -17
  71. package/scripts/sw-db.sh +506 -15
  72. package/scripts/sw-decompose.sh +1 -11
  73. package/scripts/sw-deps.sh +15 -25
  74. package/scripts/sw-developer-simulation.sh +1 -11
  75. package/scripts/sw-discovery.sh +112 -30
  76. package/scripts/sw-doc-fleet.sh +7 -17
  77. package/scripts/sw-docs-agent.sh +6 -16
  78. package/scripts/sw-docs.sh +4 -12
  79. package/scripts/sw-doctor.sh +134 -43
  80. package/scripts/sw-dora.sh +11 -19
  81. package/scripts/sw-durable.sh +35 -52
  82. package/scripts/sw-e2e-orchestrator.sh +11 -27
  83. package/scripts/sw-eventbus.sh +115 -115
  84. package/scripts/sw-evidence.sh +114 -30
  85. package/scripts/sw-feedback.sh +3 -13
  86. package/scripts/sw-fix.sh +2 -20
  87. package/scripts/sw-fleet-discover.sh +1 -11
  88. package/scripts/sw-fleet-viz.sh +10 -18
  89. package/scripts/sw-fleet.sh +13 -17
  90. package/scripts/sw-github-app.sh +6 -16
  91. package/scripts/sw-github-checks.sh +1 -11
  92. package/scripts/sw-github-deploy.sh +1 -11
  93. package/scripts/sw-github-graphql.sh +2 -12
  94. package/scripts/sw-guild.sh +1 -11
  95. package/scripts/sw-heartbeat.sh +49 -12
  96. package/scripts/sw-hygiene.sh +45 -43
  97. package/scripts/sw-incident.sh +48 -74
  98. package/scripts/sw-init.sh +35 -37
  99. package/scripts/sw-instrument.sh +1 -11
  100. package/scripts/sw-intelligence.sh +362 -51
  101. package/scripts/sw-jira.sh +5 -14
  102. package/scripts/sw-launchd.sh +2 -12
  103. package/scripts/sw-linear.sh +8 -17
  104. package/scripts/sw-logs.sh +4 -12
  105. package/scripts/sw-loop.sh +641 -90
  106. package/scripts/sw-memory.sh +243 -17
  107. package/scripts/sw-mission-control.sh +2 -12
  108. package/scripts/sw-model-router.sh +73 -34
  109. package/scripts/sw-otel.sh +11 -21
  110. package/scripts/sw-oversight.sh +1 -11
  111. package/scripts/sw-patrol-meta.sh +5 -11
  112. package/scripts/sw-pipeline-composer.sh +7 -17
  113. package/scripts/sw-pipeline-vitals.sh +1 -11
  114. package/scripts/sw-pipeline.sh +478 -122
  115. package/scripts/sw-pm.sh +2 -12
  116. package/scripts/sw-pr-lifecycle.sh +27 -25
  117. package/scripts/sw-predictive.sh +16 -22
  118. package/scripts/sw-prep.sh +6 -16
  119. package/scripts/sw-ps.sh +1 -11
  120. package/scripts/sw-public-dashboard.sh +2 -12
  121. package/scripts/sw-quality.sh +77 -10
  122. package/scripts/sw-reaper.sh +1 -11
  123. package/scripts/sw-recruit.sh +15 -25
  124. package/scripts/sw-regression.sh +11 -21
  125. package/scripts/sw-release-manager.sh +19 -28
  126. package/scripts/sw-release.sh +8 -16
  127. package/scripts/sw-remote.sh +1 -11
  128. package/scripts/sw-replay.sh +48 -44
  129. package/scripts/sw-retro.sh +70 -92
  130. package/scripts/sw-review-rerun.sh +1 -1
  131. package/scripts/sw-scale.sh +109 -32
  132. package/scripts/sw-security-audit.sh +12 -22
  133. package/scripts/sw-self-optimize.sh +239 -23
  134. package/scripts/sw-session.sh +3 -13
  135. package/scripts/sw-setup.sh +8 -18
  136. package/scripts/sw-standup.sh +5 -15
  137. package/scripts/sw-status.sh +32 -23
  138. package/scripts/sw-strategic.sh +129 -13
  139. package/scripts/sw-stream.sh +1 -11
  140. package/scripts/sw-swarm.sh +76 -36
  141. package/scripts/sw-team-stages.sh +10 -20
  142. package/scripts/sw-templates.sh +4 -14
  143. package/scripts/sw-testgen.sh +3 -13
  144. package/scripts/sw-tmux-pipeline.sh +1 -19
  145. package/scripts/sw-tmux-role-color.sh +0 -10
  146. package/scripts/sw-tmux-status.sh +3 -11
  147. package/scripts/sw-tmux.sh +2 -20
  148. package/scripts/sw-trace.sh +1 -19
  149. package/scripts/sw-tracker-github.sh +0 -10
  150. package/scripts/sw-tracker-jira.sh +1 -11
  151. package/scripts/sw-tracker-linear.sh +1 -11
  152. package/scripts/sw-tracker.sh +7 -24
  153. package/scripts/sw-triage.sh +24 -34
  154. package/scripts/sw-upgrade.sh +5 -23
  155. package/scripts/sw-ux.sh +1 -19
  156. package/scripts/sw-webhook.sh +18 -32
  157. package/scripts/sw-widgets.sh +3 -21
  158. package/scripts/sw-worktree.sh +11 -27
  159. package/scripts/update-homebrew-sha.sh +67 -0
  160. package/templates/pipelines/tdd.json +72 -0
  161. 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: ["src/**/*.test.ts", "src/types/**"],
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: 60,
16
- branches: 50,
17
- functions: 60,
18
- lines: 60,
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`, `cct`) to see a dashboard of running team sessions:
134
+ Use `shipwright status` (alias: `sw`) to see a dashboard of running team sessions:
135
135
 
136
136
  ```bash
137
137
  shipwright status
@@ -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`, `cct`) to set up the team:
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": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "Orchestrate autonomous Claude Code agent teams in tmux",
5
5
  "bin": {
6
- "shipwright": "./scripts/sw",
7
- "sw": "./scripts/sw",
8
- "cct": "./scripts/sw"
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",
@@ -38,7 +38,7 @@ get_rollback_cmd() {
38
38
  }
39
39
 
40
40
  get_health_url() {
41
- echo "http://localhost:3000/health"
41
+ echo "http://localhost:8767/health"
42
42
  }
43
43
 
44
44
  get_smoke_cmd() {
@@ -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 'rm -f "$_TMUX_PANE_MAP"' EXIT
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 &>/dev/null; then
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 &>/dev/null; then
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
+ }