@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,354 @@
|
|
|
1
|
+
import type { Phase, AgentResult, PhaseGateResult, Finding } from './types';
|
|
2
|
+
import type { ValidatedConfig } from './config';
|
|
3
|
+
import type { BaseAgent } from '../agents/base-agent';
|
|
4
|
+
import { runHealthChecks } from './health-check';
|
|
5
|
+
import type { HealthCheck } from './health-check';
|
|
6
|
+
import { initRunDirectory, writeAgentResult, writeFinding } from './evidence';
|
|
7
|
+
import { classifyFinding } from './fix-loop';
|
|
8
|
+
import { CostTracker } from './cost-tracker';
|
|
9
|
+
import { evaluateGate } from './phase-gate';
|
|
10
|
+
import { getLicenseTier } from './license';
|
|
11
|
+
import type { LicenseTier } from './license';
|
|
12
|
+
import { isAgentAvailable, getAgentTier } from '../agents/registry';
|
|
13
|
+
import { agentGatedMessage, phaseGatedMessage, getAgentDisplayName } from './messages';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
|
|
16
|
+
/** Agent IDs to dispatch per phase, in execution order. */
|
|
17
|
+
const PHASE_DISPATCH: Record<Phase, readonly number[]> = {
|
|
18
|
+
alpha: [1, 2, 23, 3, 4, 5],
|
|
19
|
+
beta: [5, 6, 7, 8, 12, 13, 14, 15, 16, 18, 19, 21, 22],
|
|
20
|
+
uat: [5, 6, 12, 13, 14, 15, 18, 19, 20, 21, 22, 17],
|
|
21
|
+
release: [10],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Agent IDs that are safe to run in parallel (read-only / non-destructive).
|
|
26
|
+
* Agents that write data (2, 3, 5, 16) must run sequentially.
|
|
27
|
+
*/
|
|
28
|
+
const PARALLEL_SAFE: ReadonlySet<number> = new Set([
|
|
29
|
+
6, 7, 8, 12, 13, 14, 15, 18, 19, 21, 22,
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/** Maximum number of fix-loop iterations per phase to prevent infinite heal/fix loops. */
|
|
33
|
+
const MAX_FIX_LOOP_ITERATIONS = 3;
|
|
34
|
+
|
|
35
|
+
export interface DispatchPlanEntry {
|
|
36
|
+
agentId: number;
|
|
37
|
+
status: 'scheduled' | 'disabled' | 'unregistered';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PhaseRunResult {
|
|
41
|
+
gate: PhaseGateResult;
|
|
42
|
+
agentResults: Record<number, AgentResult>;
|
|
43
|
+
skippedAgents: number[];
|
|
44
|
+
costTracker: CostTracker;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Phases available per license tier. */
|
|
48
|
+
const PHASE_ACCESS: Record<LicenseTier, ReadonlySet<Phase>> = {
|
|
49
|
+
free: new Set<Phase>(['alpha']),
|
|
50
|
+
pro: new Set<Phase>(['alpha', 'beta', 'uat', 'release']),
|
|
51
|
+
enterprise: new Set<Phase>(['alpha', 'beta', 'uat', 'release']),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** Minimum tier required for non-free phases. */
|
|
55
|
+
const PHASE_REQUIRED_TIER: Record<Phase, LicenseTier> = {
|
|
56
|
+
alpha: 'free',
|
|
57
|
+
beta: 'pro',
|
|
58
|
+
uat: 'pro',
|
|
59
|
+
release: 'pro',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export class Orchestrator {
|
|
63
|
+
private readonly config: ValidatedConfig;
|
|
64
|
+
private readonly agents: Map<number, BaseAgent> = new Map();
|
|
65
|
+
private readonly healthChecks: HealthCheck[] = [];
|
|
66
|
+
private runDir: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* License tier resolver — injectable for testing.
|
|
70
|
+
* Defaults to `getLicenseTier()` from the license module.
|
|
71
|
+
*/
|
|
72
|
+
_getLicenseTier: () => LicenseTier = () => getLicenseTier();
|
|
73
|
+
|
|
74
|
+
constructor(config: ValidatedConfig, runDir?: string) {
|
|
75
|
+
this.config = config;
|
|
76
|
+
this.runDir = runDir ?? path.join(process.cwd(), '.run', `run-${Date.now()}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Register an agent instance for a given agent ID. */
|
|
80
|
+
registerAgent(agentId: number, agent: BaseAgent): void {
|
|
81
|
+
this.agents.set(agentId, agent);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Register health checks to run before agent dispatch. */
|
|
85
|
+
registerHealthChecks(checks: HealthCheck[]): void {
|
|
86
|
+
this.healthChecks.push(...checks);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Return the dispatch plan for a phase without executing anything. */
|
|
90
|
+
dryRun(phase: Phase): DispatchPlanEntry[] {
|
|
91
|
+
const agentIds = PHASE_DISPATCH[phase];
|
|
92
|
+
return agentIds.map(agentId => {
|
|
93
|
+
if (this.config.disabledAgentSet.has(agentId)) {
|
|
94
|
+
return { agentId, status: 'disabled' as const };
|
|
95
|
+
}
|
|
96
|
+
if (!this.agents.has(agentId)) {
|
|
97
|
+
return { agentId, status: 'unregistered' as const };
|
|
98
|
+
}
|
|
99
|
+
return { agentId, status: 'scheduled' as const };
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Execute all agents for the given phase, evaluate the gate, and return results. */
|
|
104
|
+
async runPhase(phase: Phase): Promise<PhaseRunResult> {
|
|
105
|
+
// Step 0: License-based phase gating
|
|
106
|
+
const currentTier = this._getLicenseTier();
|
|
107
|
+
if (!PHASE_ACCESS[currentTier].has(phase)) {
|
|
108
|
+
const requiredTier = PHASE_REQUIRED_TIER[phase];
|
|
109
|
+
const message = phaseGatedMessage(phase, requiredTier, currentTier);
|
|
110
|
+
console.warn(`[Orchestrator] ${message}`);
|
|
111
|
+
|
|
112
|
+
// Return empty result with a failed gate
|
|
113
|
+
const gate: PhaseGateResult = {
|
|
114
|
+
phase,
|
|
115
|
+
passed: false,
|
|
116
|
+
blockingResults: [],
|
|
117
|
+
advisoryResults: [],
|
|
118
|
+
failedBlockingAgents: [],
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
gate,
|
|
123
|
+
agentResults: {},
|
|
124
|
+
skippedAgents: PHASE_DISPATCH[phase].slice(),
|
|
125
|
+
costTracker: new CostTracker({
|
|
126
|
+
maxPerRun: this.config.costBudget.maxPerRun,
|
|
127
|
+
maxPerAgent: this.config.costBudget.maxPerAgent,
|
|
128
|
+
alertThreshold: this.config.costBudget.alertThreshold,
|
|
129
|
+
onExceeded: this.config.costBudget.onExceeded,
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Step 1: Health checks
|
|
135
|
+
if (this.healthChecks.length > 0) {
|
|
136
|
+
const healthSummary = await runHealthChecks({ checks: this.healthChecks });
|
|
137
|
+
if (!healthSummary.canProceed) {
|
|
138
|
+
const failedNames = healthSummary.results
|
|
139
|
+
.filter(r => r.required && !r.passed)
|
|
140
|
+
.map(r => r.name)
|
|
141
|
+
.join(', ');
|
|
142
|
+
throw new Error(`Health checks failed: ${failedNames}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Step 2: Initialize run directory
|
|
147
|
+
await initRunDirectory(this.runDir);
|
|
148
|
+
|
|
149
|
+
// Step 3: Create cost tracker
|
|
150
|
+
const costTracker = new CostTracker({
|
|
151
|
+
maxPerRun: this.config.costBudget.maxPerRun,
|
|
152
|
+
maxPerAgent: this.config.costBudget.maxPerAgent,
|
|
153
|
+
alertThreshold: this.config.costBudget.alertThreshold,
|
|
154
|
+
onExceeded: this.config.costBudget.onExceeded,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Step 4: Dispatch agents
|
|
158
|
+
const agentIds = PHASE_DISPATCH[phase];
|
|
159
|
+
const agentResults: Record<number, AgentResult> = {};
|
|
160
|
+
const skippedAgents: number[] = [];
|
|
161
|
+
|
|
162
|
+
let budgetExhausted = false;
|
|
163
|
+
|
|
164
|
+
// Build dispatch batches: consecutive parallel-safe agents form a batch;
|
|
165
|
+
// sequential agents are dispatched alone.
|
|
166
|
+
const batches: number[][] = [];
|
|
167
|
+
let currentBatch: number[] = [];
|
|
168
|
+
|
|
169
|
+
for (const agentId of agentIds) {
|
|
170
|
+
if (this.config.disabledAgentSet.has(agentId) || !this.agents.has(agentId)) {
|
|
171
|
+
// Flush current batch before skipping
|
|
172
|
+
if (currentBatch.length > 0) {
|
|
173
|
+
batches.push(currentBatch);
|
|
174
|
+
currentBatch = [];
|
|
175
|
+
}
|
|
176
|
+
skippedAgents.push(agentId);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// License-based agent gating
|
|
181
|
+
if (!isAgentAvailable(agentId, currentTier)) {
|
|
182
|
+
if (currentBatch.length > 0) {
|
|
183
|
+
batches.push(currentBatch);
|
|
184
|
+
currentBatch = [];
|
|
185
|
+
}
|
|
186
|
+
const agentName = getAgentDisplayName(agentId);
|
|
187
|
+
const requiredTier = getAgentTier(agentId);
|
|
188
|
+
const message = agentGatedMessage(agentId, agentName, requiredTier, currentTier);
|
|
189
|
+
console.warn(`[Orchestrator] ${message}`);
|
|
190
|
+
|
|
191
|
+
// Record a skipped result with an info finding
|
|
192
|
+
agentResults[agentId] = {
|
|
193
|
+
agentId,
|
|
194
|
+
agentName,
|
|
195
|
+
phase,
|
|
196
|
+
status: 'skipped',
|
|
197
|
+
startedAt: new Date().toISOString(),
|
|
198
|
+
completedAt: new Date().toISOString(),
|
|
199
|
+
durationMs: 0,
|
|
200
|
+
findings: [{
|
|
201
|
+
id: `license-gate-${agentId}`,
|
|
202
|
+
type: 'infra-issue',
|
|
203
|
+
severity: 'info',
|
|
204
|
+
agentId,
|
|
205
|
+
module: 'license',
|
|
206
|
+
description: message,
|
|
207
|
+
}],
|
|
208
|
+
evidence: [],
|
|
209
|
+
summary: message,
|
|
210
|
+
};
|
|
211
|
+
skippedAgents.push(agentId);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (PARALLEL_SAFE.has(agentId)) {
|
|
216
|
+
currentBatch.push(agentId);
|
|
217
|
+
} else {
|
|
218
|
+
// Flush any accumulated parallel batch first
|
|
219
|
+
if (currentBatch.length > 0) {
|
|
220
|
+
batches.push(currentBatch);
|
|
221
|
+
currentBatch = [];
|
|
222
|
+
}
|
|
223
|
+
// Sequential agent: single-element batch
|
|
224
|
+
batches.push([agentId]);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Flush remaining parallel batch
|
|
228
|
+
if (currentBatch.length > 0) {
|
|
229
|
+
batches.push(currentBatch);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (const batch of batches) {
|
|
233
|
+
if (budgetExhausted) {
|
|
234
|
+
skippedAgents.push(...batch);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const runAgent = async (agentId: number): Promise<void> => {
|
|
239
|
+
const agent = this.agents.get(agentId)!;
|
|
240
|
+
const result = await agent.run();
|
|
241
|
+
agentResults[agentId] = result;
|
|
242
|
+
|
|
243
|
+
// Track cost (duration-based placeholder: $0.0001 per second)
|
|
244
|
+
costTracker.trackCost(String(agentId), (result.durationMs / 1000) * 0.0001);
|
|
245
|
+
|
|
246
|
+
// Write results to evidence directory
|
|
247
|
+
await writeAgentResult(this.runDir, agentId, result);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
if (batch.length === 1) {
|
|
251
|
+
await runAgent(batch[0]);
|
|
252
|
+
} else {
|
|
253
|
+
await Promise.all(batch.map(runAgent));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Enforce cost budget after each batch
|
|
257
|
+
const budgetCheck = costTracker.checkBudget();
|
|
258
|
+
if (budgetCheck.status === 'exceeded') {
|
|
259
|
+
const onExceeded = this.config.costBudget.onExceeded;
|
|
260
|
+
if (onExceeded === 'abort' || onExceeded === 'skip-remaining') {
|
|
261
|
+
budgetExhausted = true;
|
|
262
|
+
} else {
|
|
263
|
+
console.warn(
|
|
264
|
+
`[Orchestrator] Cost budget exceeded (total: $${budgetCheck.totalCost.toFixed(4)}) — continuing per alert-continue policy`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Step 5b: Run fix loop for cross-phase agents (Healer/Fixer)
|
|
271
|
+
await this.runFixLoop(agentResults, costTracker);
|
|
272
|
+
|
|
273
|
+
// Step 6: Evaluate the phase gate
|
|
274
|
+
const scheduledForPhase = new Set(PHASE_DISPATCH[phase]);
|
|
275
|
+
const gate = evaluateGate(
|
|
276
|
+
phase,
|
|
277
|
+
this.config.agentClassification,
|
|
278
|
+
agentResults,
|
|
279
|
+
this.config.disabledAgentSet,
|
|
280
|
+
scheduledForPhase,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return { gate, agentResults, skippedAgents, costTracker };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* After all phase agents complete, classify findings and dispatch cross-phase
|
|
288
|
+
* agents on demand:
|
|
289
|
+
* - Agent 9 (Healer) for test-bug findings
|
|
290
|
+
* - Agent 11 (Fixer) for code-bug findings
|
|
291
|
+
*
|
|
292
|
+
* Iterates up to MAX_FIX_LOOP_ITERATIONS times to prevent infinite heal/fix loops.
|
|
293
|
+
*/
|
|
294
|
+
private async runFixLoop(
|
|
295
|
+
agentResults: Record<number, AgentResult>,
|
|
296
|
+
costTracker: CostTracker,
|
|
297
|
+
): Promise<void> {
|
|
298
|
+
for (let iteration = 0; iteration < MAX_FIX_LOOP_ITERATIONS; iteration++) {
|
|
299
|
+
const allFindings: Finding[] = Object.values(agentResults).flatMap(r => r.findings);
|
|
300
|
+
if (allFindings.length === 0) return;
|
|
301
|
+
|
|
302
|
+
// changedFiles is empty — we don't track VCS changes during a run
|
|
303
|
+
const changedFiles: string[] = [];
|
|
304
|
+
|
|
305
|
+
let hasTestBugs = false;
|
|
306
|
+
let hasCodeBugs = false;
|
|
307
|
+
|
|
308
|
+
for (const finding of allFindings) {
|
|
309
|
+
const findingType = classifyFinding(finding, changedFiles);
|
|
310
|
+
await writeFinding(this.runDir, { ...finding, type: findingType });
|
|
311
|
+
|
|
312
|
+
if (findingType === 'test-bug') {
|
|
313
|
+
hasTestBugs = true;
|
|
314
|
+
} else {
|
|
315
|
+
hasCodeBugs = true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let dispatched = false;
|
|
320
|
+
|
|
321
|
+
// Dispatch Agent 9 (Healer) for test-bugs
|
|
322
|
+
if (hasTestBugs) {
|
|
323
|
+
const healer = this.agents.get(9);
|
|
324
|
+
if (healer && !this.config.disabledAgentSet.has(9)) {
|
|
325
|
+
const result = await healer.run();
|
|
326
|
+
agentResults[9] = result;
|
|
327
|
+
costTracker.trackCost('9', result.durationMs * 0.001);
|
|
328
|
+
await writeAgentResult(this.runDir, 9, result);
|
|
329
|
+
dispatched = true;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Dispatch Agent 11 (Fixer) for code-bugs
|
|
334
|
+
if (hasCodeBugs) {
|
|
335
|
+
const fixer = this.agents.get(11);
|
|
336
|
+
if (fixer && !this.config.disabledAgentSet.has(11)) {
|
|
337
|
+
const result = await fixer.run();
|
|
338
|
+
agentResults[11] = result;
|
|
339
|
+
costTracker.trackCost('11', result.durationMs * 0.001);
|
|
340
|
+
await writeAgentResult(this.runDir, 11, result);
|
|
341
|
+
dispatched = true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// If no agents were dispatched, no further iterations are needed
|
|
346
|
+
if (!dispatched) return;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Get the run directory path. */
|
|
351
|
+
getRunDir(): string {
|
|
352
|
+
return this.runDir;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Phase, AgentResult, PhaseGateResult } from './types';
|
|
2
|
+
import type { AgentClassificationConfig } from './config';
|
|
3
|
+
|
|
4
|
+
export function evaluateGate(
|
|
5
|
+
phase: Phase,
|
|
6
|
+
classification: AgentClassificationConfig,
|
|
7
|
+
results: Record<number, AgentResult>,
|
|
8
|
+
disabledAgents: Set<number> = new Set(),
|
|
9
|
+
scheduledAgents: Set<number> = new Set(),
|
|
10
|
+
): PhaseGateResult {
|
|
11
|
+
const failedBlockingAgents: number[] = [];
|
|
12
|
+
const blockingResults: AgentResult[] = [];
|
|
13
|
+
const advisoryResults: AgentResult[] = [];
|
|
14
|
+
|
|
15
|
+
for (const agentId of classification.blocking) {
|
|
16
|
+
if (disabledAgents.has(agentId)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Only evaluate agents that were actually scheduled for this phase
|
|
21
|
+
if (scheduledAgents.size > 0 && !scheduledAgents.has(agentId)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const result = results[agentId];
|
|
26
|
+
|
|
27
|
+
if (result === undefined || result.status === 'failed') {
|
|
28
|
+
failedBlockingAgents.push(agentId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (result !== undefined) {
|
|
32
|
+
blockingResults.push(result);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const agentId of classification.advisory) {
|
|
37
|
+
if (disabledAgents.has(agentId)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const result = results[agentId];
|
|
42
|
+
if (result !== undefined) {
|
|
43
|
+
advisoryResults.push(result);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
phase,
|
|
49
|
+
passed: failedBlockingAgents.length === 0,
|
|
50
|
+
blockingResults,
|
|
51
|
+
advisoryResults,
|
|
52
|
+
failedBlockingAgents,
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
};
|
|
55
|
+
}
|