crewswarm 0.9.2 → 0.9.3

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 (207) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
  3. package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
  4. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  5. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  6. package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
  7. package/apps/dashboard/dist/index.html +82 -11
  8. package/apps/vibe/README.md +2 -2
  9. package/apps/vibe/package.json +1 -1
  10. package/apps/vibe/server.mjs +3 -3
  11. package/crew-lead.mjs +34 -4
  12. package/lib/bridges/gateway-ws.mjs +4 -0
  13. package/lib/crew-lead/chat-handler.mjs +34 -0
  14. package/lib/crew-lead/http-server.mjs +55 -14
  15. package/lib/crew-lead/llm-caller.mjs +24 -8
  16. package/lib/crew-lead/prompts.mjs +7 -0
  17. package/lib/crew-lead/wave-dispatcher.mjs +15 -3
  18. package/lib/crew-lead/ws-router.mjs +219 -27
  19. package/lib/engines/engine-registry.mjs +9 -0
  20. package/lib/engines/rt-envelope.mjs +1 -0
  21. package/lib/engines/runners.mjs +5 -2
  22. package/lib/runtime/paths.mjs +12 -8
  23. package/package.json +35 -15
  24. package/scripts/capture-build-flow.mjs +118 -0
  25. package/scripts/coverage-report.mjs +209 -0
  26. package/scripts/coverage-summary.mjs +47 -0
  27. package/scripts/dashboard-validation.mjs +74 -0
  28. package/scripts/dashboard.mjs +560 -70
  29. package/scripts/live-bridge-matrix.mjs +79 -0
  30. package/scripts/live-cli-matrix.mjs +166 -0
  31. package/scripts/live-crewchat-check.mjs +42 -0
  32. package/scripts/live-engine-matrix.mjs +50 -0
  33. package/scripts/live-provider-failover-matrix.mjs +107 -0
  34. package/scripts/live-provider-matrix.mjs +228 -0
  35. package/scripts/restart-all-from-repo.sh +4 -4
  36. package/scripts/smoke-dispatch.mjs +4 -1
  37. package/scripts/test-blast-radius.mjs +204 -0
  38. package/scripts/test-report-summary.mjs +88 -0
  39. package/scripts/test-reporter.mjs +651 -0
  40. package/scripts/test-rerun.mjs +136 -0
  41. package/scripts/tmux-bridge +130 -0
  42. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  43. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  44. package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
  45. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
  46. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  47. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  48. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  49. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  50. package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
  51. package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
  52. package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
  53. package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
  54. package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
  55. package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
  56. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  57. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  58. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  59. package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
  60. package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
  61. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  62. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  63. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  64. package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
  65. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
  66. package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
  67. package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
  68. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  69. package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
  70. package/apps/dashboard/dist/index.html.br +0 -0
  71. package/apps/dashboard/dist/index.html.gz +0 -0
  72. package/apps/dashboard/index.html +0 -6529
  73. package/apps/dashboard/package.json +0 -15
  74. package/apps/dashboard/src/app.js +0 -2828
  75. package/apps/dashboard/src/app.js.br +0 -0
  76. package/apps/dashboard/src/app.js.gz +0 -0
  77. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  78. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  79. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  80. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  81. package/apps/dashboard/src/cli-process.js +0 -208
  82. package/apps/dashboard/src/cli-process.js.br +0 -0
  83. package/apps/dashboard/src/cli-process.js.gz +0 -0
  84. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  85. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  86. package/apps/dashboard/src/core/api.js +0 -18
  87. package/apps/dashboard/src/core/api.js.br +0 -0
  88. package/apps/dashboard/src/core/dom.js +0 -228
  89. package/apps/dashboard/src/core/dom.js.br +0 -0
  90. package/apps/dashboard/src/core/state.js +0 -91
  91. package/apps/dashboard/src/core/state.js.br +0 -0
  92. package/apps/dashboard/src/core/task-manager.js +0 -134
  93. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  94. package/apps/dashboard/src/orchestration-status.js +0 -127
  95. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  96. package/apps/dashboard/src/setup-wizard.js +0 -562
  97. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  98. package/apps/dashboard/src/styles.css +0 -2085
  99. package/apps/dashboard/src/styles.css.br +0 -0
  100. package/apps/dashboard/src/styles.css.gz +0 -0
  101. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  102. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  103. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  104. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  105. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  106. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  107. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  108. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  109. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  110. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  111. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  112. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  113. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  114. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  115. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  116. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  117. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  118. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  119. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  120. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  121. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  122. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  123. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  125. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  127. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  129. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  131. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  133. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  135. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  137. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  139. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  140. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  141. package/apps/vibe/.crew/cost.json +0 -17
  142. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  143. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  144. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  145. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  146. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  147. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  148. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  149. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  150. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  151. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  152. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  153. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  154. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  155. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  156. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  157. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  158. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  159. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  160. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  161. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  162. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  163. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  164. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  172. package/apps/vibe/.crew/sandbox.json +0 -7
  173. package/apps/vibe/.crew/session.json +0 -330
  174. package/apps/vibe/.crew/training-data.jsonl +0 -0
  175. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  176. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  177. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  178. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  179. package/apps/vibe/ARCHITECTURE.md +0 -3393
  180. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  181. package/apps/vibe/ROADMAP.md +0 -41
  182. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  183. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  184. package/apps/vibe/capture-demo.mjs +0 -160
  185. package/apps/vibe/capture-full-demo.mjs +0 -255
  186. package/apps/vibe/capture-quickstart.mjs +0 -256
  187. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  188. package/apps/vibe/capture-vibe-video.mjs +0 -260
  189. package/apps/vibe/check-buttons.js +0 -41
  190. package/apps/vibe/diagnose.html +0 -106
  191. package/apps/vibe/fix-buttons.js +0 -103
  192. package/apps/vibe/index.html +0 -3404
  193. package/apps/vibe/package-lock.json +0 -920
  194. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  195. package/apps/vibe/src/main.js +0 -2940
  196. package/apps/vibe/src/register-all-languages.js +0 -98
  197. package/apps/vibe/start-studio.sh +0 -11
  198. package/apps/vibe/test/accessibility-tests.js +0 -77
  199. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  200. package/apps/vibe/test/performance-tests.js +0 -120
  201. package/apps/vibe/test/security-tests.js +0 -213
  202. package/apps/vibe/tests/e2e.local.mjs +0 -54
  203. package/apps/vibe/tests/server.smoke.mjs +0 -106
  204. package/apps/vibe/update_website.mjs +0 -74
  205. package/apps/vibe/vite.config.js +0 -19
  206. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  207. package/lib/engines/rt-envelope.mjs.backup-current +0 -870
@@ -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
+ }