@taodigital/agents 0.0.0-stage → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +99 -2
  2. package/dist/api-test-agent.d.ts +126 -0
  3. package/dist/api-test-agent.js +267 -0
  4. package/dist/artifact-intel-agent.d.ts +11 -0
  5. package/dist/artifact-intel-agent.js +152 -0
  6. package/dist/coverage-gap-agent.d.ts +25 -0
  7. package/dist/coverage-gap-agent.js +269 -0
  8. package/dist/coverage-map-agent.d.ts +15 -0
  9. package/dist/coverage-map-agent.js +255 -0
  10. package/dist/crawl-diff-agent.d.ts +27 -0
  11. package/dist/crawl-diff-agent.js +412 -0
  12. package/dist/data-agent.d.ts +101 -0
  13. package/dist/data-agent.js +2284 -0
  14. package/dist/document-agent.d.ts +10 -0
  15. package/dist/document-agent.js +32 -0
  16. package/dist/enhancement-agent.d.ts +48 -0
  17. package/dist/enhancement-agent.js +342 -0
  18. package/dist/field-detector-agent.d.ts +35 -0
  19. package/dist/field-detector-agent.js +220 -0
  20. package/dist/flow-agent.d.ts +130 -0
  21. package/dist/flow-agent.js +169 -0
  22. package/dist/github-agent.d.ts +18 -0
  23. package/dist/github-agent.js +39 -0
  24. package/dist/github-write-agent.d.ts +67 -0
  25. package/dist/github-write-agent.js +249 -0
  26. package/dist/grouping-agent.d.ts +18 -0
  27. package/dist/grouping-agent.js +338 -0
  28. package/dist/healing-agent.d.ts +73 -0
  29. package/dist/healing-agent.js +137 -0
  30. package/dist/index.d.ts +36 -0
  31. package/dist/index.js +67 -0
  32. package/dist/integration-test-agent.d.ts +118 -0
  33. package/dist/integration-test-agent.js +165 -0
  34. package/dist/label-assignment-agent.d.ts +15 -0
  35. package/dist/label-assignment-agent.js +97 -0
  36. package/dist/llm-config.d.ts +38 -0
  37. package/dist/llm-config.js +287 -0
  38. package/dist/llm-mock.d.ts +77 -0
  39. package/dist/llm-mock.js +192 -0
  40. package/dist/llm.d.ts +58 -0
  41. package/dist/llm.js +438 -0
  42. package/dist/nlp-agent.d.ts +208 -0
  43. package/dist/nlp-agent.js +340 -0
  44. package/dist/orchestrator.d.ts +285 -0
  45. package/dist/orchestrator.js +141 -0
  46. package/dist/plan-agent.d.ts +4 -0
  47. package/dist/plan-agent.js +200 -0
  48. package/dist/repo-scan-agent.d.ts +189 -0
  49. package/dist/repo-scan-agent.js +2032 -0
  50. package/dist/requirements-agent.d.ts +59 -0
  51. package/dist/requirements-agent.js +641 -0
  52. package/dist/scenario-agent.d.ts +20 -0
  53. package/dist/scenario-agent.js +123 -0
  54. package/dist/script-agent.d.ts +447 -0
  55. package/dist/script-agent.js +11813 -0
  56. package/dist/security-test-agent.d.ts +111 -0
  57. package/dist/security-test-agent.js +141 -0
  58. package/dist/skill-file-agent.d.ts +264 -0
  59. package/dist/skill-file-agent.js +3470 -0
  60. package/dist/step-coverage.d.ts +46 -0
  61. package/dist/step-coverage.js +288 -0
  62. package/dist/step-utils.d.ts +2 -0
  63. package/dist/step-utils.js +62 -0
  64. package/dist/test-case-agent.d.ts +206 -0
  65. package/dist/test-case-agent.js +2141 -0
  66. package/dist/test-import-agent.d.ts +148 -0
  67. package/dist/test-import-agent.js +3235 -0
  68. package/dist/test-script-agent.d.ts +16 -0
  69. package/dist/test-script-agent.js +48 -0
  70. package/dist/token-pricing.d.ts +14 -0
  71. package/dist/token-pricing.js +62 -0
  72. package/dist/types.d.ts +232 -0
  73. package/dist/types.js +7 -0
  74. package/dist/visual-agent.d.ts +114 -0
  75. package/dist/visual-agent.js +261 -0
  76. package/package.json +26 -4
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ /**
3
+ * ZeTa-AI NLP Test Input Agent
4
+ *
5
+ * Converts plain-English test descriptions into structured, grounded test cases.
6
+ * Unlike competitors (testRigor, Functionize) this grounds the output in the
7
+ * Zeta Core — only uses elements that ACTUALLY EXIST on the real screen.
8
+ *
9
+ * Input: "When a user logs in with valid credentials, they should see the dashboard"
10
+ * Output: A structured TestCase grounded in real elements, ready for review.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.convertNlpToTest = convertNlpToTest;
14
+ exports.extractLocatorsWithLLM = extractLocatorsWithLLM;
15
+ const app_brain_1 = require("@taodigital/app-brain");
16
+ const llm_js_1 = require("./llm.js");
17
+ const llm_config_js_1 = require("./llm-config.js");
18
+ const core_1 = require("@taodigital/core");
19
+ async function convertNlpToTest(opts) {
20
+ // Load all project screens to find the most relevant one.
21
+ const project = await app_brain_1.AppBrain.getProjectMap(opts.tenantId, opts.projectId);
22
+ if (!project)
23
+ throw new Error('Project not found.');
24
+ const ELEMENT_LIMIT = parseInt(process.env.NLP_ELEMENT_LIMIT ?? '10', 10);
25
+ const screenSummaries = project.screens.map((s) => ({
26
+ id: s.id,
27
+ name: s.name,
28
+ url: s.url,
29
+ elementCount: s.elements.length,
30
+ elements: s.elements.slice(0, ELEMENT_LIMIT).map((e) => e.meaning),
31
+ }));
32
+ // ZETA-041: load existing test titles from the suite so the AI can avoid
33
+ // generating duplicate or off-topic test items.
34
+ let existingTitles = [];
35
+ if (opts.suiteId) {
36
+ try {
37
+ const suite = await app_brain_1.AppBrain.getTestSuiteWithCases(opts.tenantId, opts.suiteId);
38
+ existingTitles = (suite?.testCases ?? []).map((tc) => String(tc.title ?? '')).filter(Boolean);
39
+ }
40
+ catch { /* non-fatal — proceed without existing titles */ }
41
+ }
42
+ const SYSTEM_DEFAULT = [
43
+ 'You are ZeTa-AI\'s Natural Language Test Converter.',
44
+ 'Convert a plain-English test description into a structured test case.',
45
+ 'ONLY use elements that exist in the provided Zeta Core context.',
46
+ 'Never invent buttons, fields, or screens that are not listed.',
47
+ 'The generated test case MUST be distinct from any existing test titles listed below.',
48
+ 'Return JSON: { targetScreenId, confidence, understoodIntent, unmatchedTerms, testCase: { title, kind, steps, expectedResult, groundedOn } }',
49
+ ].join(' ');
50
+ const system = await (0, core_1.getAgentPrompt)('nlp-locator-extractor', undefined, opts.tenantId) ?? SYSTEM_DEFAULT;
51
+ const user = [
52
+ `USER DESCRIPTION: "${opts.description}"`,
53
+ '',
54
+ 'AVAILABLE SCREENS IN Zeta Core:',
55
+ screenSummaries.map((s) => `Screen ID: ${s.id}\nName: ${s.name}\nURL: ${s.url ?? 'n/a'}\nSample elements: ${s.elements.join(', ')}`).join('\n\n'),
56
+ '',
57
+ existingTitles.length > 0
58
+ ? `EXISTING TEST CASES IN SUITE (do NOT generate a duplicate of any of these):\n${existingTitles.map((t) => `- ${t}`).join('\n')}`
59
+ : '',
60
+ '',
61
+ 'Instructions:',
62
+ '1. Identify which screen the user is describing',
63
+ '2. Map user terms to real element meanings from that screen',
64
+ '3. Build structured test steps using ONLY real element names',
65
+ '4. Note any terms you could not match to real elements',
66
+ '5. Set confidence 0-100 (lower if many terms unmatched)',
67
+ '6. Ensure the generated test title is meaningfully different from existing test titles',
68
+ ].filter((line) => line !== '').join('\n');
69
+ let parsed;
70
+ try {
71
+ const t0 = Date.now();
72
+ // GAP-14: NLP conversion needs full reasoning model — was using fast model
73
+ const llmCfg = await (0, llm_config_js_1.getTaskAgentLLMConfig)(opts.tenantId, 'testGeneration');
74
+ const seed = (0, llm_js_1.deterministicSeed)(`${opts.tenantId}:${opts.projectId}:nlp:${opts.description.slice(0, 500)}:${opts.suiteId ?? ''}`);
75
+ const { text: raw, usage } = await (0, llm_js_1.askTracked)(system, user, llmCfg, undefined, undefined, { temperature: 0, seed });
76
+ const latencyMs = Date.now() - t0;
77
+ const json = raw.slice(raw.indexOf('{'), raw.lastIndexOf('}') + 1);
78
+ parsed = JSON.parse(json);
79
+ (0, app_brain_1.recordLLMCall)({
80
+ tenantId: opts.tenantId,
81
+ projectId: opts.projectId,
82
+ agentType: 'nlp',
83
+ label: 'nlp_to_test',
84
+ entityType: 'test_case',
85
+ usage,
86
+ latencyMs,
87
+ }).catch(console.error);
88
+ }
89
+ catch {
90
+ return {
91
+ error: 'Could not parse test from description. Try being more specific about which screen and elements you want to test.',
92
+ description: opts.description,
93
+ };
94
+ }
95
+ // Validate that groundedOn elements actually exist on the target screen.
96
+ let grounded;
97
+ try {
98
+ const screen = await app_brain_1.AppBrain.getScreenWithElements(opts.tenantId, parsed.targetScreenId);
99
+ const allowed = new Set((screen?.elements ?? []).map((e) => e.meaning));
100
+ parsed.testCase.groundedOn = parsed.testCase.groundedOn.filter((e) => allowed.has(e));
101
+ grounded = parsed;
102
+ }
103
+ catch {
104
+ grounded = parsed;
105
+ }
106
+ // Save as PENDING_REVIEW (human must approve NLP-generated tests).
107
+ const suiteName = opts.suiteId ? undefined : `NLP: ${grounded.understoodIntent.slice(0, 60)}`;
108
+ const saved = opts.suiteId
109
+ ? await app_brain_1.AppBrain.addTestCaseToSuite(opts.tenantId, opts.projectId, opts.suiteId, {
110
+ ...grounded.testCase,
111
+ screenId: parsed.targetScreenId,
112
+ expectedResult: grounded.testCase.expectedResult,
113
+ })
114
+ : await app_brain_1.AppBrain.saveGeneratedTestCases(opts.tenantId, opts.projectId, suiteName, [{
115
+ ...grounded.testCase,
116
+ screenId: parsed.targetScreenId,
117
+ expectedResult: grounded.testCase.expectedResult,
118
+ }]);
119
+ const savedCase = opts.suiteId
120
+ ? saved
121
+ : saved?.testCases?.[0] ?? saved;
122
+ const resolvedRef = savedCase?.ref ?? `TC-NLP-001`;
123
+ const finalizedTestCase = {
124
+ ...grounded.testCase,
125
+ id: savedCase?.id,
126
+ ref: resolvedRef,
127
+ screenId: parsed.targetScreenId,
128
+ };
129
+ return {
130
+ id: savedCase?.id,
131
+ ref: resolvedRef,
132
+ title: grounded.testCase.title,
133
+ steps: grounded.testCase.steps,
134
+ expectedResult: grounded.testCase.expectedResult,
135
+ confidence: grounded.confidence,
136
+ understoodIntent: grounded.understoodIntent,
137
+ unmatchedTerms: grounded.unmatchedTerms,
138
+ testCase: finalizedTestCase,
139
+ saved,
140
+ warning: grounded.confidence < 70
141
+ ? 'Low confidence. Review carefully — some user-described elements may not have been matched correctly.'
142
+ : undefined,
143
+ };
144
+ }
145
+ /**
146
+ * Use an LLM to extract Playwright locators from test case steps of any format.
147
+ * Handles structured (QA Wolf), plain-English (AI-generated), or mixed step formats.
148
+ * Falls back to empty array if no LLM is configured or extraction fails.
149
+ */
150
+ async function extractLocatorsWithLLM(opts) {
151
+ const llmCfg = await (0, llm_config_js_1.getTaskAgentLLMConfig)(opts.tenantId, 'testGeneration');
152
+ if (!llmCfg)
153
+ return [];
154
+ const cleanStepForLLM = (s) => s.replace(/\{\{\s*fze\.env\.([^}\s]+)\s*\}\}/gi, (_, v) => `<${v}>`)
155
+ .replace(/\[protected\]/gi, '<credential>')
156
+ .replace(/\[captured:\s*([^\]]+)\]/gi, (_, v) => `<captured_${v}>`)
157
+ .replace(/\[random[^\]]*\]/gi, '<random_value>');
158
+ const stepLines = opts.steps
159
+ .map((s, i) => {
160
+ const action = typeof s === 'string'
161
+ ? s
162
+ : [s.action, s.text, s.description, s.label]
163
+ .filter((v) => typeof v === 'string' && String(v).trim().length > 0)
164
+ .join(' ')
165
+ .trim();
166
+ return `${i + 1}. ${cleanStepForLLM(action)}`;
167
+ })
168
+ .filter(l => l.trim().length > 2)
169
+ .slice(0, 80)
170
+ .join('\n');
171
+ if (!stepLines.trim())
172
+ return [];
173
+ // Deterministic pre-extraction for credential and critical action steps
174
+ const deterministicLocators = [];
175
+ const seenDetExpr = new Set();
176
+ const addDet = (key, expr, desc, confidence) => {
177
+ if (seenDetExpr.has(expr))
178
+ return;
179
+ seenDetExpr.add(expr);
180
+ deterministicLocators.push({ key, expression: expr, elementType: 'textbox', confidence, source: 'llm-step-analysis', description: desc });
181
+ };
182
+ for (const rawStep of opts.steps) {
183
+ const stepStr = typeof rawStep === 'string' ? rawStep : String(rawStep.action ?? '');
184
+ if (/enter\s+username\b/i.test(stepStr))
185
+ addDet('usernameInput', "page.getByLabel('Username')", 'Username field', 'high');
186
+ if (/enter\s+password\b/i.test(stepStr) || /\[protected\]/i.test(stepStr))
187
+ addDet('passwordInput', "page.getByLabel('Password')", 'Password field', 'high');
188
+ }
189
+ // Build value→fieldKey map for locator key alignment
190
+ const dataValueMap = new Map(); // exampleValue → fieldKey
191
+ for (const f of opts.dataFields ?? []) {
192
+ if (f.exampleValue && f.exampleValue.trim().length >= 3) {
193
+ dataValueMap.set(f.exampleValue.trim(), f.fieldKey);
194
+ }
195
+ }
196
+ const dataFieldsHint = dataValueMap.size > 0
197
+ ? `\nDATA FIELDS (use these fieldKey names when a locator's value matches the exampleValue):\n${Array.from(dataValueMap.entries()).map(([v, k]) => ` fieldKey="${k}" exampleValue="${v}"`).join('\n')}\nWhen an element's name/text exactly matches an exampleValue above, use the corresponding fieldKey as the locator key (append "Button", "Link", or "Text" as appropriate).`
198
+ : '';
199
+ const SYSTEM_DEFAULT = `You are a Playwright test automation expert. Your task: analyze test steps and extract Playwright locators for every UI element that is interacted with or verified.
200
+
201
+ Output ONLY a JSON array — no markdown, no explanation, no wrapper object. Each item:
202
+ {"key":"camelCaseName","expression":"page.getByRole(...)","elementType":"button|link|heading|textbox|combobox|checkbox|text","confidence":"high|medium|low","description":"short label"}
203
+
204
+ Playwright locator priority (use the first that applies):
205
+ 1. getByRole('button'|'link'|'heading'|'textbox'|'combobox'|'checkbox'|'radio', { name: '...' }) — for semantic elements
206
+ 2. getByLabel('...') — for form fields identified by label text
207
+ 3. getByPlaceholder('...') — for inputs with placeholder
208
+ 4. getByText('...') — for verifying visible text content
209
+ 5. getByTestId('...') — only if a data-testid attribute is explicitly mentioned
210
+
211
+ Rules:
212
+ - Deduplicate by expression
213
+ - key must be camelCase, max 40 chars
214
+ - Skip pure navigation steps (URL-only, no element)
215
+ - confidence: high=role+name match, medium=text match, low=ambiguous
216
+ - If a step is too vague to produce a locator, skip it
217
+ - SKIP steps that verify a plain numeric value (e.g. 'Verify text: "392"', 'Verify element text: "458"') — these are dynamic dashboard counts, not stable locators
218
+ - SKIP multi-line element strings containing "Remove Chip" — these are filter chip combinations, not reusable locators
219
+ - SKIP filter buttons whose name ends with \\n(count) like "Source: All\\n(68)" or "UP Type: All\\n(4)" — the count is dynamic; if the same button appears without the count suffix use that instead
220
+ - For steps like "Enter username: X" → generate getByLabel('Username') or getByRole('textbox', { name: 'Username' }) with key "usernameInput"
221
+ - For steps like "Enter password: [protected]" → generate getByLabel('Password') with key "passwordInput" — do NOT skip protected/masked credential steps
222
+ - For steps like "Click login button: X" → generate getByRole('button', { name: X }) with key "signInButton"
223
+ - For date range buttons (element name contains a date pattern like "02/01/2025 - 02/13/2025"), use key "dateRangeButton"
224
+ - Do NOT strip date separators (/ and -) to create numeric keys like "0201202502132025" — use semantic names instead${dataFieldsHint}`;
225
+ const locatorSystemBase = await (0, core_1.getAgentPrompt)('nlp-playwright-locator', undefined, opts.tenantId) ?? SYSTEM_DEFAULT;
226
+ // Append the dataFieldsHint (which is dynamic per-call) only when using the fallback; DB prompt already has the static rules.
227
+ const SYSTEM = locatorSystemBase.includes(dataFieldsHint) || !dataFieldsHint
228
+ ? locatorSystemBase
229
+ : `${locatorSystemBase}${dataFieldsHint}`;
230
+ // Build optional context block for scenario-derived TCs where steps may be vague BDD phrases.
231
+ // Gherkin text and title give the LLM enough signal to infer element names even when individual
232
+ // step lines say things like "the user completes the registration".
233
+ const ctx = opts.tcContext;
234
+ const contextParts = [];
235
+ if (ctx?.title?.trim())
236
+ contextParts.push(`Test case: ${ctx.title.trim()}`);
237
+ if (ctx?.preconditions?.trim())
238
+ contextParts.push(`Preconditions: ${ctx.preconditions.trim()}`);
239
+ if (ctx?.gherkin?.trim())
240
+ contextParts.push(`Full Gherkin scenario:\n${ctx.gherkin.trim().slice(0, 2000)}`);
241
+ const tcContextBlock = contextParts.length > 0
242
+ ? `\n\nADDITIONAL CONTEXT (use to infer element names when steps are vague):\n${contextParts.join('\n')}`
243
+ : '';
244
+ const USER = `Test steps:\n${stepLines}${tcContextBlock}\n\nReturn JSON array of Playwright locators:`;
245
+ try {
246
+ const seed = (0, llm_js_1.deterministicSeed)(`${opts.tenantId}:nlp-locators:${stepLines.slice(0, 500)}`);
247
+ const result = await (0, llm_js_1.askTracked)(SYSTEM, USER, llmCfg, 2048, undefined, { temperature: 0, seed });
248
+ const raw = result.text.trim();
249
+ const json = raw.startsWith('```') ? raw.replace(/^```[^\n]*\n?/, '').replace(/\n?```$/, '') : raw;
250
+ const parsed = JSON.parse(json);
251
+ if (!Array.isArray(parsed))
252
+ return [];
253
+ const seenKeys = new Set();
254
+ const seenExprs = new Set();
255
+ const locators = [];
256
+ for (const l of parsed) {
257
+ if (!l?.key || !l?.expression || !String(l.expression).startsWith('page.'))
258
+ continue;
259
+ const expr = String(l.expression);
260
+ // Filter 1: skip multi-line chip combo expressions
261
+ // Match both real newlines (LLM JSON parsed) and literal \n (safety)
262
+ const newlineCount = (expr.match(/\\n|\n/g) ?? []).length;
263
+ if (expr.includes('Remove Chip') || newlineCount >= 2)
264
+ continue;
265
+ // Filter 2: skip pure numeric descriptions or keys (dynamic dashboard counts like 392, 458)
266
+ const desc = String(l.description ?? '').trim();
267
+ if (/^\d+$/.test(desc))
268
+ continue;
269
+ if (/^\d+$/.test(String(l.key ?? '')))
270
+ continue;
271
+ // Filter 3: skip filter-chip buttons with dynamic count suffix like "Source: All\n(68)"
272
+ // LLM JSON "Source: All\n(68)" parses to a real newline, so match real newline char
273
+ if (/\n\(\d+\)/.test(expr))
274
+ continue;
275
+ // Filter 4: skip locators that use display artifact tokens — these are never real DOM text
276
+ if (/\[captured:\s*|'\[random|\[dynamic\]/i.test(expr))
277
+ continue;
278
+ // Deduplicate by expression
279
+ if (seenExprs.has(expr))
280
+ continue;
281
+ seenExprs.add(expr);
282
+ let key = String(l.key).replace(/[^a-zA-Z0-9]/g, '').slice(0, 40) || 'element';
283
+ // Fix 1: rename all-digit keys (date string munging like "0201202502132025")
284
+ if (/^\d{6,}$/.test(key)) {
285
+ if (/\d{1,2}\/\d{1,2}\/\d{4}/.test(expr)) {
286
+ key = 'dateRangeButton';
287
+ }
288
+ else {
289
+ key = `element${key.slice(0, 6)}`;
290
+ }
291
+ }
292
+ // Fix 2: align key with data field name when expression matches a field's example value
293
+ for (const [val, fieldKey] of dataValueMap) {
294
+ if (expr.includes(val)) {
295
+ const suffix = String(l.elementType ?? '').toLowerCase().includes('button') ? 'Button'
296
+ : String(l.elementType ?? '').toLowerCase().includes('link') ? 'Link'
297
+ : String(l.elementType ?? '').toLowerCase().includes('text') ? 'Text'
298
+ : 'Element';
299
+ key = (fieldKey + suffix).replace(/[^a-zA-Z0-9]/g, '').slice(0, 40);
300
+ break;
301
+ }
302
+ }
303
+ // Disambiguate duplicate keys
304
+ const baseKey = key;
305
+ let dedupeIdx = 2;
306
+ while (seenKeys.has(key)) {
307
+ key = `${baseKey}${dedupeIdx++}`;
308
+ }
309
+ seenKeys.add(key);
310
+ locators.push({
311
+ key,
312
+ expression: expr,
313
+ elementType: String(l.elementType ?? 'element'),
314
+ confidence: ['high', 'medium', 'low'].includes(l.confidence) ? l.confidence : 'medium',
315
+ source: 'llm-step-analysis',
316
+ description: desc.slice(0, 80),
317
+ });
318
+ }
319
+ // Merge deterministic locators first (they take priority on key), then LLM locators
320
+ const finalLocators = [...deterministicLocators];
321
+ const seenFinalExprs = new Set(deterministicLocators.map(l => l.expression));
322
+ const seenFinalKeys = new Set(deterministicLocators.map(l => l.key));
323
+ for (const loc of locators) {
324
+ if (seenFinalExprs.has(loc.expression))
325
+ continue;
326
+ // Disambiguate key
327
+ let k = loc.key;
328
+ let idx = 2;
329
+ while (seenFinalKeys.has(k))
330
+ k = `${loc.key}${idx++}`;
331
+ seenFinalKeys.add(k);
332
+ seenFinalExprs.add(loc.expression);
333
+ finalLocators.push({ ...loc, key: k });
334
+ }
335
+ return finalLocators;
336
+ }
337
+ catch {
338
+ return [];
339
+ }
340
+ }
@@ -0,0 +1,285 @@
1
+ export interface OrchestratorRequest {
2
+ tenantId: string;
3
+ projectId: string;
4
+ jobId?: string;
5
+ targetScreenId?: string;
6
+ targetFlowStartScreenId?: string;
7
+ targetFlowEndScreenId?: string;
8
+ viewport?: string;
9
+ suiteNameOverride?: string;
10
+ force?: boolean;
11
+ /**
12
+ * 'append' (default) — deduplicates by content hash + grounding hash + title Jaccard.
13
+ * 'replace' — deletes existing AI-generated cases, then inserts fresh.
14
+ * 'edit' — upserts matched cases (updates steps/gherkin/expectedResult, snapshots old version).
15
+ */
16
+ mode?: 'replace' | 'append' | 'edit';
17
+ /**
18
+ * When provided, only generates test cases of these kinds.
19
+ * Passed through to generateTestCases. Omit for all-kinds generation (default).
20
+ */
21
+ kinds?: readonly string[];
22
+ }
23
+ /**
24
+ * Dual-Context Orchestrator — routes test generation to the right agent.
25
+ *
26
+ * ROUTE 1: targetScreenId → Pure CAG → generateTestCases() (21-pass)
27
+ * ROUTE 2: targetFlowStart + targetFlowEnd → KAG BFS + CAG → generateFlowTestCases()
28
+ */
29
+ export declare class DualContextOrchestrator {
30
+ static execute(req: OrchestratorRequest): Promise<{
31
+ status: string;
32
+ cases: never[];
33
+ saved: null;
34
+ screenCount?: undefined;
35
+ suiteName?: undefined;
36
+ } | {
37
+ status: string;
38
+ cases: never[];
39
+ saved: null;
40
+ screenCount: number;
41
+ suiteName?: undefined;
42
+ } | {
43
+ status: string;
44
+ cases: import("./flow-agent.js").FlowTestCase[];
45
+ saved: {
46
+ suite: {
47
+ tenantId: string;
48
+ id: string;
49
+ projectId: string;
50
+ name: string;
51
+ createdAt: Date;
52
+ genModel: string | null;
53
+ category: import(".prisma/client").$Enums.SuiteCategory | null;
54
+ parentId: string | null;
55
+ ownerUserId: string | null;
56
+ genTotalTokens: number | null;
57
+ genTotalCostUsd: number | null;
58
+ genAt: Date | null;
59
+ genScreenCount: number | null;
60
+ coverageReport: import("@prisma/client/runtime/library").JsonValue | null;
61
+ };
62
+ testCases: {
63
+ tenantId: string;
64
+ screenId: string | null;
65
+ id: string;
66
+ projectId: string;
67
+ source: string;
68
+ status: string;
69
+ createdAt: Date;
70
+ updatedAt: Date;
71
+ steps: import("@prisma/client/runtime/library").JsonValue;
72
+ kind: string;
73
+ tags: import("@prisma/client/runtime/library").JsonValue;
74
+ approvedBy: string | null;
75
+ suiteId: string;
76
+ ref: string | null;
77
+ title: string;
78
+ priority: string;
79
+ preconditions: string | null;
80
+ expectedResult: string | null;
81
+ gherkin: string | null;
82
+ approvedAt: Date | null;
83
+ groundedOn: import("@prisma/client/runtime/library").JsonValue;
84
+ caseRefs: import("@prisma/client/runtime/library").JsonValue;
85
+ viewports: import("@prisma/client/runtime/library").JsonValue;
86
+ viewportConstraint: string;
87
+ techniqueOfRecord: string | null;
88
+ traceTo: string | null;
89
+ traceToExtra: import("@prisma/client/runtime/library").JsonValue;
90
+ testDataInline: import("@prisma/client/runtime/library").JsonValue | null;
91
+ dataNote: string | null;
92
+ scriptNote: string | null;
93
+ structuredSteps: import("@prisma/client/runtime/library").JsonValue | null;
94
+ artifactId: string | null;
95
+ scenarioId: string | null;
96
+ suspect: boolean;
97
+ suspectReason: string | null;
98
+ flakyScore: number;
99
+ consecutiveFlakes: number;
100
+ lastFlakyAt: Date | null;
101
+ quarantined: boolean;
102
+ quarantinedAt: Date | null;
103
+ quarantineReason: string | null;
104
+ genPromptTokens: number | null;
105
+ genCompletionTokens: number | null;
106
+ genCostUsd: number | null;
107
+ genModel: string | null;
108
+ dataSetId: string | null;
109
+ repoTargetPath: string | null;
110
+ stepsKey: string | null;
111
+ gherkinKey: string | null;
112
+ contentHash: string | null;
113
+ groundingHash: string | null;
114
+ locatorsJson: import("@prisma/client/runtime/library").JsonValue | null;
115
+ locatorsExtractedAt: Date | null;
116
+ captureStepScreenshots: import("@prisma/client/runtime/library").JsonValue | null;
117
+ }[];
118
+ };
119
+ screenCount: number;
120
+ suiteName: string;
121
+ } | {
122
+ skipped: boolean;
123
+ reason: string;
124
+ existingCaseCount: number;
125
+ status?: undefined;
126
+ raw?: undefined;
127
+ cases?: undefined;
128
+ groundedFacts?: undefined;
129
+ saved?: undefined;
130
+ passBreakdown?: undefined;
131
+ coverageReport?: undefined;
132
+ } | {
133
+ status: string;
134
+ raw: string;
135
+ cases: {
136
+ title: string;
137
+ ref: string;
138
+ viewportConstraint: "ALL" | "MOBILE_ONLY" | "TABLET_ONLY";
139
+ techniqueOfRecord: string | undefined;
140
+ traceTo: string | undefined;
141
+ testDataInline: Record<string, unknown> | undefined;
142
+ category: string[] | undefined;
143
+ dataNote: string | undefined;
144
+ scriptNote: string | undefined;
145
+ structuredSteps: {
146
+ action: string;
147
+ target?: string;
148
+ data?: string;
149
+ }[] | undefined;
150
+ kind: import("@taodigital/core").TestKind;
151
+ priority: "CRITICAL" | "HIGH" | "MEDIUM" | "LOW";
152
+ preconditions: string;
153
+ expectedResult: string;
154
+ steps: string[];
155
+ groundedOn: string[];
156
+ gherkin: string;
157
+ tags: string[];
158
+ }[];
159
+ groundedFacts: import("@taodigital/core").ElementFact[];
160
+ saved: {
161
+ suite: {
162
+ tenantId: string;
163
+ id: string;
164
+ projectId: string;
165
+ name: string;
166
+ createdAt: Date;
167
+ genModel: string | null;
168
+ category: import(".prisma/client").$Enums.SuiteCategory | null;
169
+ parentId: string | null;
170
+ ownerUserId: string | null;
171
+ genTotalTokens: number | null;
172
+ genTotalCostUsd: number | null;
173
+ genAt: Date | null;
174
+ genScreenCount: number | null;
175
+ coverageReport: import("@prisma/client/runtime/library").JsonValue | null;
176
+ };
177
+ testCases: {
178
+ tenantId: string;
179
+ screenId: string | null;
180
+ id: string;
181
+ projectId: string;
182
+ source: string;
183
+ status: string;
184
+ createdAt: Date;
185
+ updatedAt: Date;
186
+ steps: import("@prisma/client/runtime/library").JsonValue;
187
+ kind: string;
188
+ tags: import("@prisma/client/runtime/library").JsonValue;
189
+ approvedBy: string | null;
190
+ suiteId: string;
191
+ ref: string | null;
192
+ title: string;
193
+ priority: string;
194
+ preconditions: string | null;
195
+ expectedResult: string | null;
196
+ gherkin: string | null;
197
+ approvedAt: Date | null;
198
+ groundedOn: import("@prisma/client/runtime/library").JsonValue;
199
+ caseRefs: import("@prisma/client/runtime/library").JsonValue;
200
+ viewports: import("@prisma/client/runtime/library").JsonValue;
201
+ viewportConstraint: string;
202
+ techniqueOfRecord: string | null;
203
+ traceTo: string | null;
204
+ traceToExtra: import("@prisma/client/runtime/library").JsonValue;
205
+ testDataInline: import("@prisma/client/runtime/library").JsonValue | null;
206
+ dataNote: string | null;
207
+ scriptNote: string | null;
208
+ structuredSteps: import("@prisma/client/runtime/library").JsonValue | null;
209
+ artifactId: string | null;
210
+ scenarioId: string | null;
211
+ suspect: boolean;
212
+ suspectReason: string | null;
213
+ flakyScore: number;
214
+ consecutiveFlakes: number;
215
+ lastFlakyAt: Date | null;
216
+ quarantined: boolean;
217
+ quarantinedAt: Date | null;
218
+ quarantineReason: string | null;
219
+ genPromptTokens: number | null;
220
+ genCompletionTokens: number | null;
221
+ genCostUsd: number | null;
222
+ genModel: string | null;
223
+ dataSetId: string | null;
224
+ repoTargetPath: string | null;
225
+ stepsKey: string | null;
226
+ gherkinKey: string | null;
227
+ contentHash: string | null;
228
+ groundingHash: string | null;
229
+ locatorsJson: import("@prisma/client/runtime/library").JsonValue | null;
230
+ locatorsExtractedAt: Date | null;
231
+ captureStepScreenshots: import("@prisma/client/runtime/library").JsonValue | null;
232
+ }[];
233
+ };
234
+ passBreakdown: {
235
+ [k: string]: number;
236
+ };
237
+ coverageReport: {
238
+ byKind: Record<string, number>;
239
+ byPriority: Record<string, number>;
240
+ byTechnique: Record<string, number>;
241
+ targetsDiscovered: number;
242
+ targetsCovered: number;
243
+ gaps: string[];
244
+ passBreakdown: {
245
+ [k: string]: number;
246
+ };
247
+ };
248
+ skipped?: undefined;
249
+ reason?: undefined;
250
+ existingCaseCount?: undefined;
251
+ }>;
252
+ }
253
+ export interface SwarmRequest {
254
+ tenantId: string;
255
+ projectId: string;
256
+ jobId?: string;
257
+ /** Run all screens in parallel — swarm mode */
258
+ screenIds: string[];
259
+ /** Max concurrent agents. Default 4. */
260
+ concurrency?: number;
261
+ }
262
+ export interface SwarmResult {
263
+ succeeded: string[];
264
+ failed: {
265
+ screenId: string;
266
+ error: string;
267
+ }[];
268
+ totalGenerated: number;
269
+ }
270
+ /**
271
+ * Swarm Orchestrator — runs multiple test-generation agents in parallel.
272
+ *
273
+ * Each agent reads shared skill files before generating (so they all share
274
+ * the same application knowledge). After all agents complete, COVERAGE_MATRIX
275
+ * skill file is updated with aggregated counts.
276
+ *
277
+ * Skill files act as the shared memory bus — any knowledge learned from
278
+ * artifacts (via learnFromArtifact) or from crawls (via skill-file-agent)
279
+ * is automatically available to every agent in the swarm.
280
+ */
281
+ export declare class SwarmOrchestrator {
282
+ static execute(req: SwarmRequest): Promise<SwarmResult>;
283
+ /** Run swarm over ALL crawled screens in a project */
284
+ static executeForProject(req: Omit<SwarmRequest, 'screenIds'>): Promise<SwarmResult>;
285
+ }