crewswarm 0.9.2 → 0.9.4

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 (228) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
  3. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
  4. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
  5. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
  6. package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
  7. package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
  8. package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
  9. package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
  10. package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
  11. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
  12. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
  13. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  14. package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
  15. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  16. package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
  17. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
  18. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
  19. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
  20. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
  21. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
  22. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
  23. package/apps/dashboard/dist/index.html +135 -15
  24. package/apps/dashboard/dist/index.html.br +0 -0
  25. package/apps/dashboard/dist/index.html.gz +0 -0
  26. package/apps/vibe/README.md +2 -2
  27. package/apps/vibe/package.json +1 -1
  28. package/apps/vibe/server.mjs +101 -56
  29. package/crew-lead.mjs +34 -4
  30. package/lib/bridges/cli-executor.mjs +1 -1
  31. package/lib/bridges/gateway-ws.mjs +4 -0
  32. package/lib/browser/passthrough-stderr.js +1 -0
  33. package/lib/chat/project-messages.mjs +3 -5
  34. package/lib/cli-process-tracker.mjs +3 -2
  35. package/lib/contacts/identity-linker.mjs +1 -0
  36. package/lib/crew-judge/judge.mjs +19 -18
  37. package/lib/crew-lead/agent-manager.mjs +1 -1
  38. package/lib/crew-lead/background.mjs +14 -1
  39. package/lib/crew-lead/chat-handler.mjs +38 -1
  40. package/lib/crew-lead/http-server.mjs +106 -57
  41. package/lib/crew-lead/llm-caller.mjs +24 -8
  42. package/lib/crew-lead/prompts.mjs +14 -1
  43. package/lib/crew-lead/tools.mjs +3 -2
  44. package/lib/crew-lead/wave-dispatcher.mjs +19 -5
  45. package/lib/crew-lead/ws-router.mjs +219 -27
  46. package/lib/engines/crew-cli.mjs +1 -1
  47. package/lib/engines/engine-registry.mjs +14 -3
  48. package/lib/engines/rt-envelope.mjs +1 -0
  49. package/lib/engines/runners.mjs +28 -4
  50. package/lib/gemini-cli-passthrough-noise.mjs +1 -1
  51. package/lib/integrations/code-search.mjs +4 -3
  52. package/lib/memory/shared-adapter.mjs +23 -10
  53. package/lib/pipeline/manager.mjs +2 -1
  54. package/lib/runtime/config.mjs +1 -1
  55. package/lib/runtime/paths.mjs +12 -8
  56. package/lib/runtime/spending.mjs +2 -1
  57. package/package.json +42 -14
  58. package/scripts/capture-build-flow.mjs +118 -0
  59. package/scripts/coverage-report.mjs +209 -0
  60. package/scripts/coverage-summary.mjs +47 -0
  61. package/scripts/dashboard-validation.mjs +76 -0
  62. package/scripts/dashboard.mjs +1667 -551
  63. package/scripts/generate-openapi.mjs +683 -277
  64. package/scripts/live-bridge-matrix.mjs +79 -0
  65. package/scripts/live-cli-matrix.mjs +166 -0
  66. package/scripts/live-crewchat-check.mjs +42 -0
  67. package/scripts/live-engine-matrix.mjs +50 -0
  68. package/scripts/live-provider-failover-matrix.mjs +107 -0
  69. package/scripts/live-provider-matrix.mjs +228 -0
  70. package/scripts/restart-all-from-repo.sh +4 -4
  71. package/scripts/restart-service.sh +12 -9
  72. package/scripts/smoke-dispatch.mjs +4 -1
  73. package/scripts/test-blast-radius.mjs +204 -0
  74. package/scripts/test-report-summary.mjs +88 -0
  75. package/scripts/test-reporter.mjs +651 -0
  76. package/scripts/test-rerun.mjs +136 -0
  77. package/scripts/tmux-bridge +130 -0
  78. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
  79. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  80. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
  81. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  82. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  83. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  84. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  85. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
  86. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  87. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  88. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  89. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  90. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  91. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
  92. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  93. package/apps/dashboard/index.html +0 -6529
  94. package/apps/dashboard/package.json +0 -15
  95. package/apps/dashboard/src/app.js +0 -2828
  96. package/apps/dashboard/src/app.js.br +0 -0
  97. package/apps/dashboard/src/app.js.gz +0 -0
  98. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  99. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  100. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  101. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  102. package/apps/dashboard/src/cli-process.js +0 -208
  103. package/apps/dashboard/src/cli-process.js.br +0 -0
  104. package/apps/dashboard/src/cli-process.js.gz +0 -0
  105. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  106. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  107. package/apps/dashboard/src/core/api.js +0 -18
  108. package/apps/dashboard/src/core/api.js.br +0 -0
  109. package/apps/dashboard/src/core/dom.js +0 -228
  110. package/apps/dashboard/src/core/dom.js.br +0 -0
  111. package/apps/dashboard/src/core/state.js +0 -91
  112. package/apps/dashboard/src/core/state.js.br +0 -0
  113. package/apps/dashboard/src/core/task-manager.js +0 -134
  114. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  115. package/apps/dashboard/src/orchestration-status.js +0 -127
  116. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  117. package/apps/dashboard/src/setup-wizard.js +0 -562
  118. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  119. package/apps/dashboard/src/styles.css +0 -2085
  120. package/apps/dashboard/src/styles.css.br +0 -0
  121. package/apps/dashboard/src/styles.css.gz +0 -0
  122. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  123. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  125. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  127. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  129. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  131. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  133. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  135. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  137. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  139. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  140. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  141. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  142. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  143. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  144. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  145. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  146. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  147. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  148. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  149. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  150. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  151. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  152. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  153. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  154. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  155. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  156. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  157. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  158. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  159. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  160. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  161. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  162. package/apps/vibe/.crew/cost.json +0 -17
  163. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  164. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  172. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  173. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  174. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  175. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  176. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  177. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  178. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  179. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  180. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  181. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  182. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  183. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  184. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  185. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  186. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  187. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  188. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  189. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  190. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  191. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  192. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  193. package/apps/vibe/.crew/sandbox.json +0 -7
  194. package/apps/vibe/.crew/session.json +0 -330
  195. package/apps/vibe/.crew/training-data.jsonl +0 -0
  196. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  197. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  198. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  199. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  200. package/apps/vibe/ARCHITECTURE.md +0 -3393
  201. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  202. package/apps/vibe/ROADMAP.md +0 -41
  203. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  204. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  205. package/apps/vibe/capture-demo.mjs +0 -160
  206. package/apps/vibe/capture-full-demo.mjs +0 -255
  207. package/apps/vibe/capture-quickstart.mjs +0 -256
  208. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  209. package/apps/vibe/capture-vibe-video.mjs +0 -260
  210. package/apps/vibe/check-buttons.js +0 -41
  211. package/apps/vibe/diagnose.html +0 -106
  212. package/apps/vibe/fix-buttons.js +0 -103
  213. package/apps/vibe/index.html +0 -3404
  214. package/apps/vibe/package-lock.json +0 -920
  215. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  216. package/apps/vibe/src/main.js +0 -2940
  217. package/apps/vibe/src/register-all-languages.js +0 -98
  218. package/apps/vibe/start-studio.sh +0 -11
  219. package/apps/vibe/test/accessibility-tests.js +0 -77
  220. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  221. package/apps/vibe/test/performance-tests.js +0 -120
  222. package/apps/vibe/test/security-tests.js +0 -213
  223. package/apps/vibe/tests/e2e.local.mjs +0 -54
  224. package/apps/vibe/tests/server.smoke.mjs +0 -106
  225. package/apps/vibe/update_website.mjs +0 -74
  226. package/apps/vibe/vite.config.js +0 -19
  227. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  228. package/lib/engines/rt-envelope.mjs.backup-current +0 -870
@@ -135,15 +135,18 @@ case "$SERVICE_ID" in
135
135
  exec bash "$CREWSWARM_DIR/scripts/restart-crew-lead.sh"
136
136
  ;;
137
137
  rt-bus)
138
- pkill -f "opencrew-rt-daemon" 2>/dev/null || true
139
- _wait_for_port_free 18889 12 || true
140
- _start_detached /tmp/opencrew-rt-daemon.log \
141
- env \
142
- NODE_DISABLE_COMPILE_CACHE=1 \
143
- CREWSWARM_RT_AUTH_TOKEN="$(_rt_token)" \
144
- OPENCLAW_ALLOWED_AGENTS="$(_allowed_agents)" \
145
- "$NODE_BIN" "$CREWSWARM_DIR/scripts/opencrew-rt-daemon.mjs"
146
- echo " rt-bus restart requested"
138
+ (
139
+ flock -n 9 || { echo "⚠️ rt-bus restart already in progress — skipping duplicate"; exit 0; }
140
+ pkill -f "opencrew-rt-daemon" 2>/dev/null || true
141
+ _wait_for_port_free 18889 12 || true
142
+ _start_detached /tmp/opencrew-rt-daemon.log \
143
+ env \
144
+ NODE_DISABLE_COMPILE_CACHE=1 \
145
+ CREWSWARM_RT_AUTH_TOKEN="$(_rt_token)" \
146
+ OPENCLAW_ALLOWED_AGENTS="$(_allowed_agents)" \
147
+ "$NODE_BIN" "$CREWSWARM_DIR/scripts/opencrew-rt-daemon.mjs"
148
+ echo "✅ rt-bus restart requested"
149
+ ) 9>/tmp/crewswarm-rt-bus-restart.lock
147
150
  ;;
148
151
  agents)
149
152
  pkill -f "gateway-bridge.mjs --rt-daemon" 2>/dev/null || true
@@ -108,7 +108,10 @@ async function run() {
108
108
 
109
109
  const chatMarker = `SMOKE_CHAT_OK_${runId}`;
110
110
  console.log("[1/4] chat...");
111
- const chatResult = await chat(`say: ${chatMarker}`, `smoke-chat-${runId}`);
111
+ const chatResult = await chat(
112
+ `Reply with exactly: ${chatMarker}`,
113
+ `smoke-chat-${runId}`,
114
+ );
112
115
  const chatReply = String(chatResult?.reply || chatResult?.message || "");
113
116
  assert(chatReply.includes(chatMarker), `chat reply missing marker (${chatMarker})`);
114
117
  console.log("[ok] /chat");
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import crypto from "node:crypto";
6
+ import { execSync } from "node:child_process";
7
+
8
+ function safeExec(command) {
9
+ try {
10
+ return execSync(command, {
11
+ cwd: process.cwd(),
12
+ encoding: "utf8",
13
+ stdio: ["ignore", "pipe", "ignore"],
14
+ timeout: 5000,
15
+ }).trim();
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ function normalizeRelative(filePath) {
22
+ return path.relative(process.cwd(), filePath).replace(/\\/g, "/");
23
+ }
24
+
25
+ function fingerprintFile(filePath) {
26
+ const stat = fs.statSync(filePath);
27
+ const content = fs.readFileSync(filePath);
28
+ return {
29
+ path: filePath,
30
+ relative_file: normalizeRelative(filePath),
31
+ size_bytes: stat.size,
32
+ mtime: stat.mtime.toISOString(),
33
+ sha256: crypto.createHash("sha256").update(content).digest("hex"),
34
+ };
35
+ }
36
+
37
+ function tryResolve(baseDir, specifier) {
38
+ const raw = path.resolve(baseDir, specifier);
39
+ const candidates = [
40
+ raw,
41
+ `${raw}.mjs`,
42
+ `${raw}.js`,
43
+ `${raw}.json`,
44
+ path.join(raw, "index.mjs"),
45
+ path.join(raw, "index.js"),
46
+ ];
47
+ for (const candidate of candidates) {
48
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
49
+ return candidate;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+
55
+ function collectImports(filePath) {
56
+ const text = fs.readFileSync(filePath, "utf8");
57
+ const patterns = [
58
+ /(?:import|export)\s+[^'"]*?from\s+['"]([^'"]+)['"]/g,
59
+ /import\(\s*['"]([^'"]+)['"]\s*\)/g,
60
+ ];
61
+ const imports = new Set();
62
+ for (const pattern of patterns) {
63
+ for (const match of text.matchAll(pattern)) {
64
+ const specifier = match[1];
65
+ if (!specifier || !specifier.startsWith(".")) continue;
66
+ const resolved = tryResolve(path.dirname(filePath), specifier);
67
+ if (resolved && resolved.startsWith(process.cwd())) imports.add(resolved);
68
+ }
69
+ }
70
+ return [...imports];
71
+ }
72
+
73
+ export function buildDependencySnapshot(filePath, maxDepth = 3) {
74
+ if (!filePath || !fs.existsSync(filePath)) return { files: [] };
75
+ const visited = new Set();
76
+ const queue = [{ filePath, depth: 0 }];
77
+ while (queue.length) {
78
+ const current = queue.shift();
79
+ if (!current?.filePath || visited.has(current.filePath)) continue;
80
+ visited.add(current.filePath);
81
+ if (current.depth >= maxDepth) continue;
82
+ for (const imported of collectImports(current.filePath)) {
83
+ queue.push({ filePath: imported, depth: current.depth + 1 });
84
+ }
85
+ }
86
+ return {
87
+ max_depth: maxDepth,
88
+ files: [...visited]
89
+ .sort()
90
+ .map((item) => {
91
+ try {
92
+ return fingerprintFile(item);
93
+ } catch {
94
+ return null;
95
+ }
96
+ })
97
+ .filter(Boolean),
98
+ };
99
+ }
100
+
101
+ export function getWorkspaceState() {
102
+ const tracked = safeExec("git diff --name-only HEAD --");
103
+ const untracked = safeExec("git ls-files --others --exclude-standard");
104
+ const changedFiles = [
105
+ ...(tracked ? tracked.split("\n").filter(Boolean) : []),
106
+ ...(untracked ? untracked.split("\n").filter(Boolean) : []),
107
+ ].map((item) => item.replace(/\\/g, "/"));
108
+ return {
109
+ git_commit: safeExec("git rev-parse HEAD"),
110
+ git_branch: safeExec("git branch --show-current"),
111
+ changed_files: [...new Set(changedFiles)].sort(),
112
+ dirty: changedFiles.length > 0,
113
+ };
114
+ }
115
+
116
+ export function assessTestFreshness(test, workspaceState = getWorkspaceState(), baselineWorkspaceState = test.workspace_state_at_run_start || null) {
117
+ const snapshotFiles = test.dependency_snapshot?.files || [];
118
+ if (snapshotFiles.length === 0) {
119
+ return {
120
+ status: "unknown",
121
+ rerun_advice: workspaceState.dirty ? "rerun_recommended" : "rerun_not_needed",
122
+ reason: "no dependency snapshot available",
123
+ changed_relevant_files: [],
124
+ dependency_changes: [],
125
+ };
126
+ }
127
+
128
+ const changedSet = new Set(workspaceState.changed_files || []);
129
+ const baselineChangedSet = new Set(baselineWorkspaceState?.changed_files || []);
130
+ const newlyChangedSet = new Set(
131
+ [...changedSet].filter((file) => !baselineChangedSet.has(file))
132
+ );
133
+ const changedRelevant = snapshotFiles
134
+ .map((file) => file.relative_file)
135
+ .filter((relativeFile) => newlyChangedSet.has(relativeFile));
136
+
137
+ const dependencyChanges = [];
138
+ for (const dependency of snapshotFiles) {
139
+ const absolutePath = dependency.path || path.join(process.cwd(), dependency.relative_file);
140
+ try {
141
+ const current = fingerprintFile(absolutePath);
142
+ if (current.sha256 !== dependency.sha256) {
143
+ dependencyChanges.push({
144
+ file: dependency.relative_file,
145
+ previous_sha256: dependency.sha256,
146
+ current_sha256: current.sha256,
147
+ });
148
+ }
149
+ } catch {
150
+ dependencyChanges.push({
151
+ file: dependency.relative_file,
152
+ previous_sha256: dependency.sha256,
153
+ current_sha256: null,
154
+ missing: true,
155
+ });
156
+ }
157
+ }
158
+
159
+ if (changedRelevant.length || dependencyChanges.length) {
160
+ return {
161
+ status: "stale",
162
+ rerun_advice: "rerun_required",
163
+ reason: changedRelevant.length
164
+ ? "tracked workspace changes intersect this test's dependency graph"
165
+ : "dependency fingerprints changed since this test last ran",
166
+ changed_relevant_files: changedRelevant,
167
+ dependency_changes: dependencyChanges,
168
+ };
169
+ }
170
+
171
+ if (!workspaceState.dirty) {
172
+ return {
173
+ status: "fresh",
174
+ rerun_advice: "rerun_not_needed",
175
+ reason: "workspace has no uncommitted changes",
176
+ changed_relevant_files: [],
177
+ dependency_changes: [],
178
+ };
179
+ }
180
+
181
+ return {
182
+ status: "fresh",
183
+ rerun_advice: "rerun_not_needed",
184
+ reason: baselineWorkspaceState
185
+ ? "workspace changed, but not since this test run in this test's dependency graph"
186
+ : "workspace changed, but not in this test's dependency graph",
187
+ changed_relevant_files: [],
188
+ dependency_changes: [],
189
+ };
190
+ }
191
+
192
+ export function summarizeFreshness(tests, workspaceState = getWorkspaceState()) {
193
+ const assessments = tests.map((test) => ({
194
+ test,
195
+ freshness: assessTestFreshness(test, workspaceState),
196
+ }));
197
+ return {
198
+ workspace: workspaceState,
199
+ stale: assessments.filter((item) => item.freshness.status === "stale").length,
200
+ fresh: assessments.filter((item) => item.freshness.status === "fresh").length,
201
+ unknown: assessments.filter((item) => item.freshness.status === "unknown").length,
202
+ assessments,
203
+ };
204
+ }
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { getWorkspaceState, summarizeFreshness, assessTestFreshness } from "./test-blast-radius.mjs";
6
+
7
+ const resultsDir = process.env.TEST_RESULTS_DIR || path.join(process.cwd(), "test-results");
8
+ const summaryPath = path.join(resultsDir, ".last-run.json");
9
+
10
+ if (!fs.existsSync(summaryPath)) {
11
+ console.error(`No summary found at ${summaryPath}`);
12
+ process.exit(1);
13
+ }
14
+
15
+ const summary = JSON.parse(fs.readFileSync(summaryPath, "utf8"));
16
+ const runDir = path.join(resultsDir, "runs", summary.runId);
17
+ const markdownPath = path.join(runDir, "summary.md");
18
+ const workspaceState = getWorkspaceState();
19
+ const baselineWorkspaceState = summary.workspace_state_at_run_start || null;
20
+ const freshnessSummary = summarizeFreshness(
21
+ (summary.tests || []).map((test) => ({ ...test, workspace_state_at_run_start: baselineWorkspaceState })),
22
+ workspaceState
23
+ );
24
+
25
+ function shellQuote(value) {
26
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
27
+ }
28
+
29
+ function rerunCommand(test) {
30
+ return test.rerun_command || `node --test --test-reporter=./scripts/test-reporter.mjs --test-name-pattern=${shellQuote(test.name)} ${shellQuote(test.file)}`;
31
+ }
32
+
33
+ console.log(`Run: ${summary.runId}`);
34
+ console.log(`Status: ${summary.status}`);
35
+ console.log(`Passed: ${summary.passed} Failed: ${summary.failed} Skipped: ${summary.skipped}`);
36
+ console.log(`Duration: ${(summary.duration_ms / 1000).toFixed(2)}s`);
37
+ console.log(`Artifacts: ${runDir}`);
38
+ console.log(`Workspace: ${workspaceState.dirty ? `${workspaceState.changed_files.length} changed file(s)` : "clean"}`);
39
+ console.log(`Blast radius: ${freshnessSummary.stale} stale ${freshnessSummary.fresh} fresh ${freshnessSummary.unknown} unknown`);
40
+
41
+ if (!workspaceState.dirty) {
42
+ console.log("Rerun guidance: no workspace changes detected, so prior results are still valid unless the environment changed.");
43
+ } else if (freshnessSummary.stale === 0) {
44
+ console.log("Rerun guidance: workspace changed, but no recorded test dependency graph was hit.");
45
+ } else {
46
+ console.log("Rerun guidance: rerun the stale tests rather than the full suite first.");
47
+ }
48
+
49
+ if (summary.failedTests?.length) {
50
+ console.log("\nFailed tests:");
51
+ for (const failure of summary.failedTests) {
52
+ const freshness = assessTestFreshness(failure, workspaceState, baselineWorkspaceState);
53
+ console.log(`- ${failure.name}`);
54
+ console.log(` file: ${failure.file}`);
55
+ console.log(` reason: ${failure.reason_code} (${failure.reason_summary})`);
56
+ console.log(` error: ${failure.error || "unknown"}`);
57
+ console.log(` freshness: ${freshness.status} (${freshness.rerun_advice})`);
58
+ if (freshness.changed_relevant_files?.length) {
59
+ console.log(` blast radius: ${freshness.changed_relevant_files.join(", ")}`);
60
+ }
61
+ if (failure.engine?.engine || failure.engine?.provider || failure.engine?.model) {
62
+ console.log(` engine: ${failure.engine?.engine || "n/a"} | provider: ${failure.engine?.provider || "n/a"} | model: ${failure.engine?.model || "n/a"}`);
63
+ }
64
+ console.log(` artifacts: ${failure.artifactDir}`);
65
+ console.log(` rerun: ${rerunCommand(failure)}`);
66
+ }
67
+ }
68
+
69
+ if (summary.skippedTests?.length) {
70
+ console.log("\nSkipped tests:");
71
+ for (const skipped of summary.skippedTests) {
72
+ const freshness = assessTestFreshness(skipped, workspaceState, baselineWorkspaceState);
73
+ console.log(`- ${skipped.name}`);
74
+ console.log(` file: ${skipped.file}`);
75
+ console.log(` reason: ${skipped.reason_code} (${skipped.reason_summary})`);
76
+ console.log(` reason: ${skipped.skip_reason}`);
77
+ console.log(` freshness: ${freshness.status} (${freshness.rerun_advice})`);
78
+ if (freshness.changed_relevant_files?.length) {
79
+ console.log(` blast radius: ${freshness.changed_relevant_files.join(", ")}`);
80
+ }
81
+ console.log(` artifacts: ${skipped.artifactDir}`);
82
+ console.log(` rerun: ${rerunCommand(skipped)}`);
83
+ }
84
+ }
85
+
86
+ if (fs.existsSync(markdownPath)) {
87
+ console.log(`\nMarkdown summary: ${markdownPath}`);
88
+ }