@avi770/testteam 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/agents/01-analyst.ts +100 -0
  5. package/agents/02-seed-architect.ts +59 -0
  6. package/agents/03-test-generator.ts +191 -0
  7. package/agents/04-unit-runner.ts +160 -0
  8. package/agents/05-browser-crawler.ts +790 -0
  9. package/agents/06-api-exerciser.ts +311 -0
  10. package/agents/07-security-scout.ts +188 -0
  11. package/agents/08-a11y-guardian.ts +212 -0
  12. package/agents/09-healer.ts +228 -0
  13. package/agents/10-reporter.ts +266 -0
  14. package/agents/11-fixer.ts +253 -0
  15. package/agents/12-ux-inspector.ts +444 -0
  16. package/agents/13-performance-profiler.ts +271 -0
  17. package/agents/14-data-integrity-auditor.ts +417 -0
  18. package/agents/15-regression-sentinel.ts +307 -0
  19. package/agents/16-chaos-agent.ts +228 -0
  20. package/agents/17-documentation-validator.ts +266 -0
  21. package/agents/18-integration-watchdog.ts +178 -0
  22. package/agents/19-tenant-isolation-auditor.ts +199 -0
  23. package/agents/20-workflow-completion-tester.ts +203 -0
  24. package/agents/21-state-session-tester.ts +262 -0
  25. package/agents/22-email-notification-verifier.ts +244 -0
  26. package/agents/23-migration-tester.ts +80 -0
  27. package/agents/__tests__/01-analyst.test.ts +188 -0
  28. package/agents/__tests__/02-seed-architect.test.ts +152 -0
  29. package/agents/__tests__/03-test-generator-full.test.ts +321 -0
  30. package/agents/__tests__/03-test-generator.test.ts +318 -0
  31. package/agents/__tests__/04-unit-runner.test.ts +320 -0
  32. package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
  33. package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
  34. package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
  35. package/agents/__tests__/05-browser-crawler.test.ts +518 -0
  36. package/agents/__tests__/06-api-exerciser.test.ts +619 -0
  37. package/agents/__tests__/07-security-scout.test.ts +382 -0
  38. package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
  39. package/agents/__tests__/09-healer.test.ts +384 -0
  40. package/agents/__tests__/10-reporter.test.ts +366 -0
  41. package/agents/__tests__/11-fixer.test.ts +406 -0
  42. package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
  43. package/agents/__tests__/12-ux-inspector.test.ts +443 -0
  44. package/agents/__tests__/13-performance-profiler.test.ts +411 -0
  45. package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
  46. package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
  47. package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
  48. package/agents/__tests__/16-chaos-agent.test.ts +427 -0
  49. package/agents/__tests__/17-documentation-validator.test.ts +402 -0
  50. package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
  51. package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
  52. package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
  53. package/agents/__tests__/21-state-session-tester.test.ts +374 -0
  54. package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
  55. package/agents/__tests__/23-migration-tester.test.ts +145 -0
  56. package/agents/__tests__/base-agent.test.ts +188 -0
  57. package/agents/__tests__/registry.test.ts +218 -0
  58. package/agents/base-agent.ts +77 -0
  59. package/agents/registry.ts +136 -0
  60. package/baselines/api-schemas/.gitkeep +0 -0
  61. package/baselines/performance/.gitkeep +0 -0
  62. package/baselines/screenshots/.gitkeep +0 -0
  63. package/bin/testteam.js +10 -0
  64. package/core/__tests__/ci-output.test.ts +430 -0
  65. package/core/__tests__/cli.test.ts +387 -0
  66. package/core/__tests__/config.test.ts +78 -0
  67. package/core/__tests__/cost-tracker.test.ts +158 -0
  68. package/core/__tests__/evidence.test.ts +265 -0
  69. package/core/__tests__/fix-loop.test.ts +210 -0
  70. package/core/__tests__/health-check.test.ts +44 -0
  71. package/core/__tests__/init.test.ts +609 -0
  72. package/core/__tests__/integration.test.ts +204 -0
  73. package/core/__tests__/license-gen.test.ts +227 -0
  74. package/core/__tests__/license.test.ts +326 -0
  75. package/core/__tests__/multi-browser.test.ts +278 -0
  76. package/core/__tests__/orchestrator.test.ts +519 -0
  77. package/core/__tests__/phase-gate.test.ts +43 -0
  78. package/core/__tests__/report-html.test.ts +398 -0
  79. package/core/__tests__/report-upload.test.ts +325 -0
  80. package/core/__tests__/run-counter.test.ts +234 -0
  81. package/core/ci-output.ts +240 -0
  82. package/core/cli.ts +232 -0
  83. package/core/config.ts +178 -0
  84. package/core/cost-tracker.ts +59 -0
  85. package/core/evidence.ts +132 -0
  86. package/core/fix-loop.ts +85 -0
  87. package/core/health-check.ts +54 -0
  88. package/core/init.ts +546 -0
  89. package/core/license-gen.ts +212 -0
  90. package/core/license.ts +211 -0
  91. package/core/messages.ts +67 -0
  92. package/core/multi-browser.ts +136 -0
  93. package/core/orchestrator.ts +354 -0
  94. package/core/phase-gate.ts +55 -0
  95. package/core/report-html.ts +657 -0
  96. package/core/report-upload.ts +188 -0
  97. package/core/run-counter.ts +175 -0
  98. package/core/types.ts +57 -0
  99. package/dist/agents/01-analyst.d.ts +11 -0
  100. package/dist/agents/01-analyst.d.ts.map +1 -0
  101. package/dist/agents/01-analyst.js +75 -0
  102. package/dist/agents/01-analyst.js.map +1 -0
  103. package/dist/agents/02-seed-architect.d.ts +11 -0
  104. package/dist/agents/02-seed-architect.d.ts.map +1 -0
  105. package/dist/agents/02-seed-architect.js +51 -0
  106. package/dist/agents/02-seed-architect.js.map +1 -0
  107. package/dist/agents/03-test-generator.d.ts +9 -0
  108. package/dist/agents/03-test-generator.d.ts.map +1 -0
  109. package/dist/agents/03-test-generator.js +167 -0
  110. package/dist/agents/03-test-generator.js.map +1 -0
  111. package/dist/agents/04-unit-runner.d.ts +9 -0
  112. package/dist/agents/04-unit-runner.d.ts.map +1 -0
  113. package/dist/agents/04-unit-runner.js +113 -0
  114. package/dist/agents/04-unit-runner.js.map +1 -0
  115. package/dist/agents/05-browser-crawler.d.ts +30 -0
  116. package/dist/agents/05-browser-crawler.d.ts.map +1 -0
  117. package/dist/agents/05-browser-crawler.js +685 -0
  118. package/dist/agents/05-browser-crawler.js.map +1 -0
  119. package/dist/agents/06-api-exerciser.d.ts +23 -0
  120. package/dist/agents/06-api-exerciser.d.ts.map +1 -0
  121. package/dist/agents/06-api-exerciser.js +253 -0
  122. package/dist/agents/06-api-exerciser.js.map +1 -0
  123. package/dist/agents/07-security-scout.d.ts +11 -0
  124. package/dist/agents/07-security-scout.d.ts.map +1 -0
  125. package/dist/agents/07-security-scout.js +142 -0
  126. package/dist/agents/07-security-scout.js.map +1 -0
  127. package/dist/agents/08-a11y-guardian.d.ts +13 -0
  128. package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
  129. package/dist/agents/08-a11y-guardian.js +176 -0
  130. package/dist/agents/08-a11y-guardian.js.map +1 -0
  131. package/dist/agents/09-healer.d.ts +33 -0
  132. package/dist/agents/09-healer.d.ts.map +1 -0
  133. package/dist/agents/09-healer.js +167 -0
  134. package/dist/agents/09-healer.js.map +1 -0
  135. package/dist/agents/10-reporter.d.ts +26 -0
  136. package/dist/agents/10-reporter.d.ts.map +1 -0
  137. package/dist/agents/10-reporter.js +215 -0
  138. package/dist/agents/10-reporter.js.map +1 -0
  139. package/dist/agents/11-fixer.d.ts +26 -0
  140. package/dist/agents/11-fixer.d.ts.map +1 -0
  141. package/dist/agents/11-fixer.js +195 -0
  142. package/dist/agents/11-fixer.js.map +1 -0
  143. package/dist/agents/12-ux-inspector.d.ts +15 -0
  144. package/dist/agents/12-ux-inspector.d.ts.map +1 -0
  145. package/dist/agents/12-ux-inspector.js +364 -0
  146. package/dist/agents/12-ux-inspector.js.map +1 -0
  147. package/dist/agents/13-performance-profiler.d.ts +13 -0
  148. package/dist/agents/13-performance-profiler.d.ts.map +1 -0
  149. package/dist/agents/13-performance-profiler.js +216 -0
  150. package/dist/agents/13-performance-profiler.js.map +1 -0
  151. package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
  152. package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
  153. package/dist/agents/14-data-integrity-auditor.js +356 -0
  154. package/dist/agents/14-data-integrity-auditor.js.map +1 -0
  155. package/dist/agents/15-regression-sentinel.d.ts +25 -0
  156. package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
  157. package/dist/agents/15-regression-sentinel.js +251 -0
  158. package/dist/agents/15-regression-sentinel.js.map +1 -0
  159. package/dist/agents/16-chaos-agent.d.ts +9 -0
  160. package/dist/agents/16-chaos-agent.d.ts.map +1 -0
  161. package/dist/agents/16-chaos-agent.js +207 -0
  162. package/dist/agents/16-chaos-agent.js.map +1 -0
  163. package/dist/agents/17-documentation-validator.d.ts +31 -0
  164. package/dist/agents/17-documentation-validator.d.ts.map +1 -0
  165. package/dist/agents/17-documentation-validator.js +246 -0
  166. package/dist/agents/17-documentation-validator.js.map +1 -0
  167. package/dist/agents/18-integration-watchdog.d.ts +10 -0
  168. package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
  169. package/dist/agents/18-integration-watchdog.js +138 -0
  170. package/dist/agents/18-integration-watchdog.js.map +1 -0
  171. package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
  172. package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
  173. package/dist/agents/19-tenant-isolation-auditor.js +166 -0
  174. package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
  175. package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
  176. package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
  177. package/dist/agents/20-workflow-completion-tester.js +159 -0
  178. package/dist/agents/20-workflow-completion-tester.js.map +1 -0
  179. package/dist/agents/21-state-session-tester.d.ts +10 -0
  180. package/dist/agents/21-state-session-tester.d.ts.map +1 -0
  181. package/dist/agents/21-state-session-tester.js +233 -0
  182. package/dist/agents/21-state-session-tester.js.map +1 -0
  183. package/dist/agents/22-email-notification-verifier.d.ts +11 -0
  184. package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
  185. package/dist/agents/22-email-notification-verifier.js +199 -0
  186. package/dist/agents/22-email-notification-verifier.js.map +1 -0
  187. package/dist/agents/23-migration-tester.d.ts +10 -0
  188. package/dist/agents/23-migration-tester.d.ts.map +1 -0
  189. package/dist/agents/23-migration-tester.js +74 -0
  190. package/dist/agents/23-migration-tester.js.map +1 -0
  191. package/dist/agents/base-agent.d.ts +19 -0
  192. package/dist/agents/base-agent.d.ts.map +1 -0
  193. package/dist/agents/base-agent.js +67 -0
  194. package/dist/agents/base-agent.js.map +1 -0
  195. package/dist/agents/registry.d.ts +29 -0
  196. package/dist/agents/registry.d.ts.map +1 -0
  197. package/dist/agents/registry.js +117 -0
  198. package/dist/agents/registry.js.map +1 -0
  199. package/dist/core/ci-output.d.ts +35 -0
  200. package/dist/core/ci-output.d.ts.map +1 -0
  201. package/dist/core/ci-output.js +193 -0
  202. package/dist/core/ci-output.js.map +1 -0
  203. package/dist/core/cli.d.ts +11 -0
  204. package/dist/core/cli.d.ts.map +1 -0
  205. package/dist/core/cli.js +197 -0
  206. package/dist/core/cli.js.map +1 -0
  207. package/dist/core/config.d.ts +111 -0
  208. package/dist/core/config.d.ts.map +1 -0
  209. package/dist/core/config.js +42 -0
  210. package/dist/core/config.js.map +1 -0
  211. package/dist/core/cost-tracker.d.ts +22 -0
  212. package/dist/core/cost-tracker.d.ts.map +1 -0
  213. package/dist/core/cost-tracker.js +41 -0
  214. package/dist/core/cost-tracker.js.map +1 -0
  215. package/dist/core/evidence.d.ts +28 -0
  216. package/dist/core/evidence.d.ts.map +1 -0
  217. package/dist/core/evidence.js +95 -0
  218. package/dist/core/evidence.js.map +1 -0
  219. package/dist/core/fix-loop.d.ts +29 -0
  220. package/dist/core/fix-loop.d.ts.map +1 -0
  221. package/dist/core/fix-loop.js +70 -0
  222. package/dist/core/fix-loop.js.map +1 -0
  223. package/dist/core/health-check.d.ts +21 -0
  224. package/dist/core/health-check.d.ts.map +1 -0
  225. package/dist/core/health-check.js +26 -0
  226. package/dist/core/health-check.js.map +1 -0
  227. package/dist/core/init.d.ts +2 -0
  228. package/dist/core/init.d.ts.map +1 -0
  229. package/dist/core/init.js +435 -0
  230. package/dist/core/init.js.map +1 -0
  231. package/dist/core/license-gen.d.ts +12 -0
  232. package/dist/core/license-gen.d.ts.map +1 -0
  233. package/dist/core/license-gen.js +169 -0
  234. package/dist/core/license-gen.js.map +1 -0
  235. package/dist/core/license.d.ts +33 -0
  236. package/dist/core/license.d.ts.map +1 -0
  237. package/dist/core/license.js +170 -0
  238. package/dist/core/license.js.map +1 -0
  239. package/dist/core/messages.d.ts +10 -0
  240. package/dist/core/messages.d.ts.map +1 -0
  241. package/dist/core/messages.js +47 -0
  242. package/dist/core/messages.js.map +1 -0
  243. package/dist/core/multi-browser.d.ts +36 -0
  244. package/dist/core/multi-browser.d.ts.map +1 -0
  245. package/dist/core/multi-browser.js +88 -0
  246. package/dist/core/multi-browser.js.map +1 -0
  247. package/dist/core/orchestrator.d.ts +48 -0
  248. package/dist/core/orchestrator.d.ts.map +1 -0
  249. package/dist/core/orchestrator.js +291 -0
  250. package/dist/core/orchestrator.js.map +1 -0
  251. package/dist/core/phase-gate.d.ts +4 -0
  252. package/dist/core/phase-gate.d.ts.map +1 -0
  253. package/dist/core/phase-gate.js +39 -0
  254. package/dist/core/phase-gate.js.map +1 -0
  255. package/dist/core/report-html.d.ts +9 -0
  256. package/dist/core/report-html.d.ts.map +1 -0
  257. package/dist/core/report-html.js +617 -0
  258. package/dist/core/report-html.js.map +1 -0
  259. package/dist/core/report-upload.d.ts +16 -0
  260. package/dist/core/report-upload.d.ts.map +1 -0
  261. package/dist/core/report-upload.js +124 -0
  262. package/dist/core/report-upload.js.map +1 -0
  263. package/dist/core/run-counter.d.ts +40 -0
  264. package/dist/core/run-counter.d.ts.map +1 -0
  265. package/dist/core/run-counter.js +120 -0
  266. package/dist/core/run-counter.js.map +1 -0
  267. package/dist/core/types.d.ts +53 -0
  268. package/dist/core/types.d.ts.map +1 -0
  269. package/dist/core/types.js +2 -0
  270. package/dist/core/types.js.map +1 -0
  271. package/dist/helpers/api-client.d.ts +30 -0
  272. package/dist/helpers/api-client.d.ts.map +1 -0
  273. package/dist/helpers/api-client.js +77 -0
  274. package/dist/helpers/api-client.js.map +1 -0
  275. package/dist/helpers/element-discovery.d.ts +18 -0
  276. package/dist/helpers/element-discovery.d.ts.map +1 -0
  277. package/dist/helpers/element-discovery.js +82 -0
  278. package/dist/helpers/element-discovery.js.map +1 -0
  279. package/dist/helpers/env-resolver.d.ts +29 -0
  280. package/dist/helpers/env-resolver.d.ts.map +1 -0
  281. package/dist/helpers/env-resolver.js +51 -0
  282. package/dist/helpers/env-resolver.js.map +1 -0
  283. package/dist/helpers/form-filler.d.ts +13 -0
  284. package/dist/helpers/form-filler.d.ts.map +1 -0
  285. package/dist/helpers/form-filler.js +98 -0
  286. package/dist/helpers/form-filler.js.map +1 -0
  287. package/dist/helpers/modal-handler.d.ts +16 -0
  288. package/dist/helpers/modal-handler.d.ts.map +1 -0
  289. package/dist/helpers/modal-handler.js +95 -0
  290. package/dist/helpers/modal-handler.js.map +1 -0
  291. package/dist/helpers/navigation.d.ts +37 -0
  292. package/dist/helpers/navigation.d.ts.map +1 -0
  293. package/dist/helpers/navigation.js +83 -0
  294. package/dist/helpers/navigation.js.map +1 -0
  295. package/dist/helpers/quality-gate.d.ts +17 -0
  296. package/dist/helpers/quality-gate.d.ts.map +1 -0
  297. package/dist/helpers/quality-gate.js +144 -0
  298. package/dist/helpers/quality-gate.js.map +1 -0
  299. package/dist/helpers/screenshot.d.ts +24 -0
  300. package/dist/helpers/screenshot.d.ts.map +1 -0
  301. package/dist/helpers/screenshot.js +76 -0
  302. package/dist/helpers/screenshot.js.map +1 -0
  303. package/dist/helpers/seed-validator.d.ts +15 -0
  304. package/dist/helpers/seed-validator.d.ts.map +1 -0
  305. package/dist/helpers/seed-validator.js +53 -0
  306. package/dist/helpers/seed-validator.js.map +1 -0
  307. package/helpers/__tests__/api-client.test.ts +199 -0
  308. package/helpers/__tests__/element-discovery.test.ts +202 -0
  309. package/helpers/__tests__/form-filler-extended.test.ts +212 -0
  310. package/helpers/__tests__/form-filler.test.ts +99 -0
  311. package/helpers/__tests__/modal-handler.test.ts +152 -0
  312. package/helpers/__tests__/navigation.test.ts +214 -0
  313. package/helpers/__tests__/quality-gate.test.ts +117 -0
  314. package/helpers/__tests__/screenshot.test.ts +139 -0
  315. package/helpers/__tests__/seed-validator.test.ts +114 -0
  316. package/helpers/api-client.ts +111 -0
  317. package/helpers/element-discovery.ts +105 -0
  318. package/helpers/env-resolver.ts +69 -0
  319. package/helpers/form-filler.ts +126 -0
  320. package/helpers/modal-handler.ts +108 -0
  321. package/helpers/navigation.ts +100 -0
  322. package/helpers/quality-gate.ts +180 -0
  323. package/helpers/screenshot.ts +111 -0
  324. package/helpers/seed-validator.ts +70 -0
  325. package/package.json +88 -0
@@ -0,0 +1,265 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import {
6
+ initRunDirectory,
7
+ writeAgentResult,
8
+ readAgentResult,
9
+ writeFinding,
10
+ archivePhaseResults,
11
+ } from '../evidence';
12
+ import type { AgentResult, Finding } from '../types';
13
+
14
+ let tempDir: string;
15
+
16
+ beforeEach(async () => {
17
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'tf-evidence-test-'));
18
+ });
19
+
20
+ afterEach(async () => {
21
+ await fs.rm(tempDir, { recursive: true, force: true });
22
+ });
23
+
24
+ function makeRunDir(runId: string): string {
25
+ return path.join(tempDir, '.run', runId);
26
+ }
27
+
28
+ function makeAgentResult(overrides: Partial<AgentResult> = {}): AgentResult {
29
+ return {
30
+ agentId: 1,
31
+ agentName: 'test-agent',
32
+ phase: 'alpha',
33
+ status: 'passed',
34
+ startedAt: new Date().toISOString(),
35
+ completedAt: new Date().toISOString(),
36
+ durationMs: 1000,
37
+ findings: [],
38
+ evidence: [],
39
+ summary: 'All good',
40
+ ...overrides,
41
+ };
42
+ }
43
+
44
+ function makeFinding(overrides: Partial<Finding> = {}): Finding {
45
+ return {
46
+ id: 'f-001',
47
+ type: 'test-bug',
48
+ severity: 'low',
49
+ agentId: 1,
50
+ module: 'home',
51
+ description: 'A test finding',
52
+ ...overrides,
53
+ };
54
+ }
55
+
56
+ describe('initRunDirectory', () => {
57
+ it('creates the run directory structure', async () => {
58
+ const runId = 'run-abc123';
59
+ const runDir = makeRunDir(runId);
60
+
61
+ await initRunDirectory(runDir);
62
+
63
+ const expectedDirs = [
64
+ 'agent-results',
65
+ 'findings/test-bugs',
66
+ 'findings/code-bugs',
67
+ 'evidence/screenshots',
68
+ 'evidence/logs',
69
+ 'evidence/reports',
70
+ ];
71
+
72
+ for (const dir of expectedDirs) {
73
+ const stat = await fs.stat(path.join(runDir, dir));
74
+ expect(stat.isDirectory()).toBe(true);
75
+ }
76
+ });
77
+
78
+ it('is idempotent — calling twice does not throw', async () => {
79
+ const runDir = makeRunDir('run-idem');
80
+ await initRunDirectory(runDir);
81
+ await expect(initRunDirectory(runDir)).resolves.toBeUndefined();
82
+ });
83
+ });
84
+
85
+ describe('writeAgentResult', () => {
86
+ it('writes JSON file to agent-results/{agentId}.json', async () => {
87
+ const runDir = makeRunDir('run-write');
88
+ await initRunDirectory(runDir);
89
+
90
+ const result = makeAgentResult({ agentId: 7, agentName: 'agent-seven' });
91
+ await writeAgentResult(runDir, 7, result);
92
+
93
+ const filePath = path.join(runDir, 'agent-results', '7.json');
94
+ const stat = await fs.stat(filePath);
95
+ expect(stat.isFile()).toBe(true);
96
+ });
97
+
98
+ it('writes valid JSON that round-trips cleanly', async () => {
99
+ const runDir = makeRunDir('run-json');
100
+ await initRunDirectory(runDir);
101
+
102
+ const result = makeAgentResult({ agentId: 3 });
103
+ await writeAgentResult(runDir, 3, result);
104
+
105
+ const filePath = path.join(runDir, 'agent-results', '3.json');
106
+ const raw = await fs.readFile(filePath, 'utf-8');
107
+ const parsed = JSON.parse(raw) as AgentResult;
108
+ expect(parsed.agentId).toBe(3);
109
+ expect(parsed.agentName).toBe('test-agent');
110
+ });
111
+
112
+ it('creates agent-results directory lazily if not yet initialised', async () => {
113
+ const runDir = makeRunDir('run-lazy');
114
+ // Intentionally skip initRunDirectory
115
+
116
+ const result = makeAgentResult({ agentId: 5 });
117
+ await writeAgentResult(runDir, 5, result);
118
+
119
+ const filePath = path.join(runDir, 'agent-results', '5.json');
120
+ const stat = await fs.stat(filePath);
121
+ expect(stat.isFile()).toBe(true);
122
+ });
123
+ });
124
+
125
+ describe('readAgentResult', () => {
126
+ it('reads back a previously written result', async () => {
127
+ const runDir = makeRunDir('run-read');
128
+ await initRunDirectory(runDir);
129
+
130
+ const result = makeAgentResult({ agentId: 10, summary: 'Done' });
131
+ await writeAgentResult(runDir, 10, result);
132
+
133
+ const read = await readAgentResult(runDir, 10);
134
+ expect(read).not.toBeNull();
135
+ expect(read!.agentId).toBe(10);
136
+ expect(read!.summary).toBe('Done');
137
+ });
138
+
139
+ it('returns null when the file does not exist', async () => {
140
+ const runDir = makeRunDir('run-missing');
141
+ await initRunDirectory(runDir);
142
+
143
+ const read = await readAgentResult(runDir, 999);
144
+ expect(read).toBeNull();
145
+ });
146
+ });
147
+
148
+ describe('writeFinding', () => {
149
+ it('writes test-bug findings to findings/test-bugs/', async () => {
150
+ const runDir = makeRunDir('run-finding-tb');
151
+ await initRunDirectory(runDir);
152
+
153
+ const finding = makeFinding({ id: 'f-tb-01', type: 'test-bug' });
154
+ await writeFinding(runDir, finding);
155
+
156
+ const filePath = path.join(runDir, 'findings', 'test-bugs', 'f-tb-01.json');
157
+ const stat = await fs.stat(filePath);
158
+ expect(stat.isFile()).toBe(true);
159
+ });
160
+
161
+ it('writes code-bug-* findings to findings/code-bugs/', async () => {
162
+ const runDir = makeRunDir('run-finding-cb');
163
+ await initRunDirectory(runDir);
164
+
165
+ for (const type of ['code-bug-cosmetic', 'code-bug-logic', 'code-bug-security'] as const) {
166
+ const finding = makeFinding({ id: `f-${type}`, type });
167
+ await writeFinding(runDir, finding);
168
+
169
+ const filePath = path.join(runDir, 'findings', 'code-bugs', `f-${type}.json`);
170
+ const stat = await fs.stat(filePath);
171
+ expect(stat.isFile()).toBe(true);
172
+ }
173
+ });
174
+
175
+ it('writes infra-issue findings to findings/code-bugs/', async () => {
176
+ const runDir = makeRunDir('run-finding-infra');
177
+ await initRunDirectory(runDir);
178
+
179
+ const finding = makeFinding({ id: 'f-infra-01', type: 'infra-issue' });
180
+ await writeFinding(runDir, finding);
181
+
182
+ const filePath = path.join(runDir, 'findings', 'code-bugs', 'f-infra-01.json');
183
+ const stat = await fs.stat(filePath);
184
+ expect(stat.isFile()).toBe(true);
185
+ });
186
+
187
+ it('persists finding content as valid JSON', async () => {
188
+ const runDir = makeRunDir('run-finding-json');
189
+ await initRunDirectory(runDir);
190
+
191
+ const finding = makeFinding({ id: 'f-json-01', description: 'Button misaligned' });
192
+ await writeFinding(runDir, finding);
193
+
194
+ const filePath = path.join(runDir, 'findings', 'test-bugs', 'f-json-01.json');
195
+ const raw = await fs.readFile(filePath, 'utf-8');
196
+ const parsed = JSON.parse(raw) as Finding;
197
+ expect(parsed.description).toBe('Button misaligned');
198
+ });
199
+
200
+ it('creates target directory lazily if missing', async () => {
201
+ const runDir = makeRunDir('run-finding-lazy');
202
+ // Intentionally skip initRunDirectory
203
+
204
+ const finding = makeFinding({ id: 'f-lazy-01', type: 'test-bug' });
205
+ await writeFinding(runDir, finding);
206
+
207
+ const filePath = path.join(runDir, 'findings', 'test-bugs', 'f-lazy-01.json');
208
+ const stat = await fs.stat(filePath);
209
+ expect(stat.isFile()).toBe(true);
210
+ });
211
+ });
212
+
213
+ describe('archivePhaseResults', () => {
214
+ it('moves agent-results content to archive/attempt-{N}/', async () => {
215
+ const runDir = makeRunDir('run-archive');
216
+ await initRunDirectory(runDir);
217
+
218
+ const result = makeAgentResult({ agentId: 2 });
219
+ await writeAgentResult(runDir, 2, result);
220
+
221
+ await archivePhaseResults(runDir, 'alpha', 1);
222
+
223
+ const archivePath = path.join(runDir, 'archive', 'alpha-attempt-1', '2.json');
224
+ const stat = await fs.stat(archivePath);
225
+ expect(stat.isFile()).toBe(true);
226
+ });
227
+
228
+ it('clears agent-results after archiving', async () => {
229
+ const runDir = makeRunDir('run-archive-clear');
230
+ await initRunDirectory(runDir);
231
+
232
+ await writeAgentResult(runDir, 4, makeAgentResult({ agentId: 4 }));
233
+ await archivePhaseResults(runDir, 'alpha', 1);
234
+
235
+ const remaining = await fs.readdir(path.join(runDir, 'agent-results'));
236
+ expect(remaining).toHaveLength(0);
237
+ });
238
+
239
+ it('supports multiple attempts with separate archive dirs', async () => {
240
+ const runDir = makeRunDir('run-archive-multi');
241
+ await initRunDirectory(runDir);
242
+
243
+ await writeAgentResult(runDir, 1, makeAgentResult({ agentId: 1 }));
244
+ await archivePhaseResults(runDir, 'alpha', 1);
245
+
246
+ await writeAgentResult(runDir, 1, makeAgentResult({ agentId: 1, status: 'failed' }));
247
+ await archivePhaseResults(runDir, 'alpha', 2);
248
+
249
+ const attempt1 = path.join(runDir, 'archive', 'alpha-attempt-1', '1.json');
250
+ const attempt2 = path.join(runDir, 'archive', 'alpha-attempt-2', '1.json');
251
+
252
+ const raw1 = await fs.readFile(attempt1, 'utf-8');
253
+ const raw2 = await fs.readFile(attempt2, 'utf-8');
254
+
255
+ expect((JSON.parse(raw1) as AgentResult).status).toBe('passed');
256
+ expect((JSON.parse(raw2) as AgentResult).status).toBe('failed');
257
+ });
258
+
259
+ it('is a no-op when agent-results is empty', async () => {
260
+ const runDir = makeRunDir('run-archive-empty');
261
+ await initRunDirectory(runDir);
262
+
263
+ await expect(archivePhaseResults(runDir, 'beta', 1)).resolves.toBeUndefined();
264
+ });
265
+ });
@@ -0,0 +1,210 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { classifyFinding, shouldAutoCommit, shouldRequireHumanReview } from '../fix-loop';
3
+ import type { Finding, Phase } from '../types';
4
+
5
+ function makeFinding(overrides: Partial<Finding> = {}): Finding {
6
+ return {
7
+ id: 'f-001',
8
+ type: 'code-bug-logic',
9
+ severity: 'medium',
10
+ agentId: 1,
11
+ module: 'home',
12
+ description: 'A test finding',
13
+ ...overrides,
14
+ };
15
+ }
16
+
17
+ describe('classifyFinding', () => {
18
+ describe('test-bug classification', () => {
19
+ it('returns test-bug when finding is in a test file and source is unchanged', () => {
20
+ const finding = makeFinding({ file: 'src/tests/auth.test.ts' });
21
+ const changedFiles: string[] = [];
22
+ expect(classifyFinding(finding, changedFiles)).toBe('test-bug');
23
+ });
24
+
25
+ it('returns test-bug when file path contains /tests/ and the test file itself is not in changedFiles', () => {
26
+ const finding = makeFinding({ file: 'packages/portal/tests/login.spec.ts' });
27
+ // changedFiles contains a source file, not the test file itself — test file is unchanged
28
+ const changedFiles = ['packages/portal/src/login.ts'];
29
+ expect(classifyFinding(finding, changedFiles)).toBe('test-bug');
30
+ });
31
+
32
+ it('does not return test-bug when the test file itself is in changedFiles', () => {
33
+ const finding = makeFinding({ file: 'packages/portal/tests/login.spec.ts' });
34
+ const changedFiles = ['packages/portal/tests/login.spec.ts'];
35
+ expect(classifyFinding(finding, changedFiles)).not.toBe('test-bug');
36
+ });
37
+
38
+ it('returns test-bug when file is in /tests/ and changedFiles is empty', () => {
39
+ const finding = makeFinding({ file: '/app/tests/utils.test.ts' });
40
+ expect(classifyFinding(finding, [])).toBe('test-bug');
41
+ });
42
+ });
43
+
44
+ describe('code-bug-security classification', () => {
45
+ it('returns code-bug-security when description contains "xss"', () => {
46
+ const finding = makeFinding({ description: 'Potential xss vulnerability found' });
47
+ expect(classifyFinding(finding, [])).toBe('code-bug-security');
48
+ });
49
+
50
+ it('returns code-bug-security when description contains "injection"', () => {
51
+ const finding = makeFinding({ description: 'SQL injection detected in query builder' });
52
+ expect(classifyFinding(finding, [])).toBe('code-bug-security');
53
+ });
54
+
55
+ it('returns code-bug-security when description contains "csrf"', () => {
56
+ const finding = makeFinding({ description: 'Missing csrf token on form submission' });
57
+ expect(classifyFinding(finding, [])).toBe('code-bug-security');
58
+ });
59
+
60
+ it('returns code-bug-security when description contains "auth-bypass"', () => {
61
+ const finding = makeFinding({ description: 'auth-bypass possible via malformed JWT' });
62
+ expect(classifyFinding(finding, [])).toBe('code-bug-security');
63
+ });
64
+
65
+ it('returns code-bug-security when description contains "exposure"', () => {
66
+ const finding = makeFinding({ description: 'Sensitive data exposure in API response' });
67
+ expect(classifyFinding(finding, [])).toBe('code-bug-security');
68
+ });
69
+
70
+ it('returns code-bug-security for security keyword in non-test file', () => {
71
+ const finding = makeFinding({ file: 'src/auth/login.ts', description: 'xss risk in render' });
72
+ expect(classifyFinding(finding, [])).toBe('code-bug-security');
73
+ });
74
+ });
75
+
76
+ describe('code-bug-cosmetic classification', () => {
77
+ it('returns code-bug-cosmetic when description contains "css"', () => {
78
+ const finding = makeFinding({ description: 'Incorrect css class applied to button' });
79
+ expect(classifyFinding(finding, [])).toBe('code-bug-cosmetic');
80
+ });
81
+
82
+ it('returns code-bug-cosmetic when description contains "aria"', () => {
83
+ const finding = makeFinding({ description: 'Missing aria label on icon' });
84
+ expect(classifyFinding(finding, [])).toBe('code-bug-cosmetic');
85
+ });
86
+
87
+ it('returns code-bug-cosmetic when description contains "i18n"', () => {
88
+ const finding = makeFinding({ description: 'i18n string missing for German locale' });
89
+ expect(classifyFinding(finding, [])).toBe('code-bug-cosmetic');
90
+ });
91
+
92
+ it('returns code-bug-cosmetic when description contains "link"', () => {
93
+ const finding = makeFinding({ description: 'Broken link in footer navigation' });
94
+ expect(classifyFinding(finding, [])).toBe('code-bug-cosmetic');
95
+ });
96
+
97
+ it('returns code-bug-cosmetic when description contains "alt-text"', () => {
98
+ const finding = makeFinding({ description: 'Image missing alt-text attribute' });
99
+ expect(classifyFinding(finding, [])).toBe('code-bug-cosmetic');
100
+ });
101
+ });
102
+
103
+ describe('code-bug-logic classification (fallback)', () => {
104
+ it('returns code-bug-logic when no other classification matches', () => {
105
+ const finding = makeFinding({ description: 'Incorrect calculation in tax module' });
106
+ expect(classifyFinding(finding, [])).toBe('code-bug-logic');
107
+ });
108
+
109
+ it('returns code-bug-logic when file is undefined and no keywords match', () => {
110
+ const finding = makeFinding({ file: undefined, description: 'Off-by-one error in pagination' });
111
+ expect(classifyFinding(finding, [])).toBe('code-bug-logic');
112
+ });
113
+ });
114
+ });
115
+
116
+ describe('shouldAutoCommit', () => {
117
+ describe('code-bug-cosmetic', () => {
118
+ it('returns true for code-bug-cosmetic in alpha phase', () => {
119
+ expect(shouldAutoCommit('code-bug-cosmetic', 'alpha')).toBe(true);
120
+ });
121
+
122
+ it('returns true for code-bug-cosmetic in beta phase', () => {
123
+ expect(shouldAutoCommit('code-bug-cosmetic', 'beta')).toBe(true);
124
+ });
125
+
126
+ it('returns false for code-bug-cosmetic in uat phase', () => {
127
+ expect(shouldAutoCommit('code-bug-cosmetic', 'uat')).toBe(false);
128
+ });
129
+
130
+ it('returns false for code-bug-cosmetic in release phase', () => {
131
+ expect(shouldAutoCommit('code-bug-cosmetic', 'release')).toBe(false);
132
+ });
133
+ });
134
+
135
+ describe('code-bug-logic', () => {
136
+ const phases: Phase[] = ['alpha', 'beta', 'uat', 'release'];
137
+
138
+ it.each(phases)('returns false for code-bug-logic in %s phase', (phase) => {
139
+ expect(shouldAutoCommit('code-bug-logic', phase)).toBe(false);
140
+ });
141
+ });
142
+
143
+ describe('code-bug-security', () => {
144
+ const phases: Phase[] = ['alpha', 'beta', 'uat', 'release'];
145
+
146
+ it.each(phases)('returns false for code-bug-security in %s phase', (phase) => {
147
+ expect(shouldAutoCommit('code-bug-security', phase)).toBe(false);
148
+ });
149
+ });
150
+
151
+ describe('test-bug', () => {
152
+ const phases: Phase[] = ['alpha', 'beta', 'uat', 'release'];
153
+
154
+ it.each(phases)('returns true for test-bug in %s phase', (phase) => {
155
+ expect(shouldAutoCommit('test-bug', phase)).toBe(true);
156
+ });
157
+ });
158
+ });
159
+
160
+ describe('shouldRequireHumanReview', () => {
161
+ describe('code-bug-security', () => {
162
+ const phases: Phase[] = ['alpha', 'beta', 'uat', 'release'];
163
+
164
+ it.each(phases)('returns true for code-bug-security in %s phase', (phase) => {
165
+ expect(shouldRequireHumanReview('code-bug-security', phase)).toBe(true);
166
+ });
167
+ });
168
+
169
+ describe('code-bug-logic', () => {
170
+ const phases: Phase[] = ['alpha', 'beta', 'uat', 'release'];
171
+
172
+ it.each(phases)('returns true for code-bug-logic in %s phase', (phase) => {
173
+ expect(shouldRequireHumanReview('code-bug-logic', phase)).toBe(true);
174
+ });
175
+ });
176
+
177
+ describe('uat phase override', () => {
178
+ it('returns true for code-bug-cosmetic in uat phase', () => {
179
+ expect(shouldRequireHumanReview('code-bug-cosmetic', 'uat')).toBe(true);
180
+ });
181
+
182
+ it('returns true for test-bug in uat phase', () => {
183
+ expect(shouldRequireHumanReview('test-bug', 'uat')).toBe(true);
184
+ });
185
+
186
+ it('returns true for infra-issue in uat phase', () => {
187
+ expect(shouldRequireHumanReview('infra-issue', 'uat')).toBe(true);
188
+ });
189
+ });
190
+
191
+ describe('code-bug-cosmetic in non-uat phases', () => {
192
+ it('returns false for code-bug-cosmetic in alpha phase', () => {
193
+ expect(shouldRequireHumanReview('code-bug-cosmetic', 'alpha')).toBe(false);
194
+ });
195
+
196
+ it('returns false for code-bug-cosmetic in beta phase', () => {
197
+ expect(shouldRequireHumanReview('code-bug-cosmetic', 'beta')).toBe(false);
198
+ });
199
+ });
200
+
201
+ describe('test-bug in non-uat phases', () => {
202
+ it('returns false for test-bug in alpha phase', () => {
203
+ expect(shouldRequireHumanReview('test-bug', 'alpha')).toBe(false);
204
+ });
205
+
206
+ it('returns false for test-bug in beta phase', () => {
207
+ expect(shouldRequireHumanReview('test-bug', 'beta')).toBe(false);
208
+ });
209
+ });
210
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { runHealthChecks } from '../health-check';
3
+
4
+ describe('Health Checks', () => {
5
+ it('returns pass when all required checks succeed', async () => {
6
+ const results = await runHealthChecks({
7
+ checks: [
8
+ { name: 'node', check: async () => true, required: true },
9
+ ],
10
+ });
11
+ expect(results.canProceed).toBe(true);
12
+ expect(results.results[0].passed).toBe(true);
13
+ });
14
+
15
+ it('returns fail when a required check fails', async () => {
16
+ const results = await runHealthChecks({
17
+ checks: [
18
+ { name: 'db', check: async () => false, required: true },
19
+ ],
20
+ });
21
+ expect(results.canProceed).toBe(false);
22
+ });
23
+
24
+ it('returns pass with warning when optional check fails', async () => {
25
+ const results = await runHealthChecks({
26
+ checks: [
27
+ { name: 'node', check: async () => true, required: true },
28
+ { name: 'docker', check: async () => false, required: false },
29
+ ],
30
+ });
31
+ expect(results.canProceed).toBe(true);
32
+ expect(results.warnings).toHaveLength(1);
33
+ });
34
+
35
+ it('handles check that throws as failure', async () => {
36
+ const results = await runHealthChecks({
37
+ checks: [
38
+ { name: 'crash', check: async () => { throw new Error('boom'); }, required: true },
39
+ ],
40
+ });
41
+ expect(results.canProceed).toBe(false);
42
+ expect(results.results[0].error).toBe('boom');
43
+ });
44
+ });