@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.
- package/CHANGELOG.md +54 -0
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/agents/01-analyst.ts +100 -0
- package/agents/02-seed-architect.ts +59 -0
- package/agents/03-test-generator.ts +191 -0
- package/agents/04-unit-runner.ts +160 -0
- package/agents/05-browser-crawler.ts +790 -0
- package/agents/06-api-exerciser.ts +311 -0
- package/agents/07-security-scout.ts +188 -0
- package/agents/08-a11y-guardian.ts +212 -0
- package/agents/09-healer.ts +228 -0
- package/agents/10-reporter.ts +266 -0
- package/agents/11-fixer.ts +253 -0
- package/agents/12-ux-inspector.ts +444 -0
- package/agents/13-performance-profiler.ts +271 -0
- package/agents/14-data-integrity-auditor.ts +417 -0
- package/agents/15-regression-sentinel.ts +307 -0
- package/agents/16-chaos-agent.ts +228 -0
- package/agents/17-documentation-validator.ts +266 -0
- package/agents/18-integration-watchdog.ts +178 -0
- package/agents/19-tenant-isolation-auditor.ts +199 -0
- package/agents/20-workflow-completion-tester.ts +203 -0
- package/agents/21-state-session-tester.ts +262 -0
- package/agents/22-email-notification-verifier.ts +244 -0
- package/agents/23-migration-tester.ts +80 -0
- package/agents/__tests__/01-analyst.test.ts +188 -0
- package/agents/__tests__/02-seed-architect.test.ts +152 -0
- package/agents/__tests__/03-test-generator-full.test.ts +321 -0
- package/agents/__tests__/03-test-generator.test.ts +318 -0
- package/agents/__tests__/04-unit-runner.test.ts +320 -0
- package/agents/__tests__/05-browser-crawler-beta.test.ts +492 -0
- package/agents/__tests__/05-browser-crawler-release.test.ts +412 -0
- package/agents/__tests__/05-browser-crawler-uat.test.ts +578 -0
- package/agents/__tests__/05-browser-crawler.test.ts +518 -0
- package/agents/__tests__/06-api-exerciser.test.ts +619 -0
- package/agents/__tests__/07-security-scout.test.ts +382 -0
- package/agents/__tests__/08-a11y-guardian.test.ts +530 -0
- package/agents/__tests__/09-healer.test.ts +384 -0
- package/agents/__tests__/10-reporter.test.ts +366 -0
- package/agents/__tests__/11-fixer.test.ts +406 -0
- package/agents/__tests__/12-ux-inspector-extended.test.ts +465 -0
- package/agents/__tests__/12-ux-inspector.test.ts +443 -0
- package/agents/__tests__/13-performance-profiler.test.ts +411 -0
- package/agents/__tests__/14-data-integrity-auditor-extended.test.ts +573 -0
- package/agents/__tests__/14-data-integrity-auditor.test.ts +407 -0
- package/agents/__tests__/15-regression-sentinel.test.ts +657 -0
- package/agents/__tests__/16-chaos-agent.test.ts +427 -0
- package/agents/__tests__/17-documentation-validator.test.ts +402 -0
- package/agents/__tests__/18-integration-watchdog.test.ts +263 -0
- package/agents/__tests__/19-tenant-isolation-auditor.test.ts +400 -0
- package/agents/__tests__/20-workflow-completion-tester.test.ts +586 -0
- package/agents/__tests__/21-state-session-tester.test.ts +374 -0
- package/agents/__tests__/22-email-notification-verifier.test.ts +441 -0
- package/agents/__tests__/23-migration-tester.test.ts +145 -0
- package/agents/__tests__/base-agent.test.ts +188 -0
- package/agents/__tests__/registry.test.ts +218 -0
- package/agents/base-agent.ts +77 -0
- package/agents/registry.ts +136 -0
- package/baselines/api-schemas/.gitkeep +0 -0
- package/baselines/performance/.gitkeep +0 -0
- package/baselines/screenshots/.gitkeep +0 -0
- package/bin/testteam.js +10 -0
- package/core/__tests__/ci-output.test.ts +430 -0
- package/core/__tests__/cli.test.ts +387 -0
- package/core/__tests__/config.test.ts +78 -0
- package/core/__tests__/cost-tracker.test.ts +158 -0
- package/core/__tests__/evidence.test.ts +265 -0
- package/core/__tests__/fix-loop.test.ts +210 -0
- package/core/__tests__/health-check.test.ts +44 -0
- package/core/__tests__/init.test.ts +609 -0
- package/core/__tests__/integration.test.ts +204 -0
- package/core/__tests__/license-gen.test.ts +227 -0
- package/core/__tests__/license.test.ts +326 -0
- package/core/__tests__/multi-browser.test.ts +278 -0
- package/core/__tests__/orchestrator.test.ts +519 -0
- package/core/__tests__/phase-gate.test.ts +43 -0
- package/core/__tests__/report-html.test.ts +398 -0
- package/core/__tests__/report-upload.test.ts +325 -0
- package/core/__tests__/run-counter.test.ts +234 -0
- package/core/ci-output.ts +240 -0
- package/core/cli.ts +232 -0
- package/core/config.ts +178 -0
- package/core/cost-tracker.ts +59 -0
- package/core/evidence.ts +132 -0
- package/core/fix-loop.ts +85 -0
- package/core/health-check.ts +54 -0
- package/core/init.ts +546 -0
- package/core/license-gen.ts +212 -0
- package/core/license.ts +211 -0
- package/core/messages.ts +67 -0
- package/core/multi-browser.ts +136 -0
- package/core/orchestrator.ts +354 -0
- package/core/phase-gate.ts +55 -0
- package/core/report-html.ts +657 -0
- package/core/report-upload.ts +188 -0
- package/core/run-counter.ts +175 -0
- package/core/types.ts +57 -0
- package/dist/agents/01-analyst.d.ts +11 -0
- package/dist/agents/01-analyst.d.ts.map +1 -0
- package/dist/agents/01-analyst.js +75 -0
- package/dist/agents/01-analyst.js.map +1 -0
- package/dist/agents/02-seed-architect.d.ts +11 -0
- package/dist/agents/02-seed-architect.d.ts.map +1 -0
- package/dist/agents/02-seed-architect.js +51 -0
- package/dist/agents/02-seed-architect.js.map +1 -0
- package/dist/agents/03-test-generator.d.ts +9 -0
- package/dist/agents/03-test-generator.d.ts.map +1 -0
- package/dist/agents/03-test-generator.js +167 -0
- package/dist/agents/03-test-generator.js.map +1 -0
- package/dist/agents/04-unit-runner.d.ts +9 -0
- package/dist/agents/04-unit-runner.d.ts.map +1 -0
- package/dist/agents/04-unit-runner.js +113 -0
- package/dist/agents/04-unit-runner.js.map +1 -0
- package/dist/agents/05-browser-crawler.d.ts +30 -0
- package/dist/agents/05-browser-crawler.d.ts.map +1 -0
- package/dist/agents/05-browser-crawler.js +685 -0
- package/dist/agents/05-browser-crawler.js.map +1 -0
- package/dist/agents/06-api-exerciser.d.ts +23 -0
- package/dist/agents/06-api-exerciser.d.ts.map +1 -0
- package/dist/agents/06-api-exerciser.js +253 -0
- package/dist/agents/06-api-exerciser.js.map +1 -0
- package/dist/agents/07-security-scout.d.ts +11 -0
- package/dist/agents/07-security-scout.d.ts.map +1 -0
- package/dist/agents/07-security-scout.js +142 -0
- package/dist/agents/07-security-scout.js.map +1 -0
- package/dist/agents/08-a11y-guardian.d.ts +13 -0
- package/dist/agents/08-a11y-guardian.d.ts.map +1 -0
- package/dist/agents/08-a11y-guardian.js +176 -0
- package/dist/agents/08-a11y-guardian.js.map +1 -0
- package/dist/agents/09-healer.d.ts +33 -0
- package/dist/agents/09-healer.d.ts.map +1 -0
- package/dist/agents/09-healer.js +167 -0
- package/dist/agents/09-healer.js.map +1 -0
- package/dist/agents/10-reporter.d.ts +26 -0
- package/dist/agents/10-reporter.d.ts.map +1 -0
- package/dist/agents/10-reporter.js +215 -0
- package/dist/agents/10-reporter.js.map +1 -0
- package/dist/agents/11-fixer.d.ts +26 -0
- package/dist/agents/11-fixer.d.ts.map +1 -0
- package/dist/agents/11-fixer.js +195 -0
- package/dist/agents/11-fixer.js.map +1 -0
- package/dist/agents/12-ux-inspector.d.ts +15 -0
- package/dist/agents/12-ux-inspector.d.ts.map +1 -0
- package/dist/agents/12-ux-inspector.js +364 -0
- package/dist/agents/12-ux-inspector.js.map +1 -0
- package/dist/agents/13-performance-profiler.d.ts +13 -0
- package/dist/agents/13-performance-profiler.d.ts.map +1 -0
- package/dist/agents/13-performance-profiler.js +216 -0
- package/dist/agents/13-performance-profiler.js.map +1 -0
- package/dist/agents/14-data-integrity-auditor.d.ts +12 -0
- package/dist/agents/14-data-integrity-auditor.d.ts.map +1 -0
- package/dist/agents/14-data-integrity-auditor.js +356 -0
- package/dist/agents/14-data-integrity-auditor.js.map +1 -0
- package/dist/agents/15-regression-sentinel.d.ts +25 -0
- package/dist/agents/15-regression-sentinel.d.ts.map +1 -0
- package/dist/agents/15-regression-sentinel.js +251 -0
- package/dist/agents/15-regression-sentinel.js.map +1 -0
- package/dist/agents/16-chaos-agent.d.ts +9 -0
- package/dist/agents/16-chaos-agent.d.ts.map +1 -0
- package/dist/agents/16-chaos-agent.js +207 -0
- package/dist/agents/16-chaos-agent.js.map +1 -0
- package/dist/agents/17-documentation-validator.d.ts +31 -0
- package/dist/agents/17-documentation-validator.d.ts.map +1 -0
- package/dist/agents/17-documentation-validator.js +246 -0
- package/dist/agents/17-documentation-validator.js.map +1 -0
- package/dist/agents/18-integration-watchdog.d.ts +10 -0
- package/dist/agents/18-integration-watchdog.d.ts.map +1 -0
- package/dist/agents/18-integration-watchdog.js +138 -0
- package/dist/agents/18-integration-watchdog.js.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts +9 -0
- package/dist/agents/19-tenant-isolation-auditor.d.ts.map +1 -0
- package/dist/agents/19-tenant-isolation-auditor.js +166 -0
- package/dist/agents/19-tenant-isolation-auditor.js.map +1 -0
- package/dist/agents/20-workflow-completion-tester.d.ts +12 -0
- package/dist/agents/20-workflow-completion-tester.d.ts.map +1 -0
- package/dist/agents/20-workflow-completion-tester.js +159 -0
- package/dist/agents/20-workflow-completion-tester.js.map +1 -0
- package/dist/agents/21-state-session-tester.d.ts +10 -0
- package/dist/agents/21-state-session-tester.d.ts.map +1 -0
- package/dist/agents/21-state-session-tester.js +233 -0
- package/dist/agents/21-state-session-tester.js.map +1 -0
- package/dist/agents/22-email-notification-verifier.d.ts +11 -0
- package/dist/agents/22-email-notification-verifier.d.ts.map +1 -0
- package/dist/agents/22-email-notification-verifier.js +199 -0
- package/dist/agents/22-email-notification-verifier.js.map +1 -0
- package/dist/agents/23-migration-tester.d.ts +10 -0
- package/dist/agents/23-migration-tester.d.ts.map +1 -0
- package/dist/agents/23-migration-tester.js +74 -0
- package/dist/agents/23-migration-tester.js.map +1 -0
- package/dist/agents/base-agent.d.ts +19 -0
- package/dist/agents/base-agent.d.ts.map +1 -0
- package/dist/agents/base-agent.js +67 -0
- package/dist/agents/base-agent.js.map +1 -0
- package/dist/agents/registry.d.ts +29 -0
- package/dist/agents/registry.d.ts.map +1 -0
- package/dist/agents/registry.js +117 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/core/ci-output.d.ts +35 -0
- package/dist/core/ci-output.d.ts.map +1 -0
- package/dist/core/ci-output.js +193 -0
- package/dist/core/ci-output.js.map +1 -0
- package/dist/core/cli.d.ts +11 -0
- package/dist/core/cli.d.ts.map +1 -0
- package/dist/core/cli.js +197 -0
- package/dist/core/cli.js.map +1 -0
- package/dist/core/config.d.ts +111 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +42 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/cost-tracker.d.ts +22 -0
- package/dist/core/cost-tracker.d.ts.map +1 -0
- package/dist/core/cost-tracker.js +41 -0
- package/dist/core/cost-tracker.js.map +1 -0
- package/dist/core/evidence.d.ts +28 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +95 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fix-loop.d.ts +29 -0
- package/dist/core/fix-loop.d.ts.map +1 -0
- package/dist/core/fix-loop.js +70 -0
- package/dist/core/fix-loop.js.map +1 -0
- package/dist/core/health-check.d.ts +21 -0
- package/dist/core/health-check.d.ts.map +1 -0
- package/dist/core/health-check.js +26 -0
- package/dist/core/health-check.js.map +1 -0
- package/dist/core/init.d.ts +2 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +435 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/license-gen.d.ts +12 -0
- package/dist/core/license-gen.d.ts.map +1 -0
- package/dist/core/license-gen.js +169 -0
- package/dist/core/license-gen.js.map +1 -0
- package/dist/core/license.d.ts +33 -0
- package/dist/core/license.d.ts.map +1 -0
- package/dist/core/license.js +170 -0
- package/dist/core/license.js.map +1 -0
- package/dist/core/messages.d.ts +10 -0
- package/dist/core/messages.d.ts.map +1 -0
- package/dist/core/messages.js +47 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/multi-browser.d.ts +36 -0
- package/dist/core/multi-browser.d.ts.map +1 -0
- package/dist/core/multi-browser.js +88 -0
- package/dist/core/multi-browser.js.map +1 -0
- package/dist/core/orchestrator.d.ts +48 -0
- package/dist/core/orchestrator.d.ts.map +1 -0
- package/dist/core/orchestrator.js +291 -0
- package/dist/core/orchestrator.js.map +1 -0
- package/dist/core/phase-gate.d.ts +4 -0
- package/dist/core/phase-gate.d.ts.map +1 -0
- package/dist/core/phase-gate.js +39 -0
- package/dist/core/phase-gate.js.map +1 -0
- package/dist/core/report-html.d.ts +9 -0
- package/dist/core/report-html.d.ts.map +1 -0
- package/dist/core/report-html.js +617 -0
- package/dist/core/report-html.js.map +1 -0
- package/dist/core/report-upload.d.ts +16 -0
- package/dist/core/report-upload.d.ts.map +1 -0
- package/dist/core/report-upload.js +124 -0
- package/dist/core/report-upload.js.map +1 -0
- package/dist/core/run-counter.d.ts +40 -0
- package/dist/core/run-counter.d.ts.map +1 -0
- package/dist/core/run-counter.js +120 -0
- package/dist/core/run-counter.js.map +1 -0
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/helpers/api-client.d.ts +30 -0
- package/dist/helpers/api-client.d.ts.map +1 -0
- package/dist/helpers/api-client.js +77 -0
- package/dist/helpers/api-client.js.map +1 -0
- package/dist/helpers/element-discovery.d.ts +18 -0
- package/dist/helpers/element-discovery.d.ts.map +1 -0
- package/dist/helpers/element-discovery.js +82 -0
- package/dist/helpers/element-discovery.js.map +1 -0
- package/dist/helpers/env-resolver.d.ts +29 -0
- package/dist/helpers/env-resolver.d.ts.map +1 -0
- package/dist/helpers/env-resolver.js +51 -0
- package/dist/helpers/env-resolver.js.map +1 -0
- package/dist/helpers/form-filler.d.ts +13 -0
- package/dist/helpers/form-filler.d.ts.map +1 -0
- package/dist/helpers/form-filler.js +98 -0
- package/dist/helpers/form-filler.js.map +1 -0
- package/dist/helpers/modal-handler.d.ts +16 -0
- package/dist/helpers/modal-handler.d.ts.map +1 -0
- package/dist/helpers/modal-handler.js +95 -0
- package/dist/helpers/modal-handler.js.map +1 -0
- package/dist/helpers/navigation.d.ts +37 -0
- package/dist/helpers/navigation.d.ts.map +1 -0
- package/dist/helpers/navigation.js +83 -0
- package/dist/helpers/navigation.js.map +1 -0
- package/dist/helpers/quality-gate.d.ts +17 -0
- package/dist/helpers/quality-gate.d.ts.map +1 -0
- package/dist/helpers/quality-gate.js +144 -0
- package/dist/helpers/quality-gate.js.map +1 -0
- package/dist/helpers/screenshot.d.ts +24 -0
- package/dist/helpers/screenshot.d.ts.map +1 -0
- package/dist/helpers/screenshot.js +76 -0
- package/dist/helpers/screenshot.js.map +1 -0
- package/dist/helpers/seed-validator.d.ts +15 -0
- package/dist/helpers/seed-validator.d.ts.map +1 -0
- package/dist/helpers/seed-validator.js +53 -0
- package/dist/helpers/seed-validator.js.map +1 -0
- package/helpers/__tests__/api-client.test.ts +199 -0
- package/helpers/__tests__/element-discovery.test.ts +202 -0
- package/helpers/__tests__/form-filler-extended.test.ts +212 -0
- package/helpers/__tests__/form-filler.test.ts +99 -0
- package/helpers/__tests__/modal-handler.test.ts +152 -0
- package/helpers/__tests__/navigation.test.ts +214 -0
- package/helpers/__tests__/quality-gate.test.ts +117 -0
- package/helpers/__tests__/screenshot.test.ts +139 -0
- package/helpers/__tests__/seed-validator.test.ts +114 -0
- package/helpers/api-client.ts +111 -0
- package/helpers/element-discovery.ts +105 -0
- package/helpers/env-resolver.ts +69 -0
- package/helpers/form-filler.ts +126 -0
- package/helpers/modal-handler.ts +108 -0
- package/helpers/navigation.ts +100 -0
- package/helpers/quality-gate.ts +180 -0
- package/helpers/screenshot.ts +111 -0
- package/helpers/seed-validator.ts +70 -0
- package/package.json +88 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { BaseAgent } from './base-agent';
|
|
4
|
+
import { validateCodeQuality } from '../helpers/quality-gate';
|
|
5
|
+
import type { Finding } from '../core/types';
|
|
6
|
+
|
|
7
|
+
/** Maximum number of fix attempts per finding before escalation. */
|
|
8
|
+
const MAX_ATTEMPTS = 3;
|
|
9
|
+
|
|
10
|
+
/** Describes the root-cause category of a test-bug finding. */
|
|
11
|
+
type FixStrategy = 'selector' | 'timing' | 'data' | 'unknown';
|
|
12
|
+
|
|
13
|
+
/** Per-finding attempt tracking (kept in-memory for a single run). */
|
|
14
|
+
interface AttemptRecord {
|
|
15
|
+
findingId: string;
|
|
16
|
+
attempts: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Classifies a test-bug finding into one of three fix strategies based on
|
|
21
|
+
* keywords in the description.
|
|
22
|
+
*
|
|
23
|
+
* - selector: element-locator problems (element not found, stale selector, …)
|
|
24
|
+
* - timing: race-condition / timeout problems
|
|
25
|
+
* - data: seed / fixture / data mismatch problems
|
|
26
|
+
* - unknown: fallback when no keyword matches
|
|
27
|
+
*/
|
|
28
|
+
export function classifyFixStrategy(finding: Finding): FixStrategy {
|
|
29
|
+
const lower = finding.description.toLowerCase();
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
lower.includes('selector') ||
|
|
33
|
+
lower.includes('element not found') ||
|
|
34
|
+
lower.includes('locator') ||
|
|
35
|
+
lower.includes('stale') ||
|
|
36
|
+
lower.includes('no such element')
|
|
37
|
+
) {
|
|
38
|
+
return 'selector';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
lower.includes('timeout') ||
|
|
43
|
+
lower.includes('timing') ||
|
|
44
|
+
lower.includes('race') ||
|
|
45
|
+
lower.includes('wait') ||
|
|
46
|
+
lower.includes('timed out')
|
|
47
|
+
) {
|
|
48
|
+
return 'timing';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
lower.includes('seed') ||
|
|
53
|
+
lower.includes('fixture') ||
|
|
54
|
+
lower.includes('data') ||
|
|
55
|
+
lower.includes('expected value') ||
|
|
56
|
+
lower.includes('mismatch')
|
|
57
|
+
) {
|
|
58
|
+
return 'data';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return 'unknown';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generates a suggested fix string given a FixStrategy and the original
|
|
66
|
+
* finding. The generated suggestion intentionally uses plain English so it
|
|
67
|
+
* can be reviewed by a developer or fed to a downstream AI patch agent.
|
|
68
|
+
*/
|
|
69
|
+
export function generateFixSuggestion(finding: Finding, strategy: FixStrategy): string {
|
|
70
|
+
switch (strategy) {
|
|
71
|
+
case 'selector':
|
|
72
|
+
return (
|
|
73
|
+
`[Healer] Selector fix for "${finding.module}": ` +
|
|
74
|
+
`Update the element selector/locator — the target element may have changed its ` +
|
|
75
|
+
`data-testid, class, or role attribute. ` +
|
|
76
|
+
`Suggested action: inspect the current DOM and replace the stale selector with ` +
|
|
77
|
+
`a stable data-testid attribute (e.g. data-testid="<component>-<action>").`
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
case 'timing':
|
|
81
|
+
return (
|
|
82
|
+
`[Healer] Timing fix for "${finding.module}": ` +
|
|
83
|
+
`Add an explicit wait before the failing assertion. ` +
|
|
84
|
+
`Suggested action: replace hard sleeps with \`waitFor\` / \`waitForSelector\` ` +
|
|
85
|
+
`or increase the relevant timeout in the test configuration.`
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
case 'data':
|
|
89
|
+
return (
|
|
90
|
+
`[Healer] Data fix for "${finding.module}": ` +
|
|
91
|
+
`Update the test seed or fixture so the expected values match the current schema. ` +
|
|
92
|
+
`Suggested action: re-run \`pnpm seed:test\` to refresh test data, or update ` +
|
|
93
|
+
`the fixture file to reflect the current data model.`
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
default:
|
|
97
|
+
return (
|
|
98
|
+
`[Healer] Unknown strategy for "${finding.module}": ` +
|
|
99
|
+
`Manual investigation required — no known fix pattern matched the description.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class HealerAgent extends BaseAgent {
|
|
105
|
+
readonly agentId = 9;
|
|
106
|
+
readonly agentName = 'Healer';
|
|
107
|
+
|
|
108
|
+
/** Attempt tracking for the current run (finding id → attempt count). */
|
|
109
|
+
private readonly attemptMap = new Map<string, AttemptRecord>();
|
|
110
|
+
|
|
111
|
+
/** Set to true when there are no findings to process — makes execute() a no-op. */
|
|
112
|
+
private noFindings = false;
|
|
113
|
+
|
|
114
|
+
protected async preFlight(): Promise<void> {
|
|
115
|
+
const testBugsDir = path.join(this.runDir, 'findings', 'test-bugs');
|
|
116
|
+
let entries: string[];
|
|
117
|
+
try {
|
|
118
|
+
entries = await fs.readdir(testBugsDir);
|
|
119
|
+
} catch (err: unknown) {
|
|
120
|
+
if (isNodeError(err) && err.code === 'ENOENT') {
|
|
121
|
+
this.noFindings = true;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const jsonFiles = entries.filter((e) => e.endsWith('.json'));
|
|
128
|
+
if (jsonFiles.length === 0) {
|
|
129
|
+
this.noFindings = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected async execute(): Promise<Finding[]> {
|
|
134
|
+
if (this.noFindings) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const testBugsDir = path.join(this.runDir, 'findings', 'test-bugs');
|
|
139
|
+
|
|
140
|
+
let entries: string[];
|
|
141
|
+
try {
|
|
142
|
+
entries = await fs.readdir(testBugsDir);
|
|
143
|
+
} catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const jsonFiles = entries.filter((e) => e.endsWith('.json'));
|
|
148
|
+
const unresolvedFindings: Finding[] = [];
|
|
149
|
+
|
|
150
|
+
for (const filename of jsonFiles) {
|
|
151
|
+
const filePath = path.join(testBugsDir, filename);
|
|
152
|
+
let finding: Finding;
|
|
153
|
+
try {
|
|
154
|
+
const raw = await fs.readFile(filePath, 'utf-8');
|
|
155
|
+
finding = JSON.parse(raw) as Finding;
|
|
156
|
+
} catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const record = this.getOrCreateAttemptRecord(finding.id);
|
|
161
|
+
|
|
162
|
+
if (record.attempts >= MAX_ATTEMPTS) {
|
|
163
|
+
unresolvedFindings.push({
|
|
164
|
+
...finding,
|
|
165
|
+
suggestedFix:
|
|
166
|
+
`[Healer] Escalated after ${MAX_ATTEMPTS} attempts — requires manual investigation.`,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
record.attempts += 1;
|
|
172
|
+
|
|
173
|
+
const strategy = classifyFixStrategy(finding);
|
|
174
|
+
const suggestion = generateFixSuggestion(finding, strategy);
|
|
175
|
+
|
|
176
|
+
// Run quality gate on any embedded test code in suggestedFix (if present)
|
|
177
|
+
const qualityViolations = validateCodeQuality(suggestion, `healer-fix-${finding.id}.ts`);
|
|
178
|
+
const hasErrors = qualityViolations.some((v) => v.severity === 'error');
|
|
179
|
+
|
|
180
|
+
if (hasErrors) {
|
|
181
|
+
// The fix suggestion itself has quality errors — escalate
|
|
182
|
+
unresolvedFindings.push({
|
|
183
|
+
...finding,
|
|
184
|
+
suggestedFix:
|
|
185
|
+
`[Healer] Generated fix failed quality gate (attempt ${record.attempts}/${MAX_ATTEMPTS}): ` +
|
|
186
|
+
qualityViolations.map((v) => v.message).join('; '),
|
|
187
|
+
});
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const resolvedFinding: Finding = {
|
|
192
|
+
...finding,
|
|
193
|
+
suggestedFix: suggestion,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
this.addEvidence({
|
|
197
|
+
type: 'log',
|
|
198
|
+
path: filePath,
|
|
199
|
+
description: `Healer processed test-bug "${finding.id}" with strategy "${strategy}" (attempt ${record.attempts})`,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Write updated finding back to disk
|
|
203
|
+
await fs.writeFile(filePath, JSON.stringify(resolvedFinding, null, 2), 'utf-8');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return unresolvedFindings;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private getOrCreateAttemptRecord(findingId: string): AttemptRecord {
|
|
210
|
+
const existing = this.attemptMap.get(findingId);
|
|
211
|
+
if (existing) return existing;
|
|
212
|
+
const record: AttemptRecord = { findingId, attempts: 0 };
|
|
213
|
+
this.attemptMap.set(findingId, record);
|
|
214
|
+
return record;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Internal helpers
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
interface NodeError extends Error {
|
|
223
|
+
code: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isNodeError(err: unknown): err is NodeError {
|
|
227
|
+
return err instanceof Error && 'code' in err;
|
|
228
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { BaseAgent } from './base-agent';
|
|
4
|
+
import { readAgentResult } from '../core/evidence';
|
|
5
|
+
import type { AgentResult, Finding, FindingSeverity, Phase } from '../core/types';
|
|
6
|
+
|
|
7
|
+
/** Agent IDs whose results are pulled into the consolidated report. */
|
|
8
|
+
const KNOWN_AGENT_IDS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] as const;
|
|
9
|
+
|
|
10
|
+
/** Release-gate recommendation. */
|
|
11
|
+
export type ReleaseRecommendation = 'GO' | 'CONDITIONAL GO' | 'NO-GO';
|
|
12
|
+
|
|
13
|
+
/** Phase-level aggregation used in the report. */
|
|
14
|
+
interface PhaseSummary {
|
|
15
|
+
phase: Phase;
|
|
16
|
+
passCount: number;
|
|
17
|
+
failCount: number;
|
|
18
|
+
findings: Finding[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Determines the release recommendation based on the severity of all findings
|
|
23
|
+
* across every agent result.
|
|
24
|
+
*
|
|
25
|
+
* - NO-GO: any critical finding
|
|
26
|
+
* - CONDITIONAL GO: 0 critical findings, at least one high finding
|
|
27
|
+
* - GO: 0 critical and 0 high findings
|
|
28
|
+
*/
|
|
29
|
+
export function determineRecommendation(results: AgentResult[]): ReleaseRecommendation {
|
|
30
|
+
const allFindings = results.flatMap((r) => r.findings);
|
|
31
|
+
const hasCritical = allFindings.some((f) => f.severity === 'critical');
|
|
32
|
+
const hasHigh = allFindings.some((f) => f.severity === 'high');
|
|
33
|
+
|
|
34
|
+
if (hasCritical) return 'NO-GO';
|
|
35
|
+
if (hasHigh) return 'CONDITIONAL GO';
|
|
36
|
+
return 'GO';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Aggregates agent results by phase.
|
|
41
|
+
*/
|
|
42
|
+
function aggregateByPhase(results: AgentResult[]): Map<Phase, PhaseSummary> {
|
|
43
|
+
const map = new Map<Phase, PhaseSummary>();
|
|
44
|
+
|
|
45
|
+
for (const result of results) {
|
|
46
|
+
const phase = result.phase;
|
|
47
|
+
const existing = map.get(phase) ?? {
|
|
48
|
+
phase,
|
|
49
|
+
passCount: 0,
|
|
50
|
+
failCount: 0,
|
|
51
|
+
findings: [],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (result.status === 'passed') {
|
|
55
|
+
existing.passCount += 1;
|
|
56
|
+
} else {
|
|
57
|
+
existing.failCount += 1;
|
|
58
|
+
}
|
|
59
|
+
existing.findings.push(...result.findings);
|
|
60
|
+
map.set(phase, existing);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function severityEmoji(severity: FindingSeverity): string {
|
|
67
|
+
switch (severity) {
|
|
68
|
+
case 'critical': return 'CRITICAL';
|
|
69
|
+
case 'high': return 'HIGH';
|
|
70
|
+
case 'medium': return 'MEDIUM';
|
|
71
|
+
case 'low': return 'LOW';
|
|
72
|
+
case 'info': return 'INFO';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function formatFindingLine(f: Finding): string {
|
|
77
|
+
const loc = f.file ? ` (${f.file}${f.line ? `:${f.line}` : ''})` : '';
|
|
78
|
+
return `- [${severityEmoji(f.severity)}] Agent ${f.agentId} | ${f.module}${loc}: ${f.description}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extracts a summary string from the agent result with the given ID, or returns
|
|
83
|
+
* a placeholder when that result is not present in the collection.
|
|
84
|
+
*/
|
|
85
|
+
function agentSummaryLine(results: AgentResult[], agentId: number, label: string): string {
|
|
86
|
+
const result = results.find((r) => r.agentId === agentId);
|
|
87
|
+
if (!result) return `${label}: not run`;
|
|
88
|
+
return `${label}: ${result.summary}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Builds the full markdown report string from the collected agent results.
|
|
93
|
+
*/
|
|
94
|
+
export function buildMarkdownReport(
|
|
95
|
+
results: AgentResult[],
|
|
96
|
+
runId: string,
|
|
97
|
+
recommendation: ReleaseRecommendation,
|
|
98
|
+
): string {
|
|
99
|
+
const phaseMap = aggregateByPhase(results);
|
|
100
|
+
const phases: Phase[] = ['alpha', 'beta', 'uat', 'release'];
|
|
101
|
+
|
|
102
|
+
const lines: string[] = [];
|
|
103
|
+
|
|
104
|
+
lines.push(`# TestTeam — Release Report`);
|
|
105
|
+
lines.push(`**Run ID:** ${runId}`);
|
|
106
|
+
lines.push(`**Generated:** ${new Date().toISOString()}`);
|
|
107
|
+
lines.push(`**Recommendation:** ${recommendation}`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
|
|
110
|
+
// Phase summaries
|
|
111
|
+
lines.push('## Phase Summaries');
|
|
112
|
+
for (const phase of phases) {
|
|
113
|
+
const summary = phaseMap.get(phase);
|
|
114
|
+
if (!summary) {
|
|
115
|
+
lines.push(`### ${phase.toUpperCase()}`);
|
|
116
|
+
lines.push('No agents ran in this phase.');
|
|
117
|
+
lines.push('');
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
lines.push(`### ${phase.toUpperCase()}`);
|
|
121
|
+
lines.push(`- Pass: ${summary.passCount}`);
|
|
122
|
+
lines.push(`- Fail: ${summary.failCount}`);
|
|
123
|
+
lines.push(`- Total findings: ${summary.findings.length}`);
|
|
124
|
+
lines.push('');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Specialist summaries
|
|
128
|
+
lines.push('## Specialist Summaries');
|
|
129
|
+
lines.push(agentSummaryLine(results, 4, 'Coverage (Agent 4)'));
|
|
130
|
+
lines.push(agentSummaryLine(results, 7, 'Security (Agent 7)'));
|
|
131
|
+
lines.push(agentSummaryLine(results, 8, 'Accessibility (Agent 8)'));
|
|
132
|
+
lines.push(agentSummaryLine(results, 13, 'Performance (Agent 13)'));
|
|
133
|
+
lines.push(agentSummaryLine(results, 14, 'Data Integrity (Agent 14)'));
|
|
134
|
+
lines.push('');
|
|
135
|
+
|
|
136
|
+
// All findings by severity
|
|
137
|
+
const allFindings = results.flatMap((r) => r.findings);
|
|
138
|
+
const severities: FindingSeverity[] = ['critical', 'high', 'medium', 'low'];
|
|
139
|
+
|
|
140
|
+
lines.push('## Findings by Severity');
|
|
141
|
+
for (const sev of severities) {
|
|
142
|
+
const group = allFindings.filter((f) => f.severity === sev);
|
|
143
|
+
if (group.length === 0) continue;
|
|
144
|
+
lines.push(`### ${sev.toUpperCase()} (${group.length})`);
|
|
145
|
+
for (const f of group) {
|
|
146
|
+
lines.push(formatFindingLine(f));
|
|
147
|
+
}
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (allFindings.length === 0) {
|
|
152
|
+
lines.push('No findings — all agents passed.');
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
lines.push(`---`);
|
|
157
|
+
lines.push(`_Generated by TestTeam Reporter (Agent 10)_`);
|
|
158
|
+
|
|
159
|
+
return lines.join('\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export class ReporterAgent extends BaseAgent {
|
|
163
|
+
readonly agentId = 10;
|
|
164
|
+
readonly agentName = 'Reporter';
|
|
165
|
+
|
|
166
|
+
/** When true, execute() returns a single informational finding instead of a full report. */
|
|
167
|
+
private noPriorResults = false;
|
|
168
|
+
|
|
169
|
+
protected async preFlight(): Promise<void> {
|
|
170
|
+
const agentResultsDir = path.join(this.runDir, 'agent-results');
|
|
171
|
+
let entries: string[];
|
|
172
|
+
try {
|
|
173
|
+
entries = await fs.readdir(agentResultsDir);
|
|
174
|
+
} catch (err: unknown) {
|
|
175
|
+
if (isNodeError(err) && err.code === 'ENOENT') {
|
|
176
|
+
// Directory missing — gracefully degrade instead of throwing
|
|
177
|
+
this.noPriorResults = true;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const jsonFiles = entries.filter((e) => e.endsWith('.json'));
|
|
184
|
+
if (jsonFiles.length === 0) {
|
|
185
|
+
// Empty directory — gracefully degrade instead of throwing
|
|
186
|
+
this.noPriorResults = true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected async execute(): Promise<Finding[]> {
|
|
191
|
+
if (this.noPriorResults) {
|
|
192
|
+
return [{
|
|
193
|
+
id: `${this.agentId}-no-results`,
|
|
194
|
+
type: 'infra-issue',
|
|
195
|
+
severity: 'low',
|
|
196
|
+
agentId: this.agentId,
|
|
197
|
+
module: 'reporter',
|
|
198
|
+
description: 'No agent results to report — agent-results directory is empty or missing',
|
|
199
|
+
}];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Read all available agent results
|
|
203
|
+
const results: AgentResult[] = [];
|
|
204
|
+
for (const agentId of KNOWN_AGENT_IDS) {
|
|
205
|
+
const result = await readAgentResult(this.runDir, agentId);
|
|
206
|
+
if (result) {
|
|
207
|
+
results.push(result);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Also read any extra result files not in the known-IDs list
|
|
212
|
+
const agentResultsDir = path.join(this.runDir, 'agent-results');
|
|
213
|
+
try {
|
|
214
|
+
const entries = await fs.readdir(agentResultsDir);
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
if (!entry.endsWith('.json')) continue;
|
|
217
|
+
const idStr = entry.replace('.json', '');
|
|
218
|
+
const id = parseInt(idStr, 10);
|
|
219
|
+
if (isNaN(id) || KNOWN_AGENT_IDS.includes(id as typeof KNOWN_AGENT_IDS[number])) continue;
|
|
220
|
+
const raw = await fs.readFile(path.join(agentResultsDir, entry), 'utf-8');
|
|
221
|
+
results.push(JSON.parse(raw) as AgentResult);
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
// Ignore read errors for extra files
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const recommendation = determineRecommendation(results);
|
|
228
|
+
|
|
229
|
+
// Use run directory name as run ID
|
|
230
|
+
const runId = path.basename(this.runDir);
|
|
231
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
232
|
+
const reportFilename = `${date}-${runId}.md`;
|
|
233
|
+
|
|
234
|
+
const reportsDir = path.join(
|
|
235
|
+
path.dirname(path.dirname(this.runDir)), // go up from .run/<runId> to project root
|
|
236
|
+
'testteam',
|
|
237
|
+
'reports',
|
|
238
|
+
);
|
|
239
|
+
await fs.mkdir(reportsDir, { recursive: true });
|
|
240
|
+
|
|
241
|
+
const reportPath = path.join(reportsDir, reportFilename);
|
|
242
|
+
const markdownContent = buildMarkdownReport(results, runId, recommendation);
|
|
243
|
+
await fs.writeFile(reportPath, markdownContent, 'utf-8');
|
|
244
|
+
|
|
245
|
+
this.addEvidence({
|
|
246
|
+
type: 'report',
|
|
247
|
+
path: reportPath,
|
|
248
|
+
description: `Consolidated release report — recommendation: ${recommendation}`,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Reporter does not generate new findings — it only aggregates
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Internal helpers
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
interface NodeError extends Error {
|
|
261
|
+
code: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function isNodeError(err: unknown): err is NodeError {
|
|
265
|
+
return err instanceof Error && 'code' in err;
|
|
266
|
+
}
|