@yasserkhanorg/e2e-agents 1.8.5 → 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.
Files changed (256) hide show
  1. package/README.md +95 -8
  2. package/dist/adapters/cypress.d.ts +10 -0
  3. package/dist/adapters/cypress.d.ts.map +1 -0
  4. package/dist/adapters/cypress.js +86 -0
  5. package/dist/adapters/framework_adapter.d.ts +41 -0
  6. package/dist/adapters/framework_adapter.d.ts.map +1 -0
  7. package/dist/adapters/framework_adapter.js +152 -0
  8. package/dist/adapters/playwright.d.ts +10 -0
  9. package/dist/adapters/playwright.d.ts.map +1 -0
  10. package/dist/adapters/playwright.js +86 -0
  11. package/dist/adapters/pytest.d.ts +10 -0
  12. package/dist/adapters/pytest.d.ts.map +1 -0
  13. package/dist/adapters/pytest.js +96 -0
  14. package/dist/adapters/supertest.d.ts +12 -0
  15. package/dist/adapters/supertest.d.ts.map +1 -0
  16. package/dist/adapters/supertest.js +85 -0
  17. package/dist/agent/config.d.ts +1 -1
  18. package/dist/agent/config.d.ts.map +1 -1
  19. package/dist/agent/git.d.ts +1 -0
  20. package/dist/agent/git.d.ts.map +1 -1
  21. package/dist/agent/git.js +3 -0
  22. package/dist/agentic/fix_loop.d.ts.map +1 -1
  23. package/dist/agentic/fix_loop.js +5 -4
  24. package/dist/agentic/runner.d.ts +2 -0
  25. package/dist/agentic/runner.d.ts.map +1 -1
  26. package/dist/agentic/runner.js +15 -12
  27. package/dist/agents/cross-impact.d.ts.map +1 -1
  28. package/dist/agents/cross-impact.js +6 -1
  29. package/dist/agents/executor.d.ts.map +1 -1
  30. package/dist/agents/executor.js +6 -1
  31. package/dist/agents/strategist.d.ts.map +1 -1
  32. package/dist/agents/strategist.js +6 -1
  33. package/dist/agents/test-designer.d.ts.map +1 -1
  34. package/dist/agents/test-designer.js +6 -1
  35. package/dist/anthropic_provider.d.ts.map +1 -1
  36. package/dist/anthropic_provider.js +1 -0
  37. package/dist/base_provider.d.ts +56 -0
  38. package/dist/base_provider.d.ts.map +1 -1
  39. package/dist/base_provider.js +123 -1
  40. package/dist/budget_ledger.d.ts +28 -0
  41. package/dist/budget_ledger.d.ts.map +1 -0
  42. package/dist/budget_ledger.js +62 -0
  43. package/dist/cache/cached_provider.d.ts +45 -0
  44. package/dist/cache/cached_provider.d.ts.map +1 -0
  45. package/dist/cache/cached_provider.js +88 -0
  46. package/dist/cache/response_cache.d.ts +79 -0
  47. package/dist/cache/response_cache.d.ts.map +1 -0
  48. package/dist/cache/response_cache.js +177 -0
  49. package/dist/cli/commands/bootstrap.d.ts +3 -0
  50. package/dist/cli/commands/bootstrap.d.ts.map +1 -0
  51. package/dist/cli/commands/bootstrap.js +109 -0
  52. package/dist/cli/commands/cost_report.d.ts +3 -0
  53. package/dist/cli/commands/cost_report.d.ts.map +1 -0
  54. package/dist/cli/commands/cost_report.js +115 -0
  55. package/dist/cli/commands/crew.d.ts.map +1 -1
  56. package/dist/cli/commands/crew.js +118 -1
  57. package/dist/cli/commands/gate.d.ts +3 -0
  58. package/dist/cli/commands/gate.d.ts.map +1 -0
  59. package/dist/cli/commands/gate.js +86 -0
  60. package/dist/cli/commands/init.d.ts.map +1 -1
  61. package/dist/cli/commands/init.js +7 -62
  62. package/dist/cli/commands/train.d.ts.map +1 -1
  63. package/dist/cli/commands/train.js +16 -21
  64. package/dist/cli/defaults.d.ts +35 -0
  65. package/dist/cli/defaults.d.ts.map +1 -0
  66. package/dist/cli/defaults.js +125 -0
  67. package/dist/cli/errors.d.ts +27 -0
  68. package/dist/cli/errors.d.ts.map +1 -0
  69. package/dist/cli/errors.js +57 -0
  70. package/dist/cli/parse_args.d.ts.map +1 -1
  71. package/dist/cli/parse_args.js +24 -2
  72. package/dist/cli/types.d.ts +7 -1
  73. package/dist/cli/types.d.ts.map +1 -1
  74. package/dist/cli.js +47 -2
  75. package/dist/crew/context.d.ts +15 -0
  76. package/dist/crew/context.d.ts.map +1 -1
  77. package/dist/crew/orchestrator.d.ts +14 -0
  78. package/dist/crew/orchestrator.d.ts.map +1 -1
  79. package/dist/crew/orchestrator.js +162 -4
  80. package/dist/crew/protocol.d.ts +13 -0
  81. package/dist/crew/protocol.d.ts.map +1 -1
  82. package/dist/crew/provider.d.ts +15 -1
  83. package/dist/crew/provider.d.ts.map +1 -1
  84. package/dist/crew/provider.js +24 -4
  85. package/dist/custom_provider.d.ts.map +1 -1
  86. package/dist/custom_provider.js +1 -0
  87. package/dist/engine/diff_loader.d.ts.map +1 -1
  88. package/dist/engine/diff_loader.js +3 -14
  89. package/dist/engine/impact_engine.d.ts.map +1 -1
  90. package/dist/engine/impact_engine.js +9 -23
  91. package/dist/esm/adapters/cypress.js +49 -0
  92. package/dist/esm/adapters/framework_adapter.js +114 -0
  93. package/dist/esm/adapters/playwright.js +49 -0
  94. package/dist/esm/adapters/pytest.js +59 -0
  95. package/dist/esm/adapters/supertest.js +48 -0
  96. package/dist/esm/agent/git.js +3 -1
  97. package/dist/esm/agentic/fix_loop.js +5 -4
  98. package/dist/esm/agentic/runner.js +15 -12
  99. package/dist/esm/agents/cross-impact.js +6 -1
  100. package/dist/esm/agents/executor.js +6 -1
  101. package/dist/esm/agents/strategist.js +6 -1
  102. package/dist/esm/agents/test-designer.js +6 -1
  103. package/dist/esm/anthropic_provider.js +1 -0
  104. package/dist/esm/base_provider.js +121 -0
  105. package/dist/esm/budget_ledger.js +58 -0
  106. package/dist/esm/cache/cached_provider.js +82 -0
  107. package/dist/esm/cache/response_cache.js +140 -0
  108. package/dist/esm/cli/commands/bootstrap.js +106 -0
  109. package/dist/esm/cli/commands/cost_report.js +112 -0
  110. package/dist/esm/cli/commands/crew.js +118 -1
  111. package/dist/esm/cli/commands/gate.js +83 -0
  112. package/dist/esm/cli/commands/init.js +3 -58
  113. package/dist/esm/cli/commands/train.js +16 -21
  114. package/dist/esm/cli/defaults.js +118 -0
  115. package/dist/esm/cli/errors.js +52 -0
  116. package/dist/esm/cli/parse_args.js +24 -2
  117. package/dist/esm/cli.js +47 -2
  118. package/dist/esm/crew/orchestrator.js +162 -4
  119. package/dist/esm/crew/provider.js +24 -4
  120. package/dist/esm/custom_provider.js +1 -0
  121. package/dist/esm/engine/diff_loader.js +1 -12
  122. package/dist/esm/engine/impact_engine.js +9 -23
  123. package/dist/esm/index.js +21 -0
  124. package/dist/esm/knowledge/cluster_utils.js +60 -0
  125. package/dist/esm/knowledge/kg_bridge.js +381 -0
  126. package/dist/esm/knowledge/kg_types.js +3 -0
  127. package/dist/esm/knowledge/route_families.js +89 -0
  128. package/dist/esm/mcp-server.js +2 -4
  129. package/dist/esm/metrics/prometheus.js +149 -0
  130. package/dist/esm/model_router.js +59 -0
  131. package/dist/esm/ollama_provider.js +1 -0
  132. package/dist/esm/openai_provider.js +1 -0
  133. package/dist/esm/pipeline/orchestrator.js +6 -12
  134. package/dist/esm/pipeline/stage0_preprocess.js +12 -19
  135. package/dist/esm/pipeline/stage2_coverage.js +1 -0
  136. package/dist/esm/pipeline/stage3_generation.js +1 -0
  137. package/dist/esm/progress.js +112 -0
  138. package/dist/esm/prompts/coverage.js +7 -24
  139. package/dist/esm/prompts/cross-impact.js +3 -21
  140. package/dist/esm/prompts/generation.js +158 -36
  141. package/dist/esm/prompts/generation_profile.js +147 -0
  142. package/dist/esm/prompts/heal.js +33 -15
  143. package/dist/esm/prompts/impact.js +3 -22
  144. package/dist/esm/prompts/json_extract.js +36 -0
  145. package/dist/esm/prompts/strategist.js +2 -20
  146. package/dist/esm/prompts/test-designer.js +6 -21
  147. package/dist/esm/provider_factory.js +6 -4
  148. package/dist/esm/reporters/junit.js +86 -0
  149. package/dist/esm/reporters/reporter.js +3 -0
  150. package/dist/esm/reporters/sarif.js +131 -0
  151. package/dist/esm/resilience/circuit_breaker.js +78 -0
  152. package/dist/esm/resilience/retry.js +56 -0
  153. package/dist/esm/sanitize.js +66 -0
  154. package/dist/esm/training/kg_scanner.js +115 -0
  155. package/dist/esm/training/scanner.js +27 -34
  156. package/dist/esm/version.js +33 -0
  157. package/dist/index.d.ts +21 -1
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +45 -1
  160. package/dist/knowledge/cluster_utils.d.ts +28 -0
  161. package/dist/knowledge/cluster_utils.d.ts.map +1 -0
  162. package/dist/knowledge/cluster_utils.js +67 -0
  163. package/dist/knowledge/kg_bridge.d.ts +31 -0
  164. package/dist/knowledge/kg_bridge.d.ts.map +1 -0
  165. package/dist/knowledge/kg_bridge.js +388 -0
  166. package/dist/knowledge/kg_types.d.ts +75 -0
  167. package/dist/knowledge/kg_types.d.ts.map +1 -0
  168. package/dist/knowledge/kg_types.js +4 -0
  169. package/dist/knowledge/route_families.d.ts +18 -0
  170. package/dist/knowledge/route_families.d.ts.map +1 -1
  171. package/dist/knowledge/route_families.js +91 -0
  172. package/dist/mcp-server.d.ts.map +1 -1
  173. package/dist/mcp-server.js +2 -4
  174. package/dist/metrics/prometheus.d.ts +37 -0
  175. package/dist/metrics/prometheus.d.ts.map +1 -0
  176. package/dist/metrics/prometheus.js +153 -0
  177. package/dist/model_router.d.ts +28 -0
  178. package/dist/model_router.d.ts.map +1 -0
  179. package/dist/model_router.js +63 -0
  180. package/dist/ollama_provider.d.ts.map +1 -1
  181. package/dist/ollama_provider.js +1 -0
  182. package/dist/openai_provider.d.ts.map +1 -1
  183. package/dist/openai_provider.js +1 -0
  184. package/dist/pipeline/orchestrator.d.ts +2 -0
  185. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  186. package/dist/pipeline/orchestrator.js +6 -12
  187. package/dist/pipeline/stage0_preprocess.d.ts.map +1 -1
  188. package/dist/pipeline/stage0_preprocess.js +11 -18
  189. package/dist/pipeline/stage2_coverage.d.ts +2 -0
  190. package/dist/pipeline/stage2_coverage.d.ts.map +1 -1
  191. package/dist/pipeline/stage2_coverage.js +1 -0
  192. package/dist/pipeline/stage3_generation.d.ts +2 -0
  193. package/dist/pipeline/stage3_generation.d.ts.map +1 -1
  194. package/dist/pipeline/stage3_generation.js +1 -0
  195. package/dist/pipeline/stage4_heal.d.ts +2 -0
  196. package/dist/pipeline/stage4_heal.d.ts.map +1 -1
  197. package/dist/progress.d.ts +22 -0
  198. package/dist/progress.d.ts.map +1 -0
  199. package/dist/progress.js +116 -0
  200. package/dist/prompts/coverage.d.ts +2 -0
  201. package/dist/prompts/coverage.d.ts.map +1 -1
  202. package/dist/prompts/coverage.js +7 -24
  203. package/dist/prompts/cross-impact.d.ts +1 -0
  204. package/dist/prompts/cross-impact.d.ts.map +1 -1
  205. package/dist/prompts/cross-impact.js +3 -21
  206. package/dist/prompts/generation.d.ts +3 -1
  207. package/dist/prompts/generation.d.ts.map +1 -1
  208. package/dist/prompts/generation.js +158 -36
  209. package/dist/prompts/generation_profile.d.ts +29 -0
  210. package/dist/prompts/generation_profile.d.ts.map +1 -0
  211. package/dist/prompts/generation_profile.js +151 -0
  212. package/dist/prompts/heal.d.ts +3 -1
  213. package/dist/prompts/heal.d.ts.map +1 -1
  214. package/dist/prompts/heal.js +33 -15
  215. package/dist/prompts/impact.d.ts +1 -0
  216. package/dist/prompts/impact.d.ts.map +1 -1
  217. package/dist/prompts/impact.js +3 -22
  218. package/dist/prompts/json_extract.d.ts +14 -0
  219. package/dist/prompts/json_extract.d.ts.map +1 -0
  220. package/dist/prompts/json_extract.js +39 -0
  221. package/dist/prompts/strategist.d.ts.map +1 -1
  222. package/dist/prompts/strategist.js +2 -20
  223. package/dist/prompts/test-designer.d.ts +2 -0
  224. package/dist/prompts/test-designer.d.ts.map +1 -1
  225. package/dist/prompts/test-designer.js +6 -21
  226. package/dist/provider_factory.d.ts.map +1 -1
  227. package/dist/provider_factory.js +6 -4
  228. package/dist/reporters/junit.d.ts +6 -0
  229. package/dist/reporters/junit.d.ts.map +1 -0
  230. package/dist/reporters/junit.js +89 -0
  231. package/dist/reporters/reporter.d.ts +42 -0
  232. package/dist/reporters/reporter.d.ts.map +1 -0
  233. package/dist/reporters/reporter.js +4 -0
  234. package/dist/reporters/sarif.d.ts +7 -0
  235. package/dist/reporters/sarif.d.ts.map +1 -0
  236. package/dist/reporters/sarif.js +134 -0
  237. package/dist/resilience/circuit_breaker.d.ts +36 -0
  238. package/dist/resilience/circuit_breaker.d.ts.map +1 -0
  239. package/dist/resilience/circuit_breaker.js +82 -0
  240. package/dist/resilience/retry.d.ts +11 -0
  241. package/dist/resilience/retry.d.ts.map +1 -0
  242. package/dist/resilience/retry.js +59 -0
  243. package/dist/sanitize.d.ts +15 -0
  244. package/dist/sanitize.d.ts.map +1 -0
  245. package/dist/sanitize.js +71 -0
  246. package/dist/training/kg_scanner.d.ts +13 -0
  247. package/dist/training/kg_scanner.d.ts.map +1 -0
  248. package/dist/training/kg_scanner.js +118 -0
  249. package/dist/training/scanner.d.ts +7 -2
  250. package/dist/training/scanner.d.ts.map +1 -1
  251. package/dist/training/scanner.js +27 -34
  252. package/dist/version.d.ts +6 -0
  253. package/dist/version.d.ts.map +1 -0
  254. package/dist/version.js +36 -0
  255. package/package.json +7 -2
  256. package/schemas/route-families.schema.json +31 -1
@@ -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":"AAKA,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;CACxB;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,CAqBhF"}
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"}
@@ -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
- 'You are evaluating whether existing Mattermost Playwright E2E tests cover the impacted flows.',
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
- '- For create_spec, suggest a path following Mattermost conventions.',
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
- const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
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;AAIhE,MAAM,WAAW,wBAAwB;IACrC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,sDAAsD;IACtD,yBAAyB,EAAE,MAAM,EAAE,CAAC;CACvC;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,CAqBtF"}
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
- 'You are analyzing code changes in Mattermost to identify cross-family ripple effects.',
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
- const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
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;CACzC;AA6BD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,MAAM,CA2E1E;AAED,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,GACf,uBAAuB,GAAG,IAAI,CAgBhC;AAoBD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAc/F"}
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
- 'You are generating Mattermost Playwright E2E test code.',
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
- '1. Import ONLY from "@mattermost/playwright-lib" — no other test framework imports.',
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
- 'EXAMPLE SPEC STRUCTURE:',
77
- '```typescript',
78
- '// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.',
79
- '// See LICENSE.txt for license information.',
137
+ ...exampleBlock,
80
138
  '',
81
- "import {expect, test} from '@mattermost/playwright-lib';",
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
- 'test(',
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
- 'Return ONLY the TypeScript code. No explanations, no markdown fences.',
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
- if (!code.includes('@mattermost/playwright-lib')) {
109
- code = `import {expect, test} from '@mattermost/playwright-lib';\n\n${code}`;
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"}