@yasserkhanorg/e2e-agents 1.8.5 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) 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/api_surface.js +265 -34
  125. package/dist/esm/knowledge/cluster_utils.js +60 -0
  126. package/dist/esm/knowledge/failure_history.js +121 -0
  127. package/dist/esm/knowledge/kg_bridge.js +381 -0
  128. package/dist/esm/knowledge/kg_types.js +3 -0
  129. package/dist/esm/knowledge/route_families.js +119 -0
  130. package/dist/esm/mcp-server.js +2 -4
  131. package/dist/esm/metrics/prometheus.js +149 -0
  132. package/dist/esm/model_router.js +59 -0
  133. package/dist/esm/ollama_provider.js +1 -0
  134. package/dist/esm/openai_provider.js +1 -0
  135. package/dist/esm/pipeline/orchestrator.js +6 -12
  136. package/dist/esm/pipeline/stage0_preprocess.js +12 -19
  137. package/dist/esm/pipeline/stage1_impact.js +19 -3
  138. package/dist/esm/pipeline/stage2_coverage.js +29 -7
  139. package/dist/esm/pipeline/stage3_generation.js +21 -1
  140. package/dist/esm/progress.js +112 -0
  141. package/dist/esm/prompts/coverage.js +17 -24
  142. package/dist/esm/prompts/cross-impact.js +3 -21
  143. package/dist/esm/prompts/generation.js +201 -45
  144. package/dist/esm/prompts/generation_profile.js +147 -0
  145. package/dist/esm/prompts/heal.js +33 -15
  146. package/dist/esm/prompts/impact.js +3 -22
  147. package/dist/esm/prompts/json_extract.js +36 -0
  148. package/dist/esm/prompts/strategist.js +2 -20
  149. package/dist/esm/prompts/test-designer.js +6 -21
  150. package/dist/esm/provider_factory.js +6 -4
  151. package/dist/esm/reporters/junit.js +86 -0
  152. package/dist/esm/reporters/reporter.js +3 -0
  153. package/dist/esm/reporters/sarif.js +131 -0
  154. package/dist/esm/resilience/circuit_breaker.js +78 -0
  155. package/dist/esm/resilience/retry.js +56 -0
  156. package/dist/esm/sanitize.js +66 -0
  157. package/dist/esm/training/kg_scanner.js +115 -0
  158. package/dist/esm/training/scanner.js +27 -34
  159. package/dist/esm/validation/guardrails.js +5 -0
  160. package/dist/esm/version.js +33 -0
  161. package/dist/index.d.ts +21 -1
  162. package/dist/index.d.ts.map +1 -1
  163. package/dist/index.js +45 -1
  164. package/dist/knowledge/api_surface.d.ts +12 -0
  165. package/dist/knowledge/api_surface.d.ts.map +1 -1
  166. package/dist/knowledge/api_surface.js +268 -34
  167. package/dist/knowledge/cluster_utils.d.ts +28 -0
  168. package/dist/knowledge/cluster_utils.d.ts.map +1 -0
  169. package/dist/knowledge/cluster_utils.js +67 -0
  170. package/dist/knowledge/failure_history.d.ts +39 -0
  171. package/dist/knowledge/failure_history.d.ts.map +1 -0
  172. package/dist/knowledge/failure_history.js +128 -0
  173. package/dist/knowledge/kg_bridge.d.ts +31 -0
  174. package/dist/knowledge/kg_bridge.d.ts.map +1 -0
  175. package/dist/knowledge/kg_bridge.js +388 -0
  176. package/dist/knowledge/kg_types.d.ts +75 -0
  177. package/dist/knowledge/kg_types.d.ts.map +1 -0
  178. package/dist/knowledge/kg_types.js +4 -0
  179. package/dist/knowledge/route_families.d.ts +29 -0
  180. package/dist/knowledge/route_families.d.ts.map +1 -1
  181. package/dist/knowledge/route_families.js +122 -0
  182. package/dist/mcp-server.d.ts.map +1 -1
  183. package/dist/mcp-server.js +2 -4
  184. package/dist/metrics/prometheus.d.ts +37 -0
  185. package/dist/metrics/prometheus.d.ts.map +1 -0
  186. package/dist/metrics/prometheus.js +153 -0
  187. package/dist/model_router.d.ts +28 -0
  188. package/dist/model_router.d.ts.map +1 -0
  189. package/dist/model_router.js +63 -0
  190. package/dist/ollama_provider.d.ts.map +1 -1
  191. package/dist/ollama_provider.js +1 -0
  192. package/dist/openai_provider.d.ts.map +1 -1
  193. package/dist/openai_provider.js +1 -0
  194. package/dist/pipeline/orchestrator.d.ts +2 -0
  195. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  196. package/dist/pipeline/orchestrator.js +6 -12
  197. package/dist/pipeline/stage0_preprocess.d.ts.map +1 -1
  198. package/dist/pipeline/stage0_preprocess.js +11 -18
  199. package/dist/pipeline/stage1_impact.d.ts +1 -1
  200. package/dist/pipeline/stage1_impact.d.ts.map +1 -1
  201. package/dist/pipeline/stage1_impact.js +18 -2
  202. package/dist/pipeline/stage2_coverage.d.ts +2 -0
  203. package/dist/pipeline/stage2_coverage.d.ts.map +1 -1
  204. package/dist/pipeline/stage2_coverage.js +29 -7
  205. package/dist/pipeline/stage3_generation.d.ts +2 -0
  206. package/dist/pipeline/stage3_generation.d.ts.map +1 -1
  207. package/dist/pipeline/stage3_generation.js +21 -1
  208. package/dist/pipeline/stage4_heal.d.ts +2 -0
  209. package/dist/pipeline/stage4_heal.d.ts.map +1 -1
  210. package/dist/progress.d.ts +22 -0
  211. package/dist/progress.d.ts.map +1 -0
  212. package/dist/progress.js +116 -0
  213. package/dist/prompts/coverage.d.ts +2 -0
  214. package/dist/prompts/coverage.d.ts.map +1 -1
  215. package/dist/prompts/coverage.js +17 -24
  216. package/dist/prompts/cross-impact.d.ts +1 -0
  217. package/dist/prompts/cross-impact.d.ts.map +1 -1
  218. package/dist/prompts/cross-impact.js +3 -21
  219. package/dist/prompts/generation.d.ts +4 -2
  220. package/dist/prompts/generation.d.ts.map +1 -1
  221. package/dist/prompts/generation.js +201 -45
  222. package/dist/prompts/generation_profile.d.ts +29 -0
  223. package/dist/prompts/generation_profile.d.ts.map +1 -0
  224. package/dist/prompts/generation_profile.js +151 -0
  225. package/dist/prompts/heal.d.ts +3 -1
  226. package/dist/prompts/heal.d.ts.map +1 -1
  227. package/dist/prompts/heal.js +33 -15
  228. package/dist/prompts/impact.d.ts +1 -0
  229. package/dist/prompts/impact.d.ts.map +1 -1
  230. package/dist/prompts/impact.js +3 -22
  231. package/dist/prompts/json_extract.d.ts +14 -0
  232. package/dist/prompts/json_extract.d.ts.map +1 -0
  233. package/dist/prompts/json_extract.js +39 -0
  234. package/dist/prompts/strategist.d.ts.map +1 -1
  235. package/dist/prompts/strategist.js +2 -20
  236. package/dist/prompts/test-designer.d.ts +2 -0
  237. package/dist/prompts/test-designer.d.ts.map +1 -1
  238. package/dist/prompts/test-designer.js +6 -21
  239. package/dist/provider_factory.d.ts.map +1 -1
  240. package/dist/provider_factory.js +6 -4
  241. package/dist/reporters/junit.d.ts +6 -0
  242. package/dist/reporters/junit.d.ts.map +1 -0
  243. package/dist/reporters/junit.js +89 -0
  244. package/dist/reporters/reporter.d.ts +42 -0
  245. package/dist/reporters/reporter.d.ts.map +1 -0
  246. package/dist/reporters/reporter.js +4 -0
  247. package/dist/reporters/sarif.d.ts +7 -0
  248. package/dist/reporters/sarif.d.ts.map +1 -0
  249. package/dist/reporters/sarif.js +134 -0
  250. package/dist/resilience/circuit_breaker.d.ts +36 -0
  251. package/dist/resilience/circuit_breaker.d.ts.map +1 -0
  252. package/dist/resilience/circuit_breaker.js +82 -0
  253. package/dist/resilience/retry.d.ts +11 -0
  254. package/dist/resilience/retry.d.ts.map +1 -0
  255. package/dist/resilience/retry.js +59 -0
  256. package/dist/sanitize.d.ts +15 -0
  257. package/dist/sanitize.d.ts.map +1 -0
  258. package/dist/sanitize.js +71 -0
  259. package/dist/training/kg_scanner.d.ts +13 -0
  260. package/dist/training/kg_scanner.d.ts.map +1 -0
  261. package/dist/training/kg_scanner.js +118 -0
  262. package/dist/training/scanner.d.ts +7 -2
  263. package/dist/training/scanner.d.ts.map +1 -1
  264. package/dist/training/scanner.js +27 -34
  265. package/dist/validation/guardrails.d.ts +2 -0
  266. package/dist/validation/guardrails.d.ts.map +1 -1
  267. package/dist/validation/guardrails.js +5 -0
  268. package/dist/validation/output_schema.d.ts +3 -0
  269. package/dist/validation/output_schema.d.ts.map +1 -1
  270. package/dist/version.d.ts +6 -0
  271. package/dist/version.d.ts.map +1 -0
  272. package/dist/version.js +36 -0
  273. package/package.json +7 -2
  274. package/schemas/route-families.schema.json +31 -1
@@ -13,7 +13,10 @@ exports.getCypressSpecDirsForBinding = getCypressSpecDirsForBinding;
13
13
  exports.getPriorityForBinding = getPriorityForBinding;
14
14
  exports.getUserFlowsForBinding = getUserFlowsForBinding;
15
15
  exports.getRoutesForBinding = getRoutesForBinding;
16
+ exports.getAssertionPatternsForBinding = getAssertionPatternsForBinding;
16
17
  exports.clearManifestCache = clearManifestCache;
18
+ exports.buildHeuristicFamilies = buildHeuristicFamilies;
19
+ exports.serializeManifest = serializeManifest;
17
20
  const fs_1 = require("fs");
18
21
  const path_1 = require("path");
19
22
  const logger_js_1 = require("../logger.js");
@@ -44,6 +47,17 @@ function matchesGlob(filePath, pattern) {
44
47
  function matchesAnyPattern(filePath, patterns) {
45
48
  return patterns.some((pattern) => matchesGlob(filePath, pattern));
46
49
  }
50
+ function validateApiEndpoint(ep) {
51
+ if (!ep || typeof ep !== 'object')
52
+ return null;
53
+ const obj = ep;
54
+ if (typeof obj.method !== 'string' || typeof obj.path !== 'string')
55
+ return null;
56
+ const result = { method: obj.method, path: obj.path };
57
+ if (typeof obj.description === 'string')
58
+ result.description = obj.description;
59
+ return result;
60
+ }
47
61
  function validateFamily(family) {
48
62
  if (!family || typeof family !== 'object') {
49
63
  return null;
@@ -52,6 +66,10 @@ function validateFamily(family) {
52
66
  if (typeof obj.id !== 'string' || !obj.id.trim()) {
53
67
  return null;
54
68
  }
69
+ // When testType is 'api', routes may contain API paths like "GET /api/users"
70
+ const testType = (obj.testType === 'ui' || obj.testType === 'api' || obj.testType === 'both')
71
+ ? obj.testType
72
+ : undefined;
55
73
  if (!Array.isArray(obj.routes) || obj.routes.length === 0) {
56
74
  return null;
57
75
  }
@@ -95,8 +113,32 @@ function validateFamily(family) {
95
113
  .map((f) => validateFeature(f))
96
114
  .filter((f) => f !== null);
97
115
  }
116
+ if (Array.isArray(obj.apiEndpoints)) {
117
+ const endpoints = obj.apiEndpoints
118
+ .map((ep) => validateApiEndpoint(ep))
119
+ .filter((ep) => ep !== null);
120
+ if (endpoints.length > 0)
121
+ result.apiEndpoints = endpoints;
122
+ }
123
+ if (testType) {
124
+ result.testType = testType;
125
+ }
126
+ if (Array.isArray(obj.assertionPatterns)) {
127
+ result.assertionPatterns = validateAssertionPatterns(obj.assertionPatterns);
128
+ }
98
129
  return result;
99
130
  }
131
+ const VALID_ASSERTION_TYPES = [
132
+ 'state-change', 'cross-user', 'persistence', 'negative',
133
+ 'permission', 'data-integrity', 'error-handling',
134
+ ];
135
+ function validateAssertionPatterns(patterns) {
136
+ return patterns
137
+ .filter((p) => p != null && typeof p === 'object')
138
+ .filter((p) => typeof p.type === 'string' && typeof p.pattern === 'string')
139
+ .filter((p) => VALID_ASSERTION_TYPES.includes(p.type))
140
+ .map((p) => ({ type: p.type, pattern: p.pattern }));
141
+ }
100
142
  function validateFeature(feature) {
101
143
  if (!feature || typeof feature !== 'object') {
102
144
  return null;
@@ -130,6 +172,9 @@ function validateFeature(feature) {
130
172
  if (Array.isArray(obj.userFlows)) {
131
173
  result.userFlows = obj.userFlows.filter((v) => typeof v === 'string');
132
174
  }
175
+ if (Array.isArray(obj.assertionPatterns)) {
176
+ result.assertionPatterns = validateAssertionPatterns(obj.assertionPatterns);
177
+ }
133
178
  return result;
134
179
  }
135
180
  function loadRouteFamilyManifest(testsRoot, config) {
@@ -283,6 +328,83 @@ function getRoutesForBinding(manifest, binding) {
283
328
  }
284
329
  return family.routes;
285
330
  }
331
+ function getAssertionPatternsForBinding(manifest, binding) {
332
+ const family = getFamilyById(manifest, binding.family);
333
+ if (!family) {
334
+ return [];
335
+ }
336
+ if (binding.feature) {
337
+ const feature = getFeatureById(family, binding.feature);
338
+ if (feature?.assertionPatterns && feature.assertionPatterns.length > 0) {
339
+ return feature.assertionPatterns;
340
+ }
341
+ }
342
+ return family.assertionPatterns || [];
343
+ }
286
344
  function clearManifestCache() {
287
345
  manifestCache.clear();
288
346
  }
347
+ /**
348
+ * Build heuristic route families from changed files when no manifest exists.
349
+ * Groups files by their top-level directory to create rough family groupings.
350
+ * Results are lower confidence but allow analysis to proceed without training.
351
+ */
352
+ function buildHeuristicFamilies(changedFiles, testsRoot) {
353
+ const dirGroups = new Map();
354
+ for (const file of changedFiles) {
355
+ const normalized = file.replace(/\\/g, '/');
356
+ const parts = normalized.split('/');
357
+ // Use the first meaningful directory segment as the family ID
358
+ // Skip common prefixes like 'src/', 'app/', 'lib/'
359
+ const skipDirs = new Set(['src', 'app', 'lib', 'packages', 'components']);
360
+ let familyDir = parts[0] || 'root';
361
+ if (skipDirs.has(familyDir) && parts.length > 1) {
362
+ familyDir = parts[1];
363
+ }
364
+ // Normalize to a clean family name
365
+ familyDir = familyDir.replace(/\.[^.]+$/, ''); // strip file extensions for single files
366
+ if (!dirGroups.has(familyDir)) {
367
+ dirGroups.set(familyDir, []);
368
+ }
369
+ dirGroups.get(familyDir).push(normalized);
370
+ }
371
+ const families = [];
372
+ for (const [dir, files] of dirGroups) {
373
+ families.push({
374
+ id: dir,
375
+ routes: [`/${dir}`],
376
+ webappPaths: files.map((f) => `${f}*`),
377
+ });
378
+ }
379
+ logger_js_1.logger.info(`Built ${families.length} heuristic families from ${changedFiles.length} changed files (no route-families.json found)`);
380
+ logger_js_1.logger.info('Tip: Run `e2e-ai-agents train` to generate a proper route-families manifest for better accuracy.');
381
+ return {
382
+ families,
383
+ source: 'heuristic',
384
+ };
385
+ }
386
+ /**
387
+ * Serialize a RouteFamilyManifest to clean JSON, stripping empty optional fields.
388
+ */
389
+ function serializeManifest(manifest) {
390
+ const output = {
391
+ families: manifest.families.map((f) => {
392
+ const cleaned = { ...f };
393
+ const optionalArrays = [
394
+ 'pageObjects', 'components', 'webappPaths', 'serverPaths',
395
+ 'specDirs', 'cypressSpecDirs', 'tags', 'userFlows', 'features', 'apiEndpoints', 'assertionPatterns',
396
+ ];
397
+ for (const key of optionalArrays) {
398
+ if (!cleaned[key] || (Array.isArray(cleaned[key]) && cleaned[key].length === 0)) {
399
+ delete cleaned[key];
400
+ }
401
+ }
402
+ if (!cleaned.priority)
403
+ delete cleaned.priority;
404
+ if (!cleaned.testType)
405
+ delete cleaned.testType;
406
+ return cleaned;
407
+ }),
408
+ };
409
+ return JSON.stringify(output, null, 2) + '\n';
410
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AAeA,UAAU,IAAI;IACV,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AA4GD;;;GAGG;AACH,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAc;gBAErB,QAAQ,GAAE,MAAsB;IAM5C,OAAO,CAAC,WAAW;IAmGnB;;;OAGG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB5E,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,QAAQ;IAyDhB,OAAO,CAAC,aAAa;IA2BrB,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;IACH,QAAQ,IAAI,IAAI,EAAE;CAGrB;AAgBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAC,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;CAAC,CA0BtK;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAC,GAC3E,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAmEzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAsB,GAAG,IAAI,CAmCvE;AAMD,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AAeA,UAAU,IAAI;IACV,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AA4GD;;;GAGG;AACH,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,WAAW,CAAc;gBAErB,QAAQ,GAAE,MAAsB;IAM5C,OAAO,CAAC,WAAW;IAmGnB;;;OAGG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB5E,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,QAAQ;IAyDhB,OAAO,CAAC,aAAa;IA0BrB,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,eAAe;IAuBvB,OAAO,CAAC,sBAAsB;IAW9B;;OAEG;IACH,QAAQ,IAAI,IAAI,EAAE;CAGrB;AAgBD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAC,QAAQ,EAAE,KAAK,CAAC;QAAC,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAC,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;CAAC,CA0BtK;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAC,GAC3E,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAmEzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,GAAE,MAAsB,GAAG,IAAI,CAmCvE;AAMD,eAAe,kBAAkB,CAAC"}
@@ -377,8 +377,7 @@ class E2EAgentsMCPServer {
377
377
  if (!validateGitRef(since)) {
378
378
  return JSON.stringify({ error: 'Invalid git reference format' });
379
379
  }
380
- // SECURITY: Use -- to separate git options from refs
381
- const result = (0, child_process_1.spawnSync)('git', ['diff', '--name-only', '--', `${since}..HEAD`], {
380
+ const result = (0, child_process_1.spawnSync)('git', ['diff', '--name-only', `${since}..HEAD`], {
382
381
  cwd: this.repoRoot,
383
382
  encoding: 'utf-8',
384
383
  timeout: 30000,
@@ -446,8 +445,7 @@ class E2EAgentsMCPServer {
446
445
  if (!validateGitRef(since)) {
447
446
  return [];
448
447
  }
449
- // SECURITY: Use -- separator
450
- const result = (0, child_process_1.spawnSync)('git', ['diff', '--name-only', '--', `${since}..HEAD`], {
448
+ const result = (0, child_process_1.spawnSync)('git', ['diff', '--name-only', `${since}..HEAD`], {
451
449
  cwd: this.repoRoot,
452
450
  encoding: 'utf-8',
453
451
  timeout: 30000,
@@ -0,0 +1,37 @@
1
+ export declare class PrometheusMetrics {
2
+ private counters;
3
+ private gauges;
4
+ private histograms;
5
+ /**
6
+ * Record an LLM request.
7
+ */
8
+ recordLLMRequest(provider: string, agent: string, durationMs: number, costUSD: number, tokens: number): void;
9
+ /**
10
+ * Record a crew workflow run.
11
+ */
12
+ recordCrewRun(workflow: string, families: number, durationMs: number, costUSD: number): void;
13
+ /**
14
+ * Record a budget check event.
15
+ */
16
+ recordBudgetCheck(exceeded: boolean, currentUSD: number, limitUSD: number): void;
17
+ /**
18
+ * Record a circuit breaker state change.
19
+ */
20
+ recordCircuitBreakerState(state: 'closed' | 'open' | 'half-open'): void;
21
+ /**
22
+ * Record a cache hit or miss.
23
+ */
24
+ recordCacheResult(hit: boolean, agent: string): void;
25
+ /**
26
+ * Export all metrics in Prometheus text exposition format.
27
+ */
28
+ export(): string;
29
+ /**
30
+ * Reset all metrics to zero.
31
+ */
32
+ reset(): void;
33
+ private incrementCounter;
34
+ private setGauge;
35
+ private observeHistogram;
36
+ }
37
+ //# sourceMappingURL=prometheus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus.d.ts","sourceRoot":"","sources":["../../src/metrics/prometheus.ts"],"names":[],"mappings":"AAkCA,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,UAAU,CAAmB;IAErC;;OAEG;IACH,gBAAgB,CACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACf,IAAI;IAOP;;OAEG;IACH,aAAa,CACT,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAChB,IAAI;IAOP;;OAEG;IACH,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMhF;;OAEG;IACH,yBAAyB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,IAAI;IAKvE;;OAEG;IACH,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpD;;OAEG;IACH,MAAM,IAAI,MAAM;IA+ChB;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,gBAAgB;CAc3B"}
@@ -0,0 +1,153 @@
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.PrometheusMetrics = void 0;
6
+ const DURATION_BUCKETS = [0.1, 0.5, 1, 2, 5, 10, 30, 60, 120, 300];
7
+ class PrometheusMetrics {
8
+ constructor() {
9
+ this.counters = [];
10
+ this.gauges = [];
11
+ this.histograms = [];
12
+ }
13
+ /**
14
+ * Record an LLM request.
15
+ */
16
+ recordLLMRequest(provider, agent, durationMs, costUSD, tokens) {
17
+ this.incrementCounter('e2e_agents_llm_requests_total', 'Total LLM requests', { provider, agent });
18
+ this.incrementCounter('e2e_agents_llm_tokens_total', 'Total tokens consumed', { provider, agent }, tokens);
19
+ this.incrementCounter('e2e_agents_llm_cost_usd_total', 'Total LLM cost in USD', { provider, agent }, costUSD);
20
+ this.observeHistogram('e2e_agents_llm_request_duration_seconds', 'LLM request duration', { provider, agent }, durationMs / 1000);
21
+ }
22
+ /**
23
+ * Record a crew workflow run.
24
+ */
25
+ recordCrewRun(workflow, families, durationMs, costUSD) {
26
+ this.incrementCounter('e2e_agents_crew_runs_total', 'Total crew workflow runs', { workflow });
27
+ this.incrementCounter('e2e_agents_crew_families_processed_total', 'Total families processed', { workflow }, families);
28
+ this.incrementCounter('e2e_agents_crew_cost_usd_total', 'Total crew cost in USD', { workflow }, costUSD);
29
+ this.observeHistogram('e2e_agents_crew_duration_seconds', 'Crew workflow duration', { workflow }, durationMs / 1000);
30
+ }
31
+ /**
32
+ * Record a budget check event.
33
+ */
34
+ recordBudgetCheck(exceeded, currentUSD, limitUSD) {
35
+ this.incrementCounter('e2e_agents_budget_checks_total', 'Total budget checks', { exceeded: String(exceeded) });
36
+ this.setGauge('e2e_agents_budget_used_usd', 'Current budget usage in USD', {}, currentUSD);
37
+ this.setGauge('e2e_agents_budget_limit_usd', 'Budget limit in USD', {}, limitUSD);
38
+ }
39
+ /**
40
+ * Record a circuit breaker state change.
41
+ */
42
+ recordCircuitBreakerState(state) {
43
+ this.setGauge('e2e_agents_circuit_breaker_state', 'Circuit breaker state (0=closed, 1=open, 2=half-open)', {}, state === 'closed' ? 0 : state === 'open' ? 1 : 2);
44
+ }
45
+ /**
46
+ * Record a cache hit or miss.
47
+ */
48
+ recordCacheResult(hit, agent) {
49
+ this.incrementCounter('e2e_agents_cache_lookups_total', 'Total cache lookups', { result: hit ? 'hit' : 'miss', agent });
50
+ }
51
+ /**
52
+ * Export all metrics in Prometheus text exposition format.
53
+ */
54
+ export() {
55
+ const lines = [];
56
+ const seenHelp = new Set();
57
+ // Export counters
58
+ for (const counter of this.counters) {
59
+ if (!seenHelp.has(counter.name)) {
60
+ lines.push(`# HELP ${counter.name} ${counter.help}`);
61
+ lines.push(`# TYPE ${counter.name} counter`);
62
+ seenHelp.add(counter.name);
63
+ }
64
+ const labelStr = formatLabels(counter.labels);
65
+ lines.push(`${counter.name}${labelStr} ${counter.value}`);
66
+ }
67
+ // Export gauges
68
+ for (const gauge of this.gauges) {
69
+ if (!seenHelp.has(gauge.name)) {
70
+ lines.push(`# HELP ${gauge.name} ${gauge.help}`);
71
+ lines.push(`# TYPE ${gauge.name} gauge`);
72
+ seenHelp.add(gauge.name);
73
+ }
74
+ const labelStr = formatLabels(gauge.labels);
75
+ lines.push(`${gauge.name}${labelStr} ${gauge.value}`);
76
+ }
77
+ // Export histograms
78
+ for (const hist of this.histograms) {
79
+ if (!seenHelp.has(hist.name)) {
80
+ lines.push(`# HELP ${hist.name} ${hist.help}`);
81
+ lines.push(`# TYPE ${hist.name} histogram`);
82
+ seenHelp.add(hist.name);
83
+ }
84
+ const labelStr = formatLabels(hist.labels);
85
+ let cumulative = 0;
86
+ for (const bucket of DURATION_BUCKETS) {
87
+ cumulative += hist.buckets.get(bucket) || 0;
88
+ lines.push(`${hist.name}_bucket${formatLabels({ ...hist.labels, le: String(bucket) })} ${cumulative}`);
89
+ }
90
+ lines.push(`${hist.name}_bucket${formatLabels({ ...hist.labels, le: '+Inf' })} ${hist.count}`);
91
+ lines.push(`${hist.name}_sum${labelStr} ${hist.sum}`);
92
+ lines.push(`${hist.name}_count${labelStr} ${hist.count}`);
93
+ }
94
+ return lines.join('\n') + '\n';
95
+ }
96
+ /**
97
+ * Reset all metrics to zero.
98
+ */
99
+ reset() {
100
+ this.counters = [];
101
+ this.gauges = [];
102
+ this.histograms = [];
103
+ }
104
+ incrementCounter(name, help, labels, value = 1) {
105
+ const existing = this.counters.find((c) => c.name === name && labelsMatch(c.labels, labels));
106
+ if (existing) {
107
+ existing.value += value;
108
+ }
109
+ else {
110
+ this.counters.push({ name, help, labels, value });
111
+ }
112
+ }
113
+ setGauge(name, help, labels, value) {
114
+ const existing = this.gauges.find((c) => c.name === name && labelsMatch(c.labels, labels));
115
+ if (existing) {
116
+ existing.value = value;
117
+ }
118
+ else {
119
+ this.gauges.push({ name, help, labels, value });
120
+ }
121
+ }
122
+ observeHistogram(name, help, labels, value) {
123
+ let existing = this.histograms.find((h) => h.name === name && labelsMatch(h.labels, labels));
124
+ if (!existing) {
125
+ existing = { name, help, labels, sum: 0, count: 0, buckets: new Map() };
126
+ this.histograms.push(existing);
127
+ }
128
+ existing.sum += value;
129
+ existing.count++;
130
+ for (const bucket of DURATION_BUCKETS) {
131
+ if (value <= bucket) {
132
+ existing.buckets.set(bucket, (existing.buckets.get(bucket) || 0) + 1);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ exports.PrometheusMetrics = PrometheusMetrics;
138
+ function escapeLabel(v) {
139
+ return v.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
140
+ }
141
+ function formatLabels(labels) {
142
+ const entries = Object.entries(labels);
143
+ if (entries.length === 0)
144
+ return '';
145
+ return `{${entries.map(([k, v]) => `${k}="${escapeLabel(v)}"`).join(',')}}`;
146
+ }
147
+ function labelsMatch(a, b) {
148
+ const keysA = Object.keys(a);
149
+ const keysB = Object.keys(b);
150
+ if (keysA.length !== keysB.length)
151
+ return false;
152
+ return keysA.every((k) => a[k] === b[k]);
153
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Model Router — routes agent tasks to appropriate models based on task complexity.
3
+ *
4
+ * Classification/extraction tasks → cheap model (Haiku)
5
+ * Generation/reasoning tasks → capable model (Sonnet)
6
+ */
7
+ export type TaskComplexity = 'classification' | 'extraction' | 'generation' | 'reasoning';
8
+ export interface ModelRoutingConfig {
9
+ classification?: string;
10
+ extraction?: string;
11
+ generation?: string;
12
+ reasoning?: string;
13
+ }
14
+ export declare class ModelRouter {
15
+ private overrides;
16
+ private providerType;
17
+ constructor(providerType: string, overrides?: ModelRoutingConfig);
18
+ /**
19
+ * Get the recommended model for a given agent role.
20
+ * Returns undefined if no routing recommendation (use provider default).
21
+ */
22
+ getModel(role: string): string | undefined;
23
+ /**
24
+ * Get the task complexity for an agent role.
25
+ */
26
+ getComplexity(role: string): TaskComplexity;
27
+ }
28
+ //# sourceMappingURL=model_router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model_router.d.ts","sourceRoot":"","sources":["../src/model_router.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG,YAAY,GAAG,YAAY,GAAG,WAAW,CAAC;AAE1F,MAAM,WAAW,kBAAkB;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AA8BD,qBAAa,WAAW;IACpB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,YAAY,CAAS;gBAEjB,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,kBAAkB;IAKhE;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAgB1C;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc;CAG9C"}
@@ -0,0 +1,63 @@
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.ModelRouter = void 0;
6
+ const AGENT_COMPLEXITY = {
7
+ 'impact-analyst': 'classification',
8
+ 'coverage-evaluator': 'classification',
9
+ 'cross-impact': 'extraction',
10
+ 'regression-advisor': 'extraction',
11
+ 'strategist': 'classification',
12
+ 'test-designer': 'generation',
13
+ 'generator': 'generation',
14
+ 'executor': 'generation',
15
+ 'healer': 'reasoning',
16
+ 'explorer': 'reasoning',
17
+ };
18
+ const DEFAULT_MODELS = {
19
+ anthropic: {
20
+ classification: 'claude-haiku-4-5-20251001',
21
+ extraction: 'claude-haiku-4-5-20251001',
22
+ generation: 'claude-sonnet-4-5-20250514',
23
+ reasoning: 'claude-sonnet-4-5-20250514',
24
+ },
25
+ openai: {
26
+ classification: 'gpt-4o-mini',
27
+ extraction: 'gpt-4o-mini',
28
+ generation: 'gpt-4o',
29
+ reasoning: 'gpt-4o',
30
+ },
31
+ };
32
+ class ModelRouter {
33
+ constructor(providerType, overrides) {
34
+ this.providerType = providerType;
35
+ this.overrides = overrides || {};
36
+ }
37
+ /**
38
+ * Get the recommended model for a given agent role.
39
+ * Returns undefined if no routing recommendation (use provider default).
40
+ */
41
+ getModel(role) {
42
+ const complexity = AGENT_COMPLEXITY[role];
43
+ if (!complexity)
44
+ return undefined;
45
+ // Check user overrides first
46
+ const override = this.overrides[complexity];
47
+ if (override)
48
+ return override;
49
+ // Check provider defaults
50
+ const defaults = DEFAULT_MODELS[this.providerType];
51
+ if (defaults)
52
+ return defaults[complexity];
53
+ // No recommendation — use provider's default model
54
+ return undefined;
55
+ }
56
+ /**
57
+ * Get the task complexity for an agent role.
58
+ */
59
+ getComplexity(role) {
60
+ return AGENT_COMPLEXITY[role] || 'generation';
61
+ }
62
+ }
63
+ exports.ModelRouter = ModelRouter;
@@ -1 +1 @@
1
- {"version":3,"file":"ollama_provider.d.ts","sourceRoot":"","sources":["../src/ollama_provider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACR,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,oBAAoB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAiEhD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC5C,IAAI,SAAY;IAChB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IAEtB,YAAY,EAAE,oBAAoB,CAShC;gBAEU,MAAM,EAAE,YAAY;IA8B1B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAkEnF;;;OAGG;IAEG,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAIzG;;OAEG;IACI,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IAqDnG;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;IAgBjE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAaxC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC7B,CAAC,CAoCD"}
1
+ {"version":3,"file":"ollama_provider.d.ts","sourceRoot":"","sources":["../src/ollama_provider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACR,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,oBAAoB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAiEhD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC5C,IAAI,SAAY;IAChB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IAEtB,YAAY,EAAE,oBAAoB,CAShC;gBAEU,MAAM,EAAE,YAAY;IA8B1B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAmEnF;;;OAGG;IAEG,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAIzG;;OAEG;IACI,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IAqDnG;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;IAgBjE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;CAaxC;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAC9C,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;CAC7B,CAAC,CAoCD"}
@@ -129,6 +129,7 @@ class OllamaProvider extends base_provider_js_1.BaseProvider {
129
129
  this.model = model;
130
130
  }
131
131
  async generateText(prompt, options) {
132
+ this.checkBudget();
132
133
  const startTime = Date.now();
133
134
  try {
134
135
  // SECURITY: Validate prompt length
@@ -1 +1 @@
1
- {"version":3,"file":"openai_provider.d.ts","sourceRoot":"","sources":["../src/openai_provider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACR,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,oBAAoB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAehD,qBAAa,cAAe,SAAQ,YAAY;IAC5C,IAAI,SAAY;IAChB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IAEtB,YAAY,EAAE,oBAAoB,CAAC;gBAEvB,MAAM,EAAE,YAAY;IAyC1B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IA2D7E,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAgGlG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IA8CnG,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,iBAAiB;IAWnB,WAAW,IAAI,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;CAuBpE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5D,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC,CAsBD"}
1
+ {"version":3,"file":"openai_provider.d.ts","sourceRoot":"","sources":["../src/openai_provider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACR,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,oBAAoB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAehD,qBAAa,cAAe,SAAQ,YAAY;IAC5C,IAAI,SAAY;IAChB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IAEtB,YAAY,EAAE,oBAAoB,CAAC;gBAEvB,MAAM,EAAE,YAAY;IAyC1B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IA4D7E,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAgGlG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IA8CnG,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,iBAAiB;IAWnB,WAAW,IAAI,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;CAuBpE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5D,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB,CAAC,CAsBD"}
@@ -53,6 +53,7 @@ class OpenAIProvider extends base_provider_js_1.BaseProvider {
53
53
  };
54
54
  }
55
55
  async generateText(prompt, options) {
56
+ this.checkBudget();
56
57
  const startTime = Date.now();
57
58
  try {
58
59
  if (prompt.length > 10 * 1024 * 1024) {
@@ -5,6 +5,7 @@ import { type HealConfig, type HealResult } from './stage4_heal.js';
5
5
  import { type FlowDecisionReport } from '../validation/output_schema.js';
6
6
  import type { RouteFamilyConfig } from '../knowledge/route_families.js';
7
7
  import type { ApiSurfaceConfig } from '../knowledge/api_surface.js';
8
+ import type { GenerationProfile } from '../prompts/generation_profile.js';
8
9
  export interface PipelineConfig {
9
10
  appPath: string;
10
11
  testsRoot: string;
@@ -12,6 +13,7 @@ export interface PipelineConfig {
12
13
  gitIncludeUncommitted?: boolean;
13
14
  routeFamilies?: RouteFamilyConfig;
14
15
  apiSurface?: ApiSurfaceConfig;
16
+ profile?: GenerationProfile;
15
17
  impact?: ImpactConfig;
16
18
  coverage?: CoverageConfig;
17
19
  generation?: GenerationConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/pipeline/orchestrator.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAmB,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAC,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAuD,KAAK,UAAU,EAAE,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACxH,OAAO,EAAe,KAAK,kBAAkB,EAAoB,MAAM,gCAAgC,CAAC;AAExG,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAElE,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAqBD,wBAAsB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA6IjF"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/pipeline/orchestrator.ts"],"names":[],"mappings":"AAQA,OAAO,EAAiB,KAAK,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAmB,KAAK,cAAc,EAAC,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAqB,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAC,MAAM,wBAAwB,CAAC;AACrG,OAAO,EAAuD,KAAK,UAAU,EAAE,KAAK,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACxH,OAAO,EAAe,KAAK,kBAAkB,EAAoB,MAAM,gCAAgC,CAAC;AAExG,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAC;AAGxE,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,YAAY,GAAG,MAAM,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAYD,wBAAsB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CA8IjF"}
@@ -14,6 +14,7 @@ const stage3_generation_js_1 = require("./stage3_generation.js");
14
14
  const stage4_heal_js_1 = require("./stage4_heal.js");
15
15
  const output_schema_js_1 = require("../validation/output_schema.js");
16
16
  const guardrails_js_1 = require("../validation/guardrails.js");
17
+ const generation_profile_js_1 = require("../prompts/generation_profile.js");
17
18
  function createRunId() {
18
19
  const ciRunId = process.env.GITHUB_RUN_ID;
19
20
  const entropy = Math.random().toString(36).slice(2, 8);
@@ -23,19 +24,12 @@ function createRunId() {
23
24
  }
24
25
  return `pipeline-local-${ts}-${entropy}`;
25
26
  }
26
- function isTestFile(file) {
27
- const normalized = file.replace(/\\/g, '/');
28
- return /\.(spec|test)\.(ts|tsx|js|jsx)$/.test(normalized) ||
29
- /_test\.go$/.test(normalized) ||
30
- normalized.includes('__tests__/') ||
31
- normalized.includes('/tests/') ||
32
- normalized.includes('/test/');
33
- }
34
27
  async function runPipeline(config) {
35
28
  const runId = createRunId();
36
29
  const startedAt = new Date().toISOString();
37
30
  const allWarnings = [];
38
31
  const stages = config.stages || ['preprocess', 'impact', 'coverage'];
32
+ const profile = config.profile || (0, generation_profile_js_1.resolveGenerationProfile)();
39
33
  let generatedSpecs;
40
34
  let healResult;
41
35
  // Step 1: Get changed files
@@ -47,7 +41,7 @@ async function runPipeline(config) {
47
41
  }
48
42
  const changedFiles = gitResult.files
49
43
  .map((f) => f.replace(/\\/g, '/'))
50
- .filter((f) => !isTestFile(f));
44
+ .filter((f) => !(0, git_js_1.isTestFile)(f));
51
45
  if (changedFiles.length === 0) {
52
46
  allWarnings.push('No changed application files detected.');
53
47
  const emptyReport = {
@@ -90,7 +84,7 @@ async function runPipeline(config) {
90
84
  // Step 4: Coverage stage — AI-powered spec coverage evaluation
91
85
  if (stages.includes('coverage') && decisions.length > 0) {
92
86
  const coverageTimer = logger_js_1.logger.timer('coverage');
93
- const coverageResult = await (0, stage2_coverage_js_1.runCoverageStage)(decisions, preprocessResult.specIndex, preprocessResult.context, config.testsRoot, config.coverage || {});
87
+ const coverageResult = await (0, stage2_coverage_js_1.runCoverageStage)(decisions, preprocessResult.specIndex, preprocessResult.context, config.testsRoot, { ...(config.coverage || {}), profile });
94
88
  decisions = coverageResult.decisions;
95
89
  timings.coverage = coverageTimer.end();
96
90
  allWarnings.push(...coverageResult.warnings);
@@ -98,7 +92,7 @@ async function runPipeline(config) {
98
92
  // Step 5: Generation stage — AI-powered spec generation for create_spec / add_scenarios
99
93
  if (stages.includes('generation') && decisions.length > 0) {
100
94
  const generationTimer = logger_js_1.logger.timer('generation');
101
- const generationResult = await (0, stage3_generation_js_1.runGenerationStage)(decisions, preprocessResult.apiSurface, config.testsRoot, config.generation || {});
95
+ const generationResult = await (0, stage3_generation_js_1.runGenerationStage)(decisions, preprocessResult.apiSurface, config.testsRoot, { ...(config.generation || {}), profile });
102
96
  generatedSpecs = generationResult.generated;
103
97
  timings.generation = generationTimer.end();
104
98
  allWarnings.push(...generationResult.warnings);
@@ -111,7 +105,7 @@ async function runPipeline(config) {
111
105
  generatedSpecs,
112
106
  }, decisions);
113
107
  if (healTargets.length > 0) {
114
- healResult = await (0, stage4_heal_js_1.runHealStage)(config.testsRoot, healTargets, config.heal || { mcp: true });
108
+ healResult = await (0, stage4_heal_js_1.runHealStage)(config.testsRoot, healTargets, { ...(config.heal || { mcp: true }), profile });
115
109
  allWarnings.push(...healResult.warnings);
116
110
  }
117
111
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"stage0_preprocess.d.ts","sourceRoot":"","sources":["../../src/pipeline/stage0_preprocess.ts"],"names":[],"mappings":"AAKA,OAAO,EAGH,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAwB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AACjH,OAAO,EAAiB,KAAK,SAAS,EAAC,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAuB,KAAK,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AA0BD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAsF7F"}
1
+ {"version":3,"file":"stage0_preprocess.d.ts","sourceRoot":"","sources":["../../src/pipeline/stage0_preprocess.ts"],"names":[],"mappings":"AAKA,OAAO,EAIH,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAwB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAC,MAAM,6BAA6B,CAAC;AACjH,OAAO,EAAiB,KAAK,SAAS,EAAC,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAuB,KAAK,aAAa,EAAC,MAAM,gCAAgC,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AA0BD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAmF7F"}