agentic-qe 3.7.5 → 3.7.6
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/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +18 -0
- package/dist/cli/bundle.js +5199 -1335
- package/dist/cli/commands/security.d.ts.map +1 -1
- package/dist/cli/commands/security.js +66 -1
- package/dist/cli/commands/security.js.map +1 -1
- package/dist/cli/commands/test.d.ts.map +1 -1
- package/dist/cli/commands/test.js +86 -3
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/index.js +119 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/coordination/workflow-orchestrator.d.ts.map +1 -1
- package/dist/coordination/workflow-orchestrator.js +2 -6
- package/dist/coordination/workflow-orchestrator.js.map +1 -1
- package/dist/mcp/bundle.js +3977 -153
- package/dist/mcp/handlers/core-handlers.d.ts.map +1 -1
- package/dist/mcp/handlers/core-handlers.js +35 -0
- package/dist/mcp/handlers/core-handlers.js.map +1 -1
- package/dist/mcp/protocol-server.d.ts.map +1 -1
- package/dist/mcp/protocol-server.js +4 -1
- package/dist/mcp/protocol-server.js.map +1 -1
- package/dist/mcp/qe-tool-bridge.d.ts +27 -0
- package/dist/mcp/qe-tool-bridge.d.ts.map +1 -0
- package/dist/mcp/qe-tool-bridge.js +87 -0
- package/dist/mcp/qe-tool-bridge.js.map +1 -0
- package/dist/mcp/tools/registry.d.ts +4 -0
- package/dist/mcp/tools/registry.d.ts.map +1 -1
- package/dist/mcp/tools/registry.js +20 -0
- package/dist/mcp/tools/registry.js.map +1 -1
- package/dist/mcp/tools/security-compliance/visual-security.d.ts +45 -0
- package/dist/mcp/tools/security-compliance/visual-security.d.ts.map +1 -0
- package/dist/mcp/tools/security-compliance/visual-security.js +218 -0
- package/dist/mcp/tools/security-compliance/visual-security.js.map +1 -0
- package/dist/mcp/tools/test-execution/browser-workflow.d.ts +50 -0
- package/dist/mcp/tools/test-execution/browser-workflow.d.ts.map +1 -0
- package/dist/mcp/tools/test-execution/browser-workflow.js +145 -0
- package/dist/mcp/tools/test-execution/browser-workflow.js.map +1 -0
- package/dist/mcp/tools/test-execution/load-test.d.ts +37 -0
- package/dist/mcp/tools/test-execution/load-test.d.ts.map +1 -0
- package/dist/mcp/tools/test-execution/load-test.js +98 -0
- package/dist/mcp/tools/test-execution/load-test.js.map +1 -0
- package/dist/mcp/tools/test-execution/schedule.d.ts +44 -0
- package/dist/mcp/tools/test-execution/schedule.d.ts.map +1 -0
- package/dist/mcp/tools/test-execution/schedule.js +96 -0
- package/dist/mcp/tools/test-execution/schedule.js.map +1 -0
- package/dist/planning/goap-planner.d.ts.map +1 -1
- package/dist/planning/goap-planner.js +7 -28
- package/dist/planning/goap-planner.js.map +1 -1
- package/dist/planning/plan-executor.d.ts.map +1 -1
- package/dist/planning/plan-executor.js +7 -28
- package/dist/planning/plan-executor.js.map +1 -1
- package/package.json +3 -10
- package/dist/cli/commands/qe-tools.d.ts +0 -27
- package/dist/cli/commands/qe-tools.d.ts.map +0 -1
- package/dist/cli/commands/qe-tools.js +0 -771
- package/dist/cli/commands/qe-tools.js.map +0 -1
- package/dist/neural-optimizer/index.d.ts +0 -55
- package/dist/neural-optimizer/index.d.ts.map +0 -1
- package/dist/neural-optimizer/index.js +0 -57
- package/dist/neural-optimizer/index.js.map +0 -1
- package/dist/neural-optimizer/replay-buffer.d.ts +0 -126
- package/dist/neural-optimizer/replay-buffer.d.ts.map +0 -1
- package/dist/neural-optimizer/replay-buffer.js +0 -356
- package/dist/neural-optimizer/replay-buffer.js.map +0 -1
- package/dist/neural-optimizer/swarm-topology.d.ts +0 -157
- package/dist/neural-optimizer/swarm-topology.d.ts.map +0 -1
- package/dist/neural-optimizer/swarm-topology.js +0 -384
- package/dist/neural-optimizer/swarm-topology.js.map +0 -1
- package/dist/neural-optimizer/topology-optimizer.d.ts +0 -137
- package/dist/neural-optimizer/topology-optimizer.d.ts.map +0 -1
- package/dist/neural-optimizer/topology-optimizer.js +0 -657
- package/dist/neural-optimizer/topology-optimizer.js.map +0 -1
- package/dist/neural-optimizer/types.d.ts +0 -333
- package/dist/neural-optimizer/types.d.ts.map +0 -1
- package/dist/neural-optimizer/types.js +0 -57
- package/dist/neural-optimizer/types.js.map +0 -1
- package/dist/neural-optimizer/value-network.d.ts +0 -129
- package/dist/neural-optimizer/value-network.d.ts.map +0 -1
- package/dist/neural-optimizer/value-network.js +0 -279
- package/dist/neural-optimizer/value-network.js.map +0 -1
package/dist/mcp/bundle.js
CHANGED
|
@@ -20388,7 +20388,7 @@ var init_ollama_client = __esm({
|
|
|
20388
20388
|
* Sleep utility for retry delays
|
|
20389
20389
|
*/
|
|
20390
20390
|
sleep(ms) {
|
|
20391
|
-
return new Promise((
|
|
20391
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
20392
20392
|
}
|
|
20393
20393
|
/**
|
|
20394
20394
|
* Get Ollama server info
|
|
@@ -23174,7 +23174,7 @@ Focus on accuracy over speed. It's better to mark something as "INCONCLUSIVE" if
|
|
|
23174
23174
|
* Sleep for specified milliseconds
|
|
23175
23175
|
*/
|
|
23176
23176
|
sleep(ms) {
|
|
23177
|
-
return new Promise((
|
|
23177
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
23178
23178
|
}
|
|
23179
23179
|
/**
|
|
23180
23180
|
* Get cost per token for current model
|
|
@@ -23500,7 +23500,7 @@ Focus on accuracy over speed. It's better to mark something as "INCONCLUSIVE" if
|
|
|
23500
23500
|
* Sleep for specified milliseconds
|
|
23501
23501
|
*/
|
|
23502
23502
|
sleep(ms) {
|
|
23503
|
-
return new Promise((
|
|
23503
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
23504
23504
|
}
|
|
23505
23505
|
/**
|
|
23506
23506
|
* Get cost per token for current model
|
|
@@ -23784,7 +23784,7 @@ Focus on accuracy over speed. It's better to mark something as "INCONCLUSIVE" if
|
|
|
23784
23784
|
* Sleep for specified milliseconds
|
|
23785
23785
|
*/
|
|
23786
23786
|
sleep(ms) {
|
|
23787
|
-
return new Promise((
|
|
23787
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
23788
23788
|
}
|
|
23789
23789
|
/**
|
|
23790
23790
|
* Get cost per token for current model
|
|
@@ -24070,7 +24070,7 @@ var init_openrouter_provider = __esm({
|
|
|
24070
24070
|
* Sleep for a given duration
|
|
24071
24071
|
*/
|
|
24072
24072
|
sleep(ms) {
|
|
24073
|
-
return new Promise((
|
|
24073
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
24074
24074
|
}
|
|
24075
24075
|
/**
|
|
24076
24076
|
* Get default system prompt for security verification
|
|
@@ -24353,7 +24353,7 @@ var init_ollama_provider = __esm({
|
|
|
24353
24353
|
* Sleep for a given duration
|
|
24354
24354
|
*/
|
|
24355
24355
|
sleep(ms) {
|
|
24356
|
-
return new Promise((
|
|
24356
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
24357
24357
|
}
|
|
24358
24358
|
/**
|
|
24359
24359
|
* Get default system prompt for security verification
|
|
@@ -40800,7 +40800,7 @@ Provide:
|
|
|
40800
40800
|
return err(new Error(`Test file not found: ${file}`));
|
|
40801
40801
|
}
|
|
40802
40802
|
const { command, args } = this.buildTestCommand(file, framework);
|
|
40803
|
-
return new Promise((
|
|
40803
|
+
return new Promise((resolve10) => {
|
|
40804
40804
|
let stdout = "";
|
|
40805
40805
|
let stderr = "";
|
|
40806
40806
|
let killed = false;
|
|
@@ -40817,7 +40817,7 @@ Provide:
|
|
|
40817
40817
|
const timeoutId = setTimeout(() => {
|
|
40818
40818
|
killed = true;
|
|
40819
40819
|
proc.kill("SIGTERM");
|
|
40820
|
-
|
|
40820
|
+
resolve10(err(new Error(`Test execution timed out after ${timeout}ms for file: ${file}`)));
|
|
40821
40821
|
}, timeout);
|
|
40822
40822
|
proc.stdout?.on("data", (data) => {
|
|
40823
40823
|
stdout += data.toString();
|
|
@@ -40831,11 +40831,11 @@ Provide:
|
|
|
40831
40831
|
return;
|
|
40832
40832
|
}
|
|
40833
40833
|
const parseResult = this.parseTestOutput(stdout, stderr, file, framework, code);
|
|
40834
|
-
|
|
40834
|
+
resolve10(parseResult);
|
|
40835
40835
|
});
|
|
40836
40836
|
proc.on("error", (error) => {
|
|
40837
40837
|
clearTimeout(timeoutId);
|
|
40838
|
-
|
|
40838
|
+
resolve10(err(new Error(`Failed to spawn test runner: ${error.message}. Is '${command}' installed?`)));
|
|
40839
40839
|
});
|
|
40840
40840
|
});
|
|
40841
40841
|
}
|
|
@@ -41430,7 +41430,7 @@ var init_flaky_detector = __esm({
|
|
|
41430
41430
|
const results = /* @__PURE__ */ new Map();
|
|
41431
41431
|
const runId = v4_default();
|
|
41432
41432
|
const startTime = Date.now();
|
|
41433
|
-
return new Promise((
|
|
41433
|
+
return new Promise((resolve10, reject) => {
|
|
41434
41434
|
const args = [...this.config.testRunnerArgs, file];
|
|
41435
41435
|
const cwd = this.config.cwd ?? process.cwd();
|
|
41436
41436
|
const child = spawn2(this.config.testRunner, args, {
|
|
@@ -41495,7 +41495,7 @@ var init_flaky_detector = __esm({
|
|
|
41495
41495
|
results.set(testId, records);
|
|
41496
41496
|
}
|
|
41497
41497
|
}
|
|
41498
|
-
|
|
41498
|
+
resolve10(results);
|
|
41499
41499
|
} catch (parseError) {
|
|
41500
41500
|
const testId = this.generateTestId(file, "main");
|
|
41501
41501
|
results.set(testId, [
|
|
@@ -41511,7 +41511,7 @@ var init_flaky_detector = __esm({
|
|
|
41511
41511
|
}
|
|
41512
41512
|
}
|
|
41513
41513
|
]);
|
|
41514
|
-
|
|
41514
|
+
resolve10(results);
|
|
41515
41515
|
}
|
|
41516
41516
|
});
|
|
41517
41517
|
});
|
|
@@ -42394,7 +42394,7 @@ var init_retry_handler = __esm({
|
|
|
42394
42394
|
* Spawn a test process and parse the result
|
|
42395
42395
|
*/
|
|
42396
42396
|
spawnTestProcess(command, args, cwd) {
|
|
42397
|
-
return new Promise((
|
|
42397
|
+
return new Promise((resolve10, reject) => {
|
|
42398
42398
|
const timeout = this.config.testTimeout;
|
|
42399
42399
|
let stdout = "";
|
|
42400
42400
|
let stderr = "";
|
|
@@ -42423,7 +42423,7 @@ var init_retry_handler = __esm({
|
|
|
42423
42423
|
return;
|
|
42424
42424
|
}
|
|
42425
42425
|
const result = this.parseTestResult(code, stdout, stderr);
|
|
42426
|
-
|
|
42426
|
+
resolve10(result);
|
|
42427
42427
|
});
|
|
42428
42428
|
proc.on("error", (err4) => {
|
|
42429
42429
|
clearTimeout(timeoutHandle);
|
|
@@ -42512,7 +42512,7 @@ var init_retry_handler = __esm({
|
|
|
42512
42512
|
return Math.min(delay, maxDelay);
|
|
42513
42513
|
}
|
|
42514
42514
|
sleep(ms) {
|
|
42515
|
-
return new Promise((
|
|
42515
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
42516
42516
|
}
|
|
42517
42517
|
async recordRetry(testId, result) {
|
|
42518
42518
|
const history = this.retryHistory.get(testId) ?? [];
|
|
@@ -43253,7 +43253,7 @@ var init_command_executor = __esm({
|
|
|
43253
43253
|
* Execute a command asynchronously (for long-running ops)
|
|
43254
43254
|
*/
|
|
43255
43255
|
async executeAsync(command, args = []) {
|
|
43256
|
-
return new Promise((
|
|
43256
|
+
return new Promise((resolve10) => {
|
|
43257
43257
|
const fullArgs = this.buildArgs(command, args);
|
|
43258
43258
|
if (this.config.debug) {
|
|
43259
43259
|
console.log(`[agent-browser] Executing async: npx agent-browser ${fullArgs.join(" ")}`);
|
|
@@ -43273,16 +43273,16 @@ var init_command_executor = __esm({
|
|
|
43273
43273
|
if (code === 0) {
|
|
43274
43274
|
try {
|
|
43275
43275
|
const parsed = safeJsonParse(stdout.trim());
|
|
43276
|
-
|
|
43276
|
+
resolve10({ success: true, data: parsed });
|
|
43277
43277
|
} catch {
|
|
43278
|
-
|
|
43278
|
+
resolve10({ success: true, data: stdout.trim() });
|
|
43279
43279
|
}
|
|
43280
43280
|
} else {
|
|
43281
|
-
|
|
43281
|
+
resolve10({ success: false, error: stderr || `Exit code: ${code}` });
|
|
43282
43282
|
}
|
|
43283
43283
|
});
|
|
43284
43284
|
process2.on("error", (error) => {
|
|
43285
|
-
|
|
43285
|
+
resolve10({ success: false, error: error.message });
|
|
43286
43286
|
});
|
|
43287
43287
|
});
|
|
43288
43288
|
}
|
|
@@ -45933,7 +45933,7 @@ var init_wait_condition_handler = __esm({
|
|
|
45933
45933
|
* Delay execution
|
|
45934
45934
|
*/
|
|
45935
45935
|
delay(ms) {
|
|
45936
|
-
return new Promise((
|
|
45936
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
45937
45937
|
}
|
|
45938
45938
|
};
|
|
45939
45939
|
}
|
|
@@ -46369,7 +46369,7 @@ var init_step_executors = __esm({
|
|
|
46369
46369
|
return new URL(url, baseUrl).toString();
|
|
46370
46370
|
}
|
|
46371
46371
|
delay(ms) {
|
|
46372
|
-
return new Promise((
|
|
46372
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
46373
46373
|
}
|
|
46374
46374
|
};
|
|
46375
46375
|
}
|
|
@@ -46793,13 +46793,13 @@ var init_step_retry_handler = __esm({
|
|
|
46793
46793
|
* Wrap promise with timeout
|
|
46794
46794
|
*/
|
|
46795
46795
|
async withTimeout(promise, timeout, stepId) {
|
|
46796
|
-
return new Promise((
|
|
46796
|
+
return new Promise((resolve10, reject) => {
|
|
46797
46797
|
const timer = setTimeout(() => {
|
|
46798
46798
|
reject(new StepTimeoutError(stepId, timeout));
|
|
46799
46799
|
}, timeout);
|
|
46800
46800
|
promise.then((result) => {
|
|
46801
46801
|
clearTimeout(timer);
|
|
46802
|
-
|
|
46802
|
+
resolve10(result);
|
|
46803
46803
|
}).catch((error) => {
|
|
46804
46804
|
clearTimeout(timer);
|
|
46805
46805
|
reject(error);
|
|
@@ -46810,7 +46810,7 @@ var init_step_retry_handler = __esm({
|
|
|
46810
46810
|
* Delay execution
|
|
46811
46811
|
*/
|
|
46812
46812
|
delay(ms) {
|
|
46813
|
-
return new Promise((
|
|
46813
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
46814
46814
|
}
|
|
46815
46815
|
};
|
|
46816
46816
|
}
|
|
@@ -47173,7 +47173,7 @@ var init_e2e_coordinator = __esm({
|
|
|
47173
47173
|
* Delay execution
|
|
47174
47174
|
*/
|
|
47175
47175
|
delay(ms) {
|
|
47176
|
-
return new Promise((
|
|
47176
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
47177
47177
|
}
|
|
47178
47178
|
/**
|
|
47179
47179
|
* Log message if verbose mode is enabled
|
|
@@ -70885,6 +70885,10 @@ var init_semantic_analyzer = __esm({
|
|
|
70885
70885
|
});
|
|
70886
70886
|
|
|
70887
70887
|
// src/domains/code-intelligence/services/impact-analyzer.ts
|
|
70888
|
+
var impact_analyzer_exports = {};
|
|
70889
|
+
__export(impact_analyzer_exports, {
|
|
70890
|
+
ImpactAnalyzerService: () => ImpactAnalyzerService
|
|
70891
|
+
});
|
|
70888
70892
|
var DEFAULT_CONFIG38, ImpactAnalyzerService;
|
|
70889
70893
|
var init_impact_analyzer = __esm({
|
|
70890
70894
|
"src/domains/code-intelligence/services/impact-analyzer.ts"() {
|
|
@@ -77258,7 +77262,7 @@ var init_http_client = __esm({
|
|
|
77258
77262
|
* Sleep utility for retry delays
|
|
77259
77263
|
*/
|
|
77260
77264
|
sleep(ms) {
|
|
77261
|
-
return new Promise((
|
|
77265
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
77262
77266
|
}
|
|
77263
77267
|
};
|
|
77264
77268
|
}
|
|
@@ -92374,13 +92378,13 @@ Provide:
|
|
|
92374
92378
|
}
|
|
92375
92379
|
}
|
|
92376
92380
|
async executeTcpProbe(probe) {
|
|
92377
|
-
return new Promise((
|
|
92381
|
+
return new Promise((resolve10) => {
|
|
92378
92382
|
try {
|
|
92379
92383
|
const [host, portStr] = probe.target.split(":");
|
|
92380
92384
|
const port = parseInt(portStr, 10);
|
|
92381
92385
|
if (!host || isNaN(port)) {
|
|
92382
92386
|
console.log(`TCP probe invalid target: ${probe.target} (expected host:port)`);
|
|
92383
|
-
|
|
92387
|
+
resolve10(false);
|
|
92384
92388
|
return;
|
|
92385
92389
|
}
|
|
92386
92390
|
const timeout = probe.timeout ?? 5e3;
|
|
@@ -92388,22 +92392,22 @@ Provide:
|
|
|
92388
92392
|
const timer = setTimeout(() => {
|
|
92389
92393
|
socket.destroy();
|
|
92390
92394
|
console.log(`TCP probe timeout: ${probe.name} -> ${probe.target}`);
|
|
92391
|
-
|
|
92395
|
+
resolve10(false);
|
|
92392
92396
|
}, timeout);
|
|
92393
92397
|
socket.connect(port, host, () => {
|
|
92394
92398
|
clearTimeout(timer);
|
|
92395
92399
|
socket.destroy();
|
|
92396
|
-
|
|
92400
|
+
resolve10(true);
|
|
92397
92401
|
});
|
|
92398
92402
|
socket.on("error", (err4) => {
|
|
92399
92403
|
clearTimeout(timer);
|
|
92400
92404
|
socket.destroy();
|
|
92401
92405
|
console.log(`TCP probe error: ${probe.name} -> ${err4.message}`);
|
|
92402
|
-
|
|
92406
|
+
resolve10(false);
|
|
92403
92407
|
});
|
|
92404
92408
|
} catch (error) {
|
|
92405
92409
|
console.log(`TCP probe exception: ${probe.name} -> ${toErrorMessage(error)}`);
|
|
92406
|
-
|
|
92410
|
+
resolve10(false);
|
|
92407
92411
|
}
|
|
92408
92412
|
});
|
|
92409
92413
|
}
|
|
@@ -92442,13 +92446,13 @@ Provide:
|
|
|
92442
92446
|
// Node.js checks
|
|
92443
92447
|
];
|
|
92444
92448
|
async executeCommandProbe(probe) {
|
|
92445
|
-
return new Promise((
|
|
92449
|
+
return new Promise((resolve10) => {
|
|
92446
92450
|
const timeout = probe.timeout ?? 1e4;
|
|
92447
92451
|
const validation = validateCommand(probe.target, _ChaosEngineerService.ALLOWED_PROBE_COMMANDS);
|
|
92448
92452
|
if (!validation.valid) {
|
|
92449
92453
|
console.log(`Command probe ${probe.name} blocked: ${validation.error}`);
|
|
92450
92454
|
console.log(`Blocked patterns: ${validation.blockedPatterns?.join(", ") || "none"}`);
|
|
92451
|
-
|
|
92455
|
+
resolve10(false);
|
|
92452
92456
|
return;
|
|
92453
92457
|
}
|
|
92454
92458
|
const sanitizedCommand = validation.sanitizedCommand || probe.target;
|
|
@@ -92458,7 +92462,7 @@ Provide:
|
|
|
92458
92462
|
execFile(executable, args, { timeout }, (error, stdout, _stderr) => {
|
|
92459
92463
|
if (error) {
|
|
92460
92464
|
console.log(`Command probe failed: ${probe.name} -> ${error.message}`);
|
|
92461
|
-
|
|
92465
|
+
resolve10(false);
|
|
92462
92466
|
return;
|
|
92463
92467
|
}
|
|
92464
92468
|
if (probe.expectedOutput !== void 0) {
|
|
@@ -92466,10 +92470,10 @@ Provide:
|
|
|
92466
92470
|
if (!passed) {
|
|
92467
92471
|
console.log(`Command probe ${probe.name}: output did not contain expected value`);
|
|
92468
92472
|
}
|
|
92469
|
-
|
|
92473
|
+
resolve10(passed);
|
|
92470
92474
|
return;
|
|
92471
92475
|
}
|
|
92472
|
-
|
|
92476
|
+
resolve10(true);
|
|
92473
92477
|
});
|
|
92474
92478
|
});
|
|
92475
92479
|
}
|
|
@@ -92789,7 +92793,7 @@ Provide:
|
|
|
92789
92793
|
// No-op commands
|
|
92790
92794
|
];
|
|
92791
92795
|
executeCommandRollback(command, timeout) {
|
|
92792
|
-
return new Promise((
|
|
92796
|
+
return new Promise((resolve10, reject) => {
|
|
92793
92797
|
const validation = validateCommand(command, _ChaosEngineerService.ALLOWED_ROLLBACK_COMMANDS);
|
|
92794
92798
|
if (!validation.valid) {
|
|
92795
92799
|
reject(new Error(`Rollback command blocked: ${validation.error}`));
|
|
@@ -92803,7 +92807,7 @@ Provide:
|
|
|
92803
92807
|
if (error) {
|
|
92804
92808
|
reject(new Error(`Command rollback failed: ${error.message}. ${stderr}`));
|
|
92805
92809
|
} else {
|
|
92806
|
-
|
|
92810
|
+
resolve10();
|
|
92807
92811
|
}
|
|
92808
92812
|
});
|
|
92809
92813
|
});
|
|
@@ -92824,7 +92828,7 @@ Provide:
|
|
|
92824
92828
|
await this.sleep(100);
|
|
92825
92829
|
}
|
|
92826
92830
|
sleep(ms) {
|
|
92827
|
-
return new Promise((
|
|
92831
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
92828
92832
|
}
|
|
92829
92833
|
};
|
|
92830
92834
|
}
|
|
@@ -93382,7 +93386,7 @@ var init_load_tester = __esm({
|
|
|
93382
93386
|
return result;
|
|
93383
93387
|
}
|
|
93384
93388
|
sleep(ms) {
|
|
93385
|
-
return new Promise((
|
|
93389
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
93386
93390
|
}
|
|
93387
93391
|
};
|
|
93388
93392
|
}
|
|
@@ -93954,7 +93958,7 @@ var init_performance_profiler = __esm({
|
|
|
93954
93958
|
);
|
|
93955
93959
|
}
|
|
93956
93960
|
sleep(ms) {
|
|
93957
|
-
return new Promise((
|
|
93961
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
93958
93962
|
}
|
|
93959
93963
|
};
|
|
93960
93964
|
}
|
|
@@ -99941,7 +99945,7 @@ var init_spreading_activation = __esm({
|
|
|
99941
99945
|
* Helper to sleep for a duration
|
|
99942
99946
|
*/
|
|
99943
99947
|
sleep(ms) {
|
|
99944
|
-
return new Promise((
|
|
99948
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
99945
99949
|
}
|
|
99946
99950
|
};
|
|
99947
99951
|
}
|
|
@@ -105018,14 +105022,13 @@ var init_workflow_orchestrator = __esm({
|
|
|
105018
105022
|
let current = obj;
|
|
105019
105023
|
for (let i58 = 0; i58 < parts.length - 1; i58++) {
|
|
105020
105024
|
const part = parts[i58];
|
|
105021
|
-
if (dangerousKeys.has(part)) throw new Error(`Invalid path segment: '${part}' is a dangerous prototype key`);
|
|
105022
105025
|
if (!Object.hasOwn(current, part)) {
|
|
105023
|
-
|
|
105026
|
+
const container = /* @__PURE__ */ Object.create(null);
|
|
105027
|
+
Object.defineProperty(current, part, { value: container, writable: true, enumerable: true, configurable: true });
|
|
105024
105028
|
}
|
|
105025
105029
|
current = current[part];
|
|
105026
105030
|
}
|
|
105027
105031
|
const finalKey = parts[parts.length - 1];
|
|
105028
|
-
if (dangerousKeys.has(finalKey)) throw new Error(`Invalid final key: '${finalKey}' is a dangerous prototype key`);
|
|
105029
105032
|
Object.defineProperty(current, finalKey, { value, writable: true, enumerable: true, configurable: true });
|
|
105030
105033
|
}
|
|
105031
105034
|
// ============================================================================
|
|
@@ -105264,7 +105267,7 @@ var init_workflow_orchestrator = __esm({
|
|
|
105264
105267
|
// Private Methods - Utilities
|
|
105265
105268
|
// ============================================================================
|
|
105266
105269
|
delay(ms) {
|
|
105267
|
-
return new Promise((
|
|
105270
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
105268
105271
|
}
|
|
105269
105272
|
};
|
|
105270
105273
|
}
|
|
@@ -115075,7 +115078,7 @@ function startWorkStealingTimer(config, doStealing) {
|
|
|
115075
115078
|
clearInterval(timer);
|
|
115076
115079
|
return;
|
|
115077
115080
|
}
|
|
115078
|
-
await new Promise((
|
|
115081
|
+
await new Promise((resolve10) => setTimeout(resolve10, backoffMs));
|
|
115079
115082
|
}
|
|
115080
115083
|
}, config.workStealing.checkInterval);
|
|
115081
115084
|
return timer;
|
|
@@ -134566,13 +134569,13 @@ var init_trajectory_bridge = __esm({
|
|
|
134566
134569
|
*/
|
|
134567
134570
|
async persistTrajectory(trajectory) {
|
|
134568
134571
|
try {
|
|
134569
|
-
const { join:
|
|
134572
|
+
const { join: join29 } = await import("path");
|
|
134570
134573
|
const { existsSync: existsSync20, mkdirSync: mkdirSync6 } = await import("fs");
|
|
134571
134574
|
const { createRequire: createRequire12 } = await import("module");
|
|
134572
134575
|
const require3 = createRequire12(import.meta.url);
|
|
134573
134576
|
const { openDatabase: openDatabase2 } = require3("../../shared/safe-db.js");
|
|
134574
|
-
const dbPath =
|
|
134575
|
-
const dir =
|
|
134577
|
+
const dbPath = join29(this.options.projectRoot, ".agentic-qe", "trajectories.db");
|
|
134578
|
+
const dir = join29(this.options.projectRoot, ".agentic-qe");
|
|
134576
134579
|
if (!existsSync20(dir)) {
|
|
134577
134580
|
mkdirSync6(dir, { recursive: true });
|
|
134578
134581
|
}
|
|
@@ -134939,7 +134942,7 @@ var init_pretrain_bridge = __esm({
|
|
|
134939
134942
|
try {
|
|
134940
134943
|
const glob = await import("fast-glob");
|
|
134941
134944
|
const { existsSync: existsSync20, readFileSync: readFileSync17 } = await import("fs");
|
|
134942
|
-
const { join:
|
|
134945
|
+
const { join: join29 } = await import("path");
|
|
134943
134946
|
const patterns = depth === "shallow" ? ["*.ts", "*.js", "*.json"] : depth === "medium" ? ["**/*.ts", "**/*.js", "**/*.json", "**/*.py"] : ["**/*"];
|
|
134944
134947
|
const ignore = ["node_modules/**", "dist/**", "coverage/**", ".git/**"];
|
|
134945
134948
|
const files = await glob.default(patterns, {
|
|
@@ -134956,7 +134959,7 @@ var init_pretrain_bridge = __esm({
|
|
|
134956
134959
|
if (file.endsWith(".go")) languages.add("go");
|
|
134957
134960
|
if (file.endsWith(".rs")) languages.add("rust");
|
|
134958
134961
|
}
|
|
134959
|
-
const packageJsonPath =
|
|
134962
|
+
const packageJsonPath = join29(targetPath, "package.json");
|
|
134960
134963
|
if (existsSync20(packageJsonPath)) {
|
|
134961
134964
|
try {
|
|
134962
134965
|
const pkg2 = safeJsonParse(readFileSync17(packageJsonPath, "utf-8"));
|
|
@@ -139082,7 +139085,7 @@ Ensure prime-radiant-advanced-wasm is installed.`
|
|
|
139082
139085
|
* Sleep for a specified duration.
|
|
139083
139086
|
*/
|
|
139084
139087
|
sleep(ms) {
|
|
139085
|
-
return new Promise((
|
|
139088
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
139086
139089
|
}
|
|
139087
139090
|
// ===========================================================================
|
|
139088
139091
|
// ADR-052 A4.3: Fallback Mode Management
|
|
@@ -140864,7 +140867,7 @@ var init_aqe_learning_engine = __esm({
|
|
|
140864
140867
|
try {
|
|
140865
140868
|
const glob = await import("fast-glob");
|
|
140866
140869
|
const { existsSync: existsSync20, readFileSync: readFileSync17 } = await import("fs");
|
|
140867
|
-
const { join:
|
|
140870
|
+
const { join: join29 } = await import("path");
|
|
140868
140871
|
const patterns = depth === "shallow" ? ["*.ts", "*.js", "*.json"] : depth === "medium" ? ["**/*.ts", "**/*.js", "**/*.json", "**/*.py"] : ["**/*"];
|
|
140869
140872
|
const ignore = ["node_modules/**", "dist/**", "coverage/**", ".git/**"];
|
|
140870
140873
|
const files = await glob.default(patterns, {
|
|
@@ -140881,7 +140884,7 @@ var init_aqe_learning_engine = __esm({
|
|
|
140881
140884
|
if (file.endsWith(".go")) languages.add("go");
|
|
140882
140885
|
if (file.endsWith(".rs")) languages.add("rust");
|
|
140883
140886
|
}
|
|
140884
|
-
const packageJsonPath =
|
|
140887
|
+
const packageJsonPath = join29(targetPath, "package.json");
|
|
140885
140888
|
if (existsSync20(packageJsonPath)) {
|
|
140886
140889
|
try {
|
|
140887
140890
|
const pkg2 = safeJsonParse(readFileSync17(packageJsonPath, "utf-8"));
|
|
@@ -144467,11 +144470,11 @@ async function parseJSONCoverage(jsonPath, projectRoot) {
|
|
|
144467
144470
|
}
|
|
144468
144471
|
async function parseCoverage(coveragePath, projectRoot) {
|
|
144469
144472
|
const ext = path21.extname(coveragePath).toLowerCase();
|
|
144470
|
-
const
|
|
144471
|
-
if (ext === ".json" ||
|
|
144473
|
+
const basename7 = path21.basename(coveragePath).toLowerCase();
|
|
144474
|
+
if (ext === ".json" || basename7.includes("coverage-final")) {
|
|
144472
144475
|
return parseJSONCoverage(coveragePath, projectRoot);
|
|
144473
144476
|
}
|
|
144474
|
-
if (
|
|
144477
|
+
if (basename7 === "lcov.info" || ext === ".info" || basename7.includes("lcov")) {
|
|
144475
144478
|
return parseLCOV(coveragePath, projectRoot);
|
|
144476
144479
|
}
|
|
144477
144480
|
const content = await fs22.readFile(coveragePath, "utf-8");
|
|
@@ -151596,31 +151599,16 @@ var init_goap_planner = __esm({
|
|
|
151596
151599
|
let current = state2;
|
|
151597
151600
|
for (let i58 = 0; i58 < parts.length - 1; i58++) {
|
|
151598
151601
|
const part = parts[i58];
|
|
151599
|
-
if (DANGEROUS_PROPS.has(part)) {
|
|
151600
|
-
console.warn(`[GOAPPlanner] Blocked prototype pollution: ${part}`);
|
|
151601
|
-
return;
|
|
151602
|
-
}
|
|
151603
151602
|
if (!Object.hasOwn(current, part)) {
|
|
151604
|
-
Object.
|
|
151605
|
-
|
|
151606
|
-
writable: true,
|
|
151607
|
-
enumerable: true,
|
|
151608
|
-
configurable: true
|
|
151609
|
-
});
|
|
151603
|
+
const container = /* @__PURE__ */ Object.create(null);
|
|
151604
|
+
Object.defineProperty(current, part, { value: container, writable: true, enumerable: true, configurable: true });
|
|
151610
151605
|
}
|
|
151611
|
-
|
|
151606
|
+
const desc = Object.getOwnPropertyDescriptor(current, part);
|
|
151607
|
+
if (!desc) return;
|
|
151608
|
+
current = desc.value;
|
|
151612
151609
|
}
|
|
151613
151610
|
const finalKey = parts[parts.length - 1];
|
|
151614
|
-
|
|
151615
|
-
console.warn(`[GOAPPlanner] Blocked prototype pollution: ${finalKey}`);
|
|
151616
|
-
return;
|
|
151617
|
-
}
|
|
151618
|
-
Object.defineProperty(current, finalKey, {
|
|
151619
|
-
value,
|
|
151620
|
-
writable: true,
|
|
151621
|
-
enumerable: true,
|
|
151622
|
-
configurable: true
|
|
151623
|
-
});
|
|
151611
|
+
Object.defineProperty(current, finalKey, { value, writable: true, enumerable: true, configurable: true });
|
|
151624
151612
|
}
|
|
151625
151613
|
/**
|
|
151626
151614
|
* Create hash of state for deduplication
|
|
@@ -152663,31 +152651,16 @@ Expected effects:`;
|
|
|
152663
152651
|
let current = state2;
|
|
152664
152652
|
for (let i58 = 0; i58 < parts.length - 1; i58++) {
|
|
152665
152653
|
const part = parts[i58];
|
|
152666
|
-
if (dangerousProps.has(part)) {
|
|
152667
|
-
console.warn(`[PlanExecutor] Blocked prototype pollution: ${part}`);
|
|
152668
|
-
return;
|
|
152669
|
-
}
|
|
152670
152654
|
if (!Object.hasOwn(current, part)) {
|
|
152671
|
-
Object.
|
|
152672
|
-
|
|
152673
|
-
writable: true,
|
|
152674
|
-
enumerable: true,
|
|
152675
|
-
configurable: true
|
|
152676
|
-
});
|
|
152655
|
+
const container = /* @__PURE__ */ Object.create(null);
|
|
152656
|
+
Object.defineProperty(current, part, { value: container, writable: true, enumerable: true, configurable: true });
|
|
152677
152657
|
}
|
|
152678
|
-
|
|
152658
|
+
const desc = Object.getOwnPropertyDescriptor(current, part);
|
|
152659
|
+
if (!desc) return;
|
|
152660
|
+
current = desc.value;
|
|
152679
152661
|
}
|
|
152680
152662
|
const finalKey = parts[parts.length - 1];
|
|
152681
|
-
|
|
152682
|
-
console.warn(`[PlanExecutor] Blocked prototype pollution: ${finalKey}`);
|
|
152683
|
-
return;
|
|
152684
|
-
}
|
|
152685
|
-
Object.defineProperty(current, finalKey, {
|
|
152686
|
-
value,
|
|
152687
|
-
writable: true,
|
|
152688
|
-
enumerable: true,
|
|
152689
|
-
configurable: true
|
|
152690
|
-
});
|
|
152663
|
+
Object.defineProperty(current, finalKey, { value, writable: true, enumerable: true, configurable: true });
|
|
152691
152664
|
}
|
|
152692
152665
|
/**
|
|
152693
152666
|
* Deep clone world state.
|
|
@@ -152713,7 +152686,7 @@ Expected effects:`;
|
|
|
152713
152686
|
* Delay helper for exponential backoff
|
|
152714
152687
|
*/
|
|
152715
152688
|
delay(ms) {
|
|
152716
|
-
return new Promise((
|
|
152689
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
152717
152690
|
}
|
|
152718
152691
|
// ==========================================================================
|
|
152719
152692
|
// Cleanup
|
|
@@ -152745,7 +152718,7 @@ Expected effects:`;
|
|
|
152745
152718
|
this.executionDelay = options?.executionDelay ?? 100;
|
|
152746
152719
|
}
|
|
152747
152720
|
async spawn(agentType, task) {
|
|
152748
|
-
await new Promise((
|
|
152721
|
+
await new Promise((resolve10) => setTimeout(resolve10, this.executionDelay));
|
|
152749
152722
|
const success = secureRandom() < this.successRate;
|
|
152750
152723
|
const agentId = `mock-agent-${randomUUID23().slice(0, 8)}`;
|
|
152751
152724
|
if (success) {
|
|
@@ -156712,6 +156685,3568 @@ var init_analyze2 = __esm({
|
|
|
156712
156685
|
}
|
|
156713
156686
|
});
|
|
156714
156687
|
|
|
156688
|
+
// src/test-scheduling/interfaces.ts
|
|
156689
|
+
var DEFAULT_TEST_PHASES;
|
|
156690
|
+
var init_interfaces7 = __esm({
|
|
156691
|
+
"src/test-scheduling/interfaces.ts"() {
|
|
156692
|
+
"use strict";
|
|
156693
|
+
DEFAULT_TEST_PHASES = [
|
|
156694
|
+
{
|
|
156695
|
+
id: "unit",
|
|
156696
|
+
name: "Unit Tests",
|
|
156697
|
+
testTypes: ["unit"],
|
|
156698
|
+
testPatterns: ["**/*.test.ts", "**/*.spec.ts", "!**/*.integration.*", "!**/*.e2e.*"],
|
|
156699
|
+
thresholds: { minPassRate: 0.99, maxFlakyRatio: 0.01, minCoverage: 0.8 },
|
|
156700
|
+
parallelism: 8,
|
|
156701
|
+
timeoutMs: 6e4,
|
|
156702
|
+
failFast: true
|
|
156703
|
+
},
|
|
156704
|
+
{
|
|
156705
|
+
id: "integration",
|
|
156706
|
+
name: "Integration Tests",
|
|
156707
|
+
testTypes: ["integration", "contract"],
|
|
156708
|
+
testPatterns: ["**/*.integration.test.ts", "**/*.integration.spec.ts"],
|
|
156709
|
+
thresholds: { minPassRate: 0.95, maxFlakyRatio: 0.05, minCoverage: 0.7 },
|
|
156710
|
+
parallelism: 4,
|
|
156711
|
+
timeoutMs: 3e5,
|
|
156712
|
+
failFast: false
|
|
156713
|
+
},
|
|
156714
|
+
{
|
|
156715
|
+
id: "e2e",
|
|
156716
|
+
name: "E2E Tests",
|
|
156717
|
+
testTypes: ["e2e", "visual"],
|
|
156718
|
+
testPatterns: ["**/*.e2e.test.ts", "**/*.e2e.spec.ts"],
|
|
156719
|
+
thresholds: { minPassRate: 0.9, maxFlakyRatio: 0.1, minCoverage: 0.5 },
|
|
156720
|
+
parallelism: 2,
|
|
156721
|
+
timeoutMs: 6e5,
|
|
156722
|
+
failFast: false
|
|
156723
|
+
}
|
|
156724
|
+
];
|
|
156725
|
+
}
|
|
156726
|
+
});
|
|
156727
|
+
|
|
156728
|
+
// src/test-scheduling/phase-scheduler.ts
|
|
156729
|
+
function createPhaseScheduler(executor2, config) {
|
|
156730
|
+
return new PhaseScheduler(executor2, {
|
|
156731
|
+
phases: DEFAULT_TEST_PHASES,
|
|
156732
|
+
failFast: true,
|
|
156733
|
+
retryFailedPhases: false,
|
|
156734
|
+
maxRetries: 1,
|
|
156735
|
+
...config
|
|
156736
|
+
});
|
|
156737
|
+
}
|
|
156738
|
+
function checkQualityThresholds(result, thresholds) {
|
|
156739
|
+
return result.passRate >= thresholds.minPassRate && result.flakyRatio <= thresholds.maxFlakyRatio && result.coverage >= thresholds.minCoverage;
|
|
156740
|
+
}
|
|
156741
|
+
var PhaseScheduler;
|
|
156742
|
+
var init_phase_scheduler = __esm({
|
|
156743
|
+
"src/test-scheduling/phase-scheduler.ts"() {
|
|
156744
|
+
"use strict";
|
|
156745
|
+
init_interfaces7();
|
|
156746
|
+
PhaseScheduler = class {
|
|
156747
|
+
constructor(executor2, config = {
|
|
156748
|
+
phases: DEFAULT_TEST_PHASES,
|
|
156749
|
+
failFast: true,
|
|
156750
|
+
retryFailedPhases: false,
|
|
156751
|
+
maxRetries: 1
|
|
156752
|
+
}) {
|
|
156753
|
+
this.executor = executor2;
|
|
156754
|
+
this.config = config;
|
|
156755
|
+
}
|
|
156756
|
+
state = "idle";
|
|
156757
|
+
currentPhaseIndex = 0;
|
|
156758
|
+
results = [];
|
|
156759
|
+
startTime;
|
|
156760
|
+
endTime;
|
|
156761
|
+
abortController;
|
|
156762
|
+
// --------------------------------------------------------------------------
|
|
156763
|
+
// Public API
|
|
156764
|
+
// --------------------------------------------------------------------------
|
|
156765
|
+
/**
|
|
156766
|
+
* Run all test phases sequentially
|
|
156767
|
+
*/
|
|
156768
|
+
async run() {
|
|
156769
|
+
if (this.state === "running") {
|
|
156770
|
+
throw new Error("Scheduler is already running");
|
|
156771
|
+
}
|
|
156772
|
+
this.state = "running";
|
|
156773
|
+
this.currentPhaseIndex = 0;
|
|
156774
|
+
this.results = [];
|
|
156775
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
156776
|
+
this.abortController = new AbortController();
|
|
156777
|
+
try {
|
|
156778
|
+
for (const phase of this.config.phases) {
|
|
156779
|
+
if (this.state === "paused") {
|
|
156780
|
+
await this.waitForResume();
|
|
156781
|
+
}
|
|
156782
|
+
if (this.abortController.signal.aborted) {
|
|
156783
|
+
break;
|
|
156784
|
+
}
|
|
156785
|
+
const result = await this.executePhaseWithRetry(phase);
|
|
156786
|
+
this.results.push(result);
|
|
156787
|
+
this.currentPhaseIndex++;
|
|
156788
|
+
this.config.onPhaseComplete?.(result);
|
|
156789
|
+
if (!result.success && this.config.failFast) {
|
|
156790
|
+
this.state = "failed";
|
|
156791
|
+
break;
|
|
156792
|
+
}
|
|
156793
|
+
}
|
|
156794
|
+
this.endTime = /* @__PURE__ */ new Date();
|
|
156795
|
+
this.state = this.results.every((r54) => r54.success) ? "completed" : "failed";
|
|
156796
|
+
this.config.onAllComplete?.(this.results);
|
|
156797
|
+
return this.results;
|
|
156798
|
+
} catch (error) {
|
|
156799
|
+
this.state = "failed";
|
|
156800
|
+
this.endTime = /* @__PURE__ */ new Date();
|
|
156801
|
+
throw error;
|
|
156802
|
+
}
|
|
156803
|
+
}
|
|
156804
|
+
/**
|
|
156805
|
+
* Run a specific phase by ID
|
|
156806
|
+
*/
|
|
156807
|
+
async runPhase(phaseId) {
|
|
156808
|
+
const phase = this.config.phases.find((p74) => p74.id === phaseId);
|
|
156809
|
+
if (!phase) {
|
|
156810
|
+
throw new Error(`Phase not found: ${phaseId}`);
|
|
156811
|
+
}
|
|
156812
|
+
return this.executePhaseWithRetry(phase);
|
|
156813
|
+
}
|
|
156814
|
+
/**
|
|
156815
|
+
* Pause execution after current phase completes
|
|
156816
|
+
*/
|
|
156817
|
+
pause() {
|
|
156818
|
+
if (this.state === "running") {
|
|
156819
|
+
this.state = "paused";
|
|
156820
|
+
}
|
|
156821
|
+
}
|
|
156822
|
+
/**
|
|
156823
|
+
* Resume paused execution
|
|
156824
|
+
*/
|
|
156825
|
+
resume() {
|
|
156826
|
+
if (this.state === "paused") {
|
|
156827
|
+
this.state = "running";
|
|
156828
|
+
}
|
|
156829
|
+
}
|
|
156830
|
+
/**
|
|
156831
|
+
* Abort execution immediately
|
|
156832
|
+
*/
|
|
156833
|
+
async abort() {
|
|
156834
|
+
this.abortController?.abort();
|
|
156835
|
+
await this.executor.abort();
|
|
156836
|
+
this.state = "idle";
|
|
156837
|
+
}
|
|
156838
|
+
/**
|
|
156839
|
+
* Get current scheduler stats
|
|
156840
|
+
*/
|
|
156841
|
+
getStats() {
|
|
156842
|
+
const durationMs = this.startTime && this.endTime ? this.endTime.getTime() - this.startTime.getTime() : this.startTime ? Date.now() - this.startTime.getTime() : void 0;
|
|
156843
|
+
return {
|
|
156844
|
+
state: this.state,
|
|
156845
|
+
currentPhaseIndex: this.currentPhaseIndex,
|
|
156846
|
+
totalPhases: this.config.phases.length,
|
|
156847
|
+
completedPhases: this.results.filter((r54) => r54.success).length,
|
|
156848
|
+
failedPhases: this.results.filter((r54) => !r54.success).length,
|
|
156849
|
+
results: [...this.results],
|
|
156850
|
+
startTime: this.startTime,
|
|
156851
|
+
endTime: this.endTime,
|
|
156852
|
+
durationMs
|
|
156853
|
+
};
|
|
156854
|
+
}
|
|
156855
|
+
/**
|
|
156856
|
+
* Check if scheduler is ready to run
|
|
156857
|
+
*/
|
|
156858
|
+
async isReady() {
|
|
156859
|
+
return this.executor.isReady();
|
|
156860
|
+
}
|
|
156861
|
+
// --------------------------------------------------------------------------
|
|
156862
|
+
// Private Methods
|
|
156863
|
+
// --------------------------------------------------------------------------
|
|
156864
|
+
async executePhaseWithRetry(phase) {
|
|
156865
|
+
let lastResult;
|
|
156866
|
+
const maxAttempts = this.config.retryFailedPhases ? this.config.maxRetries : 1;
|
|
156867
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
156868
|
+
try {
|
|
156869
|
+
lastResult = await this.executor.execute(phase);
|
|
156870
|
+
if (lastResult.success || !this.config.retryFailedPhases) {
|
|
156871
|
+
return lastResult;
|
|
156872
|
+
}
|
|
156873
|
+
} catch (error) {
|
|
156874
|
+
this.config.onError?.(error, phase);
|
|
156875
|
+
if (attempt === maxAttempts) {
|
|
156876
|
+
return this.createErrorResult(phase, error);
|
|
156877
|
+
}
|
|
156878
|
+
}
|
|
156879
|
+
}
|
|
156880
|
+
return lastResult;
|
|
156881
|
+
}
|
|
156882
|
+
createErrorResult(phase, error) {
|
|
156883
|
+
return {
|
|
156884
|
+
phaseId: phase.id,
|
|
156885
|
+
phaseName: phase.name,
|
|
156886
|
+
success: false,
|
|
156887
|
+
passRate: 0,
|
|
156888
|
+
flakyRatio: 0,
|
|
156889
|
+
coverage: 0,
|
|
156890
|
+
durationMs: 0,
|
|
156891
|
+
totalTests: 0,
|
|
156892
|
+
passed: 0,
|
|
156893
|
+
failed: 0,
|
|
156894
|
+
skipped: 0,
|
|
156895
|
+
testResults: [],
|
|
156896
|
+
flakyTests: [],
|
|
156897
|
+
error: error.message
|
|
156898
|
+
};
|
|
156899
|
+
}
|
|
156900
|
+
waitForResume() {
|
|
156901
|
+
return new Promise((resolve10) => {
|
|
156902
|
+
const check = () => {
|
|
156903
|
+
if (this.state !== "paused") {
|
|
156904
|
+
resolve10();
|
|
156905
|
+
} else {
|
|
156906
|
+
setTimeout(check, 100);
|
|
156907
|
+
}
|
|
156908
|
+
};
|
|
156909
|
+
check();
|
|
156910
|
+
});
|
|
156911
|
+
}
|
|
156912
|
+
};
|
|
156913
|
+
}
|
|
156914
|
+
});
|
|
156915
|
+
|
|
156916
|
+
// src/test-scheduling/executors/vitest-executor.ts
|
|
156917
|
+
import { spawn as spawn5 } from "child_process";
|
|
156918
|
+
function createVitestExecutor(config) {
|
|
156919
|
+
return new VitestPhaseExecutor(config);
|
|
156920
|
+
}
|
|
156921
|
+
var VitestPhaseExecutor;
|
|
156922
|
+
var init_vitest_executor = __esm({
|
|
156923
|
+
"src/test-scheduling/executors/vitest-executor.ts"() {
|
|
156924
|
+
"use strict";
|
|
156925
|
+
init_safe_json();
|
|
156926
|
+
VitestPhaseExecutor = class {
|
|
156927
|
+
constructor(config = {}) {
|
|
156928
|
+
this.config = config;
|
|
156929
|
+
}
|
|
156930
|
+
currentProcess = null;
|
|
156931
|
+
isAborted = false;
|
|
156932
|
+
// --------------------------------------------------------------------------
|
|
156933
|
+
// PhaseExecutor Interface
|
|
156934
|
+
// --------------------------------------------------------------------------
|
|
156935
|
+
async execute(phase, testFiles) {
|
|
156936
|
+
this.isAborted = false;
|
|
156937
|
+
const startTime = Date.now();
|
|
156938
|
+
try {
|
|
156939
|
+
const args = this.buildArgs(phase, testFiles);
|
|
156940
|
+
const result = await this.runVitest(args, phase.timeoutMs);
|
|
156941
|
+
const endTime = Date.now();
|
|
156942
|
+
return this.parseResult(phase, result, endTime - startTime);
|
|
156943
|
+
} catch (error) {
|
|
156944
|
+
const endTime = Date.now();
|
|
156945
|
+
return this.createErrorResult(phase, error, endTime - startTime);
|
|
156946
|
+
}
|
|
156947
|
+
}
|
|
156948
|
+
async isReady() {
|
|
156949
|
+
try {
|
|
156950
|
+
const result = await this.runCommand("npx", ["vitest", "--version"], 5e3);
|
|
156951
|
+
return result.exitCode === 0;
|
|
156952
|
+
} catch {
|
|
156953
|
+
return false;
|
|
156954
|
+
}
|
|
156955
|
+
}
|
|
156956
|
+
getName() {
|
|
156957
|
+
return "vitest-executor";
|
|
156958
|
+
}
|
|
156959
|
+
async abort() {
|
|
156960
|
+
this.isAborted = true;
|
|
156961
|
+
if (this.currentProcess) {
|
|
156962
|
+
this.currentProcess.kill("SIGTERM");
|
|
156963
|
+
this.currentProcess = null;
|
|
156964
|
+
}
|
|
156965
|
+
}
|
|
156966
|
+
// --------------------------------------------------------------------------
|
|
156967
|
+
// Private Methods
|
|
156968
|
+
// --------------------------------------------------------------------------
|
|
156969
|
+
buildArgs(phase, testFiles) {
|
|
156970
|
+
const args = ["vitest", "run", "--reporter=json"];
|
|
156971
|
+
if (testFiles && testFiles.length > 0) {
|
|
156972
|
+
args.push(...testFiles);
|
|
156973
|
+
} else {
|
|
156974
|
+
for (const pattern of phase.testPatterns) {
|
|
156975
|
+
if (pattern.startsWith("!")) {
|
|
156976
|
+
args.push("--exclude", pattern.slice(1));
|
|
156977
|
+
} else {
|
|
156978
|
+
args.push(pattern);
|
|
156979
|
+
}
|
|
156980
|
+
}
|
|
156981
|
+
}
|
|
156982
|
+
if (phase.parallelism > 0) {
|
|
156983
|
+
args.push("--pool", "threads");
|
|
156984
|
+
args.push("--poolOptions.threads.maxThreads", String(phase.parallelism));
|
|
156985
|
+
}
|
|
156986
|
+
if (phase.failFast) {
|
|
156987
|
+
args.push("--bail", "1");
|
|
156988
|
+
}
|
|
156989
|
+
args.push("--coverage");
|
|
156990
|
+
args.push("--coverage.reporter", this.config.coverageReporter || "json");
|
|
156991
|
+
if (this.config.coverageDir) {
|
|
156992
|
+
args.push("--coverage.reportsDirectory", this.config.coverageDir);
|
|
156993
|
+
}
|
|
156994
|
+
if (this.config.extraArgs) {
|
|
156995
|
+
args.push(...this.config.extraArgs);
|
|
156996
|
+
}
|
|
156997
|
+
return args;
|
|
156998
|
+
}
|
|
156999
|
+
async runVitest(args, timeoutMs) {
|
|
157000
|
+
const { stdout, exitCode } = await this.runCommand(
|
|
157001
|
+
this.config.vitestPath || "npx",
|
|
157002
|
+
args,
|
|
157003
|
+
timeoutMs
|
|
157004
|
+
);
|
|
157005
|
+
try {
|
|
157006
|
+
const jsonStart = stdout.indexOf("{");
|
|
157007
|
+
const jsonEnd = stdout.lastIndexOf("}");
|
|
157008
|
+
if (jsonStart === -1 || jsonEnd === -1) {
|
|
157009
|
+
throw new Error("No JSON output from Vitest");
|
|
157010
|
+
}
|
|
157011
|
+
const jsonStr = stdout.slice(jsonStart, jsonEnd + 1);
|
|
157012
|
+
return safeJsonParse(jsonStr);
|
|
157013
|
+
} catch (parseError) {
|
|
157014
|
+
return {
|
|
157015
|
+
numTotalTestSuites: 0,
|
|
157016
|
+
numPassedTestSuites: 0,
|
|
157017
|
+
numFailedTestSuites: 0,
|
|
157018
|
+
numTotalTests: 0,
|
|
157019
|
+
numPassedTests: 0,
|
|
157020
|
+
numFailedTests: 0,
|
|
157021
|
+
numPendingTests: 0,
|
|
157022
|
+
success: exitCode === 0,
|
|
157023
|
+
startTime: Date.now(),
|
|
157024
|
+
testResults: []
|
|
157025
|
+
};
|
|
157026
|
+
}
|
|
157027
|
+
}
|
|
157028
|
+
runCommand(command, args, timeoutMs) {
|
|
157029
|
+
return new Promise((resolve10, reject) => {
|
|
157030
|
+
let stdout = "";
|
|
157031
|
+
let stderr = "";
|
|
157032
|
+
const executable = process.platform === "win32" && command === "npx" ? "npx.cmd" : command;
|
|
157033
|
+
this.currentProcess = spawn5(executable, args, {
|
|
157034
|
+
cwd: this.config.cwd || process.cwd(),
|
|
157035
|
+
env: { ...process.env, ...this.config.env },
|
|
157036
|
+
shell: false
|
|
157037
|
+
});
|
|
157038
|
+
const timeout = setTimeout(() => {
|
|
157039
|
+
this.currentProcess?.kill("SIGTERM");
|
|
157040
|
+
reject(new Error(`Test execution timed out after ${timeoutMs}ms`));
|
|
157041
|
+
}, timeoutMs);
|
|
157042
|
+
this.currentProcess.stdout?.on("data", (data) => {
|
|
157043
|
+
stdout += data.toString();
|
|
157044
|
+
});
|
|
157045
|
+
this.currentProcess.stderr?.on("data", (data) => {
|
|
157046
|
+
stderr += data.toString();
|
|
157047
|
+
});
|
|
157048
|
+
this.currentProcess.on("close", (code) => {
|
|
157049
|
+
clearTimeout(timeout);
|
|
157050
|
+
this.currentProcess = null;
|
|
157051
|
+
if (this.isAborted) {
|
|
157052
|
+
reject(new Error("Test execution aborted"));
|
|
157053
|
+
} else {
|
|
157054
|
+
resolve10({ stdout, stderr, exitCode: code ?? 1 });
|
|
157055
|
+
}
|
|
157056
|
+
});
|
|
157057
|
+
this.currentProcess.on("error", (error) => {
|
|
157058
|
+
clearTimeout(timeout);
|
|
157059
|
+
this.currentProcess = null;
|
|
157060
|
+
reject(error);
|
|
157061
|
+
});
|
|
157062
|
+
});
|
|
157063
|
+
}
|
|
157064
|
+
async parseResult(phase, vitestResult, durationMs) {
|
|
157065
|
+
const testResults = [];
|
|
157066
|
+
for (const file of vitestResult.testResults) {
|
|
157067
|
+
for (const assertion of file.assertionResults) {
|
|
157068
|
+
const testResult = {
|
|
157069
|
+
file: file.name,
|
|
157070
|
+
name: assertion.title,
|
|
157071
|
+
suite: assertion.ancestorTitles.join(" > "),
|
|
157072
|
+
passed: assertion.status === "passed",
|
|
157073
|
+
durationMs: assertion.duration || 0,
|
|
157074
|
+
// NOTE: Vitest JSON reporter doesn't include retry data.
|
|
157075
|
+
// Flakiness is detected historically via FlakyTracker.
|
|
157076
|
+
retries: 0,
|
|
157077
|
+
error: assertion.failureMessages.join("\n") || void 0
|
|
157078
|
+
};
|
|
157079
|
+
testResults.push(testResult);
|
|
157080
|
+
}
|
|
157081
|
+
}
|
|
157082
|
+
const tracker = this.config.flakyTracker;
|
|
157083
|
+
if (tracker) {
|
|
157084
|
+
tracker.recordResults(testResults);
|
|
157085
|
+
}
|
|
157086
|
+
const totalTests = vitestResult.numTotalTests;
|
|
157087
|
+
const passed = vitestResult.numPassedTests;
|
|
157088
|
+
const failed = vitestResult.numFailedTests;
|
|
157089
|
+
const skipped = vitestResult.numPendingTests;
|
|
157090
|
+
const passRate = totalTests > 0 ? passed / totalTests : 0;
|
|
157091
|
+
let flakyTests = [];
|
|
157092
|
+
let flakyRatio = 0;
|
|
157093
|
+
if (tracker) {
|
|
157094
|
+
const analysis = tracker.analyze();
|
|
157095
|
+
flakyTests = analysis.flakyTests.map((r54) => r54.testId);
|
|
157096
|
+
const runTestIds = new Set(
|
|
157097
|
+
testResults.map((r54) => `${r54.file}:${r54.suite}:${r54.name}`)
|
|
157098
|
+
);
|
|
157099
|
+
const flakyInRun = flakyTests.filter((id) => runTestIds.has(id));
|
|
157100
|
+
flakyRatio = totalTests > 0 ? flakyInRun.length / totalTests : 0;
|
|
157101
|
+
}
|
|
157102
|
+
const coverage = await this.getCoverageFromReport();
|
|
157103
|
+
const success = passRate >= phase.thresholds.minPassRate && flakyRatio <= phase.thresholds.maxFlakyRatio && coverage >= phase.thresholds.minCoverage;
|
|
157104
|
+
return {
|
|
157105
|
+
phaseId: phase.id,
|
|
157106
|
+
phaseName: phase.name,
|
|
157107
|
+
success,
|
|
157108
|
+
passRate,
|
|
157109
|
+
flakyRatio,
|
|
157110
|
+
coverage,
|
|
157111
|
+
durationMs,
|
|
157112
|
+
totalTests,
|
|
157113
|
+
passed,
|
|
157114
|
+
failed,
|
|
157115
|
+
skipped,
|
|
157116
|
+
testResults,
|
|
157117
|
+
flakyTests
|
|
157118
|
+
};
|
|
157119
|
+
}
|
|
157120
|
+
async getCoverageFromReport() {
|
|
157121
|
+
try {
|
|
157122
|
+
const fs26 = await import("fs/promises");
|
|
157123
|
+
const path26 = await import("path");
|
|
157124
|
+
const coverageDir = this.config.coverageDir || "coverage";
|
|
157125
|
+
const coverageFile = path26.join(
|
|
157126
|
+
this.config.cwd || process.cwd(),
|
|
157127
|
+
coverageDir,
|
|
157128
|
+
"coverage-summary.json"
|
|
157129
|
+
);
|
|
157130
|
+
const content = await fs26.readFile(coverageFile, "utf-8");
|
|
157131
|
+
const coverage = safeJsonParse(content);
|
|
157132
|
+
return (coverage.total?.lines?.pct ?? 0) / 100;
|
|
157133
|
+
} catch {
|
|
157134
|
+
return 0;
|
|
157135
|
+
}
|
|
157136
|
+
}
|
|
157137
|
+
createErrorResult(phase, error, durationMs) {
|
|
157138
|
+
return {
|
|
157139
|
+
phaseId: phase.id,
|
|
157140
|
+
phaseName: phase.name,
|
|
157141
|
+
success: false,
|
|
157142
|
+
passRate: 0,
|
|
157143
|
+
flakyRatio: 0,
|
|
157144
|
+
coverage: 0,
|
|
157145
|
+
durationMs,
|
|
157146
|
+
totalTests: 0,
|
|
157147
|
+
passed: 0,
|
|
157148
|
+
failed: 0,
|
|
157149
|
+
skipped: 0,
|
|
157150
|
+
testResults: [],
|
|
157151
|
+
flakyTests: [],
|
|
157152
|
+
error: error.message
|
|
157153
|
+
};
|
|
157154
|
+
}
|
|
157155
|
+
};
|
|
157156
|
+
}
|
|
157157
|
+
});
|
|
157158
|
+
|
|
157159
|
+
// src/test-scheduling/git-aware/test-selector.ts
|
|
157160
|
+
import { spawn as spawn6 } from "child_process";
|
|
157161
|
+
import { resolve as resolve7, dirname as dirname7, basename as basename4 } from "path";
|
|
157162
|
+
function createTestSelector(config) {
|
|
157163
|
+
return new GitAwareTestSelector(config);
|
|
157164
|
+
}
|
|
157165
|
+
async function getAffectedTests(impactAnalyzer, baseRef, cwd) {
|
|
157166
|
+
const selector = createTestSelector({ impactAnalyzer, baseRef, cwd });
|
|
157167
|
+
const result = await selector.selectAffectedTests();
|
|
157168
|
+
if (result.runAllTests) {
|
|
157169
|
+
return [];
|
|
157170
|
+
}
|
|
157171
|
+
return result.selectedTests;
|
|
157172
|
+
}
|
|
157173
|
+
var DEFAULT_MAPPING_RULES, GitAwareTestSelector;
|
|
157174
|
+
var init_test_selector = __esm({
|
|
157175
|
+
"src/test-scheduling/git-aware/test-selector.ts"() {
|
|
157176
|
+
"use strict";
|
|
157177
|
+
DEFAULT_MAPPING_RULES = [
|
|
157178
|
+
// src/foo/bar.ts -> tests/unit/foo/bar.test.ts
|
|
157179
|
+
{
|
|
157180
|
+
sourcePattern: /^src\/(.+)\.ts$/,
|
|
157181
|
+
toTestPaths: (match, _sourceFile) => {
|
|
157182
|
+
const path26 = match[1];
|
|
157183
|
+
return [
|
|
157184
|
+
`tests/unit/${path26}.test.ts`,
|
|
157185
|
+
`tests/unit/${path26}.spec.ts`,
|
|
157186
|
+
`tests/${path26}.test.ts`,
|
|
157187
|
+
`tests/${path26}.spec.ts`,
|
|
157188
|
+
`src/${path26}.test.ts`,
|
|
157189
|
+
`src/${path26}.spec.ts`
|
|
157190
|
+
];
|
|
157191
|
+
}
|
|
157192
|
+
},
|
|
157193
|
+
// src/foo/bar/index.ts -> tests/unit/foo/bar.test.ts
|
|
157194
|
+
{
|
|
157195
|
+
sourcePattern: /^src\/(.+)\/index\.ts$/,
|
|
157196
|
+
toTestPaths: (match, _sourceFile) => {
|
|
157197
|
+
const path26 = match[1];
|
|
157198
|
+
return [
|
|
157199
|
+
`tests/unit/${path26}.test.ts`,
|
|
157200
|
+
`tests/unit/${path26}/index.test.ts`,
|
|
157201
|
+
`tests/unit/${path26}.spec.ts`
|
|
157202
|
+
];
|
|
157203
|
+
}
|
|
157204
|
+
},
|
|
157205
|
+
// Any changed test file selects itself
|
|
157206
|
+
{
|
|
157207
|
+
sourcePattern: /\.(test|spec)\.(ts|tsx|js|jsx)$/,
|
|
157208
|
+
toTestPaths: (_match, sourceFile) => [sourceFile]
|
|
157209
|
+
},
|
|
157210
|
+
// Config files run all tests
|
|
157211
|
+
{
|
|
157212
|
+
sourcePattern: /^(vitest\.config|jest\.config|tsconfig|package\.json)/,
|
|
157213
|
+
toTestPaths: () => ["**/*.test.ts", "**/*.spec.ts"]
|
|
157214
|
+
}
|
|
157215
|
+
];
|
|
157216
|
+
GitAwareTestSelector = class {
|
|
157217
|
+
constructor(config) {
|
|
157218
|
+
this.config = config;
|
|
157219
|
+
if (!config.impactAnalyzer) {
|
|
157220
|
+
throw new Error(
|
|
157221
|
+
"GitAwareTestSelector requires impactAnalyzer. Use ImpactAnalyzerService from code-intelligence domain. NO FALLBACK TO PATTERN MATCHING - use real dependency analysis."
|
|
157222
|
+
);
|
|
157223
|
+
}
|
|
157224
|
+
this.cwd = config.cwd || process.cwd();
|
|
157225
|
+
this.baseRef = config.baseRef || "HEAD~1";
|
|
157226
|
+
this.mappingRules = config.mappingRules || DEFAULT_MAPPING_RULES;
|
|
157227
|
+
this.impactAnalyzer = config.impactAnalyzer;
|
|
157228
|
+
}
|
|
157229
|
+
cwd;
|
|
157230
|
+
baseRef;
|
|
157231
|
+
mappingRules;
|
|
157232
|
+
impactAnalyzer;
|
|
157233
|
+
// --------------------------------------------------------------------------
|
|
157234
|
+
// Public API
|
|
157235
|
+
// --------------------------------------------------------------------------
|
|
157236
|
+
/**
|
|
157237
|
+
* Get tests affected by changes since base ref
|
|
157238
|
+
*/
|
|
157239
|
+
async selectAffectedTests() {
|
|
157240
|
+
try {
|
|
157241
|
+
const changedFiles = await this.getChangedFiles();
|
|
157242
|
+
if (changedFiles.length === 0) {
|
|
157243
|
+
return {
|
|
157244
|
+
changedFiles: [],
|
|
157245
|
+
selectedTests: [],
|
|
157246
|
+
mappings: [],
|
|
157247
|
+
runAllTests: false
|
|
157248
|
+
};
|
|
157249
|
+
}
|
|
157250
|
+
const { selectedTests, mappings, runAllTests, runAllReason } = await this.mapChangesToTests(changedFiles);
|
|
157251
|
+
return {
|
|
157252
|
+
changedFiles,
|
|
157253
|
+
selectedTests: [...new Set(selectedTests)],
|
|
157254
|
+
// Dedupe
|
|
157255
|
+
mappings,
|
|
157256
|
+
runAllTests,
|
|
157257
|
+
runAllReason
|
|
157258
|
+
};
|
|
157259
|
+
} catch (error) {
|
|
157260
|
+
return {
|
|
157261
|
+
changedFiles: [],
|
|
157262
|
+
selectedTests: [],
|
|
157263
|
+
mappings: [],
|
|
157264
|
+
runAllTests: true,
|
|
157265
|
+
runAllReason: `Git error: ${error.message}`
|
|
157266
|
+
};
|
|
157267
|
+
}
|
|
157268
|
+
}
|
|
157269
|
+
/**
|
|
157270
|
+
* Get tests for a specific list of changed files
|
|
157271
|
+
*/
|
|
157272
|
+
async selectTestsForFiles(files) {
|
|
157273
|
+
const changedFiles = files.map((path26) => ({
|
|
157274
|
+
path: path26,
|
|
157275
|
+
changeType: "modified"
|
|
157276
|
+
}));
|
|
157277
|
+
const { selectedTests, mappings, runAllTests, runAllReason } = await this.mapChangesToTests(changedFiles);
|
|
157278
|
+
return {
|
|
157279
|
+
changedFiles,
|
|
157280
|
+
selectedTests: [...new Set(selectedTests)],
|
|
157281
|
+
mappings,
|
|
157282
|
+
runAllTests,
|
|
157283
|
+
runAllReason
|
|
157284
|
+
};
|
|
157285
|
+
}
|
|
157286
|
+
/**
|
|
157287
|
+
* Get changed files since base ref
|
|
157288
|
+
*/
|
|
157289
|
+
async getChangedFiles() {
|
|
157290
|
+
const output = await this.git([
|
|
157291
|
+
"diff",
|
|
157292
|
+
"--name-status",
|
|
157293
|
+
this.baseRef,
|
|
157294
|
+
"HEAD"
|
|
157295
|
+
]);
|
|
157296
|
+
const lines = output.trim().split("\n").filter(Boolean);
|
|
157297
|
+
const changedFiles = [];
|
|
157298
|
+
for (const line of lines) {
|
|
157299
|
+
const [status, ...pathParts] = line.split(" ");
|
|
157300
|
+
const path26 = pathParts.join(" ");
|
|
157301
|
+
let changeType;
|
|
157302
|
+
let previousPath;
|
|
157303
|
+
switch (status[0]) {
|
|
157304
|
+
case "A":
|
|
157305
|
+
changeType = "added";
|
|
157306
|
+
break;
|
|
157307
|
+
case "M":
|
|
157308
|
+
changeType = "modified";
|
|
157309
|
+
break;
|
|
157310
|
+
case "D":
|
|
157311
|
+
changeType = "deleted";
|
|
157312
|
+
break;
|
|
157313
|
+
case "R":
|
|
157314
|
+
changeType = "renamed";
|
|
157315
|
+
previousPath = path26;
|
|
157316
|
+
break;
|
|
157317
|
+
default:
|
|
157318
|
+
changeType = "modified";
|
|
157319
|
+
}
|
|
157320
|
+
changedFiles.push({
|
|
157321
|
+
path: changeType === "renamed" ? pathParts[1] || path26 : path26,
|
|
157322
|
+
changeType,
|
|
157323
|
+
previousPath
|
|
157324
|
+
});
|
|
157325
|
+
}
|
|
157326
|
+
return changedFiles;
|
|
157327
|
+
}
|
|
157328
|
+
/**
|
|
157329
|
+
* Get the merge base between current branch and main
|
|
157330
|
+
*/
|
|
157331
|
+
async getMergeBase(targetBranch = "main") {
|
|
157332
|
+
try {
|
|
157333
|
+
const output = await this.git(["merge-base", "HEAD", targetBranch]);
|
|
157334
|
+
return output.trim();
|
|
157335
|
+
} catch {
|
|
157336
|
+
const output = await this.git(["merge-base", "HEAD", "master"]);
|
|
157337
|
+
return output.trim();
|
|
157338
|
+
}
|
|
157339
|
+
}
|
|
157340
|
+
// --------------------------------------------------------------------------
|
|
157341
|
+
// Private Methods
|
|
157342
|
+
// --------------------------------------------------------------------------
|
|
157343
|
+
async mapChangesToTests(changedFiles) {
|
|
157344
|
+
const selectedTests = [];
|
|
157345
|
+
const mappings = [];
|
|
157346
|
+
let runAllTests = false;
|
|
157347
|
+
let runAllReason;
|
|
157348
|
+
for (const file of changedFiles) {
|
|
157349
|
+
if (this.isConfigFile(file.path)) {
|
|
157350
|
+
return {
|
|
157351
|
+
selectedTests: [],
|
|
157352
|
+
mappings: [],
|
|
157353
|
+
runAllTests: true,
|
|
157354
|
+
runAllReason: `Config file changed: ${file.path}`
|
|
157355
|
+
};
|
|
157356
|
+
}
|
|
157357
|
+
}
|
|
157358
|
+
const changedPaths = changedFiles.filter((f74) => f74.changeType !== "deleted" || this.config.includeDeletedFileTests).map((f74) => f74.path);
|
|
157359
|
+
if (changedPaths.length === 0) {
|
|
157360
|
+
return { selectedTests: [], mappings: [], runAllTests: false };
|
|
157361
|
+
}
|
|
157362
|
+
const impactResult = await this.impactAnalyzer.getImpactedTests(changedPaths);
|
|
157363
|
+
if (!impactResult.success) {
|
|
157364
|
+
const failureResult = impactResult;
|
|
157365
|
+
const errorMsg = failureResult.error instanceof Error ? failureResult.error.message : String(failureResult.error);
|
|
157366
|
+
return {
|
|
157367
|
+
selectedTests: [],
|
|
157368
|
+
mappings: [],
|
|
157369
|
+
runAllTests: true,
|
|
157370
|
+
runAllReason: `Impact analysis failed: ${errorMsg}`
|
|
157371
|
+
};
|
|
157372
|
+
}
|
|
157373
|
+
if (impactResult.value.length === 0) {
|
|
157374
|
+
return { selectedTests: [], mappings: [], runAllTests: false };
|
|
157375
|
+
}
|
|
157376
|
+
selectedTests.push(...impactResult.value);
|
|
157377
|
+
for (const changedFile of changedFiles) {
|
|
157378
|
+
if (changedFile.changeType === "deleted" && !this.config.includeDeletedFileTests) {
|
|
157379
|
+
continue;
|
|
157380
|
+
}
|
|
157381
|
+
const relatedTests = impactResult.value.filter(
|
|
157382
|
+
(test) => test.includes(basename4(changedFile.path, ".ts").replace(/\.(tsx?|jsx?)$/, ""))
|
|
157383
|
+
);
|
|
157384
|
+
if (relatedTests.length > 0) {
|
|
157385
|
+
mappings.push({
|
|
157386
|
+
sourceFile: changedFile.path,
|
|
157387
|
+
testFiles: relatedTests,
|
|
157388
|
+
confidence: 0.95
|
|
157389
|
+
// High confidence for graph-based analysis
|
|
157390
|
+
});
|
|
157391
|
+
}
|
|
157392
|
+
}
|
|
157393
|
+
return { selectedTests, mappings, runAllTests, runAllReason };
|
|
157394
|
+
}
|
|
157395
|
+
/**
|
|
157396
|
+
* Check if a file is a config file that should trigger full test run
|
|
157397
|
+
*/
|
|
157398
|
+
isConfigFile(path26) {
|
|
157399
|
+
return /^(vitest\.config|jest\.config|tsconfig|package\.json)/.test(path26);
|
|
157400
|
+
}
|
|
157401
|
+
async findTestsHeuristically(sourcePath) {
|
|
157402
|
+
const baseName = basename4(sourcePath, ".ts").replace(/\.(tsx?|jsx?)$/, "");
|
|
157403
|
+
const dir = dirname7(sourcePath);
|
|
157404
|
+
const candidates = [
|
|
157405
|
+
`${dir}/${baseName}.test.ts`,
|
|
157406
|
+
`${dir}/${baseName}.spec.ts`,
|
|
157407
|
+
`${dir}/__tests__/${baseName}.test.ts`,
|
|
157408
|
+
`tests/${dir}/${baseName}.test.ts`,
|
|
157409
|
+
`tests/unit/${dir.replace("src/", "")}/${baseName}.test.ts`
|
|
157410
|
+
];
|
|
157411
|
+
return this.filterExistingFiles(candidates);
|
|
157412
|
+
}
|
|
157413
|
+
async filterExistingFiles(files) {
|
|
157414
|
+
const fs26 = await import("fs/promises");
|
|
157415
|
+
const existing = [];
|
|
157416
|
+
for (const file of files) {
|
|
157417
|
+
try {
|
|
157418
|
+
const fullPath = resolve7(this.cwd, file);
|
|
157419
|
+
await fs26.access(fullPath);
|
|
157420
|
+
existing.push(file);
|
|
157421
|
+
} catch (error) {
|
|
157422
|
+
console.debug("[TestSelector] File access check failed:", error instanceof Error ? error.message : error);
|
|
157423
|
+
}
|
|
157424
|
+
}
|
|
157425
|
+
return existing;
|
|
157426
|
+
}
|
|
157427
|
+
git(args) {
|
|
157428
|
+
return new Promise((resolve10, reject) => {
|
|
157429
|
+
let stdout = "";
|
|
157430
|
+
let stderr = "";
|
|
157431
|
+
const proc = spawn6("git", args, { cwd: this.cwd });
|
|
157432
|
+
proc.stdout.on("data", (data) => {
|
|
157433
|
+
stdout += data.toString();
|
|
157434
|
+
});
|
|
157435
|
+
proc.stderr.on("data", (data) => {
|
|
157436
|
+
stderr += data.toString();
|
|
157437
|
+
});
|
|
157438
|
+
proc.on("close", (code) => {
|
|
157439
|
+
if (code === 0) {
|
|
157440
|
+
resolve10(stdout);
|
|
157441
|
+
} else {
|
|
157442
|
+
reject(new Error(`git ${args.join(" ")} failed: ${stderr}`));
|
|
157443
|
+
}
|
|
157444
|
+
});
|
|
157445
|
+
proc.on("error", reject);
|
|
157446
|
+
});
|
|
157447
|
+
}
|
|
157448
|
+
};
|
|
157449
|
+
}
|
|
157450
|
+
});
|
|
157451
|
+
|
|
157452
|
+
// src/test-scheduling/flaky-tracking/flaky-tracker.ts
|
|
157453
|
+
function createFlakyTracker(config) {
|
|
157454
|
+
return new FlakyTestTracker(config);
|
|
157455
|
+
}
|
|
157456
|
+
async function loadFlakyTracker(historyPath, config) {
|
|
157457
|
+
const tracker = createFlakyTracker({ ...config, historyPath });
|
|
157458
|
+
try {
|
|
157459
|
+
const fs26 = await import("fs/promises");
|
|
157460
|
+
const content = await fs26.readFile(historyPath, "utf-8");
|
|
157461
|
+
const records = safeJsonParse(content);
|
|
157462
|
+
tracker.importHistory(records);
|
|
157463
|
+
} catch (error) {
|
|
157464
|
+
console.debug("[FlakyTracker] History load failed, starting fresh:", error instanceof Error ? error.message : error);
|
|
157465
|
+
}
|
|
157466
|
+
return tracker;
|
|
157467
|
+
}
|
|
157468
|
+
async function saveFlakyTracker(tracker, historyPath) {
|
|
157469
|
+
const fs26 = await import("fs/promises");
|
|
157470
|
+
const history = tracker.exportHistory();
|
|
157471
|
+
await fs26.writeFile(historyPath, JSON.stringify(history, null, 2));
|
|
157472
|
+
}
|
|
157473
|
+
var DEFAULT_CONFIG70, FlakyTestTracker;
|
|
157474
|
+
var init_flaky_tracker = __esm({
|
|
157475
|
+
"src/test-scheduling/flaky-tracking/flaky-tracker.ts"() {
|
|
157476
|
+
"use strict";
|
|
157477
|
+
init_safe_json();
|
|
157478
|
+
DEFAULT_CONFIG70 = {
|
|
157479
|
+
minRunsForFlakiness: 5,
|
|
157480
|
+
flakinessThreshold: 0.1,
|
|
157481
|
+
// 10% failure rate = flaky
|
|
157482
|
+
maxRecentErrors: 5,
|
|
157483
|
+
historyRetentionDays: 30
|
|
157484
|
+
};
|
|
157485
|
+
FlakyTestTracker = class {
|
|
157486
|
+
records = /* @__PURE__ */ new Map();
|
|
157487
|
+
config;
|
|
157488
|
+
constructor(config) {
|
|
157489
|
+
this.config = { ...DEFAULT_CONFIG70, ...config };
|
|
157490
|
+
}
|
|
157491
|
+
// --------------------------------------------------------------------------
|
|
157492
|
+
// Public API
|
|
157493
|
+
// --------------------------------------------------------------------------
|
|
157494
|
+
/**
|
|
157495
|
+
* Record a test execution result
|
|
157496
|
+
*/
|
|
157497
|
+
recordResult(result) {
|
|
157498
|
+
const testId = this.getTestId(result);
|
|
157499
|
+
const record = this.getOrCreateRecord(testId, result);
|
|
157500
|
+
record.totalRuns++;
|
|
157501
|
+
record.lastRun = /* @__PURE__ */ new Date();
|
|
157502
|
+
if (result.passed) {
|
|
157503
|
+
record.passCount++;
|
|
157504
|
+
} else {
|
|
157505
|
+
record.failCount++;
|
|
157506
|
+
if (result.error) {
|
|
157507
|
+
record.recentErrors = [
|
|
157508
|
+
result.error,
|
|
157509
|
+
...record.recentErrors.slice(0, this.config.maxRecentErrors - 1)
|
|
157510
|
+
];
|
|
157511
|
+
}
|
|
157512
|
+
}
|
|
157513
|
+
if (result.retries > 0 && result.passed) {
|
|
157514
|
+
record.flakyCount++;
|
|
157515
|
+
record.lastFlaky = /* @__PURE__ */ new Date();
|
|
157516
|
+
}
|
|
157517
|
+
record.flakinessScore = this.calculateFlakiness(record);
|
|
157518
|
+
}
|
|
157519
|
+
/**
|
|
157520
|
+
* Record multiple test results
|
|
157521
|
+
*/
|
|
157522
|
+
recordResults(results) {
|
|
157523
|
+
for (const result of results) {
|
|
157524
|
+
this.recordResult(result);
|
|
157525
|
+
}
|
|
157526
|
+
}
|
|
157527
|
+
/**
|
|
157528
|
+
* Get flaky test record by ID
|
|
157529
|
+
*/
|
|
157530
|
+
getRecord(testId) {
|
|
157531
|
+
return this.records.get(testId);
|
|
157532
|
+
}
|
|
157533
|
+
/**
|
|
157534
|
+
* Get all flaky tests
|
|
157535
|
+
*/
|
|
157536
|
+
getFlakyTests() {
|
|
157537
|
+
return Array.from(this.records.values()).filter(
|
|
157538
|
+
(record) => record.totalRuns >= this.config.minRunsForFlakiness && record.flakinessScore >= this.config.flakinessThreshold
|
|
157539
|
+
);
|
|
157540
|
+
}
|
|
157541
|
+
/**
|
|
157542
|
+
* Check if a specific test is flaky
|
|
157543
|
+
*/
|
|
157544
|
+
isFlaky(testId) {
|
|
157545
|
+
const record = this.records.get(testId);
|
|
157546
|
+
if (!record) return false;
|
|
157547
|
+
return record.totalRuns >= this.config.minRunsForFlakiness && record.flakinessScore >= this.config.flakinessThreshold;
|
|
157548
|
+
}
|
|
157549
|
+
/**
|
|
157550
|
+
* Get comprehensive flaky analysis
|
|
157551
|
+
*/
|
|
157552
|
+
analyze() {
|
|
157553
|
+
const allRecords = Array.from(this.records.values());
|
|
157554
|
+
const flakyTests = allRecords.filter(
|
|
157555
|
+
(r54) => r54.totalRuns >= this.config.minRunsForFlakiness && r54.flakinessScore >= this.config.flakinessThreshold
|
|
157556
|
+
);
|
|
157557
|
+
const stabilizedTests = allRecords.filter(
|
|
157558
|
+
(r54) => r54.totalRuns >= this.config.minRunsForFlakiness && r54.flakyCount > 0 && r54.flakinessScore < this.config.flakinessThreshold
|
|
157559
|
+
);
|
|
157560
|
+
const insufficientData = allRecords.filter(
|
|
157561
|
+
(r54) => r54.totalRuns < this.config.minRunsForFlakiness
|
|
157562
|
+
);
|
|
157563
|
+
const totalWithSufficientData = allRecords.filter(
|
|
157564
|
+
(r54) => r54.totalRuns >= this.config.minRunsForFlakiness
|
|
157565
|
+
);
|
|
157566
|
+
const overallFlakiness = totalWithSufficientData.length > 0 ? flakyTests.length / totalWithSufficientData.length : 0;
|
|
157567
|
+
const trend = this.calculateTrend(flakyTests);
|
|
157568
|
+
return {
|
|
157569
|
+
totalTests: allRecords.length,
|
|
157570
|
+
flakyTests: flakyTests.sort((a37, b68) => b68.flakinessScore - a37.flakinessScore),
|
|
157571
|
+
stabilizedTests,
|
|
157572
|
+
insufficientData,
|
|
157573
|
+
overallFlakiness,
|
|
157574
|
+
trend
|
|
157575
|
+
};
|
|
157576
|
+
}
|
|
157577
|
+
/**
|
|
157578
|
+
* Get quarantine list (tests that should be isolated)
|
|
157579
|
+
*/
|
|
157580
|
+
getQuarantineList(threshold = 0.3) {
|
|
157581
|
+
return Array.from(this.records.values()).filter((r54) => r54.flakinessScore >= threshold).map((r54) => r54.testId);
|
|
157582
|
+
}
|
|
157583
|
+
/**
|
|
157584
|
+
* Clear old history based on retention policy
|
|
157585
|
+
*/
|
|
157586
|
+
pruneHistory() {
|
|
157587
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
157588
|
+
cutoff.setDate(cutoff.getDate() - this.config.historyRetentionDays);
|
|
157589
|
+
let pruned = 0;
|
|
157590
|
+
for (const [testId, record] of this.records) {
|
|
157591
|
+
if (record.lastRun < cutoff) {
|
|
157592
|
+
this.records.delete(testId);
|
|
157593
|
+
pruned++;
|
|
157594
|
+
}
|
|
157595
|
+
}
|
|
157596
|
+
return pruned;
|
|
157597
|
+
}
|
|
157598
|
+
/**
|
|
157599
|
+
* Export history for persistence
|
|
157600
|
+
*/
|
|
157601
|
+
exportHistory() {
|
|
157602
|
+
return Array.from(this.records.values());
|
|
157603
|
+
}
|
|
157604
|
+
/**
|
|
157605
|
+
* Import history from persistence
|
|
157606
|
+
*/
|
|
157607
|
+
importHistory(records) {
|
|
157608
|
+
for (const record of records) {
|
|
157609
|
+
const imported = {
|
|
157610
|
+
...record,
|
|
157611
|
+
lastRun: new Date(record.lastRun),
|
|
157612
|
+
lastFlaky: record.lastFlaky ? new Date(record.lastFlaky) : void 0
|
|
157613
|
+
};
|
|
157614
|
+
this.records.set(record.testId, imported);
|
|
157615
|
+
}
|
|
157616
|
+
}
|
|
157617
|
+
/**
|
|
157618
|
+
* Reset all tracking data
|
|
157619
|
+
*/
|
|
157620
|
+
reset() {
|
|
157621
|
+
this.records.clear();
|
|
157622
|
+
}
|
|
157623
|
+
// --------------------------------------------------------------------------
|
|
157624
|
+
// Private Methods
|
|
157625
|
+
// --------------------------------------------------------------------------
|
|
157626
|
+
getTestId(result) {
|
|
157627
|
+
return `${result.file}:${result.suite}:${result.name}`;
|
|
157628
|
+
}
|
|
157629
|
+
getOrCreateRecord(testId, result) {
|
|
157630
|
+
let record = this.records.get(testId);
|
|
157631
|
+
if (!record) {
|
|
157632
|
+
record = {
|
|
157633
|
+
testId,
|
|
157634
|
+
file: result.file,
|
|
157635
|
+
name: result.name,
|
|
157636
|
+
totalRuns: 0,
|
|
157637
|
+
passCount: 0,
|
|
157638
|
+
failCount: 0,
|
|
157639
|
+
flakyCount: 0,
|
|
157640
|
+
flakinessScore: 0,
|
|
157641
|
+
lastRun: /* @__PURE__ */ new Date(),
|
|
157642
|
+
recentErrors: []
|
|
157643
|
+
};
|
|
157644
|
+
this.records.set(testId, record);
|
|
157645
|
+
}
|
|
157646
|
+
return record;
|
|
157647
|
+
}
|
|
157648
|
+
calculateFlakiness(record) {
|
|
157649
|
+
if (record.totalRuns < this.config.minRunsForFlakiness) {
|
|
157650
|
+
return 0;
|
|
157651
|
+
}
|
|
157652
|
+
const flakyRatio = record.flakyCount / record.totalRuns;
|
|
157653
|
+
const passRatio = record.passCount / record.totalRuns;
|
|
157654
|
+
const inconsistencyScore = passRatio > 0 && passRatio < 1 ? Math.min(passRatio, 1 - passRatio) * 2 : 0;
|
|
157655
|
+
return Math.min(1, flakyRatio * 3 + inconsistencyScore);
|
|
157656
|
+
}
|
|
157657
|
+
calculateTrend(flakyTests) {
|
|
157658
|
+
if (flakyTests.length === 0) {
|
|
157659
|
+
return "stable";
|
|
157660
|
+
}
|
|
157661
|
+
const now = Date.now();
|
|
157662
|
+
const oneWeekAgo = now - 7 * 24 * 60 * 60 * 1e3;
|
|
157663
|
+
let recentFlaky = 0;
|
|
157664
|
+
let oldFlaky = 0;
|
|
157665
|
+
for (const test of flakyTests) {
|
|
157666
|
+
if (test.lastFlaky) {
|
|
157667
|
+
if (test.lastFlaky.getTime() > oneWeekAgo) {
|
|
157668
|
+
recentFlaky++;
|
|
157669
|
+
} else {
|
|
157670
|
+
oldFlaky++;
|
|
157671
|
+
}
|
|
157672
|
+
}
|
|
157673
|
+
}
|
|
157674
|
+
if (recentFlaky > oldFlaky * 1.5) {
|
|
157675
|
+
return "degrading";
|
|
157676
|
+
} else if (oldFlaky > recentFlaky * 1.5) {
|
|
157677
|
+
return "improving";
|
|
157678
|
+
}
|
|
157679
|
+
return "stable";
|
|
157680
|
+
}
|
|
157681
|
+
};
|
|
157682
|
+
}
|
|
157683
|
+
});
|
|
157684
|
+
|
|
157685
|
+
// src/test-scheduling/cicd/github-actions.ts
|
|
157686
|
+
function detectCIEnvironment() {
|
|
157687
|
+
const env = process.env;
|
|
157688
|
+
if (env.GITHUB_ACTIONS === "true") {
|
|
157689
|
+
return {
|
|
157690
|
+
isCI: true,
|
|
157691
|
+
provider: "github-actions",
|
|
157692
|
+
branch: env.GITHUB_HEAD_REF || env.GITHUB_REF_NAME,
|
|
157693
|
+
commitSha: env.GITHUB_SHA,
|
|
157694
|
+
prNumber: env.GITHUB_EVENT_NAME === "pull_request" ? parseInt(env.GITHUB_REF?.split("/")[2] || "", 10) || void 0 : void 0,
|
|
157695
|
+
baseBranch: env.GITHUB_BASE_REF,
|
|
157696
|
+
buildUrl: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`
|
|
157697
|
+
};
|
|
157698
|
+
}
|
|
157699
|
+
if (env.GITLAB_CI === "true") {
|
|
157700
|
+
return {
|
|
157701
|
+
isCI: true,
|
|
157702
|
+
provider: "gitlab-ci",
|
|
157703
|
+
branch: env.CI_COMMIT_REF_NAME,
|
|
157704
|
+
commitSha: env.CI_COMMIT_SHA,
|
|
157705
|
+
prNumber: env.CI_MERGE_REQUEST_IID ? parseInt(env.CI_MERGE_REQUEST_IID, 10) : void 0,
|
|
157706
|
+
baseBranch: env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME,
|
|
157707
|
+
buildUrl: env.CI_JOB_URL
|
|
157708
|
+
};
|
|
157709
|
+
}
|
|
157710
|
+
if (env.JENKINS_URL) {
|
|
157711
|
+
return {
|
|
157712
|
+
isCI: true,
|
|
157713
|
+
provider: "jenkins",
|
|
157714
|
+
branch: env.GIT_BRANCH || env.BRANCH_NAME,
|
|
157715
|
+
commitSha: env.GIT_COMMIT,
|
|
157716
|
+
prNumber: env.CHANGE_ID ? parseInt(env.CHANGE_ID, 10) : void 0,
|
|
157717
|
+
baseBranch: env.CHANGE_TARGET,
|
|
157718
|
+
buildUrl: env.BUILD_URL
|
|
157719
|
+
};
|
|
157720
|
+
}
|
|
157721
|
+
if (env.CIRCLECI === "true") {
|
|
157722
|
+
return {
|
|
157723
|
+
isCI: true,
|
|
157724
|
+
provider: "circleci",
|
|
157725
|
+
branch: env.CIRCLE_BRANCH,
|
|
157726
|
+
commitSha: env.CIRCLE_SHA1,
|
|
157727
|
+
prNumber: env.CIRCLE_PULL_REQUEST ? parseInt(env.CIRCLE_PULL_REQUEST.split("/").pop() || "", 10) : void 0,
|
|
157728
|
+
buildUrl: env.CIRCLE_BUILD_URL
|
|
157729
|
+
};
|
|
157730
|
+
}
|
|
157731
|
+
if (env.CI === "true" || env.CI === "1") {
|
|
157732
|
+
return {
|
|
157733
|
+
isCI: true,
|
|
157734
|
+
provider: "unknown"
|
|
157735
|
+
};
|
|
157736
|
+
}
|
|
157737
|
+
return { isCI: false };
|
|
157738
|
+
}
|
|
157739
|
+
function createGitHubActionsReporter(config) {
|
|
157740
|
+
return new GitHubActionsReporter(config);
|
|
157741
|
+
}
|
|
157742
|
+
async function reportToGitHubActions(results, config) {
|
|
157743
|
+
const reporter = createGitHubActionsReporter(config);
|
|
157744
|
+
await reporter.writeOutput(results);
|
|
157745
|
+
}
|
|
157746
|
+
var DEFAULT_CONFIG71, GitHubActionsReporter;
|
|
157747
|
+
var init_github_actions = __esm({
|
|
157748
|
+
"src/test-scheduling/cicd/github-actions.ts"() {
|
|
157749
|
+
"use strict";
|
|
157750
|
+
DEFAULT_CONFIG71 = {
|
|
157751
|
+
enableAnnotations: true,
|
|
157752
|
+
enableSummary: true,
|
|
157753
|
+
enableOutputs: true,
|
|
157754
|
+
maxAnnotations: 10,
|
|
157755
|
+
includeFlakyWarnings: true,
|
|
157756
|
+
includeCoverage: true
|
|
157757
|
+
};
|
|
157758
|
+
GitHubActionsReporter = class {
|
|
157759
|
+
config;
|
|
157760
|
+
constructor(config) {
|
|
157761
|
+
this.config = { ...DEFAULT_CONFIG71, ...config };
|
|
157762
|
+
}
|
|
157763
|
+
// --------------------------------------------------------------------------
|
|
157764
|
+
// Public API
|
|
157765
|
+
// --------------------------------------------------------------------------
|
|
157766
|
+
/**
|
|
157767
|
+
* Generate complete GitHub Actions output
|
|
157768
|
+
*/
|
|
157769
|
+
generateOutput(results) {
|
|
157770
|
+
const annotations = this.config.enableAnnotations ? this.generateAnnotations(results) : [];
|
|
157771
|
+
const summary = this.config.enableSummary ? this.generateSummary(results) : "";
|
|
157772
|
+
const outputs = this.config.enableOutputs ? this.generateOutputs(results) : {};
|
|
157773
|
+
return { summary, annotations, outputs };
|
|
157774
|
+
}
|
|
157775
|
+
/**
|
|
157776
|
+
* Write output to GitHub Actions
|
|
157777
|
+
*/
|
|
157778
|
+
async writeOutput(results) {
|
|
157779
|
+
const output = this.generateOutput(results);
|
|
157780
|
+
const fs26 = await import("fs/promises");
|
|
157781
|
+
for (const annotation of output.annotations) {
|
|
157782
|
+
this.writeAnnotation(annotation);
|
|
157783
|
+
}
|
|
157784
|
+
if (output.summary && process.env.GITHUB_STEP_SUMMARY) {
|
|
157785
|
+
await fs26.appendFile(process.env.GITHUB_STEP_SUMMARY, output.summary);
|
|
157786
|
+
}
|
|
157787
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
157788
|
+
const outputLines = Object.entries(output.outputs).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
157789
|
+
await fs26.appendFile(process.env.GITHUB_OUTPUT, outputLines + "\n");
|
|
157790
|
+
}
|
|
157791
|
+
}
|
|
157792
|
+
// --------------------------------------------------------------------------
|
|
157793
|
+
// Annotation Generation
|
|
157794
|
+
// --------------------------------------------------------------------------
|
|
157795
|
+
generateAnnotations(results) {
|
|
157796
|
+
const annotations = [];
|
|
157797
|
+
for (const phase of results) {
|
|
157798
|
+
for (const test of phase.testResults) {
|
|
157799
|
+
if (!test.passed && annotations.length < this.config.maxAnnotations) {
|
|
157800
|
+
annotations.push({
|
|
157801
|
+
file: test.file,
|
|
157802
|
+
line: this.extractLineNumber(test.stack) || 1,
|
|
157803
|
+
level: "error",
|
|
157804
|
+
title: `Test Failed: ${test.name}`,
|
|
157805
|
+
message: test.error || "Test failed without error message"
|
|
157806
|
+
});
|
|
157807
|
+
}
|
|
157808
|
+
}
|
|
157809
|
+
if (this.config.includeFlakyWarnings) {
|
|
157810
|
+
for (const flakyTest of phase.flakyTests) {
|
|
157811
|
+
if (annotations.length < this.config.maxAnnotations) {
|
|
157812
|
+
annotations.push({
|
|
157813
|
+
file: flakyTest,
|
|
157814
|
+
line: 1,
|
|
157815
|
+
level: "warning",
|
|
157816
|
+
title: "Flaky Test Detected",
|
|
157817
|
+
message: `This test is flaky and may cause intermittent failures`
|
|
157818
|
+
});
|
|
157819
|
+
}
|
|
157820
|
+
}
|
|
157821
|
+
}
|
|
157822
|
+
}
|
|
157823
|
+
return annotations;
|
|
157824
|
+
}
|
|
157825
|
+
// --------------------------------------------------------------------------
|
|
157826
|
+
// Summary Generation
|
|
157827
|
+
// --------------------------------------------------------------------------
|
|
157828
|
+
generateSummary(results) {
|
|
157829
|
+
const lines = ["## \u{1F9EA} Test Results\n"];
|
|
157830
|
+
const allPassed = results.every((r54) => r54.success);
|
|
157831
|
+
lines.push(allPassed ? "\u2705 **All phases passed**\n" : "\u274C **Some phases failed**\n");
|
|
157832
|
+
lines.push("| Phase | Status | Pass Rate | Duration | Tests |");
|
|
157833
|
+
lines.push("|-------|--------|-----------|----------|-------|");
|
|
157834
|
+
for (const phase of results) {
|
|
157835
|
+
const status = phase.success ? "\u2705" : "\u274C";
|
|
157836
|
+
const passRate = `${(phase.passRate * 100).toFixed(1)}%`;
|
|
157837
|
+
const duration = this.formatDuration(phase.durationMs);
|
|
157838
|
+
const tests = `${phase.passed}/${phase.totalTests}`;
|
|
157839
|
+
lines.push(`| ${phase.phaseName} | ${status} | ${passRate} | ${duration} | ${tests} |`);
|
|
157840
|
+
}
|
|
157841
|
+
lines.push("");
|
|
157842
|
+
if (this.config.includeCoverage) {
|
|
157843
|
+
lines.push("### \u{1F4CA} Coverage\n");
|
|
157844
|
+
for (const phase of results) {
|
|
157845
|
+
const coverage = `${(phase.coverage * 100).toFixed(1)}%`;
|
|
157846
|
+
const bar = this.generateCoverageBar(phase.coverage);
|
|
157847
|
+
lines.push(`- **${phase.phaseName}**: ${bar} ${coverage}`);
|
|
157848
|
+
}
|
|
157849
|
+
lines.push("");
|
|
157850
|
+
}
|
|
157851
|
+
const failedTests = results.flatMap(
|
|
157852
|
+
(r54) => r54.testResults.filter((t50) => !t50.passed)
|
|
157853
|
+
);
|
|
157854
|
+
if (failedTests.length > 0) {
|
|
157855
|
+
lines.push("### \u274C Failed Tests\n");
|
|
157856
|
+
lines.push("<details>");
|
|
157857
|
+
lines.push("<summary>Click to expand</summary>\n");
|
|
157858
|
+
for (const test of failedTests.slice(0, 20)) {
|
|
157859
|
+
lines.push(`#### ${test.suite} > ${test.name}`);
|
|
157860
|
+
lines.push(`- **File**: \`${test.file}\``);
|
|
157861
|
+
if (test.error) {
|
|
157862
|
+
lines.push("```");
|
|
157863
|
+
lines.push(test.error.slice(0, 500));
|
|
157864
|
+
lines.push("```");
|
|
157865
|
+
}
|
|
157866
|
+
lines.push("");
|
|
157867
|
+
}
|
|
157868
|
+
if (failedTests.length > 20) {
|
|
157869
|
+
lines.push(`_... and ${failedTests.length - 20} more failures_`);
|
|
157870
|
+
}
|
|
157871
|
+
lines.push("</details>\n");
|
|
157872
|
+
}
|
|
157873
|
+
const flakyTests = results.flatMap((r54) => r54.flakyTests);
|
|
157874
|
+
if (flakyTests.length > 0 && this.config.includeFlakyWarnings) {
|
|
157875
|
+
lines.push("### \u26A0\uFE0F Flaky Tests\n");
|
|
157876
|
+
for (const test of flakyTests.slice(0, 10)) {
|
|
157877
|
+
lines.push(`- \`${test}\``);
|
|
157878
|
+
}
|
|
157879
|
+
lines.push("");
|
|
157880
|
+
}
|
|
157881
|
+
return lines.join("\n");
|
|
157882
|
+
}
|
|
157883
|
+
// --------------------------------------------------------------------------
|
|
157884
|
+
// Output Variables
|
|
157885
|
+
// --------------------------------------------------------------------------
|
|
157886
|
+
generateOutputs(results) {
|
|
157887
|
+
const totalTests = results.reduce((sum, r54) => sum + r54.totalTests, 0);
|
|
157888
|
+
const totalPassed = results.reduce((sum, r54) => sum + r54.passed, 0);
|
|
157889
|
+
const totalFailed = results.reduce((sum, r54) => sum + r54.failed, 0);
|
|
157890
|
+
const allPassed = results.every((r54) => r54.success);
|
|
157891
|
+
const avgCoverage = results.length > 0 ? results.reduce((sum, r54) => sum + r54.coverage, 0) / results.length : 0;
|
|
157892
|
+
return {
|
|
157893
|
+
test_result: allPassed ? "success" : "failure",
|
|
157894
|
+
total_tests: String(totalTests),
|
|
157895
|
+
passed_tests: String(totalPassed),
|
|
157896
|
+
failed_tests: String(totalFailed),
|
|
157897
|
+
coverage_percent: String((avgCoverage * 100).toFixed(1)),
|
|
157898
|
+
phases_completed: String(results.length),
|
|
157899
|
+
phases_passed: String(results.filter((r54) => r54.success).length),
|
|
157900
|
+
has_flaky_tests: String(results.some((r54) => r54.flakyTests.length > 0))
|
|
157901
|
+
};
|
|
157902
|
+
}
|
|
157903
|
+
// --------------------------------------------------------------------------
|
|
157904
|
+
// Helpers
|
|
157905
|
+
// --------------------------------------------------------------------------
|
|
157906
|
+
writeAnnotation(annotation) {
|
|
157907
|
+
const params = [
|
|
157908
|
+
`file=${annotation.file}`,
|
|
157909
|
+
`line=${annotation.line}`,
|
|
157910
|
+
`title=${annotation.title}`
|
|
157911
|
+
].join(",");
|
|
157912
|
+
console.log(`::${annotation.level} ${params}::${annotation.message}`);
|
|
157913
|
+
}
|
|
157914
|
+
extractLineNumber(stack) {
|
|
157915
|
+
if (!stack) return void 0;
|
|
157916
|
+
const match = stack.match(/:(\d+):\d+/);
|
|
157917
|
+
return match ? parseInt(match[1], 10) : void 0;
|
|
157918
|
+
}
|
|
157919
|
+
formatDuration(ms) {
|
|
157920
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
157921
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
157922
|
+
return `${(ms / 6e4).toFixed(1)}m`;
|
|
157923
|
+
}
|
|
157924
|
+
generateCoverageBar(coverage) {
|
|
157925
|
+
const filled = Math.round(coverage * 10);
|
|
157926
|
+
const empty = 10 - filled;
|
|
157927
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
157928
|
+
}
|
|
157929
|
+
};
|
|
157930
|
+
}
|
|
157931
|
+
});
|
|
157932
|
+
|
|
157933
|
+
// src/test-scheduling/pipeline.ts
|
|
157934
|
+
async function createTestPipeline(config) {
|
|
157935
|
+
return TestSchedulingPipeline.create(config);
|
|
157936
|
+
}
|
|
157937
|
+
async function runTestPipeline(config) {
|
|
157938
|
+
const pipeline10 = await createTestPipeline(config);
|
|
157939
|
+
return pipeline10.run();
|
|
157940
|
+
}
|
|
157941
|
+
var TestSchedulingPipeline;
|
|
157942
|
+
var init_pipeline = __esm({
|
|
157943
|
+
"src/test-scheduling/pipeline.ts"() {
|
|
157944
|
+
"use strict";
|
|
157945
|
+
init_phase_scheduler();
|
|
157946
|
+
init_vitest_executor();
|
|
157947
|
+
init_test_selector();
|
|
157948
|
+
init_flaky_tracker();
|
|
157949
|
+
init_github_actions();
|
|
157950
|
+
TestSchedulingPipeline = class _TestSchedulingPipeline {
|
|
157951
|
+
constructor(config, selector, executor2, scheduler, flakyTracker, reporter) {
|
|
157952
|
+
this.config = config;
|
|
157953
|
+
this.selector = selector;
|
|
157954
|
+
this.executor = executor2;
|
|
157955
|
+
this.scheduler = scheduler;
|
|
157956
|
+
this.flakyTracker = flakyTracker;
|
|
157957
|
+
this.reporter = reporter;
|
|
157958
|
+
this.ciEnvironment = detectCIEnvironment();
|
|
157959
|
+
}
|
|
157960
|
+
selector;
|
|
157961
|
+
executor;
|
|
157962
|
+
scheduler;
|
|
157963
|
+
flakyTracker;
|
|
157964
|
+
reporter;
|
|
157965
|
+
ciEnvironment;
|
|
157966
|
+
/**
|
|
157967
|
+
* Create a fully integrated test scheduling pipeline
|
|
157968
|
+
*
|
|
157969
|
+
* This factory method:
|
|
157970
|
+
* 1. Creates FlakyTracker (loads history if exists)
|
|
157971
|
+
* 2. Creates VitestExecutor with FlakyTracker integration
|
|
157972
|
+
* 3. Optionally creates ImpactAnalyzerService for import graph analysis
|
|
157973
|
+
* 4. Creates GitAwareTestSelector with ImpactAnalyzer integration
|
|
157974
|
+
* 5. Creates PhaseScheduler with the executor
|
|
157975
|
+
* 6. Creates GitHubActionsReporter
|
|
157976
|
+
*/
|
|
157977
|
+
static async create(config) {
|
|
157978
|
+
const flakyTracker = config.flakyHistoryPath ? await loadFlakyTracker(config.flakyHistoryPath, config.flakyTracker) : createFlakyTracker(config.flakyTracker);
|
|
157979
|
+
const executor2 = new VitestPhaseExecutor({
|
|
157980
|
+
...config.vitest,
|
|
157981
|
+
cwd: config.cwd,
|
|
157982
|
+
flakyTracker
|
|
157983
|
+
// INTEGRATION: Pass tracker to executor
|
|
157984
|
+
});
|
|
157985
|
+
const { ImpactAnalyzerService: ImpactAnalyzerService2 } = await Promise.resolve().then(() => (init_impact_analyzer(), impact_analyzer_exports));
|
|
157986
|
+
const impactAnalyzer = new ImpactAnalyzerService2(config.memory);
|
|
157987
|
+
const selector = createTestSelector({
|
|
157988
|
+
cwd: config.cwd,
|
|
157989
|
+
baseRef: config.baseRef,
|
|
157990
|
+
impactAnalyzer
|
|
157991
|
+
// INTEGRATION: Pass analyzer to selector
|
|
157992
|
+
});
|
|
157993
|
+
const scheduler = createPhaseScheduler(executor2, {
|
|
157994
|
+
...config.scheduler,
|
|
157995
|
+
phases: config.phases
|
|
157996
|
+
});
|
|
157997
|
+
const reporter = createGitHubActionsReporter(config.reporter);
|
|
157998
|
+
return new _TestSchedulingPipeline(
|
|
157999
|
+
config,
|
|
158000
|
+
selector,
|
|
158001
|
+
executor2,
|
|
158002
|
+
scheduler,
|
|
158003
|
+
flakyTracker,
|
|
158004
|
+
reporter
|
|
158005
|
+
);
|
|
158006
|
+
}
|
|
158007
|
+
// --------------------------------------------------------------------------
|
|
158008
|
+
// Public API
|
|
158009
|
+
// --------------------------------------------------------------------------
|
|
158010
|
+
/**
|
|
158011
|
+
* Run the complete test scheduling pipeline:
|
|
158012
|
+
* 1. Select affected tests (git-aware + import graph)
|
|
158013
|
+
* 2. Execute tests in phases
|
|
158014
|
+
* 3. Track flaky tests
|
|
158015
|
+
* 4. Report to CI/CD
|
|
158016
|
+
*/
|
|
158017
|
+
async run() {
|
|
158018
|
+
const startTime = Date.now();
|
|
158019
|
+
let selectedTests = [];
|
|
158020
|
+
let ranAllTests = this.config.runAllTests ?? false;
|
|
158021
|
+
if (!ranAllTests) {
|
|
158022
|
+
const selectionResult = await this.selector.selectAffectedTests();
|
|
158023
|
+
if (selectionResult.runAllTests) {
|
|
158024
|
+
ranAllTests = true;
|
|
158025
|
+
console.log(`[TestSchedulingPipeline] Running all tests: ${selectionResult.runAllReason}`);
|
|
158026
|
+
} else if (selectionResult.selectedTests.length === 0) {
|
|
158027
|
+
console.log("[TestSchedulingPipeline] No affected tests found, running all");
|
|
158028
|
+
ranAllTests = true;
|
|
158029
|
+
} else {
|
|
158030
|
+
selectedTests = selectionResult.selectedTests;
|
|
158031
|
+
console.log(`[TestSchedulingPipeline] Selected ${selectedTests.length} affected tests`);
|
|
158032
|
+
}
|
|
158033
|
+
}
|
|
158034
|
+
let phaseResults;
|
|
158035
|
+
if (ranAllTests) {
|
|
158036
|
+
phaseResults = await this.scheduler.run();
|
|
158037
|
+
} else {
|
|
158038
|
+
phaseResults = await this.runWithSelectedTests(selectedTests);
|
|
158039
|
+
}
|
|
158040
|
+
const flakyAnalysis = this.flakyTracker.analyze();
|
|
158041
|
+
if (this.config.flakyHistoryPath) {
|
|
158042
|
+
await saveFlakyTracker(this.flakyTracker, this.config.flakyHistoryPath);
|
|
158043
|
+
}
|
|
158044
|
+
if (this.ciEnvironment.isCI) {
|
|
158045
|
+
await this.reporter.writeOutput(phaseResults);
|
|
158046
|
+
}
|
|
158047
|
+
const totalDurationMs = Date.now() - startTime;
|
|
158048
|
+
return {
|
|
158049
|
+
phaseResults,
|
|
158050
|
+
selectedTests,
|
|
158051
|
+
ranAllTests,
|
|
158052
|
+
flakyAnalysis,
|
|
158053
|
+
ciEnvironment: this.ciEnvironment,
|
|
158054
|
+
totalDurationMs
|
|
158055
|
+
};
|
|
158056
|
+
}
|
|
158057
|
+
/**
|
|
158058
|
+
* Run only affected tests for a specific phase
|
|
158059
|
+
*/
|
|
158060
|
+
async runPhase(phaseId) {
|
|
158061
|
+
return this.scheduler.runPhase(phaseId);
|
|
158062
|
+
}
|
|
158063
|
+
/**
|
|
158064
|
+
* Get the flaky test tracker for direct access
|
|
158065
|
+
*/
|
|
158066
|
+
getFlakyTracker() {
|
|
158067
|
+
return this.flakyTracker;
|
|
158068
|
+
}
|
|
158069
|
+
/**
|
|
158070
|
+
* Get the test selector for direct access
|
|
158071
|
+
*/
|
|
158072
|
+
getSelector() {
|
|
158073
|
+
return this.selector;
|
|
158074
|
+
}
|
|
158075
|
+
/**
|
|
158076
|
+
* Get scheduler stats
|
|
158077
|
+
*/
|
|
158078
|
+
getStats() {
|
|
158079
|
+
return this.scheduler.getStats();
|
|
158080
|
+
}
|
|
158081
|
+
/**
|
|
158082
|
+
* Abort running tests
|
|
158083
|
+
*/
|
|
158084
|
+
async abort() {
|
|
158085
|
+
await this.scheduler.abort();
|
|
158086
|
+
}
|
|
158087
|
+
// --------------------------------------------------------------------------
|
|
158088
|
+
// Private Methods
|
|
158089
|
+
// --------------------------------------------------------------------------
|
|
158090
|
+
async runWithSelectedTests(testFiles) {
|
|
158091
|
+
const results = [];
|
|
158092
|
+
const phases = this.config.phases ?? [];
|
|
158093
|
+
for (const phase of phases) {
|
|
158094
|
+
const phaseTests = testFiles.filter(
|
|
158095
|
+
(file) => phase.testPatterns.some((pattern) => {
|
|
158096
|
+
if (pattern.startsWith("!")) return false;
|
|
158097
|
+
const regex = this.patternToRegex(pattern);
|
|
158098
|
+
return regex.test(file);
|
|
158099
|
+
})
|
|
158100
|
+
);
|
|
158101
|
+
if (phaseTests.length > 0) {
|
|
158102
|
+
const result = await this.executor.execute(phase, phaseTests);
|
|
158103
|
+
results.push(result);
|
|
158104
|
+
}
|
|
158105
|
+
}
|
|
158106
|
+
return results;
|
|
158107
|
+
}
|
|
158108
|
+
patternToRegex(pattern) {
|
|
158109
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
158110
|
+
return new RegExp(escaped);
|
|
158111
|
+
}
|
|
158112
|
+
};
|
|
158113
|
+
}
|
|
158114
|
+
});
|
|
158115
|
+
|
|
158116
|
+
// src/test-scheduling/index.ts
|
|
158117
|
+
var test_scheduling_exports = {};
|
|
158118
|
+
__export(test_scheduling_exports, {
|
|
158119
|
+
DEFAULT_TEST_PHASES: () => DEFAULT_TEST_PHASES,
|
|
158120
|
+
FlakyTestTracker: () => FlakyTestTracker,
|
|
158121
|
+
GitAwareTestSelector: () => GitAwareTestSelector,
|
|
158122
|
+
GitHubActionsReporter: () => GitHubActionsReporter,
|
|
158123
|
+
PhaseScheduler: () => PhaseScheduler,
|
|
158124
|
+
TestSchedulingPipeline: () => TestSchedulingPipeline,
|
|
158125
|
+
VitestPhaseExecutor: () => VitestPhaseExecutor,
|
|
158126
|
+
checkQualityThresholds: () => checkQualityThresholds,
|
|
158127
|
+
createFlakyTracker: () => createFlakyTracker,
|
|
158128
|
+
createGitHubActionsReporter: () => createGitHubActionsReporter,
|
|
158129
|
+
createPhaseScheduler: () => createPhaseScheduler,
|
|
158130
|
+
createTestPipeline: () => createTestPipeline,
|
|
158131
|
+
createTestSelector: () => createTestSelector,
|
|
158132
|
+
createVitestExecutor: () => createVitestExecutor,
|
|
158133
|
+
detectCIEnvironment: () => detectCIEnvironment,
|
|
158134
|
+
getAffectedTests: () => getAffectedTests,
|
|
158135
|
+
loadFlakyTracker: () => loadFlakyTracker,
|
|
158136
|
+
reportToGitHubActions: () => reportToGitHubActions,
|
|
158137
|
+
runTestPipeline: () => runTestPipeline,
|
|
158138
|
+
saveFlakyTracker: () => saveFlakyTracker
|
|
158139
|
+
});
|
|
158140
|
+
var init_test_scheduling = __esm({
|
|
158141
|
+
"src/test-scheduling/index.ts"() {
|
|
158142
|
+
"use strict";
|
|
158143
|
+
init_interfaces7();
|
|
158144
|
+
init_phase_scheduler();
|
|
158145
|
+
init_vitest_executor();
|
|
158146
|
+
init_test_selector();
|
|
158147
|
+
init_flaky_tracker();
|
|
158148
|
+
init_github_actions();
|
|
158149
|
+
init_pipeline();
|
|
158150
|
+
}
|
|
158151
|
+
});
|
|
158152
|
+
|
|
158153
|
+
// src/mcp/tools/test-execution/schedule.ts
|
|
158154
|
+
var TestScheduleTool;
|
|
158155
|
+
var init_schedule = __esm({
|
|
158156
|
+
"src/mcp/tools/test-execution/schedule.ts"() {
|
|
158157
|
+
"use strict";
|
|
158158
|
+
init_base();
|
|
158159
|
+
init_error_utils();
|
|
158160
|
+
TestScheduleTool = class extends MCPToolBase {
|
|
158161
|
+
config = {
|
|
158162
|
+
name: "qe/tests/schedule",
|
|
158163
|
+
description: "Schedule and execute tests using phase-based pipeline with git-aware selection and flaky tracking. Runs tests in phases (unit, integration, e2e), selects affected tests from git changes, and tracks flaky tests.",
|
|
158164
|
+
domain: "test-execution",
|
|
158165
|
+
schema: this.buildSchema()
|
|
158166
|
+
};
|
|
158167
|
+
buildSchema() {
|
|
158168
|
+
return {
|
|
158169
|
+
type: "object",
|
|
158170
|
+
properties: {
|
|
158171
|
+
cwd: {
|
|
158172
|
+
type: "string",
|
|
158173
|
+
description: "Working directory for test execution (defaults to project root)"
|
|
158174
|
+
},
|
|
158175
|
+
gitRef: {
|
|
158176
|
+
type: "string",
|
|
158177
|
+
description: 'Git ref to compare for affected test selection (e.g., "main", "HEAD~3")'
|
|
158178
|
+
},
|
|
158179
|
+
useGitAware: {
|
|
158180
|
+
type: "boolean",
|
|
158181
|
+
description: "Enable git-aware test selection to only run affected tests",
|
|
158182
|
+
default: true
|
|
158183
|
+
},
|
|
158184
|
+
trackFlaky: {
|
|
158185
|
+
type: "boolean",
|
|
158186
|
+
description: "Enable flaky test tracking and quarantine",
|
|
158187
|
+
default: true
|
|
158188
|
+
}
|
|
158189
|
+
}
|
|
158190
|
+
};
|
|
158191
|
+
}
|
|
158192
|
+
async execute(params, context) {
|
|
158193
|
+
try {
|
|
158194
|
+
const { runTestPipeline: runTestPipeline2 } = await Promise.resolve().then(() => (init_test_scheduling(), test_scheduling_exports));
|
|
158195
|
+
const memory = await getMemoryBackend(context);
|
|
158196
|
+
const result = await runTestPipeline2({
|
|
158197
|
+
cwd: params.cwd || process.cwd(),
|
|
158198
|
+
memory,
|
|
158199
|
+
baseRef: params.gitRef,
|
|
158200
|
+
flakyHistoryPath: params.trackFlaky !== false ? ".agentic-qe/flaky-history.json" : void 0,
|
|
158201
|
+
runAllTests: params.useGitAware === false
|
|
158202
|
+
});
|
|
158203
|
+
const totalTests = result.phaseResults.reduce((sum, pr3) => sum + pr3.totalTests, 0);
|
|
158204
|
+
const totalPassed = result.phaseResults.reduce((sum, pr3) => sum + pr3.passed, 0);
|
|
158205
|
+
const totalFailed = result.phaseResults.reduce((sum, pr3) => sum + pr3.failed, 0);
|
|
158206
|
+
return {
|
|
158207
|
+
success: true,
|
|
158208
|
+
data: {
|
|
158209
|
+
pipelineId: context.requestId,
|
|
158210
|
+
phases: result.phaseResults.map((pr3) => ({
|
|
158211
|
+
phaseId: pr3.phaseId,
|
|
158212
|
+
phaseName: pr3.phaseName,
|
|
158213
|
+
totalTests: pr3.totalTests,
|
|
158214
|
+
passed: pr3.passed,
|
|
158215
|
+
failed: pr3.failed,
|
|
158216
|
+
passRate: pr3.passRate,
|
|
158217
|
+
durationMs: pr3.durationMs
|
|
158218
|
+
})),
|
|
158219
|
+
gitAware: {
|
|
158220
|
+
enabled: !result.ranAllTests,
|
|
158221
|
+
selectedTests: result.selectedTests.length,
|
|
158222
|
+
gitRef: params.gitRef
|
|
158223
|
+
},
|
|
158224
|
+
flakyTracking: {
|
|
158225
|
+
enabled: params.trackFlaky !== false
|
|
158226
|
+
},
|
|
158227
|
+
totalDuration: result.totalDurationMs,
|
|
158228
|
+
ranAllTests: result.ranAllTests,
|
|
158229
|
+
summary: `Executed ${result.phaseResults.length} phases, ${totalTests} tests (${totalPassed} passed, ${totalFailed} failed) in ${result.totalDurationMs}ms`
|
|
158230
|
+
}
|
|
158231
|
+
};
|
|
158232
|
+
} catch (error) {
|
|
158233
|
+
return {
|
|
158234
|
+
success: false,
|
|
158235
|
+
error: toErrorMessage(error)
|
|
158236
|
+
};
|
|
158237
|
+
}
|
|
158238
|
+
}
|
|
158239
|
+
};
|
|
158240
|
+
}
|
|
158241
|
+
});
|
|
158242
|
+
|
|
158243
|
+
// src/testing/load/metrics-collector.ts
|
|
158244
|
+
function createMetricsCollector(config) {
|
|
158245
|
+
return new MetricsCollector2(config);
|
|
158246
|
+
}
|
|
158247
|
+
var DEFAULT_CONFIG72, MetricsCollector2;
|
|
158248
|
+
var init_metrics_collector2 = __esm({
|
|
158249
|
+
"src/testing/load/metrics-collector.ts"() {
|
|
158250
|
+
"use strict";
|
|
158251
|
+
init_utils();
|
|
158252
|
+
DEFAULT_CONFIG72 = {
|
|
158253
|
+
maxEvents: 1e5,
|
|
158254
|
+
memorySampleInterval: 1e3,
|
|
158255
|
+
enableTimeline: true,
|
|
158256
|
+
timelineSampleInterval: 1e3
|
|
158257
|
+
};
|
|
158258
|
+
MetricsCollector2 = class {
|
|
158259
|
+
config;
|
|
158260
|
+
// Active agent tracking
|
|
158261
|
+
activeAgents = /* @__PURE__ */ new Map();
|
|
158262
|
+
// agentId -> spawnTimestamp
|
|
158263
|
+
peakAgentCount = 0;
|
|
158264
|
+
totalAgentsSpawned = 0;
|
|
158265
|
+
// Task tracking
|
|
158266
|
+
activeTasks = /* @__PURE__ */ new Map();
|
|
158267
|
+
totalTasksStarted = 0;
|
|
158268
|
+
totalTasksCompleted = 0;
|
|
158269
|
+
// Latency buffers (using CircularBuffer for memory efficiency)
|
|
158270
|
+
coordinationLatencies;
|
|
158271
|
+
taskDurations;
|
|
158272
|
+
// Memory tracking
|
|
158273
|
+
memorySnapshots;
|
|
158274
|
+
peakMemoryUsed = 0;
|
|
158275
|
+
memorySum = 0;
|
|
158276
|
+
memorySamples = 0;
|
|
158277
|
+
// Timeline tracking
|
|
158278
|
+
agentTimeline = [];
|
|
158279
|
+
latencyTimeline = [];
|
|
158280
|
+
memoryTimeline = [];
|
|
158281
|
+
// Timing
|
|
158282
|
+
startTime = 0;
|
|
158283
|
+
endTime = 0;
|
|
158284
|
+
running = false;
|
|
158285
|
+
memorySamplerTimer = null;
|
|
158286
|
+
timelineSamplerTimer = null;
|
|
158287
|
+
// Issue tracking
|
|
158288
|
+
issues = [];
|
|
158289
|
+
constructor(config = {}) {
|
|
158290
|
+
this.config = { ...DEFAULT_CONFIG72, ...config };
|
|
158291
|
+
this.coordinationLatencies = new CircularBuffer(this.config.maxEvents);
|
|
158292
|
+
this.taskDurations = new CircularBuffer(this.config.maxEvents);
|
|
158293
|
+
this.memorySnapshots = new CircularBuffer(1e4);
|
|
158294
|
+
}
|
|
158295
|
+
// ============================================================================
|
|
158296
|
+
// Lifecycle
|
|
158297
|
+
// ============================================================================
|
|
158298
|
+
/**
|
|
158299
|
+
* Start collecting metrics
|
|
158300
|
+
*/
|
|
158301
|
+
start() {
|
|
158302
|
+
if (this.running) return;
|
|
158303
|
+
this.running = true;
|
|
158304
|
+
this.startTime = Date.now();
|
|
158305
|
+
this.memorySamplerTimer = setInterval(() => {
|
|
158306
|
+
this.sampleMemory();
|
|
158307
|
+
}, this.config.memorySampleInterval);
|
|
158308
|
+
if (this.config.enableTimeline) {
|
|
158309
|
+
this.timelineSamplerTimer = setInterval(() => {
|
|
158310
|
+
this.sampleTimeline();
|
|
158311
|
+
}, this.config.timelineSampleInterval);
|
|
158312
|
+
}
|
|
158313
|
+
}
|
|
158314
|
+
/**
|
|
158315
|
+
* Stop collecting metrics
|
|
158316
|
+
*/
|
|
158317
|
+
stop() {
|
|
158318
|
+
if (!this.running) return;
|
|
158319
|
+
this.running = false;
|
|
158320
|
+
this.endTime = Date.now();
|
|
158321
|
+
if (this.memorySamplerTimer) {
|
|
158322
|
+
clearInterval(this.memorySamplerTimer);
|
|
158323
|
+
this.memorySamplerTimer = null;
|
|
158324
|
+
}
|
|
158325
|
+
if (this.timelineSamplerTimer) {
|
|
158326
|
+
clearInterval(this.timelineSamplerTimer);
|
|
158327
|
+
this.timelineSamplerTimer = null;
|
|
158328
|
+
}
|
|
158329
|
+
}
|
|
158330
|
+
/**
|
|
158331
|
+
* Reset all metrics
|
|
158332
|
+
*/
|
|
158333
|
+
reset() {
|
|
158334
|
+
this.stop();
|
|
158335
|
+
this.activeAgents.clear();
|
|
158336
|
+
this.peakAgentCount = 0;
|
|
158337
|
+
this.totalAgentsSpawned = 0;
|
|
158338
|
+
this.activeTasks.clear();
|
|
158339
|
+
this.totalTasksStarted = 0;
|
|
158340
|
+
this.totalTasksCompleted = 0;
|
|
158341
|
+
this.coordinationLatencies.clear();
|
|
158342
|
+
this.taskDurations.clear();
|
|
158343
|
+
this.memorySnapshots.clear();
|
|
158344
|
+
this.peakMemoryUsed = 0;
|
|
158345
|
+
this.memorySum = 0;
|
|
158346
|
+
this.memorySamples = 0;
|
|
158347
|
+
this.agentTimeline.length = 0;
|
|
158348
|
+
this.latencyTimeline.length = 0;
|
|
158349
|
+
this.memoryTimeline.length = 0;
|
|
158350
|
+
this.startTime = 0;
|
|
158351
|
+
this.endTime = 0;
|
|
158352
|
+
this.issues.length = 0;
|
|
158353
|
+
}
|
|
158354
|
+
// ============================================================================
|
|
158355
|
+
// Recording Methods
|
|
158356
|
+
// ============================================================================
|
|
158357
|
+
/**
|
|
158358
|
+
* Record agent spawn
|
|
158359
|
+
*/
|
|
158360
|
+
recordAgentSpawn(agentId, timestamp = Date.now()) {
|
|
158361
|
+
this.activeAgents.set(agentId, timestamp);
|
|
158362
|
+
this.totalAgentsSpawned++;
|
|
158363
|
+
const currentCount = this.activeAgents.size;
|
|
158364
|
+
if (currentCount > this.peakAgentCount) {
|
|
158365
|
+
this.peakAgentCount = currentCount;
|
|
158366
|
+
}
|
|
158367
|
+
}
|
|
158368
|
+
/**
|
|
158369
|
+
* Record agent termination
|
|
158370
|
+
*/
|
|
158371
|
+
recordAgentTerminate(agentId, _timestamp = Date.now()) {
|
|
158372
|
+
this.activeAgents.delete(agentId);
|
|
158373
|
+
}
|
|
158374
|
+
/**
|
|
158375
|
+
* Record task start
|
|
158376
|
+
*/
|
|
158377
|
+
recordTaskStart(agentId, taskId, timestamp = Date.now()) {
|
|
158378
|
+
this.activeTasks.set(taskId, { agentId, startTime: timestamp });
|
|
158379
|
+
this.totalTasksStarted++;
|
|
158380
|
+
}
|
|
158381
|
+
/**
|
|
158382
|
+
* Record task completion with duration
|
|
158383
|
+
*/
|
|
158384
|
+
recordTaskComplete(agentId, taskId, duration, _timestamp = Date.now()) {
|
|
158385
|
+
const taskInfo = this.activeTasks.get(taskId);
|
|
158386
|
+
if (taskInfo && taskInfo.agentId === agentId) {
|
|
158387
|
+
this.activeTasks.delete(taskId);
|
|
158388
|
+
this.taskDurations.push(duration);
|
|
158389
|
+
this.totalTasksCompleted++;
|
|
158390
|
+
}
|
|
158391
|
+
}
|
|
158392
|
+
/**
|
|
158393
|
+
* Record coordination latency
|
|
158394
|
+
*/
|
|
158395
|
+
recordCoordination(agentId, latency, _timestamp = Date.now()) {
|
|
158396
|
+
this.coordinationLatencies.push(latency);
|
|
158397
|
+
if (latency > 100) {
|
|
158398
|
+
this.issues.push(`High coordination latency for agent ${agentId}: ${latency}ms`);
|
|
158399
|
+
}
|
|
158400
|
+
}
|
|
158401
|
+
/**
|
|
158402
|
+
* Record memory usage
|
|
158403
|
+
*/
|
|
158404
|
+
recordMemoryUsage(heapUsed, heapTotal) {
|
|
158405
|
+
const snapshot = {
|
|
158406
|
+
timestamp: Date.now(),
|
|
158407
|
+
heapUsed,
|
|
158408
|
+
heapTotal,
|
|
158409
|
+
external: 0,
|
|
158410
|
+
rss: 0
|
|
158411
|
+
};
|
|
158412
|
+
this.memorySnapshots.push(snapshot);
|
|
158413
|
+
this.memorySum += heapUsed;
|
|
158414
|
+
this.memorySamples++;
|
|
158415
|
+
if (heapUsed > this.peakMemoryUsed) {
|
|
158416
|
+
this.peakMemoryUsed = heapUsed;
|
|
158417
|
+
}
|
|
158418
|
+
const MEMORY_LIMIT = 4 * 1024 * 1024 * 1024;
|
|
158419
|
+
if (heapUsed > MEMORY_LIMIT) {
|
|
158420
|
+
this.issues.push(`Memory exceeded 4GB limit: ${(heapUsed / 1024 / 1024 / 1024).toFixed(2)}GB`);
|
|
158421
|
+
}
|
|
158422
|
+
}
|
|
158423
|
+
/**
|
|
158424
|
+
* Record an issue
|
|
158425
|
+
*/
|
|
158426
|
+
recordIssue(message) {
|
|
158427
|
+
this.issues.push(message);
|
|
158428
|
+
}
|
|
158429
|
+
// ============================================================================
|
|
158430
|
+
// Query Methods
|
|
158431
|
+
// ============================================================================
|
|
158432
|
+
/**
|
|
158433
|
+
* Get current active agent count
|
|
158434
|
+
*/
|
|
158435
|
+
getAgentCount() {
|
|
158436
|
+
return this.activeAgents.size;
|
|
158437
|
+
}
|
|
158438
|
+
/**
|
|
158439
|
+
* Get peak agent count
|
|
158440
|
+
*/
|
|
158441
|
+
getPeakAgentCount() {
|
|
158442
|
+
return this.peakAgentCount;
|
|
158443
|
+
}
|
|
158444
|
+
/**
|
|
158445
|
+
* Get total agents spawned
|
|
158446
|
+
*/
|
|
158447
|
+
getTotalAgentsSpawned() {
|
|
158448
|
+
return this.totalAgentsSpawned;
|
|
158449
|
+
}
|
|
158450
|
+
/**
|
|
158451
|
+
* Get coordination latency at P95
|
|
158452
|
+
*/
|
|
158453
|
+
getP95CoordinationLatency() {
|
|
158454
|
+
return this.calculatePercentile(this.coordinationLatencies.toArray(), 0.95);
|
|
158455
|
+
}
|
|
158456
|
+
/**
|
|
158457
|
+
* Get coordination latency at P99
|
|
158458
|
+
*/
|
|
158459
|
+
getP99CoordinationLatency() {
|
|
158460
|
+
return this.calculatePercentile(this.coordinationLatencies.toArray(), 0.99);
|
|
158461
|
+
}
|
|
158462
|
+
/**
|
|
158463
|
+
* Get coordination latency percentiles
|
|
158464
|
+
*/
|
|
158465
|
+
getCoordinationLatencyPercentiles() {
|
|
158466
|
+
return this.calculatePercentiles(this.coordinationLatencies.toArray());
|
|
158467
|
+
}
|
|
158468
|
+
/**
|
|
158469
|
+
* Get task latency percentiles
|
|
158470
|
+
*/
|
|
158471
|
+
getTaskLatencyPercentiles() {
|
|
158472
|
+
return this.calculatePercentiles(this.taskDurations.toArray());
|
|
158473
|
+
}
|
|
158474
|
+
/**
|
|
158475
|
+
* Get maximum memory usage
|
|
158476
|
+
*/
|
|
158477
|
+
getMaxMemoryUsage() {
|
|
158478
|
+
return this.peakMemoryUsed;
|
|
158479
|
+
}
|
|
158480
|
+
/**
|
|
158481
|
+
* Get average memory usage
|
|
158482
|
+
*/
|
|
158483
|
+
getAverageMemoryUsage() {
|
|
158484
|
+
return this.memorySamples > 0 ? this.memorySum / this.memorySamples : 0;
|
|
158485
|
+
}
|
|
158486
|
+
/**
|
|
158487
|
+
* Get throughput metrics
|
|
158488
|
+
*/
|
|
158489
|
+
getThroughput() {
|
|
158490
|
+
const duration = this.getDuration();
|
|
158491
|
+
const durationSeconds = duration / 1e3;
|
|
158492
|
+
return {
|
|
158493
|
+
tasks: this.totalTasksCompleted,
|
|
158494
|
+
tasksPerSecond: durationSeconds > 0 ? this.totalTasksCompleted / durationSeconds : 0,
|
|
158495
|
+
agents: this.totalAgentsSpawned,
|
|
158496
|
+
agentsPerSecond: durationSeconds > 0 ? this.totalAgentsSpawned / durationSeconds : 0
|
|
158497
|
+
};
|
|
158498
|
+
}
|
|
158499
|
+
/**
|
|
158500
|
+
* Get test duration in milliseconds
|
|
158501
|
+
*/
|
|
158502
|
+
getDuration() {
|
|
158503
|
+
if (this.startTime === 0) return 0;
|
|
158504
|
+
const end = this.endTime > 0 ? this.endTime : Date.now();
|
|
158505
|
+
return end - this.startTime;
|
|
158506
|
+
}
|
|
158507
|
+
/**
|
|
158508
|
+
* Check if any agents are starving (no tasks for extended period)
|
|
158509
|
+
*/
|
|
158510
|
+
hasAgentStarvation() {
|
|
158511
|
+
const activeAgentCount = this.activeAgents.size;
|
|
158512
|
+
const activeTaskCount = this.activeTasks.size;
|
|
158513
|
+
return activeAgentCount > 0 && activeTaskCount === 0 && this.totalTasksCompleted > 0;
|
|
158514
|
+
}
|
|
158515
|
+
/**
|
|
158516
|
+
* Check for potential deadlocks (tasks started but not completing)
|
|
158517
|
+
*/
|
|
158518
|
+
hasDeadlocks() {
|
|
158519
|
+
const now = Date.now();
|
|
158520
|
+
const DEADLOCK_THRESHOLD = 6e4;
|
|
158521
|
+
const tasks = Array.from(this.activeTasks.values());
|
|
158522
|
+
for (const taskInfo of tasks) {
|
|
158523
|
+
if (now - taskInfo.startTime > DEADLOCK_THRESHOLD) {
|
|
158524
|
+
return true;
|
|
158525
|
+
}
|
|
158526
|
+
}
|
|
158527
|
+
return false;
|
|
158528
|
+
}
|
|
158529
|
+
// ============================================================================
|
|
158530
|
+
// Report Generation
|
|
158531
|
+
// ============================================================================
|
|
158532
|
+
/**
|
|
158533
|
+
* Export complete load test report
|
|
158534
|
+
*/
|
|
158535
|
+
exportReport() {
|
|
158536
|
+
const duration = this.getDuration();
|
|
158537
|
+
const coordinationLatency = this.getCoordinationLatencyPercentiles();
|
|
158538
|
+
const taskLatency = this.getTaskLatencyPercentiles();
|
|
158539
|
+
const throughput = this.getThroughput();
|
|
158540
|
+
const AGENT_TARGET = 100;
|
|
158541
|
+
const MEMORY_LIMIT = 4 * 1024 * 1024 * 1024;
|
|
158542
|
+
const LATENCY_TARGET = 100;
|
|
158543
|
+
const successCriteria = {
|
|
158544
|
+
agentCount: this.peakAgentCount >= AGENT_TARGET,
|
|
158545
|
+
memoryLimit: this.peakMemoryUsed < MEMORY_LIMIT,
|
|
158546
|
+
coordinationLatency: coordinationLatency.p95 <= LATENCY_TARGET,
|
|
158547
|
+
noDeadlocks: !this.hasDeadlocks(),
|
|
158548
|
+
noStarvation: !this.hasAgentStarvation()
|
|
158549
|
+
};
|
|
158550
|
+
const allCriteriaMet = Object.values(successCriteria).every(Boolean);
|
|
158551
|
+
const recommendations = this.generateRecommendations(
|
|
158552
|
+
coordinationLatency,
|
|
158553
|
+
this.peakMemoryUsed,
|
|
158554
|
+
throughput
|
|
158555
|
+
);
|
|
158556
|
+
return {
|
|
158557
|
+
summary: {
|
|
158558
|
+
totalAgents: this.totalAgentsSpawned,
|
|
158559
|
+
peakAgents: this.peakAgentCount,
|
|
158560
|
+
totalTasks: this.totalTasksCompleted,
|
|
158561
|
+
duration,
|
|
158562
|
+
success: allCriteriaMet,
|
|
158563
|
+
successCriteria
|
|
158564
|
+
},
|
|
158565
|
+
performance: {
|
|
158566
|
+
coordinationLatency,
|
|
158567
|
+
taskLatency,
|
|
158568
|
+
throughput
|
|
158569
|
+
},
|
|
158570
|
+
resources: {
|
|
158571
|
+
memoryPeak: this.peakMemoryUsed,
|
|
158572
|
+
memoryAverage: this.getAverageMemoryUsage(),
|
|
158573
|
+
cpuPeak: 0,
|
|
158574
|
+
// Not tracked yet
|
|
158575
|
+
cpuAverage: 0
|
|
158576
|
+
},
|
|
158577
|
+
timeline: {
|
|
158578
|
+
agentCounts: [...this.agentTimeline],
|
|
158579
|
+
latencies: [...this.latencyTimeline],
|
|
158580
|
+
memoryUsage: [...this.memoryTimeline]
|
|
158581
|
+
},
|
|
158582
|
+
issues: [...this.issues],
|
|
158583
|
+
recommendations
|
|
158584
|
+
};
|
|
158585
|
+
}
|
|
158586
|
+
// ============================================================================
|
|
158587
|
+
// Private Methods
|
|
158588
|
+
// ============================================================================
|
|
158589
|
+
sampleMemory() {
|
|
158590
|
+
const memUsage = process.memoryUsage();
|
|
158591
|
+
this.recordMemoryUsage(memUsage.heapUsed, memUsage.heapTotal);
|
|
158592
|
+
}
|
|
158593
|
+
sampleTimeline() {
|
|
158594
|
+
const now = Date.now();
|
|
158595
|
+
this.agentTimeline.push({
|
|
158596
|
+
timestamp: now,
|
|
158597
|
+
count: this.activeAgents.size
|
|
158598
|
+
});
|
|
158599
|
+
const latencies = this.coordinationLatencies.toArray();
|
|
158600
|
+
if (latencies.length > 0) {
|
|
158601
|
+
this.latencyTimeline.push({
|
|
158602
|
+
timestamp: now,
|
|
158603
|
+
p95: this.calculatePercentile(latencies, 0.95)
|
|
158604
|
+
});
|
|
158605
|
+
}
|
|
158606
|
+
const memUsage = process.memoryUsage();
|
|
158607
|
+
this.memoryTimeline.push({
|
|
158608
|
+
timestamp: now,
|
|
158609
|
+
heapUsed: memUsage.heapUsed
|
|
158610
|
+
});
|
|
158611
|
+
const MAX_TIMELINE_POINTS = 1e3;
|
|
158612
|
+
if (this.agentTimeline.length > MAX_TIMELINE_POINTS) {
|
|
158613
|
+
this.agentTimeline.shift();
|
|
158614
|
+
}
|
|
158615
|
+
if (this.latencyTimeline.length > MAX_TIMELINE_POINTS) {
|
|
158616
|
+
this.latencyTimeline.shift();
|
|
158617
|
+
}
|
|
158618
|
+
if (this.memoryTimeline.length > MAX_TIMELINE_POINTS) {
|
|
158619
|
+
this.memoryTimeline.shift();
|
|
158620
|
+
}
|
|
158621
|
+
}
|
|
158622
|
+
calculatePercentile(values, percentile) {
|
|
158623
|
+
if (values.length === 0) return 0;
|
|
158624
|
+
const sorted = [...values].sort((a37, b68) => a37 - b68);
|
|
158625
|
+
const index = Math.ceil(sorted.length * percentile) - 1;
|
|
158626
|
+
return sorted[Math.max(0, index)];
|
|
158627
|
+
}
|
|
158628
|
+
calculatePercentiles(values) {
|
|
158629
|
+
if (values.length === 0) {
|
|
158630
|
+
return { p50: 0, p95: 0, p99: 0, max: 0, min: 0, avg: 0, count: 0 };
|
|
158631
|
+
}
|
|
158632
|
+
const sorted = [...values].sort((a37, b68) => a37 - b68);
|
|
158633
|
+
const sum = sorted.reduce((a37, b68) => a37 + b68, 0);
|
|
158634
|
+
return {
|
|
158635
|
+
p50: this.calculatePercentile(sorted, 0.5),
|
|
158636
|
+
p95: this.calculatePercentile(sorted, 0.95),
|
|
158637
|
+
p99: this.calculatePercentile(sorted, 0.99),
|
|
158638
|
+
max: sorted[sorted.length - 1],
|
|
158639
|
+
min: sorted[0],
|
|
158640
|
+
avg: sum / sorted.length,
|
|
158641
|
+
count: sorted.length
|
|
158642
|
+
};
|
|
158643
|
+
}
|
|
158644
|
+
generateRecommendations(latency, memoryPeak, throughput) {
|
|
158645
|
+
const recommendations = [];
|
|
158646
|
+
if (latency.p95 > 100) {
|
|
158647
|
+
recommendations.push(
|
|
158648
|
+
`P95 coordination latency (${latency.p95.toFixed(1)}ms) exceeds 100ms target. Consider optimizing gossip protocol or reducing agent count.`
|
|
158649
|
+
);
|
|
158650
|
+
}
|
|
158651
|
+
if (latency.p99 > latency.p95 * 2) {
|
|
158652
|
+
recommendations.push(
|
|
158653
|
+
"High P99/P95 ratio indicates latency outliers. Investigate specific agents or network conditions."
|
|
158654
|
+
);
|
|
158655
|
+
}
|
|
158656
|
+
const memoryGB = memoryPeak / 1024 / 1024 / 1024;
|
|
158657
|
+
if (memoryGB > 3) {
|
|
158658
|
+
recommendations.push(
|
|
158659
|
+
`Memory usage (${memoryGB.toFixed(2)}GB) approaching 4GB limit. Consider implementing agent pooling or reducing per-agent memory.`
|
|
158660
|
+
);
|
|
158661
|
+
}
|
|
158662
|
+
if (throughput.tasksPerSecond < 10) {
|
|
158663
|
+
recommendations.push(
|
|
158664
|
+
`Task throughput (${throughput.tasksPerSecond.toFixed(1)}/s) is low. Consider parallelizing task execution or reducing task complexity.`
|
|
158665
|
+
);
|
|
158666
|
+
}
|
|
158667
|
+
if (this.issues.length > 10) {
|
|
158668
|
+
recommendations.push(
|
|
158669
|
+
`${this.issues.length} issues recorded. Review issue log for patterns and prioritize fixes.`
|
|
158670
|
+
);
|
|
158671
|
+
}
|
|
158672
|
+
return recommendations;
|
|
158673
|
+
}
|
|
158674
|
+
};
|
|
158675
|
+
}
|
|
158676
|
+
});
|
|
158677
|
+
|
|
158678
|
+
// src/testing/load/bottleneck-analyzer.ts
|
|
158679
|
+
function createBottleneckAnalyzer(config) {
|
|
158680
|
+
return new BottleneckAnalyzer(config);
|
|
158681
|
+
}
|
|
158682
|
+
function createBottleneckAnalyzerWithThresholds(thresholds) {
|
|
158683
|
+
return new BottleneckAnalyzer({
|
|
158684
|
+
thresholds: { ...DEFAULT_THRESHOLDS2, ...thresholds }
|
|
158685
|
+
});
|
|
158686
|
+
}
|
|
158687
|
+
var DEFAULT_THRESHOLDS2, DEFAULT_CONFIG73, BottleneckAnalyzer;
|
|
158688
|
+
var init_bottleneck_analyzer = __esm({
|
|
158689
|
+
"src/testing/load/bottleneck-analyzer.ts"() {
|
|
158690
|
+
"use strict";
|
|
158691
|
+
DEFAULT_THRESHOLDS2 = {
|
|
158692
|
+
// Memory (Issue #177: <4GB)
|
|
158693
|
+
memoryWarning: 3 * 1024 * 1024 * 1024,
|
|
158694
|
+
// 3GB
|
|
158695
|
+
memoryCritical: 4 * 1024 * 1024 * 1024,
|
|
158696
|
+
// 4GB
|
|
158697
|
+
// Coordination latency (Issue #177: <100ms p95)
|
|
158698
|
+
latencyP95Warning: 75,
|
|
158699
|
+
latencyP95Critical: 100,
|
|
158700
|
+
latencyP99Warning: 150,
|
|
158701
|
+
latencyP99Critical: 200,
|
|
158702
|
+
// Agent health
|
|
158703
|
+
agentStarvationTime: 3e4,
|
|
158704
|
+
// 30 seconds
|
|
158705
|
+
deadlockTimeout: 6e4,
|
|
158706
|
+
// 60 seconds
|
|
158707
|
+
// Throughput
|
|
158708
|
+
minTasksPerSecond: 5,
|
|
158709
|
+
minAgentUtilization: 0.5,
|
|
158710
|
+
// Gossip protocol
|
|
158711
|
+
gossipConvergenceTime: 5e3,
|
|
158712
|
+
gossipMaxPartitions: 0
|
|
158713
|
+
};
|
|
158714
|
+
DEFAULT_CONFIG73 = {
|
|
158715
|
+
thresholds: DEFAULT_THRESHOLDS2,
|
|
158716
|
+
enableDetailedAnalysis: true
|
|
158717
|
+
};
|
|
158718
|
+
BottleneckAnalyzer = class {
|
|
158719
|
+
config;
|
|
158720
|
+
constructor(config = {}) {
|
|
158721
|
+
this.config = {
|
|
158722
|
+
...DEFAULT_CONFIG73,
|
|
158723
|
+
...config,
|
|
158724
|
+
thresholds: {
|
|
158725
|
+
...DEFAULT_THRESHOLDS2,
|
|
158726
|
+
...config.thresholds
|
|
158727
|
+
}
|
|
158728
|
+
};
|
|
158729
|
+
}
|
|
158730
|
+
// ============================================================================
|
|
158731
|
+
// Main Analysis Methods
|
|
158732
|
+
// ============================================================================
|
|
158733
|
+
/**
|
|
158734
|
+
* Perform complete bottleneck analysis on collected metrics
|
|
158735
|
+
*/
|
|
158736
|
+
analyze(metrics) {
|
|
158737
|
+
const bottlenecks = [];
|
|
158738
|
+
bottlenecks.push(this.checkMemoryPressure(metrics));
|
|
158739
|
+
bottlenecks.push(this.checkCoordinationLatencyP95(metrics));
|
|
158740
|
+
bottlenecks.push(this.checkCoordinationLatencyP99(metrics));
|
|
158741
|
+
bottlenecks.push(this.checkAgentStarvation(metrics));
|
|
158742
|
+
bottlenecks.push(this.checkDeadlocks(metrics));
|
|
158743
|
+
bottlenecks.push(this.checkThroughput(metrics));
|
|
158744
|
+
const detected = bottlenecks.filter((b68) => b68.detected);
|
|
158745
|
+
const summary = {
|
|
158746
|
+
totalChecks: bottlenecks.length,
|
|
158747
|
+
detected: detected.length,
|
|
158748
|
+
critical: detected.filter((b68) => b68.severity === "critical").length,
|
|
158749
|
+
high: detected.filter((b68) => b68.severity === "high").length,
|
|
158750
|
+
medium: detected.filter((b68) => b68.severity === "medium").length,
|
|
158751
|
+
low: detected.filter((b68) => b68.severity === "low").length
|
|
158752
|
+
};
|
|
158753
|
+
let overallSeverity = "low";
|
|
158754
|
+
if (summary.critical > 0) {
|
|
158755
|
+
overallSeverity = "critical";
|
|
158756
|
+
} else if (summary.high > 0) {
|
|
158757
|
+
overallSeverity = "high";
|
|
158758
|
+
} else if (summary.medium > 0) {
|
|
158759
|
+
overallSeverity = "medium";
|
|
158760
|
+
}
|
|
158761
|
+
const recommendations = this.generatePrioritizedRecommendations(detected);
|
|
158762
|
+
return {
|
|
158763
|
+
overallSeverity,
|
|
158764
|
+
hasCritical: summary.critical > 0,
|
|
158765
|
+
bottlenecks,
|
|
158766
|
+
summary,
|
|
158767
|
+
recommendations,
|
|
158768
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
158769
|
+
};
|
|
158770
|
+
}
|
|
158771
|
+
/**
|
|
158772
|
+
* Analyze from a load test report
|
|
158773
|
+
*/
|
|
158774
|
+
analyzeReport(report) {
|
|
158775
|
+
const bottlenecks = [];
|
|
158776
|
+
bottlenecks.push(this.checkMemoryFromReport(report));
|
|
158777
|
+
bottlenecks.push(this.checkLatencyP95FromReport(report));
|
|
158778
|
+
bottlenecks.push(this.checkLatencyP99FromReport(report));
|
|
158779
|
+
bottlenecks.push(this.checkThroughputFromReport(report));
|
|
158780
|
+
bottlenecks.push(this.checkSuccessCriteria(report));
|
|
158781
|
+
const detected = bottlenecks.filter((b68) => b68.detected);
|
|
158782
|
+
const summary = {
|
|
158783
|
+
totalChecks: bottlenecks.length,
|
|
158784
|
+
detected: detected.length,
|
|
158785
|
+
critical: detected.filter((b68) => b68.severity === "critical").length,
|
|
158786
|
+
high: detected.filter((b68) => b68.severity === "high").length,
|
|
158787
|
+
medium: detected.filter((b68) => b68.severity === "medium").length,
|
|
158788
|
+
low: detected.filter((b68) => b68.severity === "low").length
|
|
158789
|
+
};
|
|
158790
|
+
let overallSeverity = "low";
|
|
158791
|
+
if (summary.critical > 0) {
|
|
158792
|
+
overallSeverity = "critical";
|
|
158793
|
+
} else if (summary.high > 0) {
|
|
158794
|
+
overallSeverity = "high";
|
|
158795
|
+
} else if (summary.medium > 0) {
|
|
158796
|
+
overallSeverity = "medium";
|
|
158797
|
+
}
|
|
158798
|
+
const recommendations = this.generatePrioritizedRecommendations(detected);
|
|
158799
|
+
return {
|
|
158800
|
+
overallSeverity,
|
|
158801
|
+
hasCritical: summary.critical > 0,
|
|
158802
|
+
bottlenecks,
|
|
158803
|
+
summary,
|
|
158804
|
+
recommendations,
|
|
158805
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
158806
|
+
};
|
|
158807
|
+
}
|
|
158808
|
+
// ============================================================================
|
|
158809
|
+
// Individual Check Methods
|
|
158810
|
+
// ============================================================================
|
|
158811
|
+
/**
|
|
158812
|
+
* Check for memory pressure
|
|
158813
|
+
*/
|
|
158814
|
+
checkMemoryPressure(metrics) {
|
|
158815
|
+
const actual = metrics?.getMaxMemoryUsage() ?? process.memoryUsage().heapUsed;
|
|
158816
|
+
const { memoryWarning, memoryCritical } = this.config.thresholds;
|
|
158817
|
+
const detected = actual >= memoryWarning;
|
|
158818
|
+
let severity = "low";
|
|
158819
|
+
let recommendation = "Memory usage is within acceptable limits.";
|
|
158820
|
+
if (actual >= memoryCritical) {
|
|
158821
|
+
severity = "critical";
|
|
158822
|
+
recommendation = "Memory usage exceeds 4GB limit. Implement agent pooling, reduce per-agent state, or decrease concurrent agent count.";
|
|
158823
|
+
} else if (actual >= memoryWarning) {
|
|
158824
|
+
severity = "high";
|
|
158825
|
+
recommendation = "Memory usage approaching critical threshold. Consider optimizing agent memory footprint or implementing memory pressure relief mechanisms.";
|
|
158826
|
+
}
|
|
158827
|
+
return {
|
|
158828
|
+
detected,
|
|
158829
|
+
severity,
|
|
158830
|
+
metric: "memory_pressure",
|
|
158831
|
+
threshold: memoryCritical,
|
|
158832
|
+
actual,
|
|
158833
|
+
recommendation,
|
|
158834
|
+
context: {
|
|
158835
|
+
usedGB: actual / 1024 / 1024 / 1024,
|
|
158836
|
+
warningThresholdGB: memoryWarning / 1024 / 1024 / 1024,
|
|
158837
|
+
criticalThresholdGB: memoryCritical / 1024 / 1024 / 1024
|
|
158838
|
+
}
|
|
158839
|
+
};
|
|
158840
|
+
}
|
|
158841
|
+
/**
|
|
158842
|
+
* Check coordination latency P95
|
|
158843
|
+
*/
|
|
158844
|
+
checkCoordinationLatency() {
|
|
158845
|
+
return this.checkCoordinationLatencyP95();
|
|
158846
|
+
}
|
|
158847
|
+
checkCoordinationLatencyP95(metrics) {
|
|
158848
|
+
const actual = metrics?.getP95CoordinationLatency() ?? 0;
|
|
158849
|
+
const { latencyP95Warning, latencyP95Critical } = this.config.thresholds;
|
|
158850
|
+
const detected = actual >= latencyP95Warning;
|
|
158851
|
+
let severity = "low";
|
|
158852
|
+
let recommendation = "P95 coordination latency is within acceptable limits.";
|
|
158853
|
+
if (actual >= latencyP95Critical) {
|
|
158854
|
+
severity = "critical";
|
|
158855
|
+
recommendation = "P95 coordination latency exceeds 100ms target. Optimize gossip protocol, reduce message size, or implement batching.";
|
|
158856
|
+
} else if (actual >= latencyP95Warning) {
|
|
158857
|
+
severity = "medium";
|
|
158858
|
+
recommendation = "P95 coordination latency approaching target. Monitor closely and consider proactive optimization.";
|
|
158859
|
+
}
|
|
158860
|
+
return {
|
|
158861
|
+
detected,
|
|
158862
|
+
severity,
|
|
158863
|
+
metric: "coordination_latency_p95",
|
|
158864
|
+
threshold: latencyP95Critical,
|
|
158865
|
+
actual,
|
|
158866
|
+
recommendation
|
|
158867
|
+
};
|
|
158868
|
+
}
|
|
158869
|
+
checkCoordinationLatencyP99(metrics) {
|
|
158870
|
+
const actual = metrics?.getP99CoordinationLatency() ?? 0;
|
|
158871
|
+
const { latencyP99Warning, latencyP99Critical } = this.config.thresholds;
|
|
158872
|
+
const detected = actual >= latencyP99Warning;
|
|
158873
|
+
let severity = "low";
|
|
158874
|
+
let recommendation = "P99 coordination latency is within acceptable limits.";
|
|
158875
|
+
if (actual >= latencyP99Critical) {
|
|
158876
|
+
severity = "high";
|
|
158877
|
+
recommendation = "P99 coordination latency indicates outliers. Investigate specific agents or network conditions causing delays.";
|
|
158878
|
+
} else if (actual >= latencyP99Warning) {
|
|
158879
|
+
severity = "medium";
|
|
158880
|
+
recommendation = "P99 coordination latency elevated. Some agents may be experiencing delays.";
|
|
158881
|
+
}
|
|
158882
|
+
return {
|
|
158883
|
+
detected,
|
|
158884
|
+
severity,
|
|
158885
|
+
metric: "coordination_latency_p99",
|
|
158886
|
+
threshold: latencyP99Critical,
|
|
158887
|
+
actual,
|
|
158888
|
+
recommendation
|
|
158889
|
+
};
|
|
158890
|
+
}
|
|
158891
|
+
/**
|
|
158892
|
+
* Check for agent starvation
|
|
158893
|
+
*/
|
|
158894
|
+
checkAgentStarvation(metrics) {
|
|
158895
|
+
const hasStarvation = metrics?.hasAgentStarvation() ?? false;
|
|
158896
|
+
return {
|
|
158897
|
+
detected: hasStarvation,
|
|
158898
|
+
severity: hasStarvation ? "high" : "low",
|
|
158899
|
+
metric: "agent_starvation",
|
|
158900
|
+
threshold: this.config.thresholds.agentStarvationTime,
|
|
158901
|
+
actual: hasStarvation ? 1 : 0,
|
|
158902
|
+
recommendation: hasStarvation ? "Agent starvation detected. Agents are idle without tasks. Review task distribution algorithm and work stealing configuration." : "No agent starvation detected."
|
|
158903
|
+
};
|
|
158904
|
+
}
|
|
158905
|
+
/**
|
|
158906
|
+
* Check for deadlocks
|
|
158907
|
+
*/
|
|
158908
|
+
checkDeadlocks(metrics) {
|
|
158909
|
+
const hasDeadlocks = metrics?.hasDeadlocks() ?? false;
|
|
158910
|
+
return {
|
|
158911
|
+
detected: hasDeadlocks,
|
|
158912
|
+
severity: hasDeadlocks ? "critical" : "low",
|
|
158913
|
+
metric: "deadlock_detection",
|
|
158914
|
+
threshold: this.config.thresholds.deadlockTimeout,
|
|
158915
|
+
actual: hasDeadlocks ? 1 : 0,
|
|
158916
|
+
recommendation: hasDeadlocks ? "Potential deadlock detected. Tasks running for extended period. Review lock ordering and add timeout mechanisms." : "No deadlocks detected."
|
|
158917
|
+
};
|
|
158918
|
+
}
|
|
158919
|
+
/**
|
|
158920
|
+
* Check gossip protocol stability
|
|
158921
|
+
*/
|
|
158922
|
+
checkGossipStability() {
|
|
158923
|
+
return {
|
|
158924
|
+
detected: false,
|
|
158925
|
+
severity: "low",
|
|
158926
|
+
metric: "gossip_stability",
|
|
158927
|
+
threshold: this.config.thresholds.gossipMaxPartitions,
|
|
158928
|
+
actual: 0,
|
|
158929
|
+
recommendation: "Gossip protocol stability check requires integration with gossip metrics."
|
|
158930
|
+
};
|
|
158931
|
+
}
|
|
158932
|
+
/**
|
|
158933
|
+
* Check throughput
|
|
158934
|
+
*/
|
|
158935
|
+
checkThroughput(metrics) {
|
|
158936
|
+
const throughput = metrics?.getThroughput();
|
|
158937
|
+
const actual = throughput?.tasksPerSecond ?? 0;
|
|
158938
|
+
const threshold = this.config.thresholds.minTasksPerSecond;
|
|
158939
|
+
const detected = actual < threshold && actual > 0;
|
|
158940
|
+
return {
|
|
158941
|
+
detected,
|
|
158942
|
+
severity: detected ? "medium" : "low",
|
|
158943
|
+
metric: "throughput",
|
|
158944
|
+
threshold,
|
|
158945
|
+
actual,
|
|
158946
|
+
recommendation: detected ? `Task throughput (${actual.toFixed(1)}/s) below minimum (${threshold}/s). Consider parallelizing task execution or optimizing task handlers.` : "Task throughput is acceptable."
|
|
158947
|
+
};
|
|
158948
|
+
}
|
|
158949
|
+
// ============================================================================
|
|
158950
|
+
// Report-based Check Methods
|
|
158951
|
+
// ============================================================================
|
|
158952
|
+
checkMemoryFromReport(report) {
|
|
158953
|
+
const actual = report.resources.memoryPeak;
|
|
158954
|
+
const { memoryCritical } = this.config.thresholds;
|
|
158955
|
+
return {
|
|
158956
|
+
detected: actual >= memoryCritical,
|
|
158957
|
+
severity: actual >= memoryCritical ? "critical" : "low",
|
|
158958
|
+
metric: "memory_pressure",
|
|
158959
|
+
threshold: memoryCritical,
|
|
158960
|
+
actual,
|
|
158961
|
+
recommendation: actual >= memoryCritical ? "Memory exceeded 4GB limit during test." : "Memory usage within limits."
|
|
158962
|
+
};
|
|
158963
|
+
}
|
|
158964
|
+
checkLatencyP95FromReport(report) {
|
|
158965
|
+
const actual = report.performance.coordinationLatency.p95;
|
|
158966
|
+
const { latencyP95Critical } = this.config.thresholds;
|
|
158967
|
+
return {
|
|
158968
|
+
detected: actual >= latencyP95Critical,
|
|
158969
|
+
severity: actual >= latencyP95Critical ? "critical" : "low",
|
|
158970
|
+
metric: "coordination_latency_p95",
|
|
158971
|
+
threshold: latencyP95Critical,
|
|
158972
|
+
actual,
|
|
158973
|
+
recommendation: actual >= latencyP95Critical ? "P95 coordination latency exceeded 100ms target." : "P95 latency within target."
|
|
158974
|
+
};
|
|
158975
|
+
}
|
|
158976
|
+
checkLatencyP99FromReport(report) {
|
|
158977
|
+
const actual = report.performance.coordinationLatency.p99;
|
|
158978
|
+
const { latencyP99Critical } = this.config.thresholds;
|
|
158979
|
+
return {
|
|
158980
|
+
detected: actual >= latencyP99Critical,
|
|
158981
|
+
severity: actual >= latencyP99Critical ? "high" : "low",
|
|
158982
|
+
metric: "coordination_latency_p99",
|
|
158983
|
+
threshold: latencyP99Critical,
|
|
158984
|
+
actual,
|
|
158985
|
+
recommendation: actual >= latencyP99Critical ? "P99 coordination latency indicates significant outliers." : "P99 latency acceptable."
|
|
158986
|
+
};
|
|
158987
|
+
}
|
|
158988
|
+
checkThroughputFromReport(report) {
|
|
158989
|
+
const actual = report.performance.throughput.tasksPerSecond;
|
|
158990
|
+
const threshold = this.config.thresholds.minTasksPerSecond;
|
|
158991
|
+
return {
|
|
158992
|
+
detected: actual < threshold && actual > 0,
|
|
158993
|
+
severity: actual < threshold ? "medium" : "low",
|
|
158994
|
+
metric: "throughput",
|
|
158995
|
+
threshold,
|
|
158996
|
+
actual,
|
|
158997
|
+
recommendation: actual < threshold ? "Throughput below minimum target." : "Throughput acceptable."
|
|
158998
|
+
};
|
|
158999
|
+
}
|
|
159000
|
+
checkSuccessCriteria(report) {
|
|
159001
|
+
const criteria = report.summary.successCriteria;
|
|
159002
|
+
const failedCriteria = Object.entries(criteria).filter(([_56, passed]) => !passed).map(([name]) => name);
|
|
159003
|
+
return {
|
|
159004
|
+
detected: failedCriteria.length > 0,
|
|
159005
|
+
severity: failedCriteria.length > 0 ? "high" : "low",
|
|
159006
|
+
metric: "success_criteria",
|
|
159007
|
+
threshold: 0,
|
|
159008
|
+
actual: failedCriteria.length,
|
|
159009
|
+
recommendation: failedCriteria.length > 0 ? `Failed criteria: ${failedCriteria.join(", ")}` : "All success criteria met.",
|
|
159010
|
+
context: { failedCriteria }
|
|
159011
|
+
};
|
|
159012
|
+
}
|
|
159013
|
+
// ============================================================================
|
|
159014
|
+
// Private Methods
|
|
159015
|
+
// ============================================================================
|
|
159016
|
+
generatePrioritizedRecommendations(detectedBottlenecks) {
|
|
159017
|
+
const priorityMap = {
|
|
159018
|
+
critical: 0,
|
|
159019
|
+
high: 1,
|
|
159020
|
+
medium: 2,
|
|
159021
|
+
low: 3
|
|
159022
|
+
};
|
|
159023
|
+
const sorted = [...detectedBottlenecks].sort(
|
|
159024
|
+
(a37, b68) => priorityMap[a37.severity] - priorityMap[b68.severity]
|
|
159025
|
+
);
|
|
159026
|
+
const seen = /* @__PURE__ */ new Set();
|
|
159027
|
+
const recommendations = [];
|
|
159028
|
+
for (const bottleneck of sorted) {
|
|
159029
|
+
if (!seen.has(bottleneck.recommendation)) {
|
|
159030
|
+
seen.add(bottleneck.recommendation);
|
|
159031
|
+
recommendations.push(
|
|
159032
|
+
`[${bottleneck.severity.toUpperCase()}] ${bottleneck.metric}: ${bottleneck.recommendation}`
|
|
159033
|
+
);
|
|
159034
|
+
}
|
|
159035
|
+
}
|
|
159036
|
+
return recommendations;
|
|
159037
|
+
}
|
|
159038
|
+
};
|
|
159039
|
+
}
|
|
159040
|
+
});
|
|
159041
|
+
|
|
159042
|
+
// src/testing/load/agent-load-tester.ts
|
|
159043
|
+
function createAgentLoadTester(config, metricsConfig, thresholds) {
|
|
159044
|
+
return new AgentLoadTester(config, metricsConfig, thresholds);
|
|
159045
|
+
}
|
|
159046
|
+
function createLoadTesterForTarget(targetAgents, workloadProfile = "medium") {
|
|
159047
|
+
return new AgentLoadTester({
|
|
159048
|
+
maxAgents: targetAgents,
|
|
159049
|
+
workloadProfile,
|
|
159050
|
+
mockMode: true
|
|
159051
|
+
});
|
|
159052
|
+
}
|
|
159053
|
+
var DEFAULT_CONFIG74, DEFAULT_SUCCESS_CRITERIA, WORKLOAD_PROFILES, SCENARIO_RAMP_UP_100, SCENARIO_BURST_100, SCENARIO_CHURN_100, SCENARIO_STRESS_150, AgentLoadTester;
|
|
159054
|
+
var init_agent_load_tester = __esm({
|
|
159055
|
+
"src/testing/load/agent-load-tester.ts"() {
|
|
159056
|
+
"use strict";
|
|
159057
|
+
init_esm_node();
|
|
159058
|
+
init_metrics_collector2();
|
|
159059
|
+
init_error_utils();
|
|
159060
|
+
init_bottleneck_analyzer();
|
|
159061
|
+
DEFAULT_CONFIG74 = {
|
|
159062
|
+
maxAgents: 100,
|
|
159063
|
+
memoryLimit: 4 * 1024 * 1024 * 1024,
|
|
159064
|
+
// 4GB
|
|
159065
|
+
coordinationTimeout: 100,
|
|
159066
|
+
workloadProfile: "medium",
|
|
159067
|
+
mockMode: true
|
|
159068
|
+
};
|
|
159069
|
+
DEFAULT_SUCCESS_CRITERIA = {
|
|
159070
|
+
agentCount: 100,
|
|
159071
|
+
memoryLimit: 4 * 1024 * 1024 * 1024,
|
|
159072
|
+
coordinationLatency: 100,
|
|
159073
|
+
noAgentStarvation: true,
|
|
159074
|
+
noDeadlocks: true,
|
|
159075
|
+
gossipStable: true
|
|
159076
|
+
};
|
|
159077
|
+
WORKLOAD_PROFILES = {
|
|
159078
|
+
light: {
|
|
159079
|
+
taskCount: 5,
|
|
159080
|
+
taskDuration: 50,
|
|
159081
|
+
memoryUsage: 5 * 1024 * 1024,
|
|
159082
|
+
// 5MB
|
|
159083
|
+
coordinationFrequency: 2,
|
|
159084
|
+
durationVariance: 0.1
|
|
159085
|
+
},
|
|
159086
|
+
medium: {
|
|
159087
|
+
taskCount: 10,
|
|
159088
|
+
taskDuration: 100,
|
|
159089
|
+
memoryUsage: 10 * 1024 * 1024,
|
|
159090
|
+
// 10MB
|
|
159091
|
+
coordinationFrequency: 5,
|
|
159092
|
+
durationVariance: 0.2
|
|
159093
|
+
},
|
|
159094
|
+
heavy: {
|
|
159095
|
+
taskCount: 20,
|
|
159096
|
+
taskDuration: 200,
|
|
159097
|
+
memoryUsage: 20 * 1024 * 1024,
|
|
159098
|
+
// 20MB
|
|
159099
|
+
coordinationFrequency: 10,
|
|
159100
|
+
durationVariance: 0.3
|
|
159101
|
+
}
|
|
159102
|
+
};
|
|
159103
|
+
SCENARIO_RAMP_UP_100 = {
|
|
159104
|
+
name: "ramp-up-100",
|
|
159105
|
+
description: "Gradually add agents until reaching 100",
|
|
159106
|
+
steps: [
|
|
159107
|
+
{ agents: 25, holdTime: 3e4 },
|
|
159108
|
+
{ agents: 50, holdTime: 3e4 },
|
|
159109
|
+
{ agents: 75, holdTime: 3e4 },
|
|
159110
|
+
{ agents: 100, holdTime: 6e4 }
|
|
159111
|
+
],
|
|
159112
|
+
workload: WORKLOAD_PROFILES.medium
|
|
159113
|
+
};
|
|
159114
|
+
SCENARIO_BURST_100 = {
|
|
159115
|
+
name: "burst-100",
|
|
159116
|
+
description: "Instantly spawn 100 agents",
|
|
159117
|
+
steps: [{ agents: 100, holdTime: 12e4 }],
|
|
159118
|
+
workload: {
|
|
159119
|
+
taskCount: 20,
|
|
159120
|
+
taskDuration: 50,
|
|
159121
|
+
memoryUsage: 10 * 1024 * 1024,
|
|
159122
|
+
coordinationFrequency: 5
|
|
159123
|
+
}
|
|
159124
|
+
};
|
|
159125
|
+
SCENARIO_CHURN_100 = {
|
|
159126
|
+
name: "churn-100",
|
|
159127
|
+
description: "Maintain 100 agents with continuous spawn/terminate",
|
|
159128
|
+
steps: [{ agents: 100, holdTime: 18e4, churnRate: 0.1 }],
|
|
159129
|
+
workload: {
|
|
159130
|
+
taskCount: 15,
|
|
159131
|
+
taskDuration: 75,
|
|
159132
|
+
memoryUsage: 10 * 1024 * 1024,
|
|
159133
|
+
coordinationFrequency: 5
|
|
159134
|
+
}
|
|
159135
|
+
};
|
|
159136
|
+
SCENARIO_STRESS_150 = {
|
|
159137
|
+
name: "stress-150",
|
|
159138
|
+
description: "Push beyond 100 agents to find limits",
|
|
159139
|
+
steps: [
|
|
159140
|
+
{ agents: 100, holdTime: 3e4 },
|
|
159141
|
+
{ agents: 125, holdTime: 3e4 },
|
|
159142
|
+
{ agents: 150, holdTime: 6e4 }
|
|
159143
|
+
],
|
|
159144
|
+
workload: WORKLOAD_PROFILES.heavy,
|
|
159145
|
+
criteria: {
|
|
159146
|
+
agentCount: 150
|
|
159147
|
+
}
|
|
159148
|
+
};
|
|
159149
|
+
AgentLoadTester = class {
|
|
159150
|
+
config;
|
|
159151
|
+
metrics;
|
|
159152
|
+
analyzer;
|
|
159153
|
+
running = false;
|
|
159154
|
+
stopRequested = false;
|
|
159155
|
+
// Mock agents for mock mode
|
|
159156
|
+
mockAgents = /* @__PURE__ */ new Map();
|
|
159157
|
+
mockTaskTimers = /* @__PURE__ */ new Map();
|
|
159158
|
+
mockCoordinationTimers = /* @__PURE__ */ new Map();
|
|
159159
|
+
churnTimer = null;
|
|
159160
|
+
// Random number generator (seeded for reproducibility)
|
|
159161
|
+
random;
|
|
159162
|
+
constructor(config = {}, metricsConfig, thresholds) {
|
|
159163
|
+
this.config = { ...DEFAULT_CONFIG74, ...config };
|
|
159164
|
+
this.metrics = createMetricsCollector(metricsConfig);
|
|
159165
|
+
this.analyzer = thresholds ? createBottleneckAnalyzerWithThresholds(thresholds) : createBottleneckAnalyzer();
|
|
159166
|
+
this.random = this.config.seed !== void 0 ? this.seededRandom(this.config.seed) : Math.random.bind(Math);
|
|
159167
|
+
}
|
|
159168
|
+
// ============================================================================
|
|
159169
|
+
// Public API
|
|
159170
|
+
// ============================================================================
|
|
159171
|
+
/**
|
|
159172
|
+
* Run a load test with specified agent count and duration
|
|
159173
|
+
*/
|
|
159174
|
+
async runTest(agentCount, duration) {
|
|
159175
|
+
const scenario = {
|
|
159176
|
+
name: "custom",
|
|
159177
|
+
description: `Custom test with ${agentCount} agents for ${duration}ms`,
|
|
159178
|
+
steps: [{ agents: agentCount, holdTime: duration }],
|
|
159179
|
+
workload: this.getWorkload()
|
|
159180
|
+
};
|
|
159181
|
+
return this.runScenario(scenario);
|
|
159182
|
+
}
|
|
159183
|
+
/**
|
|
159184
|
+
* Run a predefined load test scenario
|
|
159185
|
+
*/
|
|
159186
|
+
async runScenario(scenario) {
|
|
159187
|
+
if (this.running) {
|
|
159188
|
+
throw new Error("Load test already running");
|
|
159189
|
+
}
|
|
159190
|
+
this.running = true;
|
|
159191
|
+
this.stopRequested = false;
|
|
159192
|
+
const startTime = Date.now();
|
|
159193
|
+
try {
|
|
159194
|
+
this.metrics.reset();
|
|
159195
|
+
this.metrics.start();
|
|
159196
|
+
for (const step of scenario.steps) {
|
|
159197
|
+
if (this.stopRequested) break;
|
|
159198
|
+
await this.executeStep(step, scenario.workload);
|
|
159199
|
+
}
|
|
159200
|
+
this.metrics.stop();
|
|
159201
|
+
const report = this.metrics.exportReport();
|
|
159202
|
+
const bottlenecks = this.analyzer.analyzeReport(report);
|
|
159203
|
+
const criteria = { ...DEFAULT_SUCCESS_CRITERIA, ...scenario.criteria };
|
|
159204
|
+
const success = this.evaluateSuccess(report, criteria);
|
|
159205
|
+
return {
|
|
159206
|
+
success,
|
|
159207
|
+
report,
|
|
159208
|
+
bottlenecks,
|
|
159209
|
+
config: this.config,
|
|
159210
|
+
duration: Date.now() - startTime
|
|
159211
|
+
};
|
|
159212
|
+
} catch (error) {
|
|
159213
|
+
this.metrics.stop();
|
|
159214
|
+
return {
|
|
159215
|
+
success: false,
|
|
159216
|
+
report: this.metrics.exportReport(),
|
|
159217
|
+
bottlenecks: this.analyzer.analyze(this.metrics),
|
|
159218
|
+
config: this.config,
|
|
159219
|
+
duration: Date.now() - startTime,
|
|
159220
|
+
error: toErrorMessage(error)
|
|
159221
|
+
};
|
|
159222
|
+
} finally {
|
|
159223
|
+
await this.cleanup();
|
|
159224
|
+
this.running = false;
|
|
159225
|
+
}
|
|
159226
|
+
}
|
|
159227
|
+
/**
|
|
159228
|
+
* Gradually ramp up to target agent count
|
|
159229
|
+
*/
|
|
159230
|
+
async rampUp(targetCount, rampDuration) {
|
|
159231
|
+
const currentCount = this.mockAgents.size;
|
|
159232
|
+
const agentsToSpawn = targetCount - currentCount;
|
|
159233
|
+
if (agentsToSpawn <= 0) return;
|
|
159234
|
+
const spawnInterval = rampDuration / agentsToSpawn;
|
|
159235
|
+
const workload = this.getWorkload();
|
|
159236
|
+
for (let i58 = 0; i58 < agentsToSpawn && !this.stopRequested; i58++) {
|
|
159237
|
+
await this.spawnMockAgent(workload);
|
|
159238
|
+
await this.delay(spawnInterval);
|
|
159239
|
+
}
|
|
159240
|
+
}
|
|
159241
|
+
/**
|
|
159242
|
+
* Simulate a single agent's lifecycle
|
|
159243
|
+
*/
|
|
159244
|
+
async simulateAgent(agentId, workload) {
|
|
159245
|
+
const startTime = Date.now();
|
|
159246
|
+
let tasksCompleted = 0;
|
|
159247
|
+
let coordinationEvents = 0;
|
|
159248
|
+
for (let i58 = 0; i58 < workload.taskCount && !this.stopRequested; i58++) {
|
|
159249
|
+
const taskId = `${agentId}_task_${i58}`;
|
|
159250
|
+
const taskStart = Date.now();
|
|
159251
|
+
this.metrics.recordTaskStart(agentId, taskId, taskStart);
|
|
159252
|
+
const variance = workload.durationVariance ?? 0.2;
|
|
159253
|
+
const actualDuration = workload.taskDuration * (1 + (this.random() - 0.5) * 2 * variance);
|
|
159254
|
+
await this.delay(actualDuration);
|
|
159255
|
+
this.metrics.recordTaskComplete(agentId, taskId, Date.now() - taskStart);
|
|
159256
|
+
tasksCompleted++;
|
|
159257
|
+
const coordLatency = this.simulateCoordinationLatency();
|
|
159258
|
+
this.metrics.recordCoordination(agentId, coordLatency);
|
|
159259
|
+
coordinationEvents++;
|
|
159260
|
+
}
|
|
159261
|
+
return {
|
|
159262
|
+
agentId,
|
|
159263
|
+
tasksCompleted,
|
|
159264
|
+
totalDuration: Date.now() - startTime,
|
|
159265
|
+
coordinationEvents
|
|
159266
|
+
};
|
|
159267
|
+
}
|
|
159268
|
+
/**
|
|
159269
|
+
* Stop the running test
|
|
159270
|
+
*/
|
|
159271
|
+
async stop() {
|
|
159272
|
+
this.stopRequested = true;
|
|
159273
|
+
await this.delay(100);
|
|
159274
|
+
await this.cleanup();
|
|
159275
|
+
}
|
|
159276
|
+
/**
|
|
159277
|
+
* Get current metrics collector
|
|
159278
|
+
*/
|
|
159279
|
+
getMetrics() {
|
|
159280
|
+
return this.metrics;
|
|
159281
|
+
}
|
|
159282
|
+
/**
|
|
159283
|
+
* Get current agent count
|
|
159284
|
+
*/
|
|
159285
|
+
getAgentCount() {
|
|
159286
|
+
return this.mockAgents.size;
|
|
159287
|
+
}
|
|
159288
|
+
/**
|
|
159289
|
+
* Check if test is running
|
|
159290
|
+
*/
|
|
159291
|
+
isRunning() {
|
|
159292
|
+
return this.running;
|
|
159293
|
+
}
|
|
159294
|
+
// ============================================================================
|
|
159295
|
+
// Private Methods - Scenario Execution
|
|
159296
|
+
// ============================================================================
|
|
159297
|
+
async executeStep(step, workload) {
|
|
159298
|
+
const targetAgents = step.agents;
|
|
159299
|
+
const currentAgents = this.mockAgents.size;
|
|
159300
|
+
if (targetAgents > currentAgents) {
|
|
159301
|
+
await this.spawnAgents(targetAgents - currentAgents, workload);
|
|
159302
|
+
} else if (targetAgents < currentAgents) {
|
|
159303
|
+
await this.terminateAgents(currentAgents - targetAgents);
|
|
159304
|
+
}
|
|
159305
|
+
if (step.churnRate && step.churnRate > 0) {
|
|
159306
|
+
this.startChurn(step.churnRate, targetAgents, workload);
|
|
159307
|
+
}
|
|
159308
|
+
await this.hold(step.holdTime);
|
|
159309
|
+
this.stopChurn();
|
|
159310
|
+
}
|
|
159311
|
+
async spawnAgents(count, workload) {
|
|
159312
|
+
const promises2 = [];
|
|
159313
|
+
for (let i58 = 0; i58 < count && !this.stopRequested; i58++) {
|
|
159314
|
+
promises2.push(this.spawnMockAgent(workload));
|
|
159315
|
+
if (promises2.length >= 10) {
|
|
159316
|
+
await Promise.all(promises2);
|
|
159317
|
+
promises2.length = 0;
|
|
159318
|
+
await this.delay(10);
|
|
159319
|
+
}
|
|
159320
|
+
}
|
|
159321
|
+
if (promises2.length > 0) {
|
|
159322
|
+
await Promise.all(promises2);
|
|
159323
|
+
}
|
|
159324
|
+
}
|
|
159325
|
+
async terminateAgents(count) {
|
|
159326
|
+
const agents = Array.from(this.mockAgents.keys()).slice(0, count);
|
|
159327
|
+
for (const agentId of agents) {
|
|
159328
|
+
await this.terminateMockAgent(agentId);
|
|
159329
|
+
}
|
|
159330
|
+
}
|
|
159331
|
+
async hold(duration) {
|
|
159332
|
+
const checkInterval = 100;
|
|
159333
|
+
let elapsed = 0;
|
|
159334
|
+
while (elapsed < duration && !this.stopRequested) {
|
|
159335
|
+
await this.delay(checkInterval);
|
|
159336
|
+
elapsed += checkInterval;
|
|
159337
|
+
}
|
|
159338
|
+
}
|
|
159339
|
+
startChurn(rate, targetCount, workload) {
|
|
159340
|
+
const churnPerMinute = Math.ceil(targetCount * rate);
|
|
159341
|
+
const churnInterval = 6e4 / churnPerMinute;
|
|
159342
|
+
this.churnTimer = setInterval(async () => {
|
|
159343
|
+
if (this.random() < 0.5) {
|
|
159344
|
+
const agents = Array.from(this.mockAgents.keys());
|
|
159345
|
+
if (agents.length > 0) {
|
|
159346
|
+
const randomAgent = agents[Math.floor(this.random() * agents.length)];
|
|
159347
|
+
await this.terminateMockAgent(randomAgent);
|
|
159348
|
+
}
|
|
159349
|
+
}
|
|
159350
|
+
if (this.mockAgents.size < targetCount) {
|
|
159351
|
+
await this.spawnMockAgent(workload);
|
|
159352
|
+
}
|
|
159353
|
+
}, churnInterval);
|
|
159354
|
+
}
|
|
159355
|
+
stopChurn() {
|
|
159356
|
+
if (this.churnTimer) {
|
|
159357
|
+
clearInterval(this.churnTimer);
|
|
159358
|
+
this.churnTimer = null;
|
|
159359
|
+
}
|
|
159360
|
+
}
|
|
159361
|
+
// ============================================================================
|
|
159362
|
+
// Private Methods - Mock Agent Management
|
|
159363
|
+
// ============================================================================
|
|
159364
|
+
async spawnMockAgent(workload) {
|
|
159365
|
+
const agentId = `agent_${v4_default().slice(0, 8)}`;
|
|
159366
|
+
const now = Date.now();
|
|
159367
|
+
const agent = {
|
|
159368
|
+
id: agentId,
|
|
159369
|
+
domain: this.getRandomDomain(),
|
|
159370
|
+
spawnedAt: now,
|
|
159371
|
+
workload,
|
|
159372
|
+
activeTasks: 0,
|
|
159373
|
+
totalTasks: 0,
|
|
159374
|
+
terminated: false
|
|
159375
|
+
};
|
|
159376
|
+
this.mockAgents.set(agentId, agent);
|
|
159377
|
+
this.metrics.recordAgentSpawn(agentId, now);
|
|
159378
|
+
this.startAgentTasks(agent);
|
|
159379
|
+
this.startAgentCoordination(agent);
|
|
159380
|
+
this.simulateMemoryUsage();
|
|
159381
|
+
}
|
|
159382
|
+
async terminateMockAgent(agentId) {
|
|
159383
|
+
const agent = this.mockAgents.get(agentId);
|
|
159384
|
+
if (!agent) return;
|
|
159385
|
+
agent.terminated = true;
|
|
159386
|
+
const taskTimer = this.mockTaskTimers.get(agentId);
|
|
159387
|
+
if (taskTimer) {
|
|
159388
|
+
clearInterval(taskTimer);
|
|
159389
|
+
this.mockTaskTimers.delete(agentId);
|
|
159390
|
+
}
|
|
159391
|
+
const coordTimer = this.mockCoordinationTimers.get(agentId);
|
|
159392
|
+
if (coordTimer) {
|
|
159393
|
+
clearInterval(coordTimer);
|
|
159394
|
+
this.mockCoordinationTimers.delete(agentId);
|
|
159395
|
+
}
|
|
159396
|
+
this.mockAgents.delete(agentId);
|
|
159397
|
+
this.metrics.recordAgentTerminate(agentId, Date.now());
|
|
159398
|
+
}
|
|
159399
|
+
startAgentTasks(agent) {
|
|
159400
|
+
const taskInterval = agent.workload.taskDuration * 1.5;
|
|
159401
|
+
const timer = setInterval(() => {
|
|
159402
|
+
if (agent.terminated || agent.totalTasks >= agent.workload.taskCount) {
|
|
159403
|
+
clearInterval(timer);
|
|
159404
|
+
this.mockTaskTimers.delete(agent.id);
|
|
159405
|
+
return;
|
|
159406
|
+
}
|
|
159407
|
+
const taskId = `${agent.id}_task_${agent.totalTasks}`;
|
|
159408
|
+
const taskStart = Date.now();
|
|
159409
|
+
agent.activeTasks++;
|
|
159410
|
+
agent.totalTasks++;
|
|
159411
|
+
this.metrics.recordTaskStart(agent.id, taskId, taskStart);
|
|
159412
|
+
const variance = agent.workload.durationVariance ?? 0.2;
|
|
159413
|
+
const duration = agent.workload.taskDuration * (1 + (this.random() - 0.5) * 2 * variance);
|
|
159414
|
+
setTimeout(() => {
|
|
159415
|
+
if (!agent.terminated) {
|
|
159416
|
+
agent.activeTasks--;
|
|
159417
|
+
this.metrics.recordTaskComplete(agent.id, taskId, Date.now() - taskStart);
|
|
159418
|
+
}
|
|
159419
|
+
}, duration);
|
|
159420
|
+
}, taskInterval);
|
|
159421
|
+
this.mockTaskTimers.set(agent.id, timer);
|
|
159422
|
+
}
|
|
159423
|
+
startAgentCoordination(agent) {
|
|
159424
|
+
const coordInterval = 1e3 / agent.workload.coordinationFrequency;
|
|
159425
|
+
const timer = setInterval(() => {
|
|
159426
|
+
if (agent.terminated) {
|
|
159427
|
+
clearInterval(timer);
|
|
159428
|
+
this.mockCoordinationTimers.delete(agent.id);
|
|
159429
|
+
return;
|
|
159430
|
+
}
|
|
159431
|
+
const latency = this.simulateCoordinationLatency();
|
|
159432
|
+
this.metrics.recordCoordination(agent.id, latency);
|
|
159433
|
+
}, coordInterval);
|
|
159434
|
+
this.mockCoordinationTimers.set(agent.id, timer);
|
|
159435
|
+
}
|
|
159436
|
+
// ============================================================================
|
|
159437
|
+
// Private Methods - Simulation Helpers
|
|
159438
|
+
// ============================================================================
|
|
159439
|
+
simulateCoordinationLatency() {
|
|
159440
|
+
const baseLatency = 5;
|
|
159441
|
+
const variableLatency = this.random() * 30;
|
|
159442
|
+
const spike = this.random() < 0.05 ? this.random() * 100 : 0;
|
|
159443
|
+
const loadFactor = Math.min(this.mockAgents.size / 100, 2);
|
|
159444
|
+
const loadLatency = loadFactor * 10;
|
|
159445
|
+
return baseLatency + variableLatency + spike + loadLatency;
|
|
159446
|
+
}
|
|
159447
|
+
simulateMemoryUsage() {
|
|
159448
|
+
const agentCount = this.mockAgents.size;
|
|
159449
|
+
const avgMemoryPerAgent = this.getWorkload().memoryUsage;
|
|
159450
|
+
const baseMemory = 50 * 1024 * 1024;
|
|
159451
|
+
const agentMemory = agentCount * avgMemoryPerAgent;
|
|
159452
|
+
const overhead = agentCount * 1024 * 100;
|
|
159453
|
+
const estimatedHeapUsed = baseMemory + agentMemory + overhead;
|
|
159454
|
+
const estimatedHeapTotal = estimatedHeapUsed * 1.5;
|
|
159455
|
+
this.metrics.recordMemoryUsage(estimatedHeapUsed, estimatedHeapTotal);
|
|
159456
|
+
}
|
|
159457
|
+
getRandomDomain() {
|
|
159458
|
+
const domains = [
|
|
159459
|
+
"test-generation",
|
|
159460
|
+
"test-execution",
|
|
159461
|
+
"coverage-analysis",
|
|
159462
|
+
"quality-assessment",
|
|
159463
|
+
"defect-intelligence",
|
|
159464
|
+
"security-compliance"
|
|
159465
|
+
];
|
|
159466
|
+
return domains[Math.floor(this.random() * domains.length)];
|
|
159467
|
+
}
|
|
159468
|
+
getWorkload() {
|
|
159469
|
+
if (this.config.customWorkload) {
|
|
159470
|
+
return this.config.customWorkload;
|
|
159471
|
+
}
|
|
159472
|
+
return WORKLOAD_PROFILES[this.config.workloadProfile];
|
|
159473
|
+
}
|
|
159474
|
+
evaluateSuccess(report, criteria) {
|
|
159475
|
+
const { summary, performance: performance4 } = report;
|
|
159476
|
+
if (summary.peakAgents < criteria.agentCount) return false;
|
|
159477
|
+
if (report.resources.memoryPeak >= criteria.memoryLimit) return false;
|
|
159478
|
+
if (performance4.coordinationLatency.p95 > criteria.coordinationLatency) return false;
|
|
159479
|
+
if (criteria.noAgentStarvation && !summary.successCriteria.noStarvation) return false;
|
|
159480
|
+
if (criteria.noDeadlocks && !summary.successCriteria.noDeadlocks) return false;
|
|
159481
|
+
return true;
|
|
159482
|
+
}
|
|
159483
|
+
async cleanup() {
|
|
159484
|
+
this.stopChurn();
|
|
159485
|
+
const agents = Array.from(this.mockAgents.keys());
|
|
159486
|
+
for (const agentId of agents) {
|
|
159487
|
+
await this.terminateMockAgent(agentId);
|
|
159488
|
+
}
|
|
159489
|
+
this.mockTaskTimers.forEach((timer) => {
|
|
159490
|
+
clearInterval(timer);
|
|
159491
|
+
});
|
|
159492
|
+
this.mockTaskTimers.clear();
|
|
159493
|
+
this.mockCoordinationTimers.forEach((timer) => {
|
|
159494
|
+
clearInterval(timer);
|
|
159495
|
+
});
|
|
159496
|
+
this.mockCoordinationTimers.clear();
|
|
159497
|
+
}
|
|
159498
|
+
delay(ms) {
|
|
159499
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
159500
|
+
}
|
|
159501
|
+
seededRandom(seed) {
|
|
159502
|
+
return function() {
|
|
159503
|
+
let t50 = seed += 1831565813;
|
|
159504
|
+
t50 = Math.imul(t50 ^ t50 >>> 15, t50 | 1);
|
|
159505
|
+
t50 ^= t50 + Math.imul(t50 ^ t50 >>> 7, t50 | 61);
|
|
159506
|
+
return ((t50 ^ t50 >>> 14) >>> 0) / 4294967296;
|
|
159507
|
+
};
|
|
159508
|
+
}
|
|
159509
|
+
};
|
|
159510
|
+
}
|
|
159511
|
+
});
|
|
159512
|
+
|
|
159513
|
+
// src/testing/load/index.ts
|
|
159514
|
+
var load_exports = {};
|
|
159515
|
+
__export(load_exports, {
|
|
159516
|
+
AgentLoadTester: () => AgentLoadTester,
|
|
159517
|
+
BottleneckAnalyzer: () => BottleneckAnalyzer,
|
|
159518
|
+
DEFAULT_ANALYZER_CONFIG: () => DEFAULT_CONFIG73,
|
|
159519
|
+
DEFAULT_LOAD_TEST_CONFIG: () => DEFAULT_CONFIG74,
|
|
159520
|
+
DEFAULT_SUCCESS_CRITERIA: () => DEFAULT_SUCCESS_CRITERIA,
|
|
159521
|
+
DEFAULT_THRESHOLDS: () => DEFAULT_THRESHOLDS2,
|
|
159522
|
+
MetricsCollector: () => MetricsCollector2,
|
|
159523
|
+
SCENARIO_BURST_100: () => SCENARIO_BURST_100,
|
|
159524
|
+
SCENARIO_CHURN_100: () => SCENARIO_CHURN_100,
|
|
159525
|
+
SCENARIO_RAMP_UP_100: () => SCENARIO_RAMP_UP_100,
|
|
159526
|
+
SCENARIO_STRESS_150: () => SCENARIO_STRESS_150,
|
|
159527
|
+
WORKLOAD_PROFILES: () => WORKLOAD_PROFILES,
|
|
159528
|
+
createAgentLoadTester: () => createAgentLoadTester,
|
|
159529
|
+
createBottleneckAnalyzer: () => createBottleneckAnalyzer,
|
|
159530
|
+
createBottleneckAnalyzerWithThresholds: () => createBottleneckAnalyzerWithThresholds,
|
|
159531
|
+
createLoadTesterForTarget: () => createLoadTesterForTarget,
|
|
159532
|
+
createMetricsCollector: () => createMetricsCollector
|
|
159533
|
+
});
|
|
159534
|
+
var init_load = __esm({
|
|
159535
|
+
"src/testing/load/index.ts"() {
|
|
159536
|
+
"use strict";
|
|
159537
|
+
init_agent_load_tester();
|
|
159538
|
+
init_metrics_collector2();
|
|
159539
|
+
init_bottleneck_analyzer();
|
|
159540
|
+
}
|
|
159541
|
+
});
|
|
159542
|
+
|
|
159543
|
+
// src/mcp/tools/test-execution/load-test.ts
|
|
159544
|
+
var LoadTestTool;
|
|
159545
|
+
var init_load_test = __esm({
|
|
159546
|
+
"src/mcp/tools/test-execution/load-test.ts"() {
|
|
159547
|
+
"use strict";
|
|
159548
|
+
init_base();
|
|
159549
|
+
init_error_utils();
|
|
159550
|
+
LoadTestTool = class extends MCPToolBase {
|
|
159551
|
+
config = {
|
|
159552
|
+
name: "qe/tests/load",
|
|
159553
|
+
description: "Run agent load tests to validate fleet scalability. Supports light, medium, and heavy workload profiles. Uses mock agents by default (safe); set mockMode=false to test with real fleet agents (requires fleet_init). Reports bottlenecks and pass/fail criteria.",
|
|
159554
|
+
domain: "test-execution",
|
|
159555
|
+
schema: this.buildSchema(),
|
|
159556
|
+
timeout: 3e5
|
|
159557
|
+
};
|
|
159558
|
+
buildSchema() {
|
|
159559
|
+
return {
|
|
159560
|
+
type: "object",
|
|
159561
|
+
properties: {
|
|
159562
|
+
targetAgents: {
|
|
159563
|
+
type: "number",
|
|
159564
|
+
description: "Target number of concurrent agents to simulate",
|
|
159565
|
+
default: 10,
|
|
159566
|
+
minimum: 1,
|
|
159567
|
+
maximum: 200
|
|
159568
|
+
},
|
|
159569
|
+
profile: {
|
|
159570
|
+
type: "string",
|
|
159571
|
+
description: "Workload profile: light, medium, or heavy",
|
|
159572
|
+
enum: ["light", "medium", "heavy"],
|
|
159573
|
+
default: "medium"
|
|
159574
|
+
},
|
|
159575
|
+
durationMs: {
|
|
159576
|
+
type: "number",
|
|
159577
|
+
description: "Test duration in milliseconds",
|
|
159578
|
+
default: 3e4,
|
|
159579
|
+
minimum: 5e3,
|
|
159580
|
+
maximum: 3e5
|
|
159581
|
+
},
|
|
159582
|
+
mockMode: {
|
|
159583
|
+
type: "boolean",
|
|
159584
|
+
description: "Use mock agents (true, default) or real fleet agents (false, requires fleet_init)",
|
|
159585
|
+
default: true
|
|
159586
|
+
}
|
|
159587
|
+
}
|
|
159588
|
+
};
|
|
159589
|
+
}
|
|
159590
|
+
async execute(params, context) {
|
|
159591
|
+
try {
|
|
159592
|
+
const { createAgentLoadTester: createAgentLoadTester2 } = await Promise.resolve().then(() => (init_load(), load_exports));
|
|
159593
|
+
const profile = params.profile || "medium";
|
|
159594
|
+
const targetAgents = params.targetAgents || 10;
|
|
159595
|
+
const durationMs = params.durationMs || 3e4;
|
|
159596
|
+
const mockMode = params.mockMode !== false;
|
|
159597
|
+
const tester = createAgentLoadTester2({
|
|
159598
|
+
maxAgents: targetAgents,
|
|
159599
|
+
workloadProfile: profile,
|
|
159600
|
+
mockMode
|
|
159601
|
+
});
|
|
159602
|
+
const result = await tester.runTest(targetAgents, durationMs);
|
|
159603
|
+
const bottlenecks = result.bottlenecks;
|
|
159604
|
+
return {
|
|
159605
|
+
success: true,
|
|
159606
|
+
data: {
|
|
159607
|
+
testId: context.requestId,
|
|
159608
|
+
profile,
|
|
159609
|
+
targetAgents,
|
|
159610
|
+
duration: result.duration,
|
|
159611
|
+
mockMode,
|
|
159612
|
+
passed: result.success,
|
|
159613
|
+
bottleneckCount: bottlenecks?.bottlenecks?.length ?? 0,
|
|
159614
|
+
report: {
|
|
159615
|
+
overallSeverity: bottlenecks?.overallSeverity ?? "none",
|
|
159616
|
+
hasCritical: bottlenecks?.hasCritical ?? false,
|
|
159617
|
+
checksPerformed: bottlenecks?.summary?.totalChecks ?? 0,
|
|
159618
|
+
bottlenecksDetected: bottlenecks?.summary?.detected ?? 0
|
|
159619
|
+
},
|
|
159620
|
+
summary: `Load test (${profile}, ${mockMode ? "mock" : "real"}): ${targetAgents} agents, ${durationMs}ms \u2014 ${result.success ? "PASSED" : "FAILED"}` + (bottlenecks?.hasCritical ? " [CRITICAL BOTTLENECKS]" : "")
|
|
159621
|
+
}
|
|
159622
|
+
};
|
|
159623
|
+
} catch (error) {
|
|
159624
|
+
return {
|
|
159625
|
+
success: false,
|
|
159626
|
+
error: toErrorMessage(error)
|
|
159627
|
+
};
|
|
159628
|
+
}
|
|
159629
|
+
}
|
|
159630
|
+
};
|
|
159631
|
+
}
|
|
159632
|
+
});
|
|
159633
|
+
|
|
159634
|
+
// src/mcp/tools/security-compliance/visual-security.ts
|
|
159635
|
+
var PII_PATTERNS, VisualSecurityTool;
|
|
159636
|
+
var init_visual_security = __esm({
|
|
159637
|
+
"src/mcp/tools/security-compliance/visual-security.ts"() {
|
|
159638
|
+
"use strict";
|
|
159639
|
+
init_base();
|
|
159640
|
+
init_error_utils();
|
|
159641
|
+
PII_PATTERNS = [
|
|
159642
|
+
{
|
|
159643
|
+
type: "email",
|
|
159644
|
+
pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
159645
|
+
mask: (m74) => m74[0] + "***@" + m74.split("@")[1]
|
|
159646
|
+
},
|
|
159647
|
+
{
|
|
159648
|
+
type: "ssn",
|
|
159649
|
+
pattern: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
159650
|
+
mask: () => "***-**-****"
|
|
159651
|
+
},
|
|
159652
|
+
{
|
|
159653
|
+
type: "credit-card",
|
|
159654
|
+
pattern: /\b(?:\d[ -]*?){13,19}\b/g,
|
|
159655
|
+
mask: (m74) => "****-****-****-" + m74.replace(/\D/g, "").slice(-4)
|
|
159656
|
+
},
|
|
159657
|
+
{
|
|
159658
|
+
type: "phone",
|
|
159659
|
+
pattern: /\b(?:\+?1[-.\s]?)?(?:\(?\d{3}\)?[-.\s]?)?\d{3}[-.\s]?\d{4}\b/g,
|
|
159660
|
+
mask: (m74) => m74.slice(0, 3) + "***" + m74.slice(-2)
|
|
159661
|
+
},
|
|
159662
|
+
{
|
|
159663
|
+
type: "api-key",
|
|
159664
|
+
// Common API key patterns: long hex/base64 strings in query params
|
|
159665
|
+
pattern: /(?:key|token|api_key|apikey|secret|password|passwd|auth)=([A-Za-z0-9_\-]{16,})/gi,
|
|
159666
|
+
mask: () => "***REDACTED***"
|
|
159667
|
+
}
|
|
159668
|
+
];
|
|
159669
|
+
VisualSecurityTool = class extends MCPToolBase {
|
|
159670
|
+
config = {
|
|
159671
|
+
name: "qe/security/url-validate",
|
|
159672
|
+
description: "Validate URL security: checks for XSS/injection patterns, unsafe protocols, and scans URL query parameters for PII exposure (emails, SSNs, credit cards, API keys).",
|
|
159673
|
+
domain: "security-compliance",
|
|
159674
|
+
schema: this.buildSchema()
|
|
159675
|
+
};
|
|
159676
|
+
buildSchema() {
|
|
159677
|
+
return {
|
|
159678
|
+
type: "object",
|
|
159679
|
+
properties: {
|
|
159680
|
+
url: {
|
|
159681
|
+
type: "string",
|
|
159682
|
+
description: "URL to validate for security threats and PII exposure"
|
|
159683
|
+
},
|
|
159684
|
+
enablePII: {
|
|
159685
|
+
type: "boolean",
|
|
159686
|
+
description: "Enable PII exposure scanning in URL and query parameters",
|
|
159687
|
+
default: true
|
|
159688
|
+
}
|
|
159689
|
+
},
|
|
159690
|
+
required: ["url"]
|
|
159691
|
+
};
|
|
159692
|
+
}
|
|
159693
|
+
async execute(params, context) {
|
|
159694
|
+
try {
|
|
159695
|
+
const urlSecurity = this.validateURLSecurity(params.url);
|
|
159696
|
+
const piiExposure = params.enablePII !== false ? this.scanForPII(params.url) : { scanned: false, found: false, types: [], details: [] };
|
|
159697
|
+
const issueCount = urlSecurity.issues.length + (piiExposure.found ? piiExposure.types.length : 0);
|
|
159698
|
+
const summary = issueCount === 0 ? `URL passed all checks (security: clean, PII: ${piiExposure.scanned ? "none found" : "not scanned"})` : `URL has ${urlSecurity.issues.length} security issue(s)${piiExposure.found ? ` and ${piiExposure.types.length} PII type(s) exposed in URL` : ""}`;
|
|
159699
|
+
return {
|
|
159700
|
+
success: true,
|
|
159701
|
+
data: {
|
|
159702
|
+
url: params.url,
|
|
159703
|
+
urlSecurity,
|
|
159704
|
+
piiExposure,
|
|
159705
|
+
summary
|
|
159706
|
+
}
|
|
159707
|
+
};
|
|
159708
|
+
} catch (error) {
|
|
159709
|
+
return {
|
|
159710
|
+
success: false,
|
|
159711
|
+
error: toErrorMessage(error)
|
|
159712
|
+
};
|
|
159713
|
+
}
|
|
159714
|
+
}
|
|
159715
|
+
validateURLSecurity(url) {
|
|
159716
|
+
const issues = [];
|
|
159717
|
+
let riskLevel = "none";
|
|
159718
|
+
try {
|
|
159719
|
+
const parsed = new URL(url);
|
|
159720
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
159721
|
+
issues.push({
|
|
159722
|
+
type: "unsafe-protocol",
|
|
159723
|
+
description: `Protocol ${parsed.protocol} is not allowed`,
|
|
159724
|
+
severity: "high"
|
|
159725
|
+
});
|
|
159726
|
+
riskLevel = "high";
|
|
159727
|
+
}
|
|
159728
|
+
const xssPatterns = [/<script/i, /javascript:/i, /on\w+=/i, /data:text\/html/i];
|
|
159729
|
+
for (const pattern of xssPatterns) {
|
|
159730
|
+
if (pattern.test(url)) {
|
|
159731
|
+
issues.push({
|
|
159732
|
+
type: "xss",
|
|
159733
|
+
description: "Potential XSS pattern detected in URL",
|
|
159734
|
+
severity: "critical"
|
|
159735
|
+
});
|
|
159736
|
+
riskLevel = "critical";
|
|
159737
|
+
break;
|
|
159738
|
+
}
|
|
159739
|
+
}
|
|
159740
|
+
const sqlPatterns = [/'.*or.*'/i, /union.*select/i, /drop.*table/i, /;\s*--/i];
|
|
159741
|
+
for (const pattern of sqlPatterns) {
|
|
159742
|
+
if (pattern.test(url)) {
|
|
159743
|
+
issues.push({
|
|
159744
|
+
type: "sql-injection",
|
|
159745
|
+
description: "Potential SQL injection pattern detected in URL",
|
|
159746
|
+
severity: "critical"
|
|
159747
|
+
});
|
|
159748
|
+
riskLevel = "critical";
|
|
159749
|
+
break;
|
|
159750
|
+
}
|
|
159751
|
+
}
|
|
159752
|
+
if (/\.\.[/\\]/.test(url)) {
|
|
159753
|
+
issues.push({
|
|
159754
|
+
type: "path-traversal",
|
|
159755
|
+
description: "Potential path traversal pattern (../) detected",
|
|
159756
|
+
severity: "high"
|
|
159757
|
+
});
|
|
159758
|
+
if (riskLevel !== "critical") riskLevel = "high";
|
|
159759
|
+
}
|
|
159760
|
+
const redirectParams = ["redirect", "url", "next", "return", "returnUrl", "goto"];
|
|
159761
|
+
for (const param of redirectParams) {
|
|
159762
|
+
const value = parsed.searchParams.get(param);
|
|
159763
|
+
if (value && /^https?:\/\//.test(value)) {
|
|
159764
|
+
issues.push({
|
|
159765
|
+
type: "open-redirect",
|
|
159766
|
+
description: `Query parameter "${param}" contains an external URL \u2014 potential open redirect`,
|
|
159767
|
+
severity: "medium"
|
|
159768
|
+
});
|
|
159769
|
+
if (riskLevel === "none") riskLevel = "medium";
|
|
159770
|
+
}
|
|
159771
|
+
}
|
|
159772
|
+
} catch {
|
|
159773
|
+
issues.push({
|
|
159774
|
+
type: "invalid-url",
|
|
159775
|
+
description: "URL could not be parsed",
|
|
159776
|
+
severity: "critical"
|
|
159777
|
+
});
|
|
159778
|
+
riskLevel = "critical";
|
|
159779
|
+
}
|
|
159780
|
+
return { valid: issues.length === 0, riskLevel, issues };
|
|
159781
|
+
}
|
|
159782
|
+
scanForPII(url) {
|
|
159783
|
+
const details = [];
|
|
159784
|
+
const typesFound = /* @__PURE__ */ new Set();
|
|
159785
|
+
let decoded;
|
|
159786
|
+
try {
|
|
159787
|
+
decoded = decodeURIComponent(url);
|
|
159788
|
+
} catch {
|
|
159789
|
+
decoded = url;
|
|
159790
|
+
}
|
|
159791
|
+
let queryString = "";
|
|
159792
|
+
let pathString = "";
|
|
159793
|
+
try {
|
|
159794
|
+
const parsed = new URL(decoded);
|
|
159795
|
+
queryString = parsed.search;
|
|
159796
|
+
pathString = parsed.pathname;
|
|
159797
|
+
} catch {
|
|
159798
|
+
queryString = decoded;
|
|
159799
|
+
}
|
|
159800
|
+
for (const { type, pattern, mask } of PII_PATTERNS) {
|
|
159801
|
+
pattern.lastIndex = 0;
|
|
159802
|
+
let match;
|
|
159803
|
+
while ((match = pattern.exec(decoded)) !== null) {
|
|
159804
|
+
typesFound.add(type);
|
|
159805
|
+
const location = queryString.includes(match[0]) ? "query-parameter" : pathString.includes(match[0]) ? "path" : "url";
|
|
159806
|
+
details.push({
|
|
159807
|
+
type,
|
|
159808
|
+
location,
|
|
159809
|
+
masked: mask(match[0])
|
|
159810
|
+
});
|
|
159811
|
+
}
|
|
159812
|
+
}
|
|
159813
|
+
return {
|
|
159814
|
+
scanned: true,
|
|
159815
|
+
found: typesFound.size > 0,
|
|
159816
|
+
types: Array.from(typesFound),
|
|
159817
|
+
details
|
|
159818
|
+
};
|
|
159819
|
+
}
|
|
159820
|
+
};
|
|
159821
|
+
}
|
|
159822
|
+
});
|
|
159823
|
+
|
|
159824
|
+
// src/workflows/browser/workflow-loader.ts
|
|
159825
|
+
import { readFile as readFile16, readdir as readdir5 } from "fs/promises";
|
|
159826
|
+
import { join as join25, basename as basename5 } from "path";
|
|
159827
|
+
import { parse as parseYaml2 } from "yaml";
|
|
159828
|
+
function interpolateVariables(template, variables) {
|
|
159829
|
+
return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
159830
|
+
const trimmedKey = key.trim();
|
|
159831
|
+
const keys = trimmedKey.split(".");
|
|
159832
|
+
let value = variables;
|
|
159833
|
+
for (const k68 of keys) {
|
|
159834
|
+
if (value && typeof value === "object" && k68 in value) {
|
|
159835
|
+
value = value[k68];
|
|
159836
|
+
} else {
|
|
159837
|
+
return match;
|
|
159838
|
+
}
|
|
159839
|
+
}
|
|
159840
|
+
return value !== void 0 && value !== null ? String(value) : match;
|
|
159841
|
+
});
|
|
159842
|
+
}
|
|
159843
|
+
function evaluateCondition(condition, context) {
|
|
159844
|
+
const evalContext = {
|
|
159845
|
+
...context.variables,
|
|
159846
|
+
result: context.results.get("__last_result__")
|
|
159847
|
+
};
|
|
159848
|
+
const interpolated = interpolateVariables(condition, evalContext);
|
|
159849
|
+
return safeEvaluateBoolean(interpolated, evalContext, false);
|
|
159850
|
+
}
|
|
159851
|
+
var WorkflowLoader, defaultWorkflowLoader;
|
|
159852
|
+
var init_workflow_loader = __esm({
|
|
159853
|
+
"src/workflows/browser/workflow-loader.ts"() {
|
|
159854
|
+
"use strict";
|
|
159855
|
+
init_safe_expression_evaluator();
|
|
159856
|
+
WorkflowLoader = class {
|
|
159857
|
+
templatesDir;
|
|
159858
|
+
cache = /* @__PURE__ */ new Map();
|
|
159859
|
+
constructor(templatesDir) {
|
|
159860
|
+
this.templatesDir = templatesDir || join25(__dirname, "templates");
|
|
159861
|
+
}
|
|
159862
|
+
/**
|
|
159863
|
+
* Load a workflow template by name
|
|
159864
|
+
*/
|
|
159865
|
+
async load(templateName) {
|
|
159866
|
+
if (this.cache.has(templateName)) {
|
|
159867
|
+
return this.cache.get(templateName);
|
|
159868
|
+
}
|
|
159869
|
+
try {
|
|
159870
|
+
const templatePath = join25(this.templatesDir, `${templateName}.yaml`);
|
|
159871
|
+
const content = await readFile16(templatePath, "utf-8");
|
|
159872
|
+
const workflow = parseYaml2(content);
|
|
159873
|
+
const validation = await this.validate(workflow);
|
|
159874
|
+
if (!validation.valid) {
|
|
159875
|
+
throw new Error(
|
|
159876
|
+
`Invalid workflow template ${templateName}:
|
|
159877
|
+
${validation.errors.join("\n")}`
|
|
159878
|
+
);
|
|
159879
|
+
}
|
|
159880
|
+
this.cache.set(templateName, workflow);
|
|
159881
|
+
return workflow;
|
|
159882
|
+
} catch (error) {
|
|
159883
|
+
if (error.code === "ENOENT") {
|
|
159884
|
+
throw new Error(`Workflow template not found: ${templateName}`);
|
|
159885
|
+
}
|
|
159886
|
+
throw error;
|
|
159887
|
+
}
|
|
159888
|
+
}
|
|
159889
|
+
/**
|
|
159890
|
+
* List all available workflow templates
|
|
159891
|
+
*/
|
|
159892
|
+
async list() {
|
|
159893
|
+
try {
|
|
159894
|
+
const files = await readdir5(this.templatesDir);
|
|
159895
|
+
return files.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml")).map((file) => basename5(file, file.endsWith(".yaml") ? ".yaml" : ".yml")).sort();
|
|
159896
|
+
} catch (error) {
|
|
159897
|
+
if (error.code === "ENOENT") {
|
|
159898
|
+
return [];
|
|
159899
|
+
}
|
|
159900
|
+
throw error;
|
|
159901
|
+
}
|
|
159902
|
+
}
|
|
159903
|
+
/**
|
|
159904
|
+
* Validate a workflow template
|
|
159905
|
+
*/
|
|
159906
|
+
async validate(workflow) {
|
|
159907
|
+
const errors = [];
|
|
159908
|
+
const warnings = [];
|
|
159909
|
+
if (!workflow.name) {
|
|
159910
|
+
errors.push("Workflow must have a name");
|
|
159911
|
+
}
|
|
159912
|
+
if (!workflow.version) {
|
|
159913
|
+
errors.push("Workflow must have a version");
|
|
159914
|
+
}
|
|
159915
|
+
if (!workflow.description) {
|
|
159916
|
+
warnings.push("Workflow should have a description");
|
|
159917
|
+
}
|
|
159918
|
+
if (!Array.isArray(workflow.variables)) {
|
|
159919
|
+
errors.push("Workflow must have a variables array");
|
|
159920
|
+
} else {
|
|
159921
|
+
workflow.variables.forEach((variable, index) => {
|
|
159922
|
+
if (!variable.name) {
|
|
159923
|
+
errors.push(`Variable at index ${index} must have a name`);
|
|
159924
|
+
}
|
|
159925
|
+
if (!variable.type) {
|
|
159926
|
+
errors.push(`Variable ${variable.name} must have a type`);
|
|
159927
|
+
}
|
|
159928
|
+
if (variable.required === void 0) {
|
|
159929
|
+
warnings.push(`Variable ${variable.name} should specify if it's required`);
|
|
159930
|
+
}
|
|
159931
|
+
});
|
|
159932
|
+
}
|
|
159933
|
+
if (!Array.isArray(workflow.steps)) {
|
|
159934
|
+
errors.push("Workflow must have a steps array");
|
|
159935
|
+
} else if (workflow.steps.length === 0) {
|
|
159936
|
+
errors.push("Workflow must have at least one step");
|
|
159937
|
+
} else {
|
|
159938
|
+
workflow.steps.forEach((step, index) => {
|
|
159939
|
+
if (!step.name) {
|
|
159940
|
+
errors.push(`Step at index ${index} must have a name`);
|
|
159941
|
+
}
|
|
159942
|
+
if (!step.action) {
|
|
159943
|
+
errors.push(`Step ${step.name || index} must have an action`);
|
|
159944
|
+
}
|
|
159945
|
+
if (!step.config || typeof step.config !== "object") {
|
|
159946
|
+
errors.push(`Step ${step.name || index} must have a config object`);
|
|
159947
|
+
}
|
|
159948
|
+
if (step.assertions && Array.isArray(step.assertions)) {
|
|
159949
|
+
step.assertions.forEach((assertion, assertionIndex) => {
|
|
159950
|
+
if (!assertion.condition) {
|
|
159951
|
+
errors.push(
|
|
159952
|
+
`Assertion ${assertionIndex} in step ${step.name || index} must have a condition`
|
|
159953
|
+
);
|
|
159954
|
+
}
|
|
159955
|
+
if (!assertion.message) {
|
|
159956
|
+
warnings.push(
|
|
159957
|
+
`Assertion ${assertionIndex} in step ${step.name || index} should have a message`
|
|
159958
|
+
);
|
|
159959
|
+
}
|
|
159960
|
+
});
|
|
159961
|
+
}
|
|
159962
|
+
});
|
|
159963
|
+
const stepNames = workflow.steps.map((s70) => s70.name);
|
|
159964
|
+
const duplicates = stepNames.filter(
|
|
159965
|
+
(name, index) => stepNames.indexOf(name) !== index
|
|
159966
|
+
);
|
|
159967
|
+
if (duplicates.length > 0) {
|
|
159968
|
+
errors.push(`Duplicate step names found: ${duplicates.join(", ")}`);
|
|
159969
|
+
}
|
|
159970
|
+
}
|
|
159971
|
+
return {
|
|
159972
|
+
valid: errors.length === 0,
|
|
159973
|
+
errors,
|
|
159974
|
+
warnings
|
|
159975
|
+
};
|
|
159976
|
+
}
|
|
159977
|
+
/**
|
|
159978
|
+
* Create a workflow execution context with resolved variables
|
|
159979
|
+
*/
|
|
159980
|
+
async createContext(templateName, variables) {
|
|
159981
|
+
const workflow = await this.load(templateName);
|
|
159982
|
+
const missingVars = workflow.variables.filter((v62) => v62.required && !(v62.name in variables)).map((v62) => v62.name);
|
|
159983
|
+
if (missingVars.length > 0) {
|
|
159984
|
+
throw new Error(
|
|
159985
|
+
`Missing required variables: ${missingVars.join(", ")}`
|
|
159986
|
+
);
|
|
159987
|
+
}
|
|
159988
|
+
const resolvedVariables = {};
|
|
159989
|
+
workflow.variables.forEach((varDef) => {
|
|
159990
|
+
if (varDef.name in variables) {
|
|
159991
|
+
resolvedVariables[varDef.name] = variables[varDef.name];
|
|
159992
|
+
} else if ("default" in varDef) {
|
|
159993
|
+
resolvedVariables[varDef.name] = varDef.default;
|
|
159994
|
+
}
|
|
159995
|
+
});
|
|
159996
|
+
return {
|
|
159997
|
+
variables: resolvedVariables,
|
|
159998
|
+
results: /* @__PURE__ */ new Map(),
|
|
159999
|
+
metadata: {
|
|
160000
|
+
startTime: Date.now(),
|
|
160001
|
+
workflow: workflow.name,
|
|
160002
|
+
templateName
|
|
160003
|
+
}
|
|
160004
|
+
};
|
|
160005
|
+
}
|
|
160006
|
+
/**
|
|
160007
|
+
* Get workflow metadata without loading the full workflow
|
|
160008
|
+
*/
|
|
160009
|
+
async getMetadata(templateName) {
|
|
160010
|
+
try {
|
|
160011
|
+
const templatePath = join25(this.templatesDir, `${templateName}.yaml`);
|
|
160012
|
+
const content = await readFile16(templatePath, "utf-8");
|
|
160013
|
+
const workflow = parseYaml2(content);
|
|
160014
|
+
return {
|
|
160015
|
+
name: workflow.name,
|
|
160016
|
+
version: workflow.version,
|
|
160017
|
+
description: workflow.description
|
|
160018
|
+
};
|
|
160019
|
+
} catch (error) {
|
|
160020
|
+
if (error.code === "ENOENT") {
|
|
160021
|
+
throw new Error(`Workflow template not found: ${templateName}`);
|
|
160022
|
+
}
|
|
160023
|
+
throw error;
|
|
160024
|
+
}
|
|
160025
|
+
}
|
|
160026
|
+
/**
|
|
160027
|
+
* Clear the workflow cache
|
|
160028
|
+
*/
|
|
160029
|
+
clearCache() {
|
|
160030
|
+
this.cache.clear();
|
|
160031
|
+
}
|
|
160032
|
+
/**
|
|
160033
|
+
* Reload a specific workflow template
|
|
160034
|
+
*/
|
|
160035
|
+
async reload(templateName) {
|
|
160036
|
+
this.cache.delete(templateName);
|
|
160037
|
+
return this.load(templateName);
|
|
160038
|
+
}
|
|
160039
|
+
};
|
|
160040
|
+
defaultWorkflowLoader = new WorkflowLoader();
|
|
160041
|
+
}
|
|
160042
|
+
});
|
|
160043
|
+
|
|
160044
|
+
// src/workflows/browser/index.ts
|
|
160045
|
+
var browser_exports = {};
|
|
160046
|
+
__export(browser_exports, {
|
|
160047
|
+
WORKFLOW_CATEGORIES: () => WORKFLOW_CATEGORIES,
|
|
160048
|
+
WORKFLOW_DESCRIPTIONS: () => WORKFLOW_DESCRIPTIONS,
|
|
160049
|
+
WORKFLOW_TEMPLATES: () => WORKFLOW_TEMPLATES,
|
|
160050
|
+
WorkflowLoader: () => WorkflowLoader,
|
|
160051
|
+
defaultWorkflowLoader: () => defaultWorkflowLoader,
|
|
160052
|
+
evaluateCondition: () => evaluateCondition,
|
|
160053
|
+
getAllWorkflowTemplates: () => getAllWorkflowTemplates,
|
|
160054
|
+
getWorkflowsByCategory: () => getWorkflowsByCategory,
|
|
160055
|
+
interpolateVariables: () => interpolateVariables,
|
|
160056
|
+
isValidWorkflowTemplate: () => isValidWorkflowTemplate
|
|
160057
|
+
});
|
|
160058
|
+
function getWorkflowsByCategory(category) {
|
|
160059
|
+
return WORKFLOW_CATEGORIES[category];
|
|
160060
|
+
}
|
|
160061
|
+
function isValidWorkflowTemplate(name) {
|
|
160062
|
+
return WORKFLOW_TEMPLATES.includes(name);
|
|
160063
|
+
}
|
|
160064
|
+
function getAllWorkflowTemplates() {
|
|
160065
|
+
return WORKFLOW_TEMPLATES.map((name) => {
|
|
160066
|
+
const category = Object.entries(WORKFLOW_CATEGORIES).find(
|
|
160067
|
+
([_56, templates]) => templates.includes(name)
|
|
160068
|
+
)?.[0] || "other";
|
|
160069
|
+
return {
|
|
160070
|
+
name,
|
|
160071
|
+
description: WORKFLOW_DESCRIPTIONS[name],
|
|
160072
|
+
category
|
|
160073
|
+
};
|
|
160074
|
+
});
|
|
160075
|
+
}
|
|
160076
|
+
var WORKFLOW_TEMPLATES, WORKFLOW_DESCRIPTIONS, WORKFLOW_CATEGORIES;
|
|
160077
|
+
var init_browser2 = __esm({
|
|
160078
|
+
"src/workflows/browser/index.ts"() {
|
|
160079
|
+
"use strict";
|
|
160080
|
+
init_workflow_loader();
|
|
160081
|
+
WORKFLOW_TEMPLATES = [
|
|
160082
|
+
"login-flow",
|
|
160083
|
+
"oauth-flow",
|
|
160084
|
+
"scraping-workflow",
|
|
160085
|
+
"visual-regression",
|
|
160086
|
+
"form-validation",
|
|
160087
|
+
"navigation-flow",
|
|
160088
|
+
"api-integration",
|
|
160089
|
+
"performance-audit",
|
|
160090
|
+
"accessibility-audit"
|
|
160091
|
+
];
|
|
160092
|
+
WORKFLOW_DESCRIPTIONS = {
|
|
160093
|
+
"login-flow": "Authentication testing workflow for login forms with credential validation",
|
|
160094
|
+
"oauth-flow": "OAuth2/OIDC authentication testing workflow with provider integration",
|
|
160095
|
+
"scraping-workflow": "Data extraction workflow for web scraping with pagination and structured output",
|
|
160096
|
+
"visual-regression": "Screenshot comparison workflow for visual regression testing across breakpoints",
|
|
160097
|
+
"form-validation": "Input validation testing workflow for form fields with error handling",
|
|
160098
|
+
"navigation-flow": "Multi-page navigation workflow for testing user journeys and state persistence",
|
|
160099
|
+
"api-integration": "Browser-API hybrid testing workflow for validating frontend-backend integration",
|
|
160100
|
+
"performance-audit": "Lighthouse-style performance audit workflow with Core Web Vitals",
|
|
160101
|
+
"accessibility-audit": "WCAG 2.1 Level AA compliance audit workflow with automated accessibility testing"
|
|
160102
|
+
};
|
|
160103
|
+
WORKFLOW_CATEGORIES = {
|
|
160104
|
+
authentication: ["login-flow", "oauth-flow"],
|
|
160105
|
+
testing: ["form-validation", "navigation-flow", "api-integration"],
|
|
160106
|
+
quality: ["visual-regression", "performance-audit", "accessibility-audit"],
|
|
160107
|
+
automation: ["scraping-workflow"]
|
|
160108
|
+
};
|
|
160109
|
+
}
|
|
160110
|
+
});
|
|
160111
|
+
|
|
160112
|
+
// src/mcp/tools/test-execution/browser-workflow.ts
|
|
160113
|
+
var BrowserWorkflowTool;
|
|
160114
|
+
var init_browser_workflow = __esm({
|
|
160115
|
+
"src/mcp/tools/test-execution/browser-workflow.ts"() {
|
|
160116
|
+
"use strict";
|
|
160117
|
+
init_base();
|
|
160118
|
+
init_error_utils();
|
|
160119
|
+
BrowserWorkflowTool = class extends MCPToolBase {
|
|
160120
|
+
config = {
|
|
160121
|
+
name: "qe/workflows/browser-load",
|
|
160122
|
+
description: "Load, validate, and prepare browser automation workflows from inline YAML or built-in templates. Returns the resolved workflow with steps and variable bindings, ready for browser execution. Templates: login-flow, form-validation, visual-regression, oauth-flow, etc.",
|
|
160123
|
+
domain: "test-execution",
|
|
160124
|
+
schema: this.buildSchema()
|
|
160125
|
+
};
|
|
160126
|
+
buildSchema() {
|
|
160127
|
+
return {
|
|
160128
|
+
type: "object",
|
|
160129
|
+
properties: {
|
|
160130
|
+
workflowYaml: {
|
|
160131
|
+
type: "string",
|
|
160132
|
+
description: "Inline YAML workflow definition. Mutually exclusive with templateName."
|
|
160133
|
+
},
|
|
160134
|
+
templateName: {
|
|
160135
|
+
type: "string",
|
|
160136
|
+
description: "Built-in template name to load.",
|
|
160137
|
+
enum: [
|
|
160138
|
+
"login-flow",
|
|
160139
|
+
"oauth-flow",
|
|
160140
|
+
"scraping-workflow",
|
|
160141
|
+
"visual-regression",
|
|
160142
|
+
"form-validation",
|
|
160143
|
+
"navigation-flow",
|
|
160144
|
+
"api-integration",
|
|
160145
|
+
"performance-audit",
|
|
160146
|
+
"accessibility-audit"
|
|
160147
|
+
]
|
|
160148
|
+
},
|
|
160149
|
+
variables: {
|
|
160150
|
+
type: "object",
|
|
160151
|
+
description: 'Runtime variable overrides (e.g., { "baseUrl": "https://example.com" })'
|
|
160152
|
+
}
|
|
160153
|
+
}
|
|
160154
|
+
};
|
|
160155
|
+
}
|
|
160156
|
+
async execute(params, context) {
|
|
160157
|
+
try {
|
|
160158
|
+
const {
|
|
160159
|
+
WorkflowLoader: WorkflowLoader2,
|
|
160160
|
+
WORKFLOW_TEMPLATES: WORKFLOW_TEMPLATES2,
|
|
160161
|
+
WORKFLOW_DESCRIPTIONS: WORKFLOW_DESCRIPTIONS2,
|
|
160162
|
+
interpolateVariables: interpolateVariables2
|
|
160163
|
+
} = await Promise.resolve().then(() => (init_browser2(), browser_exports));
|
|
160164
|
+
const { parse: parseYaml4 } = await import("yaml");
|
|
160165
|
+
const loader = new WorkflowLoader2();
|
|
160166
|
+
const templateList = [...WORKFLOW_TEMPLATES2];
|
|
160167
|
+
const descriptions = WORKFLOW_DESCRIPTIONS2;
|
|
160168
|
+
if (!params.workflowYaml && !params.templateName) {
|
|
160169
|
+
return {
|
|
160170
|
+
success: true,
|
|
160171
|
+
data: {
|
|
160172
|
+
workflowName: "none",
|
|
160173
|
+
description: "No workflow specified. Use templateName or workflowYaml.",
|
|
160174
|
+
source: "none",
|
|
160175
|
+
templateUsed: null,
|
|
160176
|
+
steps: [],
|
|
160177
|
+
variables: { defined: [], provided: {} },
|
|
160178
|
+
validation: { valid: true, errors: [], warnings: [] },
|
|
160179
|
+
availableTemplates: templateList,
|
|
160180
|
+
summary: `Available templates: ${templateList.join(", ")}`
|
|
160181
|
+
}
|
|
160182
|
+
};
|
|
160183
|
+
}
|
|
160184
|
+
let workflow;
|
|
160185
|
+
let source;
|
|
160186
|
+
let templateUsed = null;
|
|
160187
|
+
if (params.workflowYaml) {
|
|
160188
|
+
source = "inline-yaml";
|
|
160189
|
+
workflow = parseYaml4(params.workflowYaml);
|
|
160190
|
+
} else {
|
|
160191
|
+
source = "template";
|
|
160192
|
+
templateUsed = params.templateName;
|
|
160193
|
+
workflow = await loader.load(params.templateName);
|
|
160194
|
+
}
|
|
160195
|
+
const validation = await loader.validate(workflow);
|
|
160196
|
+
const resolvedSteps = (workflow.steps ?? []).map((step) => {
|
|
160197
|
+
let config = step.config;
|
|
160198
|
+
if (params.variables) {
|
|
160199
|
+
const interpolated = {};
|
|
160200
|
+
for (const [k68, v62] of Object.entries(config)) {
|
|
160201
|
+
interpolated[k68] = typeof v62 === "string" ? interpolateVariables2(v62, params.variables) : v62;
|
|
160202
|
+
}
|
|
160203
|
+
config = interpolated;
|
|
160204
|
+
}
|
|
160205
|
+
return {
|
|
160206
|
+
name: step.name,
|
|
160207
|
+
action: step.action,
|
|
160208
|
+
config,
|
|
160209
|
+
optional: step.optional ?? false,
|
|
160210
|
+
assertionCount: step.assertions?.length ?? 0
|
|
160211
|
+
};
|
|
160212
|
+
});
|
|
160213
|
+
return {
|
|
160214
|
+
success: true,
|
|
160215
|
+
data: {
|
|
160216
|
+
workflowName: workflow.name || templateUsed || "custom",
|
|
160217
|
+
description: workflow.description || (templateUsed ? descriptions[templateUsed] || "" : ""),
|
|
160218
|
+
source,
|
|
160219
|
+
templateUsed,
|
|
160220
|
+
steps: resolvedSteps,
|
|
160221
|
+
variables: {
|
|
160222
|
+
defined: (workflow.variables ?? []).map((v62) => ({
|
|
160223
|
+
name: v62.name,
|
|
160224
|
+
type: v62.type,
|
|
160225
|
+
required: v62.required,
|
|
160226
|
+
hasDefault: v62.default !== void 0
|
|
160227
|
+
})),
|
|
160228
|
+
provided: params.variables || {}
|
|
160229
|
+
},
|
|
160230
|
+
validation: {
|
|
160231
|
+
valid: validation.valid,
|
|
160232
|
+
errors: validation.errors,
|
|
160233
|
+
warnings: validation.warnings
|
|
160234
|
+
},
|
|
160235
|
+
availableTemplates: templateList,
|
|
160236
|
+
summary: validation.valid ? `Workflow "${workflow.name}" loaded (${source}): ${resolvedSteps.length} steps, ${(workflow.variables ?? []).length} variables` : `Workflow "${workflow.name}" has validation errors: ${validation.errors.join("; ")}`
|
|
160237
|
+
}
|
|
160238
|
+
};
|
|
160239
|
+
} catch (error) {
|
|
160240
|
+
return {
|
|
160241
|
+
success: false,
|
|
160242
|
+
error: toErrorMessage(error)
|
|
160243
|
+
};
|
|
160244
|
+
}
|
|
160245
|
+
}
|
|
160246
|
+
};
|
|
160247
|
+
}
|
|
160248
|
+
});
|
|
160249
|
+
|
|
156715
160250
|
// src/mcp/tools/registry.ts
|
|
156716
160251
|
function resetAllToolCaches() {
|
|
156717
160252
|
for (const tool of QE_TOOLS) {
|
|
@@ -156741,6 +160276,10 @@ var init_registry = __esm({
|
|
|
156741
160276
|
init_embeddings2();
|
|
156742
160277
|
init_coherence3();
|
|
156743
160278
|
init_analyze2();
|
|
160279
|
+
init_schedule();
|
|
160280
|
+
init_load_test();
|
|
160281
|
+
init_visual_security();
|
|
160282
|
+
init_browser_workflow();
|
|
156744
160283
|
QE_TOOL_NAMES = {
|
|
156745
160284
|
// Test Generation
|
|
156746
160285
|
TEST_GENERATE: "qe/tests/generate",
|
|
@@ -156787,7 +160326,15 @@ var init_registry = __esm({
|
|
|
156787
160326
|
// Coherence Tools (ADR-052)
|
|
156788
160327
|
...COHERENCE_TOOL_NAMES,
|
|
156789
160328
|
// QX Analysis (Quality Experience)
|
|
156790
|
-
QX_ANALYZE: "qe/qx/analyze"
|
|
160329
|
+
QX_ANALYZE: "qe/qx/analyze",
|
|
160330
|
+
// Test Scheduling (pipeline, git-aware, flaky tracking)
|
|
160331
|
+
TEST_SCHEDULE: "qe/tests/schedule",
|
|
160332
|
+
// Load Testing (agent fleet scalability)
|
|
160333
|
+
LOAD_TEST: "qe/tests/load",
|
|
160334
|
+
// URL Security Validation (threat detection, PII exposure scanning)
|
|
160335
|
+
URL_VALIDATE: "qe/security/url-validate",
|
|
160336
|
+
// Browser Workflow Loader (YAML template loading and validation)
|
|
160337
|
+
BROWSER_WORKFLOW: "qe/workflows/browser-load"
|
|
156791
160338
|
};
|
|
156792
160339
|
QE_TOOLS = [
|
|
156793
160340
|
// Test Generation Domain
|
|
@@ -156835,7 +160382,15 @@ var init_registry = __esm({
|
|
|
156835
160382
|
// Coherence Tools (ADR-052)
|
|
156836
160383
|
...COHERENCE_TOOLS,
|
|
156837
160384
|
// QX Analysis (Quality Experience)
|
|
156838
|
-
new QXAnalyzeTool()
|
|
160385
|
+
new QXAnalyzeTool(),
|
|
160386
|
+
// Test Scheduling (pipeline, git-aware, flaky tracking)
|
|
160387
|
+
new TestScheduleTool(),
|
|
160388
|
+
// Load Testing (agent fleet scalability)
|
|
160389
|
+
new LoadTestTool(),
|
|
160390
|
+
// Security-Visual Audit (URL validation, PII detection)
|
|
160391
|
+
new VisualSecurityTool(),
|
|
160392
|
+
// Browser Workflows (YAML template execution)
|
|
160393
|
+
new BrowserWorkflowTool()
|
|
156839
160394
|
];
|
|
156840
160395
|
}
|
|
156841
160396
|
});
|
|
@@ -157367,7 +160922,7 @@ var init_protocol_executor = __esm({
|
|
|
157367
160922
|
* Sleep helper
|
|
157368
160923
|
*/
|
|
157369
160924
|
sleep(ms) {
|
|
157370
|
-
return new Promise((
|
|
160925
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
157371
160926
|
}
|
|
157372
160927
|
};
|
|
157373
160928
|
}
|
|
@@ -158611,6 +162166,178 @@ var init_task_handlers = __esm({
|
|
|
158611
162166
|
}
|
|
158612
162167
|
});
|
|
158613
162168
|
|
|
162169
|
+
// src/monitoring/structural-health.ts
|
|
162170
|
+
var structural_health_exports = {};
|
|
162171
|
+
__export(structural_health_exports, {
|
|
162172
|
+
DEFAULT_STRUCTURAL_HEALTH_CONFIG: () => DEFAULT_STRUCTURAL_HEALTH_CONFIG,
|
|
162173
|
+
StructuralHealthMonitor: () => StructuralHealthMonitor,
|
|
162174
|
+
createStructuralHealthMonitor: () => createStructuralHealthMonitor
|
|
162175
|
+
});
|
|
162176
|
+
function createStructuralHealthMonitor(config) {
|
|
162177
|
+
return new StructuralHealthMonitor(config);
|
|
162178
|
+
}
|
|
162179
|
+
var DEFAULT_STRUCTURAL_HEALTH_CONFIG, StructuralHealthMonitor;
|
|
162180
|
+
var init_structural_health = __esm({
|
|
162181
|
+
"src/monitoring/structural-health.ts"() {
|
|
162182
|
+
"use strict";
|
|
162183
|
+
init_mincut_wrapper();
|
|
162184
|
+
DEFAULT_STRUCTURAL_HEALTH_CONFIG = {
|
|
162185
|
+
healthyThreshold: 0.4,
|
|
162186
|
+
warningThreshold: 0.2,
|
|
162187
|
+
maxHistoryEntries: 100,
|
|
162188
|
+
enableLogging: false
|
|
162189
|
+
};
|
|
162190
|
+
StructuralHealthMonitor = class {
|
|
162191
|
+
config;
|
|
162192
|
+
minCutService;
|
|
162193
|
+
history = [];
|
|
162194
|
+
constructor(config) {
|
|
162195
|
+
this.config = { ...DEFAULT_STRUCTURAL_HEALTH_CONFIG, ...config };
|
|
162196
|
+
this.minCutService = createQEMinCutService();
|
|
162197
|
+
}
|
|
162198
|
+
/**
|
|
162199
|
+
* Compute fleet health from an array of agent nodes
|
|
162200
|
+
*
|
|
162201
|
+
* @param agents - Current agent fleet
|
|
162202
|
+
* @returns Fleet health result
|
|
162203
|
+
*/
|
|
162204
|
+
computeFleetHealth(agents) {
|
|
162205
|
+
if (agents.length === 0) {
|
|
162206
|
+
return this.emptyFleetResult();
|
|
162207
|
+
}
|
|
162208
|
+
const graph = this.minCutService.buildTaskGraphFromTopology(agents);
|
|
162209
|
+
return this.computeFleetHealthFromGraph(graph, agents.length);
|
|
162210
|
+
}
|
|
162211
|
+
/**
|
|
162212
|
+
* Compute fleet health from a pre-built task graph
|
|
162213
|
+
*
|
|
162214
|
+
* @param graph - Task graph representing the fleet
|
|
162215
|
+
* @param agentCount - Number of agents (for history tracking)
|
|
162216
|
+
* @returns Fleet health result
|
|
162217
|
+
*/
|
|
162218
|
+
computeFleetHealthFromGraph(graph, agentCount) {
|
|
162219
|
+
if (graph.nodes.length === 0) {
|
|
162220
|
+
return this.emptyFleetResult();
|
|
162221
|
+
}
|
|
162222
|
+
const healthReport = this.minCutService.getStructuralHealth(graph);
|
|
162223
|
+
const status = this.determineStatus(healthReport.normalizedLambda);
|
|
162224
|
+
const result = {
|
|
162225
|
+
lambda: healthReport.lambda,
|
|
162226
|
+
healthy: healthReport.healthy,
|
|
162227
|
+
weakPoints: healthReport.weakPoints,
|
|
162228
|
+
normalizedLambda: healthReport.normalizedLambda,
|
|
162229
|
+
riskScore: healthReport.riskScore,
|
|
162230
|
+
status,
|
|
162231
|
+
suggestions: healthReport.suggestions,
|
|
162232
|
+
measuredAt: /* @__PURE__ */ new Date()
|
|
162233
|
+
};
|
|
162234
|
+
this.addHistoryEntry({
|
|
162235
|
+
lambda: healthReport.lambda,
|
|
162236
|
+
healthy: healthReport.healthy,
|
|
162237
|
+
weakPointCount: healthReport.weakPoints.length,
|
|
162238
|
+
agentCount: agentCount ?? graph.nodes.length,
|
|
162239
|
+
timestamp: result.measuredAt
|
|
162240
|
+
});
|
|
162241
|
+
if (this.config.enableLogging) {
|
|
162242
|
+
this.logHealth(result);
|
|
162243
|
+
}
|
|
162244
|
+
return result;
|
|
162245
|
+
}
|
|
162246
|
+
/**
|
|
162247
|
+
* Get health trend from history
|
|
162248
|
+
*
|
|
162249
|
+
* @returns 'improving', 'stable', or 'degrading'
|
|
162250
|
+
*/
|
|
162251
|
+
getTrend() {
|
|
162252
|
+
if (this.history.length < 2) {
|
|
162253
|
+
return "stable";
|
|
162254
|
+
}
|
|
162255
|
+
const recentCount = Math.min(5, this.history.length);
|
|
162256
|
+
const recent = this.history.slice(-recentCount);
|
|
162257
|
+
const firstLambda = recent[0].lambda;
|
|
162258
|
+
const lastLambda = recent[recent.length - 1].lambda;
|
|
162259
|
+
const delta = lastLambda - firstLambda;
|
|
162260
|
+
if (Math.abs(delta) < 0.05) {
|
|
162261
|
+
return "stable";
|
|
162262
|
+
}
|
|
162263
|
+
return delta > 0 ? "improving" : "degrading";
|
|
162264
|
+
}
|
|
162265
|
+
/**
|
|
162266
|
+
* Get health history
|
|
162267
|
+
*
|
|
162268
|
+
* @param limit - Maximum entries to return
|
|
162269
|
+
* @returns Array of history entries, most recent last
|
|
162270
|
+
*/
|
|
162271
|
+
getHistory(limit) {
|
|
162272
|
+
const entries = [...this.history];
|
|
162273
|
+
if (limit !== void 0 && limit < entries.length) {
|
|
162274
|
+
return entries.slice(-limit);
|
|
162275
|
+
}
|
|
162276
|
+
return entries;
|
|
162277
|
+
}
|
|
162278
|
+
/**
|
|
162279
|
+
* Clear health history
|
|
162280
|
+
*/
|
|
162281
|
+
clearHistory() {
|
|
162282
|
+
this.history.length = 0;
|
|
162283
|
+
}
|
|
162284
|
+
/**
|
|
162285
|
+
* Get the underlying QEMinCutService
|
|
162286
|
+
*/
|
|
162287
|
+
getMinCutService() {
|
|
162288
|
+
return this.minCutService;
|
|
162289
|
+
}
|
|
162290
|
+
// ==========================================================================
|
|
162291
|
+
// Private Helpers
|
|
162292
|
+
// ==========================================================================
|
|
162293
|
+
/**
|
|
162294
|
+
* Determine status string from normalized lambda
|
|
162295
|
+
*/
|
|
162296
|
+
determineStatus(normalizedLambda) {
|
|
162297
|
+
if (normalizedLambda >= this.config.healthyThreshold) {
|
|
162298
|
+
return "healthy";
|
|
162299
|
+
}
|
|
162300
|
+
if (normalizedLambda >= this.config.warningThreshold) {
|
|
162301
|
+
return "warning";
|
|
162302
|
+
}
|
|
162303
|
+
return "critical";
|
|
162304
|
+
}
|
|
162305
|
+
/**
|
|
162306
|
+
* Add an entry to the history, trimming if over limit
|
|
162307
|
+
*/
|
|
162308
|
+
addHistoryEntry(entry) {
|
|
162309
|
+
this.history.push(entry);
|
|
162310
|
+
if (this.history.length > this.config.maxHistoryEntries) {
|
|
162311
|
+
this.history.splice(0, this.history.length - this.config.maxHistoryEntries);
|
|
162312
|
+
}
|
|
162313
|
+
}
|
|
162314
|
+
/**
|
|
162315
|
+
* Create result for empty fleet
|
|
162316
|
+
*/
|
|
162317
|
+
emptyFleetResult() {
|
|
162318
|
+
return {
|
|
162319
|
+
lambda: 0,
|
|
162320
|
+
healthy: false,
|
|
162321
|
+
weakPoints: [],
|
|
162322
|
+
normalizedLambda: 0,
|
|
162323
|
+
riskScore: 1,
|
|
162324
|
+
status: "empty",
|
|
162325
|
+
suggestions: ["No agents in fleet. Spawn agents to build a topology."],
|
|
162326
|
+
measuredAt: /* @__PURE__ */ new Date()
|
|
162327
|
+
};
|
|
162328
|
+
}
|
|
162329
|
+
/**
|
|
162330
|
+
* Log health check result to stderr
|
|
162331
|
+
*/
|
|
162332
|
+
logHealth(result) {
|
|
162333
|
+
console.error(
|
|
162334
|
+
`[StructuralHealth] Status: ${result.status} | Lambda: ${result.normalizedLambda.toFixed(3)} | Weak points: ${result.weakPoints.length} | Risk: ${(result.riskScore * 100).toFixed(0)}%`
|
|
162335
|
+
);
|
|
162336
|
+
}
|
|
162337
|
+
};
|
|
162338
|
+
}
|
|
162339
|
+
});
|
|
162340
|
+
|
|
158614
162341
|
// src/mcp/handlers/core-handlers.ts
|
|
158615
162342
|
function getFleetState() {
|
|
158616
162343
|
return state;
|
|
@@ -158873,6 +162600,36 @@ async function handleFleetHealth(params) {
|
|
|
158873
162600
|
timestamp: issue.timestamp.toISOString()
|
|
158874
162601
|
}));
|
|
158875
162602
|
}
|
|
162603
|
+
try {
|
|
162604
|
+
const { StructuralHealthMonitor: StructuralHealthMonitor2 } = await Promise.resolve().then(() => (init_structural_health(), structural_health_exports));
|
|
162605
|
+
const monitor = new StructuralHealthMonitor2();
|
|
162606
|
+
const allAgents = [];
|
|
162607
|
+
for (const [domain] of health.domainHealth) {
|
|
162608
|
+
const domainAgents = state.queen.getAgentsByDomain(domain);
|
|
162609
|
+
for (const a37 of domainAgents) {
|
|
162610
|
+
allAgents.push({ id: a37.id, name: a37.name, domain: a37.domain });
|
|
162611
|
+
}
|
|
162612
|
+
}
|
|
162613
|
+
const agentNodes = allAgents.map((a37) => ({
|
|
162614
|
+
id: a37.id,
|
|
162615
|
+
name: a37.name,
|
|
162616
|
+
domain: a37.domain,
|
|
162617
|
+
capabilities: [a37.domain],
|
|
162618
|
+
dependsOn: [],
|
|
162619
|
+
weight: 1
|
|
162620
|
+
}));
|
|
162621
|
+
const structuralHealth = monitor.computeFleetHealth(agentNodes);
|
|
162622
|
+
result.structuralHealth = {
|
|
162623
|
+
lambda: structuralHealth.lambda,
|
|
162624
|
+
healthy: structuralHealth.healthy,
|
|
162625
|
+
normalizedLambda: structuralHealth.normalizedLambda,
|
|
162626
|
+
riskScore: structuralHealth.riskScore,
|
|
162627
|
+
status: structuralHealth.status,
|
|
162628
|
+
weakPoints: structuralHealth.weakPoints,
|
|
162629
|
+
suggestions: structuralHealth.suggestions
|
|
162630
|
+
};
|
|
162631
|
+
} catch {
|
|
162632
|
+
}
|
|
158876
162633
|
return {
|
|
158877
162634
|
success: true,
|
|
158878
162635
|
data: result
|
|
@@ -158976,7 +162733,7 @@ async function handleAQEHealth() {
|
|
|
158976
162733
|
success: true,
|
|
158977
162734
|
data: {
|
|
158978
162735
|
status: healthStatus,
|
|
158979
|
-
version: true ? "3.7.
|
|
162736
|
+
version: true ? "3.7.6" : "3.7.2",
|
|
158980
162737
|
loadedDomains: domainCount,
|
|
158981
162738
|
memory: memoryStats,
|
|
158982
162739
|
hnsw: hnswStats,
|
|
@@ -159302,17 +163059,17 @@ var StdioTransport = class {
|
|
|
159302
163059
|
async writeOnce(data) {
|
|
159303
163060
|
const message = data + "\n";
|
|
159304
163061
|
if (this.outputStream.writableNeedDrain) {
|
|
159305
|
-
await new Promise((
|
|
163062
|
+
await new Promise((resolve10, reject) => {
|
|
159306
163063
|
const drainTimeout = setTimeout(() => {
|
|
159307
163064
|
reject(new Error("Transport drain timeout after 30 seconds"));
|
|
159308
163065
|
}, 3e4);
|
|
159309
163066
|
this.outputStream.once("drain", () => {
|
|
159310
163067
|
clearTimeout(drainTimeout);
|
|
159311
|
-
|
|
163068
|
+
resolve10();
|
|
159312
163069
|
});
|
|
159313
163070
|
});
|
|
159314
163071
|
}
|
|
159315
|
-
return new Promise((
|
|
163072
|
+
return new Promise((resolve10, reject) => {
|
|
159316
163073
|
const writeTimeout = setTimeout(() => {
|
|
159317
163074
|
this.metrics.errors++;
|
|
159318
163075
|
reject(new Error("Transport write timeout after 120 seconds"));
|
|
@@ -159325,7 +163082,7 @@ var StdioTransport = class {
|
|
|
159325
163082
|
} else {
|
|
159326
163083
|
this.metrics.messagesSent++;
|
|
159327
163084
|
this.metrics.bytesSent += Buffer.byteLength(message, "utf-8");
|
|
159328
|
-
|
|
163085
|
+
resolve10();
|
|
159329
163086
|
}
|
|
159330
163087
|
});
|
|
159331
163088
|
});
|
|
@@ -159856,7 +163613,7 @@ var SSETransport = class {
|
|
|
159856
163613
|
if (!this.config.parseBody) {
|
|
159857
163614
|
throw new Error("Request body parsing is disabled");
|
|
159858
163615
|
}
|
|
159859
|
-
return new Promise((
|
|
163616
|
+
return new Promise((resolve10, reject) => {
|
|
159860
163617
|
const chunks = [];
|
|
159861
163618
|
let size = 0;
|
|
159862
163619
|
req.on("data", (chunk) => {
|
|
@@ -159875,7 +163632,7 @@ var SSETransport = class {
|
|
|
159875
163632
|
return;
|
|
159876
163633
|
}
|
|
159877
163634
|
const parsed = safeJsonParse(body);
|
|
159878
|
-
|
|
163635
|
+
resolve10(parsed);
|
|
159879
163636
|
} catch (error) {
|
|
159880
163637
|
reject(new Error("Invalid JSON in request body"));
|
|
159881
163638
|
}
|
|
@@ -160578,7 +164335,7 @@ var WebSocketTransport = class extends EventEmitter2 {
|
|
|
160578
164335
|
* Handle a manual WebSocket upgrade (for testing or custom servers)
|
|
160579
164336
|
*/
|
|
160580
164337
|
handleUpgrade(request, socket, head) {
|
|
160581
|
-
return new Promise(async (
|
|
164338
|
+
return new Promise(async (resolve10, reject) => {
|
|
160582
164339
|
if (this.disposed) {
|
|
160583
164340
|
socket.write("HTTP/1.1 503 Service Unavailable\r\n\r\n");
|
|
160584
164341
|
socket.destroy();
|
|
@@ -160606,7 +164363,7 @@ var WebSocketTransport = class extends EventEmitter2 {
|
|
|
160606
164363
|
};
|
|
160607
164364
|
this.wsServer.handleUpgrade(request, socket, head, (ws) => {
|
|
160608
164365
|
this.wsServer.emit("connection", ws, request, upgradeInfo);
|
|
160609
|
-
|
|
164366
|
+
resolve10();
|
|
160610
164367
|
});
|
|
160611
164368
|
} catch (error) {
|
|
160612
164369
|
socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
|
@@ -164316,6 +168073,72 @@ async function handleMemoryShare(params) {
|
|
|
164316
168073
|
init_cross_phase_memory();
|
|
164317
168074
|
init_cross_phase_hooks();
|
|
164318
168075
|
|
|
168076
|
+
// src/mcp/qe-tool-bridge.ts
|
|
168077
|
+
init_registry();
|
|
168078
|
+
var ALREADY_REGISTERED = /* @__PURE__ */ new Set([
|
|
168079
|
+
"qe/tests/generate",
|
|
168080
|
+
// → test_generate_enhanced
|
|
168081
|
+
"qe/tests/execute",
|
|
168082
|
+
// → test_execute_parallel
|
|
168083
|
+
"qe/coverage/analyze",
|
|
168084
|
+
// → coverage_analyze_sublinear
|
|
168085
|
+
"qe/quality/evaluate",
|
|
168086
|
+
// → quality_assess
|
|
168087
|
+
"qe/defects/predict",
|
|
168088
|
+
// → defect_predict
|
|
168089
|
+
"qe/requirements/validate",
|
|
168090
|
+
// → requirements_validate
|
|
168091
|
+
"qe/code/analyze",
|
|
168092
|
+
// → code_index
|
|
168093
|
+
"qe/security/scan",
|
|
168094
|
+
// → security_scan_comprehensive
|
|
168095
|
+
"qe/contracts/validate",
|
|
168096
|
+
// → contract_validate
|
|
168097
|
+
"qe/a11y/audit",
|
|
168098
|
+
// → accessibility_test
|
|
168099
|
+
"qe/chaos/inject"
|
|
168100
|
+
// → chaos_test
|
|
168101
|
+
]);
|
|
168102
|
+
function schemaToParameters(tool) {
|
|
168103
|
+
const schema = tool.getSchema();
|
|
168104
|
+
const requiredSet = new Set(schema.required || []);
|
|
168105
|
+
const params = [];
|
|
168106
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
168107
|
+
params.push({
|
|
168108
|
+
name,
|
|
168109
|
+
type: prop.type,
|
|
168110
|
+
description: prop.description,
|
|
168111
|
+
required: requiredSet.has(name) || void 0,
|
|
168112
|
+
default: prop.default,
|
|
168113
|
+
enum: prop.enum
|
|
168114
|
+
});
|
|
168115
|
+
}
|
|
168116
|
+
return params;
|
|
168117
|
+
}
|
|
168118
|
+
function registerMissingQETools(registerFn) {
|
|
168119
|
+
let count = 0;
|
|
168120
|
+
for (const tool of QE_TOOLS) {
|
|
168121
|
+
if (ALREADY_REGISTERED.has(tool.name)) {
|
|
168122
|
+
continue;
|
|
168123
|
+
}
|
|
168124
|
+
const entry = {
|
|
168125
|
+
definition: {
|
|
168126
|
+
name: tool.name,
|
|
168127
|
+
description: tool.description,
|
|
168128
|
+
category: "domain",
|
|
168129
|
+
parameters: schemaToParameters(tool)
|
|
168130
|
+
},
|
|
168131
|
+
handler: async (params) => {
|
|
168132
|
+
const result = await tool.invoke(params);
|
|
168133
|
+
return result;
|
|
168134
|
+
}
|
|
168135
|
+
};
|
|
168136
|
+
registerFn(entry);
|
|
168137
|
+
count++;
|
|
168138
|
+
}
|
|
168139
|
+
return count;
|
|
168140
|
+
}
|
|
168141
|
+
|
|
164319
168142
|
// src/mcp/connection-pool.ts
|
|
164320
168143
|
import { randomUUID as randomUUID25 } from "crypto";
|
|
164321
168144
|
var DEFAULT_POOL_CONFIG = {
|
|
@@ -164384,8 +168207,8 @@ var ConnectionPoolImpl = class {
|
|
|
164384
168207
|
* This is the recommended method when concurrent access is expected
|
|
164385
168208
|
*/
|
|
164386
168209
|
acquireAsync() {
|
|
164387
|
-
return new Promise((
|
|
164388
|
-
this.acquireQueue.push(
|
|
168210
|
+
return new Promise((resolve10) => {
|
|
168211
|
+
this.acquireQueue.push(resolve10);
|
|
164389
168212
|
this.processAcquireQueue();
|
|
164390
168213
|
});
|
|
164391
168214
|
}
|
|
@@ -164397,10 +168220,10 @@ var ConnectionPoolImpl = class {
|
|
|
164397
168220
|
return;
|
|
164398
168221
|
}
|
|
164399
168222
|
this.acquireLock = true;
|
|
164400
|
-
const
|
|
168223
|
+
const resolve10 = this.acquireQueue.shift();
|
|
164401
168224
|
try {
|
|
164402
168225
|
const conn = this.doAcquire();
|
|
164403
|
-
|
|
168226
|
+
resolve10(conn);
|
|
164404
168227
|
} finally {
|
|
164405
168228
|
this.acquireLock = false;
|
|
164406
168229
|
if (this.acquireQueue.length > 0) {
|
|
@@ -164984,10 +168807,10 @@ var MCPProtocolServer = class {
|
|
|
164984
168807
|
this.reconnecting = false;
|
|
164985
168808
|
const buffered2 = [...this.pendingRequests];
|
|
164986
168809
|
this.pendingRequests = [];
|
|
164987
|
-
for (const { resolve:
|
|
168810
|
+
for (const { resolve: resolve10, request } of buffered2) {
|
|
164988
168811
|
try {
|
|
164989
168812
|
const result = await this.handleRequest(request);
|
|
164990
|
-
|
|
168813
|
+
resolve10(result);
|
|
164991
168814
|
} catch (err4) {
|
|
164992
168815
|
console.error(`[MCP] Failed to replay buffered request: ${request.method}`);
|
|
164993
168816
|
}
|
|
@@ -165677,7 +169500,8 @@ var MCPProtocolServer = class {
|
|
|
165677
169500
|
},
|
|
165678
169501
|
handler: () => handleAQEHealth()
|
|
165679
169502
|
});
|
|
165680
|
-
|
|
169503
|
+
const bridgedCount = registerMissingQETools((entry) => this.registerTool(entry));
|
|
169504
|
+
console.error(`[MCP] Registered ${this.tools.size} tools (${bridgedCount} via QE bridge)`);
|
|
165681
169505
|
}
|
|
165682
169506
|
registerTool(entry) {
|
|
165683
169507
|
this.tools.set(entry.definition.name, entry);
|
|
@@ -165726,7 +169550,7 @@ async function quickStart(config) {
|
|
|
165726
169550
|
// src/mcp/http-server.ts
|
|
165727
169551
|
init_safe_json();
|
|
165728
169552
|
import { createServer } from "http";
|
|
165729
|
-
import { join as
|
|
169553
|
+
import { join as join27, dirname as dirname9 } from "path";
|
|
165730
169554
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
165731
169555
|
|
|
165732
169556
|
// src/adapters/a2a/agent-cards/schema.ts
|
|
@@ -168804,7 +172628,7 @@ var WebhookService = class extends EventEmitter7 {
|
|
|
168804
172628
|
* Sleep for a specified duration
|
|
168805
172629
|
*/
|
|
168806
172630
|
sleep(ms) {
|
|
168807
|
-
return new Promise((
|
|
172631
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
168808
172632
|
}
|
|
168809
172633
|
};
|
|
168810
172634
|
function createWebhookService(config = {}) {
|
|
@@ -168818,8 +172642,8 @@ init_error_utils();
|
|
|
168818
172642
|
|
|
168819
172643
|
// src/adapters/a2a/agent-cards/generator.ts
|
|
168820
172644
|
init_error_utils();
|
|
168821
|
-
import { readFile as
|
|
168822
|
-
import { join as
|
|
172645
|
+
import { readFile as readFile17, readdir as readdir6, stat as stat5 } from "fs/promises";
|
|
172646
|
+
import { join as join26, basename as basename6 } from "path";
|
|
168823
172647
|
var DEFAULT_GENERATOR_CONFIG = {
|
|
168824
172648
|
baseUrl: "http://localhost:8080",
|
|
168825
172649
|
defaultVersion: "3.0.0",
|
|
@@ -169154,8 +172978,8 @@ var AgentCardGenerator = class {
|
|
|
169154
172978
|
* Generate an agent card from a markdown file path
|
|
169155
172979
|
*/
|
|
169156
172980
|
async generateFromFile(filePath) {
|
|
169157
|
-
const markdown = await
|
|
169158
|
-
const agentId =
|
|
172981
|
+
const markdown = await readFile17(filePath, "utf-8");
|
|
172982
|
+
const agentId = basename6(filePath, ".md");
|
|
169159
172983
|
return this.generateFromMarkdown(markdown, agentId);
|
|
169160
172984
|
}
|
|
169161
172985
|
/**
|
|
@@ -169166,9 +172990,9 @@ var AgentCardGenerator = class {
|
|
|
169166
172990
|
const cards = /* @__PURE__ */ new Map();
|
|
169167
172991
|
const errors = /* @__PURE__ */ new Map();
|
|
169168
172992
|
const processDirectory = async (dirPath) => {
|
|
169169
|
-
const entries = await
|
|
172993
|
+
const entries = await readdir6(dirPath);
|
|
169170
172994
|
for (const entry of entries) {
|
|
169171
|
-
const fullPath =
|
|
172995
|
+
const fullPath = join26(dirPath, entry);
|
|
169172
172996
|
const stats = await stat5(fullPath);
|
|
169173
172997
|
if (stats.isDirectory() && recursive) {
|
|
169174
172998
|
await processDirectory(fullPath);
|
|
@@ -170627,8 +174451,8 @@ var HTTPServerImpl = class {
|
|
|
170627
174451
|
this.skipAgentCardLoading = config.skipAgentCardLoading ?? false;
|
|
170628
174452
|
this.skipCRDTInit = config.skipCRDTInit ?? false;
|
|
170629
174453
|
this.enableWebSocket = config.enableWebSocket ?? true;
|
|
170630
|
-
const currentDir =
|
|
170631
|
-
this.agentMarkdownDir = config.agentMarkdownDir ??
|
|
174454
|
+
const currentDir = dirname9(fileURLToPath3(import.meta.url));
|
|
174455
|
+
this.agentMarkdownDir = config.agentMarkdownDir ?? join27(currentDir, "../../assets/agents/v3");
|
|
170632
174456
|
this.taskStore = createTaskStore();
|
|
170633
174457
|
this.taskManager = config.taskManager ?? createTaskManager({
|
|
170634
174458
|
storeConfig: {}
|
|
@@ -170782,7 +174606,7 @@ var HTTPServerImpl = class {
|
|
|
170782
174606
|
});
|
|
170783
174607
|
try {
|
|
170784
174608
|
this.taskManager.startTask(task.id);
|
|
170785
|
-
await new Promise((
|
|
174609
|
+
await new Promise((resolve10) => setTimeout(resolve10, 100));
|
|
170786
174610
|
if (!signal.aborted) {
|
|
170787
174611
|
this.taskManager.completeTask(task.id, [
|
|
170788
174612
|
this.taskManager.createTextArtifact(
|
|
@@ -171117,7 +174941,7 @@ var HTTPServerImpl = class {
|
|
|
171117
174941
|
// Helpers
|
|
171118
174942
|
// ============================================================================
|
|
171119
174943
|
async parseBody(req) {
|
|
171120
|
-
return new Promise((
|
|
174944
|
+
return new Promise((resolve10, reject) => {
|
|
171121
174945
|
const chunks = [];
|
|
171122
174946
|
let size = 0;
|
|
171123
174947
|
const maxSize = 1024 * 1024;
|
|
@@ -171133,10 +174957,10 @@ var HTTPServerImpl = class {
|
|
|
171133
174957
|
try {
|
|
171134
174958
|
const body = Buffer.concat(chunks).toString("utf-8");
|
|
171135
174959
|
if (!body) {
|
|
171136
|
-
|
|
174960
|
+
resolve10({});
|
|
171137
174961
|
return;
|
|
171138
174962
|
}
|
|
171139
|
-
|
|
174963
|
+
resolve10(safeJsonParse(body));
|
|
171140
174964
|
} catch (error) {
|
|
171141
174965
|
reject(new Error("Invalid JSON in request body"));
|
|
171142
174966
|
}
|
|
@@ -171185,7 +175009,7 @@ var HTTPServerImpl = class {
|
|
|
171185
175009
|
logger21.warn("Failed to load agent cards", { error });
|
|
171186
175010
|
}
|
|
171187
175011
|
}
|
|
171188
|
-
return new Promise(async (
|
|
175012
|
+
return new Promise(async (resolve10, reject) => {
|
|
171189
175013
|
this.server = createServer((req, res) => {
|
|
171190
175014
|
this.handleHttpRequest(req, res).catch((error) => {
|
|
171191
175015
|
logger21.error("Request error", error instanceof Error ? error : new Error(String(error)));
|
|
@@ -171230,7 +175054,7 @@ var HTTPServerImpl = class {
|
|
|
171230
175054
|
console.error(`[AQE] Hot reload enabled for agent cards`);
|
|
171231
175055
|
}
|
|
171232
175056
|
console.error(`[AQE] GET /health - Health check`);
|
|
171233
|
-
|
|
175057
|
+
resolve10();
|
|
171234
175058
|
});
|
|
171235
175059
|
});
|
|
171236
175060
|
}
|
|
@@ -171250,12 +175074,12 @@ var HTTPServerImpl = class {
|
|
|
171250
175074
|
if (!this.running || !this.server) {
|
|
171251
175075
|
return;
|
|
171252
175076
|
}
|
|
171253
|
-
return new Promise((
|
|
175077
|
+
return new Promise((resolve10) => {
|
|
171254
175078
|
this.server.close(() => {
|
|
171255
175079
|
this.running = false;
|
|
171256
175080
|
this.server = null;
|
|
171257
175081
|
console.error("[AQE] HTTP server stopped");
|
|
171258
|
-
|
|
175082
|
+
resolve10();
|
|
171259
175083
|
});
|
|
171260
175084
|
this.sseTransport.dispose();
|
|
171261
175085
|
if (this.webSocketTransport) {
|
|
@@ -171367,7 +175191,7 @@ async function createDefaultMemoryBackend(autoInitialize = true) {
|
|
|
171367
175191
|
}
|
|
171368
175192
|
|
|
171369
175193
|
// src/init/token-bootstrap.ts
|
|
171370
|
-
var
|
|
175194
|
+
var DEFAULT_CONFIG75 = {
|
|
171371
175195
|
enableOptimization: true,
|
|
171372
175196
|
enablePersistence: true,
|
|
171373
175197
|
storagePath: process.env.AQE_STORAGE_PATH ?? ".aqe",
|
|
@@ -171381,7 +175205,7 @@ async function bootstrapTokenTracking(config) {
|
|
|
171381
175205
|
if (initialized) {
|
|
171382
175206
|
return;
|
|
171383
175207
|
}
|
|
171384
|
-
const cfg = { ...
|
|
175208
|
+
const cfg = { ...DEFAULT_CONFIG75, ...config };
|
|
171385
175209
|
if (cfg.verbose) {
|
|
171386
175210
|
console.log("[TokenBootstrap] Initializing token tracking...");
|
|
171387
175211
|
}
|
|
@@ -171920,7 +175744,7 @@ function createTestOutputObserver(customSignatures) {
|
|
|
171920
175744
|
|
|
171921
175745
|
// src/strange-loop/infra-healing/recovery-playbook.ts
|
|
171922
175746
|
import * as fs25 from "node:fs/promises";
|
|
171923
|
-
import { parse as
|
|
175747
|
+
import { parse as parseYaml3 } from "yaml";
|
|
171924
175748
|
var RecoveryPlaybook = class {
|
|
171925
175749
|
config = null;
|
|
171926
175750
|
variables;
|
|
@@ -171938,7 +175762,7 @@ var RecoveryPlaybook = class {
|
|
|
171938
175762
|
* Load playbook from a YAML string (for testing or inline config).
|
|
171939
175763
|
*/
|
|
171940
175764
|
loadFromString(yamlContent) {
|
|
171941
|
-
const raw =
|
|
175765
|
+
const raw = parseYaml3(yamlContent);
|
|
171942
175766
|
this.config = this.parsePlaybook(raw);
|
|
171943
175767
|
}
|
|
171944
175768
|
/**
|
|
@@ -172126,7 +175950,7 @@ var ShellCommandRunner = class {
|
|
|
172126
175950
|
async run(command, timeoutMs) {
|
|
172127
175951
|
const { execFile: execFile3 } = await import("node:child_process");
|
|
172128
175952
|
const startTime = Date.now();
|
|
172129
|
-
return new Promise((
|
|
175953
|
+
return new Promise((resolve10) => {
|
|
172130
175954
|
const child = execFile3(
|
|
172131
175955
|
"/bin/sh",
|
|
172132
175956
|
["-c", command],
|
|
@@ -172136,7 +175960,7 @@ var ShellCommandRunner = class {
|
|
|
172136
175960
|
const timedOut = (error?.message?.includes("TIMEOUT") || error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") ?? false;
|
|
172137
175961
|
const errWithCode = error;
|
|
172138
175962
|
const exitCode = error ? typeof errWithCode?.code === "number" ? errWithCode.code : 1 : 0;
|
|
172139
|
-
|
|
175963
|
+
resolve10({
|
|
172140
175964
|
exitCode,
|
|
172141
175965
|
stdout: stdout ?? "",
|
|
172142
175966
|
stderr: stderr ?? "",
|
|
@@ -172146,7 +175970,7 @@ var ShellCommandRunner = class {
|
|
|
172146
175970
|
}
|
|
172147
175971
|
);
|
|
172148
175972
|
child.on("error", () => {
|
|
172149
|
-
|
|
175973
|
+
resolve10({
|
|
172150
175974
|
exitCode: 1,
|
|
172151
175975
|
stdout: "",
|
|
172152
175976
|
stderr: "Process execution error",
|
|
@@ -172301,7 +176125,7 @@ var InfraActionExecutor = class {
|
|
|
172301
176125
|
this.stats.byService[serviceName][field]++;
|
|
172302
176126
|
}
|
|
172303
176127
|
sleep(ms) {
|
|
172304
|
-
return new Promise((
|
|
176128
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
172305
176129
|
}
|
|
172306
176130
|
};
|
|
172307
176131
|
function createInfraActionExecutor(runner, playbook, lock) {
|
|
@@ -172515,7 +176339,7 @@ init_global_instance();
|
|
|
172515
176339
|
|
|
172516
176340
|
// src/mcp/entry.ts
|
|
172517
176341
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
172518
|
-
import { resolve as
|
|
176342
|
+
import { resolve as resolve9, dirname as dirname10 } from "node:path";
|
|
172519
176343
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
172520
176344
|
var require2 = createRequire11(import.meta.url);
|
|
172521
176345
|
var pkg = require2("../../package.json");
|
|
@@ -172587,8 +176411,8 @@ async function main() {
|
|
|
172587
176411
|
originalStderrWrite("[MCP] Initializing infra-healing...\n");
|
|
172588
176412
|
try {
|
|
172589
176413
|
const __filename2 = fileURLToPath4(import.meta.url);
|
|
172590
|
-
const __dirname2 =
|
|
172591
|
-
const playbookPath =
|
|
176414
|
+
const __dirname2 = dirname10(__filename2);
|
|
176415
|
+
const playbookPath = resolve9(__dirname2, "../strange-loop/infra-healing/default-playbook.yaml");
|
|
172592
176416
|
let playbookContent;
|
|
172593
176417
|
try {
|
|
172594
176418
|
playbookContent = readFileSync16(playbookPath, "utf-8");
|