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
@@ -0,0 +1,651 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Custom Node.js test reporter that logs every test result (pass/fail/skip)
5
+ * with timestamps to a JSONL file at test-results/test-log.jsonl.
6
+ *
7
+ * Also updates test-results/.last-run.json with summary + timestamp.
8
+ *
9
+ * Usage: node --test --test-reporter=./scripts/test-reporter.mjs test/unit/*.test.mjs
10
+ */
11
+
12
+ import fs from "node:fs";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import crypto from "node:crypto";
16
+ import { execSync } from "node:child_process";
17
+ import { buildDependencySnapshot, getWorkspaceState } from "./test-blast-radius.mjs";
18
+
19
+ const ROOT = process.env.TEST_RESULTS_DIR || path.join(process.cwd(), "test-results");
20
+ const LOG_PATH = path.join(ROOT, "test-log.jsonl");
21
+ const SUMMARY_PATH = path.join(ROOT, ".last-run.json");
22
+ const CURRENT_RUN_PATH = path.join(ROOT, ".current-run.json");
23
+ const RUNS_DIR = path.join(ROOT, "runs");
24
+
25
+ fs.mkdirSync(ROOT, { recursive: true });
26
+ fs.mkdirSync(RUNS_DIR, { recursive: true });
27
+
28
+ const runId = new Date().toISOString().replace(/[:.]/g, "-");
29
+ const results = [];
30
+ let passed = 0;
31
+ let failed = 0;
32
+ let skipped = 0;
33
+ let totalDuration = 0;
34
+ const fileFingerprintCache = new Map();
35
+ const testArtifacts = new Map();
36
+ const workspaceStateAtRunStart = getWorkspaceState();
37
+
38
+ function safeExec(command) {
39
+ try {
40
+ return execSync(command, {
41
+ cwd: process.cwd(),
42
+ encoding: "utf8",
43
+ stdio: ["ignore", "pipe", "ignore"],
44
+ timeout: 5000,
45
+ }).trim();
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function truncate(value, limit = 800) {
52
+ const text = String(value ?? "");
53
+ return text.length <= limit ? text : `${text.slice(0, limit)}...`;
54
+ }
55
+
56
+ function compactText(value) {
57
+ return truncate(String(value ?? "").replace(/\s+/g, " ").trim());
58
+ }
59
+
60
+ function shellQuote(value) {
61
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
62
+ }
63
+
64
+ function slugify(value) {
65
+ return String(value || "unnamed")
66
+ .toLowerCase()
67
+ .replace(/[^a-z0-9]+/g, "-")
68
+ .replace(/^-+|-+$/g, "")
69
+ .slice(0, 80) || "unnamed";
70
+ }
71
+
72
+ function buildTestId(filePath, name) {
73
+ return `${slugify(path.relative(process.cwd(), filePath || "no-file"))}__${slugify(name || "unnamed-test")}`;
74
+ }
75
+
76
+ function buildRerunCommand(filePath, testName) {
77
+ return `node --test --test-reporter=./scripts/test-reporter.mjs --test-name-pattern=${shellQuote(testName)} ${shellQuote(filePath)}`;
78
+ }
79
+
80
+ function buildSelector(filePath, testName) {
81
+ return {
82
+ file: filePath || null,
83
+ test_name: testName || null,
84
+ test_name_pattern: testName || null,
85
+ command: buildRerunCommand(filePath, testName),
86
+ };
87
+ }
88
+
89
+ function getArtifactDir(testId) {
90
+ return path.join(RUNS_DIR, runId, testId);
91
+ }
92
+
93
+ function ensureArtifactDir(testId) {
94
+ const artifactDir = getArtifactDir(testId);
95
+ fs.mkdirSync(artifactDir, { recursive: true });
96
+ return artifactDir;
97
+ }
98
+
99
+ function writeArtifactJson(testId, filename, value) {
100
+ const artifactDir = ensureArtifactDir(testId);
101
+ fs.writeFileSync(path.join(artifactDir, filename), JSON.stringify(value, null, 2) + "\n");
102
+ }
103
+
104
+ function artifactPath(testId, filename) {
105
+ return path.join(getArtifactDir(testId), filename);
106
+ }
107
+
108
+ function upsertTestArtifact(entry) {
109
+ const current = testArtifacts.get(entry.testId) || {};
110
+ testArtifacts.set(entry.testId, { ...current, ...entry });
111
+ }
112
+
113
+ function fingerprintFile(filePath) {
114
+ if (!filePath) return null;
115
+ if (fileFingerprintCache.has(filePath)) return fileFingerprintCache.get(filePath);
116
+ try {
117
+ const stat = fs.statSync(filePath);
118
+ const content = fs.readFileSync(filePath);
119
+ const fingerprint = {
120
+ file: filePath,
121
+ relative_file: path.relative(process.cwd(), filePath),
122
+ size_bytes: stat.size,
123
+ mtime: stat.mtime.toISOString(),
124
+ sha256: crypto.createHash("sha256").update(content).digest("hex"),
125
+ git_blob: safeExec(`git hash-object ${JSON.stringify(filePath)}`),
126
+ };
127
+ fileFingerprintCache.set(filePath, fingerprint);
128
+ return fingerprint;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ function readEvidence(testId) {
135
+ const evidencePath = artifactPath(testId, "evidence.jsonl");
136
+ if (!fs.existsSync(evidencePath)) return [];
137
+ try {
138
+ return fs.readFileSync(evidencePath, "utf8")
139
+ .split("\n")
140
+ .filter(Boolean)
141
+ .map((line) => JSON.parse(line));
142
+ } catch {
143
+ return [];
144
+ }
145
+ }
146
+
147
+ function collectTestContext(testId) {
148
+ const evidence = readEvidence(testId);
149
+ const engineContext = [...evidence].reverse().find((entry) => entry.category === "engine_context") || null;
150
+ const latestHttp = [...evidence].reverse().find((entry) => entry.category === "http") || null;
151
+ const latestFileVerification = [...evidence].reverse().find((entry) => entry.category === "file_verification") || null;
152
+ const latestTimeout = [...evidence].reverse().find((entry) =>
153
+ entry.category === "task_timeout" || entry.category === "passthrough_timeout"
154
+ ) || null;
155
+ const latestPassthroughError = [...evidence].reverse().find((entry) => entry.category === "passthrough_error") || null;
156
+ const latestWorkflowEvent = [...evidence].reverse().find((entry) =>
157
+ /workflow|dispatch|task/i.test(String(entry.category || ""))
158
+ ) || null;
159
+ return {
160
+ evidence_count: evidence.length,
161
+ engine_context: engineContext,
162
+ latest_http: latestHttp,
163
+ latest_file_verification: latestFileVerification,
164
+ latest_timeout: latestTimeout,
165
+ latest_passthrough_error: latestPassthroughError,
166
+ latest_workflow_event: latestWorkflowEvent,
167
+ };
168
+ }
169
+
170
+ function extractSkipReason(data = {}) {
171
+ return data.skipReason ||
172
+ data.skip ||
173
+ data.details?.skipReason ||
174
+ data.details?.skip ||
175
+ (data.details?.skipped ? data.details?.message || "skipped" : null) ||
176
+ (data.details?.status === "skipped" ? data.details?.message || "skipped" : null) ||
177
+ null;
178
+ }
179
+
180
+ function classifyFailure(entry, context) {
181
+ const errorText = compactText([
182
+ entry.error,
183
+ entry.error_name,
184
+ entry.error_code,
185
+ context.latest_timeout?.error,
186
+ context.latest_passthrough_error?.error,
187
+ context.latest_http?.error,
188
+ context.latest_workflow_event?.last_result_preview,
189
+ ].filter(Boolean).join(" | "));
190
+
191
+ const checks = [
192
+ {
193
+ code: "timeout",
194
+ matches: /timeout|timed out|did not finish|deadline exceeded/i,
195
+ summary: "test or engine timed out",
196
+ },
197
+ {
198
+ code: "cancelled",
199
+ matches: /cancelled|canceled|abort|aborted/i,
200
+ summary: "test was cancelled",
201
+ },
202
+ {
203
+ code: "agent_unreachable",
204
+ matches: /rt bus not connected|agent unreachable|econnrefused|connect econrefused|socket hang up/i,
205
+ summary: "agent or service was unreachable",
206
+ },
207
+ {
208
+ code: "missing_task_id",
209
+ matches: /no taskid|missing taskid/i,
210
+ summary: "dispatch returned no task id",
211
+ },
212
+ {
213
+ code: "file_missing",
214
+ matches: /should exist|file not found|enoent/i,
215
+ summary: "expected output file was missing",
216
+ },
217
+ {
218
+ code: "empty_response",
219
+ matches: /non-empty response|returned nothing|response length/i,
220
+ summary: "engine returned an empty or unusable response",
221
+ },
222
+ {
223
+ code: "http_error",
224
+ matches: /dispatch failed: \d+|http|status code|unexpected status/i,
225
+ summary: "http request or api contract failed",
226
+ },
227
+ {
228
+ code: "assertion",
229
+ matches: /assert|expected .* got/i,
230
+ summary: "assertion failed",
231
+ },
232
+ ];
233
+
234
+ const match = checks.find((item) => item.matches.test(errorText));
235
+ return {
236
+ reason_code: match?.code || "unknown_failure",
237
+ reason_summary: match?.summary || "unclassified failure",
238
+ reason_detail: errorText || "no detailed failure message captured",
239
+ };
240
+ }
241
+
242
+ function classifySkip(entry) {
243
+ const reasonText = compactText(entry.skip_reason || "skipped");
244
+ const checks = [
245
+ {
246
+ code: "service_down",
247
+ matches: /not running|service unavailable|health check/i,
248
+ summary: "required local service was not running",
249
+ },
250
+ {
251
+ code: "engine_unavailable",
252
+ matches: /not available|not installed|missing/i,
253
+ summary: "required engine or dependency was unavailable",
254
+ },
255
+ {
256
+ code: "explicit_skip",
257
+ matches: /skip|skipped/i,
258
+ summary: "test was skipped intentionally",
259
+ },
260
+ ];
261
+ const match = checks.find((item) => item.matches.test(reasonText));
262
+ return {
263
+ reason_code: match?.code || "unknown_skip",
264
+ reason_summary: match?.summary || "unclassified skip",
265
+ reason_detail: reasonText,
266
+ };
267
+ }
268
+
269
+ export { buildSelector, classifyFailure, classifySkip };
270
+
271
+ function buildFailureBundle(entry) {
272
+ const context = collectTestContext(entry.testId);
273
+ const classification = classifyFailure(entry, context);
274
+ const engineRuntime = context.engine_context?.engine_runtime || {};
275
+ const agentRuntime = context.engine_context?.agent_runtime || {};
276
+ return {
277
+ ...entry,
278
+ ...classification,
279
+ selector: buildSelector(entry.file, entry.name),
280
+ workspace_state_at_run_start: workspaceStateAtRunStart,
281
+ isolation: {
282
+ isolated_rerun_command: entry.rerun_command,
283
+ latest_http_status: context.latest_http?.status ?? null,
284
+ latest_http_operation: context.latest_http?.operation ?? null,
285
+ latest_timeout_ms: context.latest_timeout?.timeout_ms ?? context.engine_context?.timeout_ms ?? null,
286
+ evidence_count: context.evidence_count,
287
+ },
288
+ engine: {
289
+ engine: context.engine_context?.engine || entry.engine || null,
290
+ provider: engineRuntime.provider || agentRuntime.provider || null,
291
+ model: agentRuntime.model || agentRuntime.cursorCliModel || agentRuntime.opencodeModel || null,
292
+ agent: context.engine_context?.agent || null,
293
+ binary_path: engineRuntime.binary?.path || null,
294
+ binary_version: engineRuntime.binary?.version || null,
295
+ route_flags: agentRuntime.routeFlags || null,
296
+ enabled_route: agentRuntime.enabledRoute || null,
297
+ },
298
+ artifacts: {
299
+ artifact_dir: entry.artifactDir,
300
+ manifest_json: artifactPath(entry.testId, "manifest.json"),
301
+ failure_json: artifactPath(entry.testId, "failure.json"),
302
+ evidence_jsonl: artifactPath(entry.testId, "evidence.jsonl"),
303
+ triage_json: artifactPath(entry.testId, "triage.json"),
304
+ },
305
+ latest_evidence: {
306
+ http: context.latest_http || null,
307
+ file_verification: context.latest_file_verification || null,
308
+ timeout: context.latest_timeout || null,
309
+ passthrough_error: context.latest_passthrough_error || null,
310
+ workflow: context.latest_workflow_event || null,
311
+ },
312
+ };
313
+ }
314
+
315
+ function buildSkipBundle(entry) {
316
+ const classification = classifySkip(entry);
317
+ return {
318
+ ...entry,
319
+ ...classification,
320
+ selector: buildSelector(entry.file, entry.name),
321
+ workspace_state_at_run_start: workspaceStateAtRunStart,
322
+ isolation: {
323
+ isolated_rerun_command: entry.rerun_command,
324
+ },
325
+ artifacts: {
326
+ artifact_dir: entry.artifactDir,
327
+ manifest_json: artifactPath(entry.testId, "manifest.json"),
328
+ evidence_jsonl: artifactPath(entry.testId, "evidence.jsonl"),
329
+ triage_json: artifactPath(entry.testId, "triage.json"),
330
+ },
331
+ };
332
+ }
333
+
334
+ const runMeta = {
335
+ runId,
336
+ entry_type: "run",
337
+ phase: "start",
338
+ timestamp: new Date().toISOString(),
339
+ cwd: process.cwd(),
340
+ hostname: os.hostname(),
341
+ platform: process.platform,
342
+ arch: process.arch,
343
+ node_version: process.version,
344
+ test_results_dir: ROOT,
345
+ test_command: process.argv.join(" "),
346
+ git_branch: safeExec("git branch --show-current"),
347
+ git_commit: safeExec("git rev-parse HEAD"),
348
+ git_dirty: !!safeExec("git status --short"),
349
+ workspace_state: workspaceStateAtRunStart,
350
+ package_version: (() => {
351
+ try {
352
+ return JSON.parse(fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8")).version || null;
353
+ } catch {
354
+ return null;
355
+ }
356
+ })(),
357
+ };
358
+
359
+ fs.writeFileSync(CURRENT_RUN_PATH, JSON.stringify(runMeta, null, 2) + "\n");
360
+ fs.mkdirSync(path.join(RUNS_DIR, runId), { recursive: true });
361
+ fs.writeFileSync(path.join(RUNS_DIR, runId, "run.json"), JSON.stringify(runMeta, null, 2) + "\n");
362
+
363
+ export default async function* reporter(source) {
364
+ const logStream = fs.createWriteStream(LOG_PATH, { flags: "a" });
365
+ logStream.write(JSON.stringify(runMeta) + "\n");
366
+
367
+ for await (const event of source) {
368
+ if (event.type === "test:start" && event.data?.details?.type !== "suite") {
369
+ const testId = buildTestId(event.data.file, event.data.name);
370
+ const artifactDir = ensureArtifactDir(testId);
371
+ const startEntry = {
372
+ runId,
373
+ timestamp: new Date().toISOString(),
374
+ entry_type: "result",
375
+ phase: "start",
376
+ status: "start",
377
+ testId,
378
+ artifactDir,
379
+ name: event.data.name,
380
+ file: event.data.file,
381
+ file_fingerprint: fingerprintFile(event.data.file),
382
+ details_type: event.data.details?.type || null,
383
+ nesting: event.data.nesting ?? null,
384
+ line: event.data.line ?? null,
385
+ column: event.data.column ?? null,
386
+ rerun_command: buildRerunCommand(event.data.file, event.data.name),
387
+ selector: buildSelector(event.data.file, event.data.name),
388
+ dependency_snapshot: buildDependencySnapshot(event.data.file),
389
+ };
390
+ upsertTestArtifact({
391
+ testId,
392
+ artifactDir,
393
+ name: event.data.name,
394
+ file: event.data.file,
395
+ status: "start",
396
+ line: event.data.line ?? null,
397
+ column: event.data.column ?? null,
398
+ dependency_snapshot: startEntry.dependency_snapshot,
399
+ });
400
+ writeArtifactJson(testId, "manifest.json", startEntry);
401
+ logStream.write(JSON.stringify(startEntry) + "\n");
402
+ }
403
+
404
+ if (event.type === "test:pass" || event.type === "test:fail") {
405
+ // Only log leaf tests (not suite/describe wrappers)
406
+ if (event.data.details?.type === "suite") continue;
407
+
408
+ const inferredSkipReason = event.type === "test:pass" ? extractSkipReason(event.data) : null;
409
+ const status = inferredSkipReason ? "skip" : event.type === "test:pass" ? "pass" : "fail";
410
+ const duration = event.data.details?.duration_ms ?? 0;
411
+ totalDuration += duration;
412
+
413
+ if (status === "pass") passed++;
414
+ else if (status === "skip") skipped++;
415
+ else failed++;
416
+
417
+ const entry = {
418
+ runId,
419
+ timestamp: new Date().toISOString(),
420
+ entry_type: "result",
421
+ phase: "finish",
422
+ status,
423
+ testId: buildTestId(event.data.file, event.data.name),
424
+ artifactDir: getArtifactDir(buildTestId(event.data.file, event.data.name)),
425
+ name: event.data.name,
426
+ file: event.data.file,
427
+ file_fingerprint: fingerprintFile(event.data.file),
428
+ duration_ms: Math.round(duration * 100) / 100,
429
+ details_type: event.data.details?.type || null,
430
+ nesting: event.data.nesting ?? null,
431
+ line: event.data.line ?? null,
432
+ column: event.data.column ?? null,
433
+ rerun_command: buildRerunCommand(event.data.file, event.data.name),
434
+ selector: buildSelector(event.data.file, event.data.name),
435
+ dependency_snapshot: (testArtifacts.get(buildTestId(event.data.file, event.data.name)) || {}).dependency_snapshot || buildDependencySnapshot(event.data.file),
436
+ ...(status === "skip" ? { skip_reason: truncate(inferredSkipReason || "skipped") } : {}),
437
+ ...(status === "fail" && event.data.details?.error
438
+ ? {
439
+ error: truncate(event.data.details.error.message || event.data.details.error),
440
+ error_name: event.data.details.error.name || null,
441
+ error_code: event.data.details.error.code || null,
442
+ error_stack: truncate(event.data.details.error.stack || ""),
443
+ timeout_detected: /timeout|cancelled|did not finish/i.test(
444
+ String(event.data.details.error.message || event.data.details.error)
445
+ ),
446
+ }
447
+ : {}),
448
+ };
449
+
450
+ results.push(entry);
451
+ upsertTestArtifact({
452
+ testId: entry.testId,
453
+ artifactDir: entry.artifactDir,
454
+ name: entry.name,
455
+ file: entry.file,
456
+ status: entry.status,
457
+ duration_ms: entry.duration_ms,
458
+ error: entry.error,
459
+ error_name: entry.error_name,
460
+ timeout_detected: entry.timeout_detected,
461
+ });
462
+ writeArtifactJson(entry.testId, "manifest.json", {
463
+ ...testArtifacts.get(entry.testId),
464
+ result: entry,
465
+ });
466
+ if (status === "fail") {
467
+ writeArtifactJson(entry.testId, "failure.json", entry);
468
+ writeArtifactJson(entry.testId, "triage.json", buildFailureBundle(entry));
469
+ } else if (status === "skip") {
470
+ writeArtifactJson(entry.testId, "triage.json", buildSkipBundle(entry));
471
+ }
472
+ logStream.write(JSON.stringify(entry) + "\n");
473
+ }
474
+
475
+ if (event.type === "test:skip") {
476
+ if (event.data.details?.type === "suite") continue;
477
+ skipped++;
478
+ const entry = {
479
+ runId,
480
+ timestamp: new Date().toISOString(),
481
+ entry_type: "result",
482
+ phase: "finish",
483
+ status: "skip",
484
+ testId: buildTestId(event.data.file, event.data.name),
485
+ artifactDir: getArtifactDir(buildTestId(event.data.file, event.data.name)),
486
+ name: event.data.name,
487
+ file: event.data.file,
488
+ skip_reason: truncate(extractSkipReason(event.data) || "skipped"),
489
+ rerun_command: buildRerunCommand(event.data.file, event.data.name),
490
+ selector: buildSelector(event.data.file, event.data.name),
491
+ dependency_snapshot: (testArtifacts.get(buildTestId(event.data.file, event.data.name)) || {}).dependency_snapshot || buildDependencySnapshot(event.data.file),
492
+ };
493
+ results.push(entry);
494
+ upsertTestArtifact({
495
+ testId: entry.testId,
496
+ artifactDir: entry.artifactDir,
497
+ name: entry.name,
498
+ file: entry.file,
499
+ status: "skip",
500
+ });
501
+ writeArtifactJson(entry.testId, "manifest.json", entry);
502
+ writeArtifactJson(entry.testId, "triage.json", buildSkipBundle(entry));
503
+ logStream.write(JSON.stringify(entry) + "\n");
504
+ }
505
+
506
+ // Pass through to default spec output
507
+ if (event.type === "test:pass" && extractSkipReason(event.data)) {
508
+ yield ` - ${event.data.name} (skipped)\n`;
509
+ } else if (event.type === "test:pass") {
510
+ yield ` ✓ ${event.data.name}\n`;
511
+ } else if (event.type === "test:fail") {
512
+ yield ` ✗ ${event.data.name}\n`;
513
+ if (event.data.details?.error) {
514
+ const msg = String(event.data.details.error.message || event.data.details.error);
515
+ yield ` ${msg.split("\n")[0]}\n`;
516
+ }
517
+ } else if (event.type === "test:skip") {
518
+ yield ` - ${event.data.name} (skipped)\n`;
519
+ } else if (event.type === "test:diagnostic") {
520
+ yield `${event.data.message}\n`;
521
+ } else if (event.type === "test:start") {
522
+ if (event.data.file) yield `\n${path.basename(event.data.file)}\n`;
523
+ }
524
+ }
525
+
526
+ // Write summary
527
+ const summary = {
528
+ runId,
529
+ timestamp: new Date().toISOString(),
530
+ entry_type: "run",
531
+ phase: "finish",
532
+ status: failed > 0 ? "failed" : "passed",
533
+ passed,
534
+ failed,
535
+ skipped,
536
+ total: passed + failed + skipped,
537
+ duration_ms: Math.round(totalDuration * 100) / 100,
538
+ workspace_state_at_run_start: workspaceStateAtRunStart,
539
+ tests: results.map((r) => ({
540
+ testId: r.testId,
541
+ name: r.name,
542
+ file: r.file,
543
+ status: r.status,
544
+ artifactDir: r.artifactDir,
545
+ rerun_command: r.rerun_command,
546
+ reason_code: r.reason_code || null,
547
+ reason_summary: r.reason_summary || null,
548
+ skip_reason: r.skip_reason || null,
549
+ dependency_snapshot: r.dependency_snapshot || null,
550
+ engine: r.engine || null,
551
+ workspace_state_at_run_start: workspaceStateAtRunStart,
552
+ })),
553
+ failedTests: results
554
+ .filter((r) => r.status === "fail")
555
+ .map((r) => buildFailureBundle(r)),
556
+ skippedTests: results
557
+ .filter((r) => r.status === "skip")
558
+ .map((r) => buildSkipBundle(r)),
559
+ };
560
+ fs.writeFileSync(SUMMARY_PATH, JSON.stringify(summary, null, 2) + "\n");
561
+ fs.writeFileSync(path.join(RUNS_DIR, runId, "summary.json"), JSON.stringify(summary, null, 2) + "\n");
562
+ fs.writeFileSync(path.join(RUNS_DIR, runId, "failures.json"), JSON.stringify(summary.failedTests, null, 2) + "\n");
563
+ fs.writeFileSync(path.join(RUNS_DIR, runId, "skips.json"), JSON.stringify(summary.skippedTests, null, 2) + "\n");
564
+ fs.writeFileSync(path.join(RUNS_DIR, runId, "rerun-plan.json"), JSON.stringify({
565
+ runId,
566
+ generated_at: new Date().toISOString(),
567
+ failed: summary.failedTests.map((test) => ({
568
+ testId: test.testId,
569
+ name: test.name,
570
+ reason_code: test.reason_code,
571
+ reason_summary: test.reason_summary,
572
+ rerun_command: test.rerun_command,
573
+ })),
574
+ skipped: summary.skippedTests.map((test) => ({
575
+ testId: test.testId,
576
+ name: test.name,
577
+ reason_code: test.reason_code,
578
+ reason_summary: test.reason_summary,
579
+ rerun_command: test.rerun_command,
580
+ })),
581
+ }, null, 2) + "\n");
582
+
583
+ const markdown = [
584
+ `# Test Run ${runId}`,
585
+ "",
586
+ `- Status: ${summary.status}`,
587
+ `- Passed: ${summary.passed}`,
588
+ `- Failed: ${summary.failed}`,
589
+ `- Skipped: ${summary.skipped}`,
590
+ `- Duration: ${(summary.duration_ms / 1000).toFixed(2)}s`,
591
+ `- Commit: ${runMeta.git_commit || "unknown"}`,
592
+ `- Branch: ${runMeta.git_branch || "unknown"}`,
593
+ `- Node: ${runMeta.node_version}`,
594
+ "",
595
+ "## Failed Tests",
596
+ ];
597
+ if (summary.failedTests.length === 0) {
598
+ markdown.push("", "None.");
599
+ } else {
600
+ for (const failure of summary.failedTests) {
601
+ markdown.push(
602
+ "",
603
+ `### ${failure.name}`,
604
+ `- Test ID: \`${failure.testId}\``,
605
+ `- File: \`${failure.file}\``,
606
+ `- Error: ${failure.error || "unknown"}`,
607
+ `- Error Name: ${failure.error_name || "n/a"}`,
608
+ `- Reason Code: ${failure.reason_code}`,
609
+ `- Reason Summary: ${failure.reason_summary}`,
610
+ `- Timeout: ${failure.timeout_detected ? "yes" : "no"}`,
611
+ `- Engine: ${failure.engine.engine || "n/a"}`,
612
+ `- Provider: ${failure.engine.provider || "n/a"}`,
613
+ `- Model: ${failure.engine.model || "n/a"}`,
614
+ `- Agent: ${failure.engine.agent || "n/a"}`,
615
+ `- Artifacts: \`${failure.artifactDir}\``,
616
+ `- Re-run: \`${failure.rerun_command}\``
617
+ );
618
+ }
619
+ }
620
+ markdown.push("", "## Skipped Tests");
621
+ if (summary.skippedTests.length === 0) {
622
+ markdown.push("", "None.");
623
+ } else {
624
+ for (const skippedTest of summary.skippedTests) {
625
+ markdown.push(
626
+ "",
627
+ `### ${skippedTest.name}`,
628
+ `- Test ID: \`${skippedTest.testId}\``,
629
+ `- File: \`${skippedTest.file}\``,
630
+ `- Reason Code: ${skippedTest.reason_code}`,
631
+ `- Reason: ${skippedTest.skip_reason}`,
632
+ `- Artifacts: \`${skippedTest.artifactDir}\``,
633
+ `- Re-run: \`${skippedTest.rerun_command}\``
634
+ );
635
+ }
636
+ }
637
+ markdown.push("", "## Artifact Directories", "");
638
+ for (const artifact of [...testArtifacts.values()].sort((a, b) => a.name.localeCompare(b.name))) {
639
+ markdown.push(`- \`${artifact.name}\` → \`${artifact.artifactDir}\``);
640
+ }
641
+ fs.writeFileSync(path.join(RUNS_DIR, runId, "summary.md"), markdown.join("\n") + "\n");
642
+
643
+ logStream.write(JSON.stringify(summary) + "\n");
644
+ logStream.end();
645
+
646
+ yield `\n─────────────────────────────────────\n`;
647
+ yield ` ${passed} passed, ${failed} failed, ${skipped} skipped (${results.length} total)\n`;
648
+ yield ` Duration: ${(totalDuration / 1000).toFixed(1)}s\n`;
649
+ yield ` Results: ${LOG_PATH}\n`;
650
+ yield `─────────────────────────────────────\n`;
651
+ }