@yasserkhanorg/e2e-agents 1.8.4 → 1.9.5
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/README.md +95 -8
- package/dist/adapters/cypress.d.ts +10 -0
- package/dist/adapters/cypress.d.ts.map +1 -0
- package/dist/adapters/cypress.js +86 -0
- package/dist/adapters/framework_adapter.d.ts +41 -0
- package/dist/adapters/framework_adapter.d.ts.map +1 -0
- package/dist/adapters/framework_adapter.js +152 -0
- package/dist/adapters/playwright.d.ts +10 -0
- package/dist/adapters/playwright.d.ts.map +1 -0
- package/dist/adapters/playwright.js +86 -0
- package/dist/adapters/pytest.d.ts +10 -0
- package/dist/adapters/pytest.d.ts.map +1 -0
- package/dist/adapters/pytest.js +96 -0
- package/dist/adapters/supertest.d.ts +12 -0
- package/dist/adapters/supertest.d.ts.map +1 -0
- package/dist/adapters/supertest.js +85 -0
- package/dist/agent/config.d.ts +1 -1
- package/dist/agent/config.d.ts.map +1 -1
- package/dist/agent/git.d.ts +1 -0
- package/dist/agent/git.d.ts.map +1 -1
- package/dist/agent/git.js +3 -0
- package/dist/agentic/fix_loop.d.ts.map +1 -1
- package/dist/agentic/fix_loop.js +5 -4
- package/dist/agentic/runner.d.ts +2 -0
- package/dist/agentic/runner.d.ts.map +1 -1
- package/dist/agentic/runner.js +15 -12
- package/dist/agents/cross-impact.d.ts.map +1 -1
- package/dist/agents/cross-impact.js +6 -1
- package/dist/agents/executor.d.ts.map +1 -1
- package/dist/agents/executor.js +6 -1
- package/dist/agents/strategist.d.ts.map +1 -1
- package/dist/agents/strategist.js +6 -1
- package/dist/agents/test-designer.d.ts.map +1 -1
- package/dist/agents/test-designer.js +6 -1
- package/dist/anthropic_provider.d.ts.map +1 -1
- package/dist/anthropic_provider.js +1 -0
- package/dist/base_provider.d.ts +56 -0
- package/dist/base_provider.d.ts.map +1 -1
- package/dist/base_provider.js +123 -1
- package/dist/budget_ledger.d.ts +28 -0
- package/dist/budget_ledger.d.ts.map +1 -0
- package/dist/budget_ledger.js +62 -0
- package/dist/cache/cached_provider.d.ts +45 -0
- package/dist/cache/cached_provider.d.ts.map +1 -0
- package/dist/cache/cached_provider.js +88 -0
- package/dist/cache/response_cache.d.ts +79 -0
- package/dist/cache/response_cache.d.ts.map +1 -0
- package/dist/cache/response_cache.js +177 -0
- package/dist/cli/commands/bootstrap.d.ts +3 -0
- package/dist/cli/commands/bootstrap.d.ts.map +1 -0
- package/dist/cli/commands/bootstrap.js +109 -0
- package/dist/cli/commands/cost_report.d.ts +3 -0
- package/dist/cli/commands/cost_report.d.ts.map +1 -0
- package/dist/cli/commands/cost_report.js +115 -0
- package/dist/cli/commands/crew.d.ts.map +1 -1
- package/dist/cli/commands/crew.js +118 -1
- package/dist/cli/commands/gate.d.ts +3 -0
- package/dist/cli/commands/gate.d.ts.map +1 -0
- package/dist/cli/commands/gate.js +86 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -62
- package/dist/cli/commands/plan_crew.d.ts.map +1 -1
- package/dist/cli/commands/plan_crew.js +33 -21
- package/dist/cli/commands/train.d.ts.map +1 -1
- package/dist/cli/commands/train.js +16 -21
- package/dist/cli/defaults.d.ts +35 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +125 -0
- package/dist/cli/errors.d.ts +27 -0
- package/dist/cli/errors.d.ts.map +1 -0
- package/dist/cli/errors.js +57 -0
- package/dist/cli/parse_args.d.ts.map +1 -1
- package/dist/cli/parse_args.js +24 -2
- package/dist/cli/types.d.ts +7 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli.js +47 -2
- package/dist/crew/context.d.ts +15 -0
- package/dist/crew/context.d.ts.map +1 -1
- package/dist/crew/orchestrator.d.ts +14 -0
- package/dist/crew/orchestrator.d.ts.map +1 -1
- package/dist/crew/orchestrator.js +162 -4
- package/dist/crew/protocol.d.ts +13 -0
- package/dist/crew/protocol.d.ts.map +1 -1
- package/dist/crew/provider.d.ts +15 -1
- package/dist/crew/provider.d.ts.map +1 -1
- package/dist/crew/provider.js +24 -4
- package/dist/custom_provider.d.ts.map +1 -1
- package/dist/custom_provider.js +1 -0
- package/dist/engine/diff_loader.d.ts.map +1 -1
- package/dist/engine/diff_loader.js +3 -14
- package/dist/engine/impact_engine.d.ts.map +1 -1
- package/dist/engine/impact_engine.js +9 -23
- package/dist/esm/adapters/cypress.js +49 -0
- package/dist/esm/adapters/framework_adapter.js +114 -0
- package/dist/esm/adapters/playwright.js +49 -0
- package/dist/esm/adapters/pytest.js +59 -0
- package/dist/esm/adapters/supertest.js +48 -0
- package/dist/esm/agent/git.js +3 -1
- package/dist/esm/agentic/fix_loop.js +5 -4
- package/dist/esm/agentic/runner.js +15 -12
- package/dist/esm/agents/cross-impact.js +6 -1
- package/dist/esm/agents/executor.js +6 -1
- package/dist/esm/agents/strategist.js +6 -1
- package/dist/esm/agents/test-designer.js +6 -1
- package/dist/esm/anthropic_provider.js +1 -0
- package/dist/esm/base_provider.js +121 -0
- package/dist/esm/budget_ledger.js +58 -0
- package/dist/esm/cache/cached_provider.js +82 -0
- package/dist/esm/cache/response_cache.js +140 -0
- package/dist/esm/cli/commands/bootstrap.js +106 -0
- package/dist/esm/cli/commands/cost_report.js +112 -0
- package/dist/esm/cli/commands/crew.js +118 -1
- package/dist/esm/cli/commands/gate.js +83 -0
- package/dist/esm/cli/commands/init.js +3 -58
- package/dist/esm/cli/commands/plan_crew.js +33 -21
- package/dist/esm/cli/commands/train.js +16 -21
- package/dist/esm/cli/defaults.js +118 -0
- package/dist/esm/cli/errors.js +52 -0
- package/dist/esm/cli/parse_args.js +24 -2
- package/dist/esm/cli.js +47 -2
- package/dist/esm/crew/orchestrator.js +162 -4
- package/dist/esm/crew/provider.js +24 -4
- package/dist/esm/custom_provider.js +1 -0
- package/dist/esm/engine/diff_loader.js +1 -12
- package/dist/esm/engine/impact_engine.js +9 -23
- package/dist/esm/index.js +21 -0
- package/dist/esm/knowledge/cluster_utils.js +60 -0
- package/dist/esm/knowledge/kg_bridge.js +381 -0
- package/dist/esm/knowledge/kg_types.js +3 -0
- package/dist/esm/knowledge/route_families.js +89 -0
- package/dist/esm/mcp-server.js +2 -4
- package/dist/esm/metrics/prometheus.js +149 -0
- package/dist/esm/model_router.js +59 -0
- package/dist/esm/ollama_provider.js +1 -0
- package/dist/esm/openai_provider.js +1 -0
- package/dist/esm/pipeline/orchestrator.js +6 -12
- package/dist/esm/pipeline/stage0_preprocess.js +12 -19
- package/dist/esm/pipeline/stage2_coverage.js +1 -0
- package/dist/esm/pipeline/stage3_generation.js +1 -0
- package/dist/esm/progress.js +112 -0
- package/dist/esm/prompts/coverage.js +7 -24
- package/dist/esm/prompts/cross-impact.js +3 -21
- package/dist/esm/prompts/generation.js +158 -36
- package/dist/esm/prompts/generation_profile.js +147 -0
- package/dist/esm/prompts/heal.js +33 -15
- package/dist/esm/prompts/impact.js +3 -22
- package/dist/esm/prompts/json_extract.js +36 -0
- package/dist/esm/prompts/strategist.js +2 -20
- package/dist/esm/prompts/test-designer.js +6 -21
- package/dist/esm/provider_factory.js +6 -4
- package/dist/esm/reporters/junit.js +86 -0
- package/dist/esm/reporters/reporter.js +3 -0
- package/dist/esm/reporters/sarif.js +131 -0
- package/dist/esm/resilience/circuit_breaker.js +78 -0
- package/dist/esm/resilience/retry.js +56 -0
- package/dist/esm/sanitize.js +66 -0
- package/dist/esm/training/kg_scanner.js +115 -0
- package/dist/esm/training/scanner.js +27 -34
- package/dist/esm/version.js +33 -0
- package/dist/index.d.ts +21 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -1
- package/dist/knowledge/cluster_utils.d.ts +28 -0
- package/dist/knowledge/cluster_utils.d.ts.map +1 -0
- package/dist/knowledge/cluster_utils.js +67 -0
- package/dist/knowledge/kg_bridge.d.ts +31 -0
- package/dist/knowledge/kg_bridge.d.ts.map +1 -0
- package/dist/knowledge/kg_bridge.js +388 -0
- package/dist/knowledge/kg_types.d.ts +75 -0
- package/dist/knowledge/kg_types.d.ts.map +1 -0
- package/dist/knowledge/kg_types.js +4 -0
- package/dist/knowledge/route_families.d.ts +18 -0
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +91 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +2 -4
- package/dist/metrics/prometheus.d.ts +37 -0
- package/dist/metrics/prometheus.d.ts.map +1 -0
- package/dist/metrics/prometheus.js +153 -0
- package/dist/model_router.d.ts +28 -0
- package/dist/model_router.d.ts.map +1 -0
- package/dist/model_router.js +63 -0
- package/dist/ollama_provider.d.ts.map +1 -1
- package/dist/ollama_provider.js +1 -0
- package/dist/openai_provider.d.ts.map +1 -1
- package/dist/openai_provider.js +1 -0
- package/dist/pipeline/orchestrator.d.ts +2 -0
- package/dist/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/pipeline/orchestrator.js +6 -12
- package/dist/pipeline/stage0_preprocess.d.ts.map +1 -1
- package/dist/pipeline/stage0_preprocess.js +11 -18
- package/dist/pipeline/stage2_coverage.d.ts +2 -0
- package/dist/pipeline/stage2_coverage.d.ts.map +1 -1
- package/dist/pipeline/stage2_coverage.js +1 -0
- package/dist/pipeline/stage3_generation.d.ts +2 -0
- package/dist/pipeline/stage3_generation.d.ts.map +1 -1
- package/dist/pipeline/stage3_generation.js +1 -0
- package/dist/pipeline/stage4_heal.d.ts +2 -0
- package/dist/pipeline/stage4_heal.d.ts.map +1 -1
- package/dist/progress.d.ts +22 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +116 -0
- package/dist/prompts/coverage.d.ts +2 -0
- package/dist/prompts/coverage.d.ts.map +1 -1
- package/dist/prompts/coverage.js +7 -24
- package/dist/prompts/cross-impact.d.ts +1 -0
- package/dist/prompts/cross-impact.d.ts.map +1 -1
- package/dist/prompts/cross-impact.js +3 -21
- package/dist/prompts/generation.d.ts +3 -1
- package/dist/prompts/generation.d.ts.map +1 -1
- package/dist/prompts/generation.js +158 -36
- package/dist/prompts/generation_profile.d.ts +29 -0
- package/dist/prompts/generation_profile.d.ts.map +1 -0
- package/dist/prompts/generation_profile.js +151 -0
- package/dist/prompts/heal.d.ts +3 -1
- package/dist/prompts/heal.d.ts.map +1 -1
- package/dist/prompts/heal.js +33 -15
- package/dist/prompts/impact.d.ts +1 -0
- package/dist/prompts/impact.d.ts.map +1 -1
- package/dist/prompts/impact.js +3 -22
- package/dist/prompts/json_extract.d.ts +14 -0
- package/dist/prompts/json_extract.d.ts.map +1 -0
- package/dist/prompts/json_extract.js +39 -0
- package/dist/prompts/strategist.d.ts.map +1 -1
- package/dist/prompts/strategist.js +2 -20
- package/dist/prompts/test-designer.d.ts +2 -0
- package/dist/prompts/test-designer.d.ts.map +1 -1
- package/dist/prompts/test-designer.js +6 -21
- package/dist/provider_factory.d.ts.map +1 -1
- package/dist/provider_factory.js +6 -4
- package/dist/reporters/junit.d.ts +6 -0
- package/dist/reporters/junit.d.ts.map +1 -0
- package/dist/reporters/junit.js +89 -0
- package/dist/reporters/reporter.d.ts +42 -0
- package/dist/reporters/reporter.d.ts.map +1 -0
- package/dist/reporters/reporter.js +4 -0
- package/dist/reporters/sarif.d.ts +7 -0
- package/dist/reporters/sarif.d.ts.map +1 -0
- package/dist/reporters/sarif.js +134 -0
- package/dist/resilience/circuit_breaker.d.ts +36 -0
- package/dist/resilience/circuit_breaker.d.ts.map +1 -0
- package/dist/resilience/circuit_breaker.js +82 -0
- package/dist/resilience/retry.d.ts +11 -0
- package/dist/resilience/retry.d.ts.map +1 -0
- package/dist/resilience/retry.js +59 -0
- package/dist/sanitize.d.ts +15 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +71 -0
- package/dist/training/kg_scanner.d.ts +13 -0
- package/dist/training/kg_scanner.d.ts.map +1 -0
- package/dist/training/kg_scanner.js +118 -0
- package/dist/training/scanner.d.ts +7 -2
- package/dist/training/scanner.d.ts.map +1 -1
- package/dist/training/scanner.js +27 -34
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +36 -0
- package/package.json +7 -2
- package/schemas/route-families.schema.json +31 -1
package/dist/progress.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.ProgressReporter = void 0;
|
|
6
|
+
const events_1 = require("events");
|
|
7
|
+
class ProgressReporter extends events_1.EventEmitter {
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super();
|
|
10
|
+
this.isTTY = options?.isTTY ?? (process.stdout.isTTY === true);
|
|
11
|
+
this.silent = (options?.quiet ?? false) || (options?.jsonMode ?? false);
|
|
12
|
+
this.completedAgents = 0;
|
|
13
|
+
this.totalAgents = 0;
|
|
14
|
+
this.currentPhase = '';
|
|
15
|
+
}
|
|
16
|
+
phaseStart(phase, agentCount) {
|
|
17
|
+
const payload = { phase, agentCount };
|
|
18
|
+
this.emit('phase-start', payload);
|
|
19
|
+
if (this.silent) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.currentPhase = phase;
|
|
23
|
+
this.completedAgents = 0;
|
|
24
|
+
this.totalAgents = agentCount;
|
|
25
|
+
const message = `--- Phase: ${phase} (${agentCount} agent${agentCount !== 1 ? 's' : ''}) ---`;
|
|
26
|
+
this.writeLine(message);
|
|
27
|
+
}
|
|
28
|
+
agentStart(agent, family) {
|
|
29
|
+
const payload = { agent, family };
|
|
30
|
+
this.emit('agent-start', payload);
|
|
31
|
+
if (this.silent) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const familyLabel = family ? ` processing ${family}` : '';
|
|
35
|
+
if (this.isTTY) {
|
|
36
|
+
const progress = `[${this.completedAgents}/${this.totalAgents} agents]`;
|
|
37
|
+
const message = `${progress} ${this.currentPhase}: ${agent}${familyLabel}...`;
|
|
38
|
+
process.stdout.write(`\r${clearLine()}${message}`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const message = `[${this.currentPhase}] ${agent} started${familyLabel ? ':' + familyLabel : ''}`;
|
|
42
|
+
this.writeLine(message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
agentComplete(agent, family, tokens, cost, durationMs) {
|
|
46
|
+
const payload = { agent, family, tokens, cost, durationMs };
|
|
47
|
+
this.emit('agent-complete', payload);
|
|
48
|
+
if (this.silent) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.completedAgents++;
|
|
52
|
+
const costStr = formatCost(cost);
|
|
53
|
+
const durationStr = formatDuration(durationMs);
|
|
54
|
+
const tokensStr = formatTokens(tokens);
|
|
55
|
+
const familyLabel = family ? ` ${family}` : '';
|
|
56
|
+
if (this.isTTY) {
|
|
57
|
+
const progress = `[${this.completedAgents}/${this.totalAgents} agents]`;
|
|
58
|
+
const message = `${progress} ${this.currentPhase}: ${agent} complete${familyLabel} (${tokensStr}, ${costStr}, ${durationStr})`;
|
|
59
|
+
process.stdout.write(`\r${clearLine()}${message}\n`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const message = `[${this.currentPhase}] ${agent} complete:${familyLabel} (${tokensStr}, ${costStr}, ${durationStr})`;
|
|
63
|
+
this.writeLine(message);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
phaseComplete(phase, elapsedMs) {
|
|
67
|
+
const payload = { phase, elapsedMs };
|
|
68
|
+
this.emit('phase-complete', payload);
|
|
69
|
+
if (this.silent) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const durationStr = formatDuration(elapsedMs);
|
|
73
|
+
const message = `--- Phase ${phase} complete (${durationStr}) ---`;
|
|
74
|
+
this.writeLine(message);
|
|
75
|
+
}
|
|
76
|
+
workflowComplete(totalCost, totalTokens, elapsedMs) {
|
|
77
|
+
const payload = { totalCost, totalTokens, elapsedMs };
|
|
78
|
+
this.emit('workflow-complete', payload);
|
|
79
|
+
if (this.silent) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const costStr = formatCost(totalCost);
|
|
83
|
+
const tokensStr = formatTokens(totalTokens);
|
|
84
|
+
const durationStr = formatDuration(elapsedMs);
|
|
85
|
+
const message = `=== Workflow complete: ${tokensStr}, ${costStr}, ${durationStr} ===`;
|
|
86
|
+
this.writeLine(message);
|
|
87
|
+
}
|
|
88
|
+
writeLine(message) {
|
|
89
|
+
process.stdout.write(message + '\n');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.ProgressReporter = ProgressReporter;
|
|
93
|
+
function clearLine() {
|
|
94
|
+
return '\x1B[2K';
|
|
95
|
+
}
|
|
96
|
+
function formatCost(cost) {
|
|
97
|
+
return `$${cost.toFixed(2)}`;
|
|
98
|
+
}
|
|
99
|
+
function formatTokens(tokens) {
|
|
100
|
+
if (tokens >= 1000000) {
|
|
101
|
+
return `${(tokens / 1000000).toFixed(1)}M tokens`;
|
|
102
|
+
}
|
|
103
|
+
if (tokens >= 1000) {
|
|
104
|
+
return `${(tokens / 1000).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, ',')} tokens`;
|
|
105
|
+
}
|
|
106
|
+
return `${tokens} tokens`;
|
|
107
|
+
}
|
|
108
|
+
function formatDuration(ms) {
|
|
109
|
+
const seconds = Math.round(ms / 1000);
|
|
110
|
+
if (seconds >= 60) {
|
|
111
|
+
const minutes = Math.floor(seconds / 60);
|
|
112
|
+
const remainingSeconds = seconds % 60;
|
|
113
|
+
return remainingSeconds > 0 ? `${minutes}m${remainingSeconds}s` : `${minutes}m`;
|
|
114
|
+
}
|
|
115
|
+
return `${seconds}s`;
|
|
116
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GenerationProfile } from './generation_profile.js';
|
|
1
2
|
export interface CoveragePromptFlow {
|
|
2
3
|
flowId: string;
|
|
3
4
|
flowName: string;
|
|
@@ -14,6 +15,7 @@ export interface CoveragePromptContext {
|
|
|
14
15
|
testTitles: string[];
|
|
15
16
|
}>;
|
|
16
17
|
contextBlock: string;
|
|
18
|
+
profile?: GenerationProfile;
|
|
17
19
|
}
|
|
18
20
|
export declare function buildCoveragePrompt(ctx: CoveragePromptContext): string;
|
|
19
21
|
export interface CoverageAgentResponse {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/prompts/coverage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/prompts/coverage.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAE/D,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,KAAK,EAAE,KAAK,CAAC;QACT,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,EAAE,CAAC;KACxB,CAAC,CAAC;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAyCtE;AAED,MAAM,WAAW,qBAAqB;IAClC,QAAQ,EAAE,KAAK,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,KAAK,CAAC;YAClB,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;YACtB,aAAa,CAAC,EAAE,MAAM,CAAC;YACvB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;SAC/B,CAAC,CAAC;QACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;CACN;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAMhF"}
|
package/dist/prompts/coverage.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.buildCoveragePrompt = buildCoveragePrompt;
|
|
6
6
|
exports.parseCoverageResponse = parseCoverageResponse;
|
|
7
|
+
const json_extract_js_1 = require("./json_extract.js");
|
|
8
|
+
const sanitize_js_1 = require("../crew/sanitize.js");
|
|
7
9
|
function buildCoveragePrompt(ctx) {
|
|
8
10
|
const flowsBlock = ctx.flows
|
|
9
11
|
.map((f) => {
|
|
10
|
-
const actions = f.userActions.length > 0 ? f.userActions.join('; ') : 'unknown';
|
|
11
|
-
return `- ${f.flowId} (${f.priority}): ${f.flowName}\n Route: ${f.route}\n User actions: ${actions}\n Evidence: ${f.evidence}`;
|
|
12
|
+
const actions = f.userActions.length > 0 ? f.userActions.map((a) => (0, sanitize_js_1.sanitizeForPrompt)(a)).join('; ') : 'unknown';
|
|
13
|
+
return `- ${f.flowId} (${f.priority}): ${f.flowName}\n Route: ${f.route}\n User actions: ${actions}\n Evidence: ${(0, sanitize_js_1.sanitizeForPrompt)(f.evidence)}`;
|
|
12
14
|
})
|
|
13
15
|
.join('\n\n');
|
|
14
16
|
const specsBlock = ctx.specs
|
|
@@ -17,7 +19,7 @@ function buildCoveragePrompt(ctx) {
|
|
|
17
19
|
})
|
|
18
20
|
.join('\n\n');
|
|
19
21
|
return [
|
|
20
|
-
|
|
22
|
+
`You are evaluating whether existing ${ctx.profile?.projectName || 'Mattermost'} ${ctx.profile?.testFramework || 'Playwright'} E2E tests cover the impacted flows.`,
|
|
21
23
|
'',
|
|
22
24
|
`IMPACTED FLOWS (${ctx.flows.length}):`,
|
|
23
25
|
flowsBlock,
|
|
@@ -40,29 +42,10 @@ function buildCoveragePrompt(ctx) {
|
|
|
40
42
|
' Wrong: "test the new isEditing state"',
|
|
41
43
|
' Right: "test editing a scheduled message while it is in pending state"',
|
|
42
44
|
'- For add_scenarios, specify which existing spec file to extend in targetSpec.',
|
|
43
|
-
|
|
45
|
+
`- For create_spec, suggest a path following ${ctx.profile?.projectName || 'Mattermost'} conventions.`,
|
|
44
46
|
'- Prefer adding scenarios to existing specs over creating new spec files.',
|
|
45
47
|
].join('\n');
|
|
46
48
|
}
|
|
47
49
|
function parseCoverageResponse(text) {
|
|
48
|
-
|
|
49
|
-
const candidates = fenced ? [fenced[1], text] : [text];
|
|
50
|
-
for (const candidate of candidates) {
|
|
51
|
-
const start = candidate.indexOf('{');
|
|
52
|
-
const end = candidate.lastIndexOf('}');
|
|
53
|
-
if (start < 0 || end <= start) {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
const raw = candidate.slice(start, end + 1);
|
|
57
|
-
try {
|
|
58
|
-
const parsed = JSON.parse(raw);
|
|
59
|
-
if (parsed && Array.isArray(parsed.coverage)) {
|
|
60
|
-
return parsed;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
50
|
+
return (0, json_extract_js_1.extractJsonFromResponse)(text, (obj) => obj != null && typeof obj === 'object' && Array.isArray(obj.coverage));
|
|
68
51
|
}
|
|
@@ -7,6 +7,7 @@ export interface CrossImpactPromptContext {
|
|
|
7
7
|
families: RouteFamily[];
|
|
8
8
|
/** The families directly impacted by changed files */
|
|
9
9
|
directlyImpactedFamilyIds: string[];
|
|
10
|
+
projectName?: string;
|
|
10
11
|
}
|
|
11
12
|
export declare function buildCrossImpactPrompt(ctx: CrossImpactPromptContext): string;
|
|
12
13
|
export interface CrossImpactAgentResponse {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-impact.d.ts","sourceRoot":"","sources":["../../src/prompts/cross-impact.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"cross-impact.d.ts","sourceRoot":"","sources":["../../src/prompts/cross-impact.ts"],"names":[],"mappings":"AAGA;;GAEG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,gCAAgC,CAAC;AAKhE,MAAM,WAAW,wBAAwB;IACrC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,sDAAsD;IACtD,yBAAyB,EAAE,MAAM,EAAE,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,wBAAwB,GAAG,MAAM,CA+C5E;AAED,MAAM,WAAW,wBAAwB;IACrC,YAAY,EAAE,KAAK,CAAC;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;QACzB,SAAS,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;QAC9C,QAAQ,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACN;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAMtF"}
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.buildCrossImpactPrompt = buildCrossImpactPrompt;
|
|
6
6
|
exports.parseCrossImpactResponse = parseCrossImpactResponse;
|
|
7
7
|
const sanitize_js_1 = require("../crew/sanitize.js");
|
|
8
|
+
const json_extract_js_1 = require("./json_extract.js");
|
|
8
9
|
function buildCrossImpactPrompt(ctx) {
|
|
9
10
|
const familiesBlock = ctx.families
|
|
10
11
|
.map((f) => {
|
|
@@ -18,7 +19,7 @@ function buildCrossImpactPrompt(ctx) {
|
|
|
18
19
|
.join('\n');
|
|
19
20
|
const changedBlock = ctx.changedFiles.map((f) => (0, sanitize_js_1.sanitizeForPrompt)(f)).join('\n');
|
|
20
21
|
return [
|
|
21
|
-
|
|
22
|
+
`You are analyzing code changes in ${ctx.projectName || 'Mattermost'} to identify cross-family ripple effects.`,
|
|
22
23
|
'When a change in one route family could affect another family through shared dependencies,',
|
|
23
24
|
'that is a cross-impact.',
|
|
24
25
|
'',
|
|
@@ -52,24 +53,5 @@ function buildCrossImpactPrompt(ctx) {
|
|
|
52
53
|
].join('\n');
|
|
53
54
|
}
|
|
54
55
|
function parseCrossImpactResponse(text) {
|
|
55
|
-
|
|
56
|
-
const candidates = fenced ? [fenced[1], text] : [text];
|
|
57
|
-
for (const candidate of candidates) {
|
|
58
|
-
const start = candidate.indexOf('{');
|
|
59
|
-
const end = candidate.lastIndexOf('}');
|
|
60
|
-
if (start < 0 || end <= start) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const raw = candidate.slice(start, end + 1);
|
|
64
|
-
try {
|
|
65
|
-
const parsed = JSON.parse(raw);
|
|
66
|
-
if (parsed && Array.isArray(parsed.crossImpacts)) {
|
|
67
|
-
return parsed;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
56
|
+
return (0, json_extract_js_1.extractJsonFromResponse)(text, (obj) => obj != null && typeof obj === 'object' && Array.isArray(obj.crossImpacts));
|
|
75
57
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { FlowDecision } from '../validation/output_schema.js';
|
|
2
2
|
import type { ApiSurfaceCatalog } from '../knowledge/api_surface.js';
|
|
3
|
+
import type { GenerationProfile } from './generation_profile.js';
|
|
3
4
|
export interface GenerationPromptContext {
|
|
4
5
|
decision: FlowDecision;
|
|
5
6
|
apiSurface: ApiSurfaceCatalog;
|
|
6
7
|
existingSpecContent?: string;
|
|
7
8
|
specPath: string;
|
|
8
9
|
mode: 'create_spec' | 'add_scenarios';
|
|
10
|
+
profile?: GenerationProfile;
|
|
9
11
|
}
|
|
10
12
|
export declare function buildGenerationPrompt(ctx: GenerationPromptContext): string;
|
|
11
13
|
export interface GenerationAgentResponse {
|
|
@@ -14,7 +16,7 @@ export interface GenerationAgentResponse {
|
|
|
14
16
|
mode: 'create_spec' | 'add_scenarios';
|
|
15
17
|
flowId: string;
|
|
16
18
|
}
|
|
17
|
-
export declare function parseGenerationResponse(text: string, expectedPath: string, mode: 'create_spec' | 'add_scenarios', flowId: string): GenerationAgentResponse | null;
|
|
19
|
+
export declare function parseGenerationResponse(text: string, expectedPath: string, mode: 'create_spec' | 'add_scenarios', flowId: string, profile?: GenerationProfile): GenerationAgentResponse | null;
|
|
18
20
|
/**
|
|
19
21
|
* Returns method names that appear in generated code but do not exist in the API surface.
|
|
20
22
|
* Used for logging; does not block generation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../../src/prompts/generation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAGnE,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,aAAa,GAAG,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../../src/prompts/generation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,6BAA6B,CAAC;AAGnE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,yBAAyB,CAAC;AAG/D,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,aAAa,GAAG,eAAe,CAAC;IACtC,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AA6BD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,MAAM,CAyH1E;AA8ED,MAAM,WAAW,uBAAuB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,GAAG,eAAe,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,uBAAuB,CACnC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,aAAa,GAAG,eAAe,EACrC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,iBAAiB,GAC5B,uBAAuB,GAAG,IAAI,CAuBhC;AAwBD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAc/F"}
|
|
@@ -6,6 +6,8 @@ exports.buildGenerationPrompt = buildGenerationPrompt;
|
|
|
6
6
|
exports.parseGenerationResponse = parseGenerationResponse;
|
|
7
7
|
exports.detectHallucinatedMethods = detectHallucinatedMethods;
|
|
8
8
|
const api_surface_js_1 = require("../knowledge/api_surface.js");
|
|
9
|
+
const sanitize_js_1 = require("../crew/sanitize.js");
|
|
10
|
+
const generation_profile_js_1 = require("./generation_profile.js");
|
|
9
11
|
function resolveRelevantPageObjects(apiSurface, decision) {
|
|
10
12
|
const relevant = [];
|
|
11
13
|
const familyHints = [
|
|
@@ -26,6 +28,8 @@ function resolveRelevantPageObjects(apiSurface, decision) {
|
|
|
26
28
|
return [...new Set(relevant)].slice(0, 10);
|
|
27
29
|
}
|
|
28
30
|
function buildGenerationPrompt(ctx) {
|
|
31
|
+
const profile = ctx.profile;
|
|
32
|
+
const isMM = profile ? (0, generation_profile_js_1.isMattermostProfile)(profile) : true;
|
|
29
33
|
const relevantClasses = resolveRelevantPageObjects(ctx.apiSurface, ctx.decision);
|
|
30
34
|
const apiBlock = relevantClasses.length > 0
|
|
31
35
|
? (0, api_surface_js_1.formatApiSurfaceForPrompt)(ctx.apiSurface, relevantClasses)
|
|
@@ -40,73 +44,187 @@ function buildGenerationPrompt(ctx) {
|
|
|
40
44
|
? `Create a NEW spec file at: ${ctx.specPath}`
|
|
41
45
|
: `ADD scenarios to the EXISTING spec at: ${ctx.specPath}`;
|
|
42
46
|
const routeFamilyTag = ctx.decision.routeFamily;
|
|
47
|
+
// Build prompt based on profile
|
|
48
|
+
const projectName = profile?.projectName || 'Mattermost';
|
|
49
|
+
const testFramework = profile?.testFramework || 'Playwright';
|
|
50
|
+
const importStatement = profile?.importStatement || '@mattermost/playwright-lib';
|
|
51
|
+
// API test mode prompt
|
|
52
|
+
if (profile?.testMode === 'api') {
|
|
53
|
+
return buildApiTestPrompt(ctx, profile, scenariosBlock, routeFamilyTag);
|
|
54
|
+
}
|
|
55
|
+
// Build rules from profile conventions or use Mattermost defaults
|
|
56
|
+
const rules = isMM
|
|
57
|
+
? [
|
|
58
|
+
`1. Import ONLY from "${importStatement}" — no other test framework imports.`,
|
|
59
|
+
'2. Every test must call `await pw.initSetup()` first.',
|
|
60
|
+
'3. Use `await pw.testBrowser.login(user)` to log in — never hardcode credentials.',
|
|
61
|
+
'4. Use ONLY page object methods listed above. Do NOT invent methods that are not listed.',
|
|
62
|
+
'5. If a method is not available, use `page.getByRole()` or `page.getByTestId()`.',
|
|
63
|
+
`6. Tag every test: {tag: '@${routeFamilyTag}'}`,
|
|
64
|
+
'7. Write one test per scenario with a descriptive name of what the user does and what is verified.',
|
|
65
|
+
`8. Use \`expect\` from "${importStatement}" — do NOT import from "@playwright/test".`,
|
|
66
|
+
'9. Include the copyright header for new files.',
|
|
67
|
+
'10. NEVER fabricate test IDs (MM-TXXXX). Use descriptive names only.',
|
|
68
|
+
]
|
|
69
|
+
: [
|
|
70
|
+
...(profile?.conventions || []).map((c, i) => `${i + 1}. ${c}`),
|
|
71
|
+
`${(profile?.conventions?.length || 0) + 1}. Use ONLY page object methods listed above. Do NOT invent methods that are not listed.`,
|
|
72
|
+
`${(profile?.conventions?.length || 0) + 2}. If a method is not available, use \`page.getByRole()\` or \`page.getByTestId()\`.`,
|
|
73
|
+
`${(profile?.conventions?.length || 0) + 3}. Tag every test: {tag: '@${routeFamilyTag}'}`,
|
|
74
|
+
];
|
|
75
|
+
// Build example block
|
|
76
|
+
const exampleBlock = isMM
|
|
77
|
+
? [
|
|
78
|
+
'EXAMPLE SPEC STRUCTURE:',
|
|
79
|
+
'```typescript',
|
|
80
|
+
'// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.',
|
|
81
|
+
'// See LICENSE.txt for license information.',
|
|
82
|
+
'',
|
|
83
|
+
`import {expect, test} from '${importStatement}';`,
|
|
84
|
+
'',
|
|
85
|
+
'test(',
|
|
86
|
+
" 'descriptive name of what is tested',",
|
|
87
|
+
` {tag: '@${routeFamilyTag}'},`,
|
|
88
|
+
' async ({pw}) => {',
|
|
89
|
+
' const {user} = await pw.initSetup();',
|
|
90
|
+
' const {channelsPage} = await pw.testBrowser.login(user);',
|
|
91
|
+
' await channelsPage.goto();',
|
|
92
|
+
' await channelsPage.toBeVisible();',
|
|
93
|
+
' // test steps...',
|
|
94
|
+
' },',
|
|
95
|
+
');',
|
|
96
|
+
'```',
|
|
97
|
+
]
|
|
98
|
+
: [
|
|
99
|
+
'EXAMPLE SPEC STRUCTURE:',
|
|
100
|
+
'```typescript',
|
|
101
|
+
...(profile?.copyrightHeader ? [profile.copyrightHeader, ''] : []),
|
|
102
|
+
`import {test, expect} from '${importStatement}';`,
|
|
103
|
+
'',
|
|
104
|
+
'test(',
|
|
105
|
+
" 'descriptive name of what is tested',",
|
|
106
|
+
` {tag: '@${routeFamilyTag}'},`,
|
|
107
|
+
' async ({page}) => {',
|
|
108
|
+
' // test steps...',
|
|
109
|
+
' },',
|
|
110
|
+
');',
|
|
111
|
+
'```',
|
|
112
|
+
];
|
|
43
113
|
return [
|
|
44
|
-
|
|
114
|
+
`You are generating ${projectName} ${testFramework} E2E test code.`,
|
|
45
115
|
'',
|
|
46
116
|
`TASK: ${modeInstruction}`,
|
|
47
117
|
'',
|
|
48
|
-
`FLOW: ${ctx.decision.flowName}`,
|
|
118
|
+
`FLOW: ${(0, sanitize_js_1.sanitizeForPrompt)(ctx.decision.flowName)}`,
|
|
49
119
|
`Route Family: ${ctx.decision.routeFamily}${ctx.decision.featureId ? ` / ${ctx.decision.featureId}` : ''}`,
|
|
50
120
|
`Route: ${ctx.decision.specificRoute || '(not specified)'}`,
|
|
51
121
|
`Priority: ${ctx.decision.priority}`,
|
|
52
|
-
`Evidence: ${ctx.decision.evidence}`,
|
|
122
|
+
`Evidence: ${(0, sanitize_js_1.sanitizeForPrompt)(ctx.decision.evidence)}`,
|
|
53
123
|
'',
|
|
54
124
|
'SCENARIOS TO IMPLEMENT:',
|
|
55
125
|
scenariosBlock || ' (implement core user actions for this flow)',
|
|
56
126
|
'',
|
|
57
127
|
'USER ACTIONS:',
|
|
58
|
-
ctx.decision.userActions.map((a) => ` - ${a}`).join('\n') || ' (none specified)',
|
|
128
|
+
ctx.decision.userActions.map((a) => ` - ${(0, sanitize_js_1.sanitizeForPrompt)(a)}`).join('\n') || ' (none specified)',
|
|
59
129
|
'',
|
|
60
130
|
'AVAILABLE PAGE OBJECTS AND METHODS:',
|
|
61
131
|
apiBlock,
|
|
62
132
|
existingBlock,
|
|
63
133
|
'',
|
|
64
134
|
'MANDATORY RULES:',
|
|
65
|
-
|
|
66
|
-
'2. Every test must call `await pw.initSetup()` first.',
|
|
67
|
-
'3. Use `await pw.testBrowser.login(user)` to log in — never hardcode credentials.',
|
|
68
|
-
'4. Use ONLY page object methods listed above. Do NOT invent methods that are not listed.',
|
|
69
|
-
'5. If a method is not available, use `page.getByRole()` or `page.getByTestId()`.',
|
|
70
|
-
`6. Tag every test: {tag: '@${routeFamilyTag}'}`,
|
|
71
|
-
'7. Write one test per scenario with a descriptive name of what the user does and what is verified.',
|
|
72
|
-
'8. Use `expect` from "@mattermost/playwright-lib" — do NOT import from "@playwright/test".',
|
|
73
|
-
'9. Include the copyright header for new files.',
|
|
74
|
-
'10. NEVER fabricate test IDs (MM-TXXXX). Use descriptive names only.',
|
|
135
|
+
...rules,
|
|
75
136
|
'',
|
|
76
|
-
|
|
77
|
-
'```typescript',
|
|
78
|
-
'// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.',
|
|
79
|
-
'// See LICENSE.txt for license information.',
|
|
137
|
+
...exampleBlock,
|
|
80
138
|
'',
|
|
81
|
-
|
|
139
|
+
'Return ONLY the TypeScript code. No explanations, no markdown fences.',
|
|
140
|
+
].join('\n');
|
|
141
|
+
}
|
|
142
|
+
function buildApiTestPrompt(ctx, profile, scenariosBlock, routeFamilyTag) {
|
|
143
|
+
const modeInstruction = ctx.mode === 'create_spec'
|
|
144
|
+
? `Create a NEW test file at: ${ctx.specPath}`
|
|
145
|
+
: `ADD test cases to the EXISTING file at: ${ctx.specPath}`;
|
|
146
|
+
const existingBlock = ctx.existingSpecContent
|
|
147
|
+
? `\nEXISTING FILE (extend this):\n\`\`\`typescript\n${ctx.existingSpecContent}\n\`\`\``
|
|
148
|
+
: '';
|
|
149
|
+
return [
|
|
150
|
+
`You are generating ${profile.projectName} API test code using ${profile.testFramework}.`,
|
|
82
151
|
'',
|
|
83
|
-
|
|
84
|
-
" 'descriptive name of what is tested',",
|
|
85
|
-
` {tag: '@${routeFamilyTag}'},`,
|
|
86
|
-
' async ({pw}) => {',
|
|
87
|
-
' const {user} = await pw.initSetup();',
|
|
88
|
-
' const {channelsPage} = await pw.testBrowser.login(user);',
|
|
89
|
-
' await channelsPage.goto();',
|
|
90
|
-
' await channelsPage.toBeVisible();',
|
|
91
|
-
' // test steps...',
|
|
92
|
-
' },',
|
|
93
|
-
');',
|
|
94
|
-
'```',
|
|
152
|
+
`TASK: ${modeInstruction}`,
|
|
95
153
|
'',
|
|
96
|
-
|
|
154
|
+
`FLOW: ${(0, sanitize_js_1.sanitizeForPrompt)(ctx.decision.flowName)}`,
|
|
155
|
+
`Route Family: ${ctx.decision.routeFamily}${ctx.decision.featureId ? ` / ${ctx.decision.featureId}` : ''}`,
|
|
156
|
+
`Endpoint: ${ctx.decision.specificRoute || '(not specified)'}`,
|
|
157
|
+
`Priority: ${ctx.decision.priority}`,
|
|
158
|
+
`Evidence: ${(0, sanitize_js_1.sanitizeForPrompt)(ctx.decision.evidence)}`,
|
|
159
|
+
'',
|
|
160
|
+
'SCENARIOS TO IMPLEMENT:',
|
|
161
|
+
scenariosBlock || ' (implement core API endpoint tests)',
|
|
162
|
+
'',
|
|
163
|
+
'USER ACTIONS:',
|
|
164
|
+
ctx.decision.userActions.map((a) => ` - ${(0, sanitize_js_1.sanitizeForPrompt)(a)}`).join('\n') || ' (none specified)',
|
|
165
|
+
existingBlock,
|
|
166
|
+
'',
|
|
167
|
+
'MANDATORY RULES:',
|
|
168
|
+
...profile.conventions.map((c, i) => `${i + 1}. ${c}`),
|
|
169
|
+
`${profile.conventions.length + 1}. Tag every test: {tag: '@${routeFamilyTag}'}`,
|
|
170
|
+
'',
|
|
171
|
+
'EXAMPLE TEST STRUCTURE:',
|
|
172
|
+
...(profile.testFramework.toLowerCase().includes('pytest')
|
|
173
|
+
? [
|
|
174
|
+
'```python',
|
|
175
|
+
...(profile.copyrightHeader ? [profile.copyrightHeader, ''] : []),
|
|
176
|
+
'import pytest',
|
|
177
|
+
'import requests',
|
|
178
|
+
'',
|
|
179
|
+
`BASE_URL = 'http://localhost:3000'`,
|
|
180
|
+
'',
|
|
181
|
+
'',
|
|
182
|
+
`class Test${ctx.decision.routeFamily.replace(/[^a-zA-Z0-9]/g, '')}:`,
|
|
183
|
+
" def test_should_return_200_for_valid_request(self):",
|
|
184
|
+
` res = requests.get(f'{BASE_URL}${ctx.decision.specificRoute || '/api/endpoint'}')`,
|
|
185
|
+
' assert res.status_code == 200',
|
|
186
|
+
'```',
|
|
187
|
+
]
|
|
188
|
+
: [
|
|
189
|
+
'```typescript',
|
|
190
|
+
...(profile.copyrightHeader ? [profile.copyrightHeader, ''] : []),
|
|
191
|
+
`import {describe, it, expect} from '${profile.importStatement}';`,
|
|
192
|
+
"import supertest from 'supertest';",
|
|
193
|
+
'',
|
|
194
|
+
"const request = supertest('http://localhost:3000');",
|
|
195
|
+
'',
|
|
196
|
+
`describe('${ctx.decision.routeFamily}', () => {`,
|
|
197
|
+
" it('should return 200 for valid request', async () => {",
|
|
198
|
+
` const res = await request.get('${ctx.decision.specificRoute || '/api/endpoint'}');`,
|
|
199
|
+
' expect(res.status).toBe(200);',
|
|
200
|
+
' });',
|
|
201
|
+
'});',
|
|
202
|
+
'```',
|
|
203
|
+
]),
|
|
204
|
+
'',
|
|
205
|
+
...(profile.testFramework.toLowerCase().includes('pytest')
|
|
206
|
+
? ['Return ONLY the Python code. No explanations, no markdown fences.']
|
|
207
|
+
: ['Return ONLY the TypeScript code. No explanations, no markdown fences.']),
|
|
97
208
|
].join('\n');
|
|
98
209
|
}
|
|
99
|
-
function parseGenerationResponse(text, expectedPath, mode, flowId) {
|
|
210
|
+
function parseGenerationResponse(text, expectedPath, mode, flowId, profile) {
|
|
100
211
|
let code = text.trim();
|
|
101
212
|
const fenced = code.match(/^```(?:typescript|ts)?\s*([\s\S]*?)```\s*$/i);
|
|
102
213
|
if (fenced) {
|
|
103
214
|
code = fenced[1].trim();
|
|
104
215
|
}
|
|
105
|
-
if (!code.includes('test(')) {
|
|
216
|
+
if (!code.includes('test(') && !code.includes('it(') && !code.includes('describe(')) {
|
|
106
217
|
return null;
|
|
107
218
|
}
|
|
108
|
-
|
|
109
|
-
|
|
219
|
+
const importStatement = profile?.importStatement || '@mattermost/playwright-lib';
|
|
220
|
+
// Auto-add import if missing
|
|
221
|
+
if (!code.includes(importStatement)) {
|
|
222
|
+
if (profile?.testMode === 'api') {
|
|
223
|
+
code = `import {describe, it, expect} from '${importStatement}';\n\n${code}`;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
code = `import {expect, test} from '${importStatement}';\n\n${code}`;
|
|
227
|
+
}
|
|
110
228
|
}
|
|
111
229
|
return { specPath: expectedPath, code, mode, flowId };
|
|
112
230
|
}
|
|
@@ -126,6 +244,10 @@ const BUILT_IN_METHODS = new Set([
|
|
|
126
244
|
'initSetup', 'login', 'waitUntil', 'skipIfNoLicense', 'ensureLicense',
|
|
127
245
|
'random', 'duration', 'isOutsideRemoteUserHour', 'setTimeout',
|
|
128
246
|
'skip', 'fixme', 'slow', 'fail',
|
|
247
|
+
// API testing built-ins
|
|
248
|
+
'get', 'post', 'put', 'patch', 'delete', 'send', 'set', 'query',
|
|
249
|
+
'toBe', 'toEqual', 'toBeDefined', 'toContain', 'toHaveProperty',
|
|
250
|
+
'toMatchObject', 'toHaveLength', 'toBeTruthy', 'toBeFalsy',
|
|
129
251
|
]);
|
|
130
252
|
/**
|
|
131
253
|
* Returns method names that appear in generated code but do not exist in the API surface.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic generation profile — replaces hardcoded Mattermost references with
|
|
3
|
+
* project-specific configuration. Enables e2e-agents to generate tests for any project.
|
|
4
|
+
*/
|
|
5
|
+
import type { KnowledgeGraph } from '../knowledge/kg_types.js';
|
|
6
|
+
import type { TestType } from '../knowledge/route_families.js';
|
|
7
|
+
export interface GenerationProfile {
|
|
8
|
+
projectName: string;
|
|
9
|
+
testFramework: string;
|
|
10
|
+
importStatement: string;
|
|
11
|
+
conventions: string[];
|
|
12
|
+
copyrightHeader?: string;
|
|
13
|
+
testMode: TestType;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the generation profile from config and optional KG metadata.
|
|
17
|
+
* - If profile='mattermost' or Mattermost is detected, returns Mattermost profile.
|
|
18
|
+
* - If KG is present, derives project-specific profile from it.
|
|
19
|
+
* - Otherwise, returns generic Playwright profile.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveGenerationProfile(config?: {
|
|
22
|
+
profile?: string;
|
|
23
|
+
testMode?: TestType;
|
|
24
|
+
}, kg?: KnowledgeGraph | null): GenerationProfile;
|
|
25
|
+
/**
|
|
26
|
+
* Checks if a profile is the Mattermost profile (for backward compatibility checks).
|
|
27
|
+
*/
|
|
28
|
+
export declare function isMattermostProfile(profile: GenerationProfile): boolean;
|
|
29
|
+
//# sourceMappingURL=generation_profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generation_profile.d.ts","sourceRoot":"","sources":["../../src/prompts/generation_profile.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,gCAAgC,CAAC;AAG7D,MAAM,WAAW,iBAAiB;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,QAAQ,CAAC;CACtB;AA8CD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,CAAC,EAAE;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAC,EAChD,EAAE,CAAC,EAAE,cAAc,GAAG,IAAI,GAC3B,iBAAiB,CAmCnB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAEvE"}
|