@taodigital/agents 0.0.0-stage → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -2
- package/dist/api-test-agent.d.ts +126 -0
- package/dist/api-test-agent.js +267 -0
- package/dist/artifact-intel-agent.d.ts +11 -0
- package/dist/artifact-intel-agent.js +152 -0
- package/dist/coverage-gap-agent.d.ts +25 -0
- package/dist/coverage-gap-agent.js +269 -0
- package/dist/coverage-map-agent.d.ts +15 -0
- package/dist/coverage-map-agent.js +255 -0
- package/dist/crawl-diff-agent.d.ts +27 -0
- package/dist/crawl-diff-agent.js +412 -0
- package/dist/data-agent.d.ts +101 -0
- package/dist/data-agent.js +2284 -0
- package/dist/document-agent.d.ts +10 -0
- package/dist/document-agent.js +32 -0
- package/dist/enhancement-agent.d.ts +48 -0
- package/dist/enhancement-agent.js +342 -0
- package/dist/field-detector-agent.d.ts +35 -0
- package/dist/field-detector-agent.js +220 -0
- package/dist/flow-agent.d.ts +130 -0
- package/dist/flow-agent.js +169 -0
- package/dist/github-agent.d.ts +18 -0
- package/dist/github-agent.js +39 -0
- package/dist/github-write-agent.d.ts +67 -0
- package/dist/github-write-agent.js +249 -0
- package/dist/grouping-agent.d.ts +18 -0
- package/dist/grouping-agent.js +338 -0
- package/dist/healing-agent.d.ts +73 -0
- package/dist/healing-agent.js +137 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +67 -0
- package/dist/integration-test-agent.d.ts +118 -0
- package/dist/integration-test-agent.js +165 -0
- package/dist/label-assignment-agent.d.ts +15 -0
- package/dist/label-assignment-agent.js +97 -0
- package/dist/llm-config.d.ts +38 -0
- package/dist/llm-config.js +287 -0
- package/dist/llm-mock.d.ts +77 -0
- package/dist/llm-mock.js +192 -0
- package/dist/llm.d.ts +58 -0
- package/dist/llm.js +438 -0
- package/dist/nlp-agent.d.ts +208 -0
- package/dist/nlp-agent.js +340 -0
- package/dist/orchestrator.d.ts +285 -0
- package/dist/orchestrator.js +141 -0
- package/dist/plan-agent.d.ts +4 -0
- package/dist/plan-agent.js +200 -0
- package/dist/repo-scan-agent.d.ts +189 -0
- package/dist/repo-scan-agent.js +2032 -0
- package/dist/requirements-agent.d.ts +59 -0
- package/dist/requirements-agent.js +641 -0
- package/dist/scenario-agent.d.ts +20 -0
- package/dist/scenario-agent.js +123 -0
- package/dist/script-agent.d.ts +447 -0
- package/dist/script-agent.js +11813 -0
- package/dist/security-test-agent.d.ts +111 -0
- package/dist/security-test-agent.js +141 -0
- package/dist/skill-file-agent.d.ts +264 -0
- package/dist/skill-file-agent.js +3470 -0
- package/dist/step-coverage.d.ts +46 -0
- package/dist/step-coverage.js +288 -0
- package/dist/step-utils.d.ts +2 -0
- package/dist/step-utils.js +62 -0
- package/dist/test-case-agent.d.ts +206 -0
- package/dist/test-case-agent.js +2141 -0
- package/dist/test-import-agent.d.ts +148 -0
- package/dist/test-import-agent.js +3235 -0
- package/dist/test-script-agent.d.ts +16 -0
- package/dist/test-script-agent.js +48 -0
- package/dist/token-pricing.d.ts +14 -0
- package/dist/token-pricing.js +62 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.js +7 -0
- package/dist/visual-agent.d.ts +114 -0
- package/dist/visual-agent.js +261 -0
- package/package.json +26 -4
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type LLMConfig } from './llm.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses content from Jira/Confluence/Markdown using heuristics
|
|
4
|
+
* and extracts testing constraints, user stories, and acceptance criteria.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseDocumentGrounding(content: string, sourceType: 'JIRA' | 'CONFLUENCE' | 'MARKDOWN', llmCfg?: LLMConfig): Promise<{
|
|
7
|
+
acceptanceCriteria: string[];
|
|
8
|
+
businessRules: string[];
|
|
9
|
+
outOfScope: string[];
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseDocumentGrounding = parseDocumentGrounding;
|
|
4
|
+
const core_1 = require("@taodigital/core");
|
|
5
|
+
const llm_js_1 = require("./llm.js");
|
|
6
|
+
/**
|
|
7
|
+
* Parses content from Jira/Confluence/Markdown using heuristics
|
|
8
|
+
* and extracts testing constraints, user stories, and acceptance criteria.
|
|
9
|
+
*/
|
|
10
|
+
async function parseDocumentGrounding(content, sourceType, llmCfg) {
|
|
11
|
+
const SYSTEM_DEFAULT = [
|
|
12
|
+
'You are a QA Requirements Analyst.',
|
|
13
|
+
'Extract structured testing intelligence from the provided document.',
|
|
14
|
+
'Identify:',
|
|
15
|
+
'1. Acceptance Criteria (explicit requirements for test cases)',
|
|
16
|
+
'2. Business Rules (implicit constraints, e.g., "users under 18 cannot register")',
|
|
17
|
+
'3. Out of Scope (things explicitly excluded from testing)',
|
|
18
|
+
'Return valid JSON: { "acceptanceCriteria": string[], "businessRules": string[], "outOfScope": string[] }'
|
|
19
|
+
].join('\n');
|
|
20
|
+
const system = await (0, core_1.getAgentPrompt)('document-grounding') ?? SYSTEM_DEFAULT;
|
|
21
|
+
const user = `Source Type: ${sourceType}\n\nDocument Content:\n${content.slice(0, 15000)}`;
|
|
22
|
+
try {
|
|
23
|
+
const seed = (0, llm_js_1.deterministicSeed)(`doc-grounding:${sourceType}:${content.slice(0, 2000)}`);
|
|
24
|
+
const { text: raw } = await (0, llm_js_1.askTracked)(system, user, llmCfg, undefined, undefined, { temperature: 0, seed });
|
|
25
|
+
const cleaned = raw.replace(/^```[\w-]*\n?/m, '').replace(/\n?```$/m, '').trim();
|
|
26
|
+
return JSON.parse(cleaned);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.error('[document-agent] Failed to parse document grounding', err);
|
|
30
|
+
return { acceptanceCriteria: [], businessRules: [], outOfScope: [] };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZeTa-AI Context Enhancement Agent
|
|
3
|
+
*
|
|
4
|
+
* Extracts UI elements from screenshots and Figma components, diffs them
|
|
5
|
+
* against existing test cases, and proposes/applies enrichment changes.
|
|
6
|
+
*
|
|
7
|
+
* Five public entry points:
|
|
8
|
+
* analyzeScreenshotForEnhancement — vision LLM → ExtractedElementInput[]
|
|
9
|
+
* diffScreenshotElementsAgainstTestCases — diff → ProposedChange[]
|
|
10
|
+
* analyzeFigmaComponentsForEnhancement — Figma diff → ProposedChange[]
|
|
11
|
+
* applyEnhancement — write approved changes to DB
|
|
12
|
+
* updateProjectContextSources — recompute contextScore / contextPhase
|
|
13
|
+
*/
|
|
14
|
+
export interface ExtractedElementInput {
|
|
15
|
+
name?: string;
|
|
16
|
+
role?: string;
|
|
17
|
+
selector: string;
|
|
18
|
+
confidence: number;
|
|
19
|
+
boundingBox?: {
|
|
20
|
+
x: number;
|
|
21
|
+
y: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
extractedFrom: 'SCREENSHOT' | 'FIGMA_COMPONENT' | 'DOM';
|
|
26
|
+
}
|
|
27
|
+
export interface ProposedChange {
|
|
28
|
+
testCaseId: string;
|
|
29
|
+
changeType: 'ADD_LOCATOR' | 'UPDATE_LOCATOR' | 'ADD_STEP' | 'MODIFY_STEP' | 'ADD_SCENARIO' | 'ADD_TEST_DATA';
|
|
30
|
+
before: string;
|
|
31
|
+
after: string;
|
|
32
|
+
confidence: number;
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ContextEnhancementPayload {
|
|
36
|
+
proposedChanges: ProposedChange[];
|
|
37
|
+
extractedElements: ExtractedElementInput[];
|
|
38
|
+
summary: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function analyzeScreenshotForEnhancement(tenantId: string, projectId: string, artifactId: string, imageBase64: string): Promise<{
|
|
41
|
+
elements: ExtractedElementInput[];
|
|
42
|
+
testDataValues: Record<string, string>;
|
|
43
|
+
navigationClues: string[];
|
|
44
|
+
}>;
|
|
45
|
+
export declare function diffScreenshotElementsAgainstTestCases(tenantId: string, projectId: string, extractedElements: ExtractedElementInput[], testCases: any[]): Promise<ContextEnhancementPayload>;
|
|
46
|
+
export declare function analyzeFigmaComponentsForEnhancement(tenantId: string, projectId: string, figmaComponents: any[], testCases: any[]): Promise<ContextEnhancementPayload>;
|
|
47
|
+
export declare function applyEnhancement(tenantId: string, enhancementId: string, approvedChangeIndexes?: number[]): Promise<void>;
|
|
48
|
+
export declare function updateProjectContextSources(tenantId: string, projectId: string, sourceType?: 'SCREENSHOT' | 'FIGMA' | 'CRAWL' | 'API_SPEC', payload?: Record<string, unknown>): Promise<void>;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ZeTa-AI Context Enhancement Agent
|
|
4
|
+
*
|
|
5
|
+
* Extracts UI elements from screenshots and Figma components, diffs them
|
|
6
|
+
* against existing test cases, and proposes/applies enrichment changes.
|
|
7
|
+
*
|
|
8
|
+
* Five public entry points:
|
|
9
|
+
* analyzeScreenshotForEnhancement — vision LLM → ExtractedElementInput[]
|
|
10
|
+
* diffScreenshotElementsAgainstTestCases — diff → ProposedChange[]
|
|
11
|
+
* analyzeFigmaComponentsForEnhancement — Figma diff → ProposedChange[]
|
|
12
|
+
* applyEnhancement — write approved changes to DB
|
|
13
|
+
* updateProjectContextSources — recompute contextScore / contextPhase
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.analyzeScreenshotForEnhancement = analyzeScreenshotForEnhancement;
|
|
17
|
+
exports.diffScreenshotElementsAgainstTestCases = diffScreenshotElementsAgainstTestCases;
|
|
18
|
+
exports.analyzeFigmaComponentsForEnhancement = analyzeFigmaComponentsForEnhancement;
|
|
19
|
+
exports.applyEnhancement = applyEnhancement;
|
|
20
|
+
exports.updateProjectContextSources = updateProjectContextSources;
|
|
21
|
+
const database_1 = require("@taodigital/database");
|
|
22
|
+
const core_1 = require("@taodigital/core");
|
|
23
|
+
const llm_js_1 = require("./llm.js");
|
|
24
|
+
// ─── Phase ordering (used by updateProjectContextSources) ─────────────────────
|
|
25
|
+
const PHASE_ORDER = [
|
|
26
|
+
'PENDING_CONTEXT',
|
|
27
|
+
'CONTEXT_PARTIAL',
|
|
28
|
+
'CONTEXT_ENRICHED',
|
|
29
|
+
'DIFF_PENDING_APPROVAL',
|
|
30
|
+
'ALL_DIFFS_APPROVED',
|
|
31
|
+
'READY_FOR_SCRIPT_GEN',
|
|
32
|
+
];
|
|
33
|
+
// ─── 1. analyzeScreenshotForEnhancement ──────────────────────────────────────
|
|
34
|
+
async function analyzeScreenshotForEnhancement(tenantId, projectId, artifactId, imageBase64) {
|
|
35
|
+
const empty = { elements: [], testDataValues: {}, navigationClues: [] };
|
|
36
|
+
if (!imageBase64)
|
|
37
|
+
return empty;
|
|
38
|
+
const promptText = await (0, core_1.getAgentPrompt)('screenshot-enhance-extract').catch(() => 'Extract all interactive UI elements from this screenshot as JSON: { elements: [{name, role, selector, confidence, boundingBox, extractedFrom: "SCREENSHOT"}], testDataValues: {}, navigationClues: [] }');
|
|
39
|
+
let rawText;
|
|
40
|
+
try {
|
|
41
|
+
const result = await (0, llm_js_1.askTrackedWithImages)(promptText ?? 'Extract all interactive UI elements from this screenshot as JSON: { elements: [], testDataValues: {}, navigationClues: [] }', 'Analyze this screenshot and return the JSON.', [{ base64: imageBase64, mediaType: 'image/png' }]);
|
|
42
|
+
rawText = result.text;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return empty;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const parsed = JSON.parse(rawText);
|
|
49
|
+
return {
|
|
50
|
+
elements: (parsed.elements ?? []),
|
|
51
|
+
testDataValues: (parsed.testDataValues ?? {}),
|
|
52
|
+
navigationClues: (parsed.navigationClues ?? []),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return empty;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ─── 2. diffScreenshotElementsAgainstTestCases ────────────────────────────────
|
|
60
|
+
async function diffScreenshotElementsAgainstTestCases(tenantId, projectId, extractedElements, testCases) {
|
|
61
|
+
const proposedChanges = [];
|
|
62
|
+
for (const element of extractedElements) {
|
|
63
|
+
if (element.confidence <= 0.6)
|
|
64
|
+
continue;
|
|
65
|
+
const elementLabel = element.name ?? element.role ?? '';
|
|
66
|
+
// Find a test case whose steps mention this element
|
|
67
|
+
const matchedTestCase = testCases.find((tc) => {
|
|
68
|
+
const steps = Array.isArray(tc.steps) ? tc.steps : [];
|
|
69
|
+
return steps.some((s) => {
|
|
70
|
+
const text = typeof s === 'string' ? s : (s?.text ?? s?.description ?? '');
|
|
71
|
+
return ((elementLabel && text.toLowerCase().includes(elementLabel.toLowerCase())) ||
|
|
72
|
+
(element.role && text.toLowerCase().includes(element.role.toLowerCase())));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
if (matchedTestCase) {
|
|
76
|
+
const locatorsJson = (matchedTestCase.locatorsJson ?? {});
|
|
77
|
+
const alreadyPresent = Object.values(locatorsJson).some((v) => {
|
|
78
|
+
if (typeof v === 'string')
|
|
79
|
+
return v === element.selector;
|
|
80
|
+
if (typeof v === 'object' && v !== null) {
|
|
81
|
+
return v.selector === element.selector;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
});
|
|
85
|
+
if (!alreadyPresent) {
|
|
86
|
+
proposedChanges.push({
|
|
87
|
+
testCaseId: matchedTestCase.id,
|
|
88
|
+
changeType: 'ADD_LOCATOR',
|
|
89
|
+
before: '',
|
|
90
|
+
after: JSON.stringify({ [element.selector]: element }),
|
|
91
|
+
confidence: element.confidence,
|
|
92
|
+
description: `Add locator for element "${elementLabel}" (${element.selector}) extracted from screenshot`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (element.confidence > 0.8 && testCases.length > 0) {
|
|
97
|
+
// High-confidence element not referenced anywhere — propose a new scenario
|
|
98
|
+
const anchorTestCase = testCases[0];
|
|
99
|
+
proposedChanges.push({
|
|
100
|
+
testCaseId: anchorTestCase.id,
|
|
101
|
+
changeType: 'ADD_SCENARIO',
|
|
102
|
+
before: '',
|
|
103
|
+
after: JSON.stringify({
|
|
104
|
+
title: `Verify interaction with "${elementLabel}" element`,
|
|
105
|
+
locator: element.selector,
|
|
106
|
+
extractedFrom: element.extractedFrom,
|
|
107
|
+
}),
|
|
108
|
+
confidence: element.confidence,
|
|
109
|
+
description: `No existing test references "${elementLabel}" (confidence ${element.confidence.toFixed(2)}) — propose new scenario`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
proposedChanges,
|
|
115
|
+
extractedElements,
|
|
116
|
+
summary: `Found ${extractedElements.length} elements, ${proposedChanges.length} proposed changes`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// ─── 3. analyzeFigmaComponentsForEnhancement ─────────────────────────────────
|
|
120
|
+
async function analyzeFigmaComponentsForEnhancement(tenantId, projectId, figmaComponents, testCases) {
|
|
121
|
+
const proposedChanges = [];
|
|
122
|
+
for (const component of figmaComponents) {
|
|
123
|
+
const componentName = component.name ?? '';
|
|
124
|
+
if (component.suggestedLocator) {
|
|
125
|
+
// Look for test cases whose steps mention this component
|
|
126
|
+
const matchedTestCase = testCases.find((tc) => {
|
|
127
|
+
const steps = Array.isArray(tc.steps) ? tc.steps : [];
|
|
128
|
+
return steps.some((s) => {
|
|
129
|
+
const text = typeof s === 'string' ? s : (s?.text ?? s?.description ?? '');
|
|
130
|
+
return componentName && text.toLowerCase().includes(componentName.toLowerCase());
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
if (matchedTestCase) {
|
|
134
|
+
const locatorsJson = (matchedTestCase.locatorsJson ?? {});
|
|
135
|
+
const existing = locatorsJson[componentName];
|
|
136
|
+
const existingStr = existing !== undefined ? (typeof existing === 'string' ? existing : JSON.stringify(existing)) : null;
|
|
137
|
+
const suggestedStr = typeof component.suggestedLocator === 'string'
|
|
138
|
+
? component.suggestedLocator
|
|
139
|
+
: JSON.stringify(component.suggestedLocator);
|
|
140
|
+
if (existingStr !== suggestedStr) {
|
|
141
|
+
proposedChanges.push({
|
|
142
|
+
testCaseId: matchedTestCase.id,
|
|
143
|
+
changeType: 'UPDATE_LOCATOR',
|
|
144
|
+
before: existingStr ?? '',
|
|
145
|
+
after: suggestedStr,
|
|
146
|
+
confidence: component.confidence ?? 0.9,
|
|
147
|
+
description: `Update locator for Figma component "${componentName}"`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (component.suggestedTestId) {
|
|
153
|
+
const testIdPattern = `getByTestId(${component.suggestedTestId})`;
|
|
154
|
+
const alreadyReferenced = testCases.some((tc) => {
|
|
155
|
+
const steps = Array.isArray(tc.steps) ? tc.steps : [];
|
|
156
|
+
return steps.some((s) => {
|
|
157
|
+
const text = typeof s === 'string' ? s : (s?.text ?? s?.description ?? '');
|
|
158
|
+
return text.includes(testIdPattern);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
if (!alreadyReferenced) {
|
|
162
|
+
const anchorTestCase = testCases.find((tc) => {
|
|
163
|
+
const steps = Array.isArray(tc.steps) ? tc.steps : [];
|
|
164
|
+
return steps.some((s) => {
|
|
165
|
+
const text = typeof s === 'string' ? s : (s?.text ?? s?.description ?? '');
|
|
166
|
+
return componentName && text.toLowerCase().includes(componentName.toLowerCase());
|
|
167
|
+
});
|
|
168
|
+
}) ?? testCases[0];
|
|
169
|
+
if (anchorTestCase) {
|
|
170
|
+
proposedChanges.push({
|
|
171
|
+
testCaseId: anchorTestCase.id,
|
|
172
|
+
changeType: 'ADD_LOCATOR',
|
|
173
|
+
before: '',
|
|
174
|
+
after: JSON.stringify({
|
|
175
|
+
[`[data-testid="${component.suggestedTestId}"]`]: {
|
|
176
|
+
selector: `[data-testid="${component.suggestedTestId}"]`,
|
|
177
|
+
name: componentName,
|
|
178
|
+
extractedFrom: 'FIGMA_COMPONENT',
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
confidence: component.confidence ?? 0.85,
|
|
182
|
+
description: `Add data-testid locator for Figma component "${componentName}"`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
proposedChanges,
|
|
190
|
+
extractedElements: [],
|
|
191
|
+
summary: `Analyzed ${figmaComponents.length} Figma components, ${proposedChanges.length} proposed changes`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// ─── 4. applyEnhancement ─────────────────────────────────────────────────────
|
|
195
|
+
async function applyEnhancement(tenantId, enhancementId, approvedChangeIndexes) {
|
|
196
|
+
const record = await database_1.prismaAdmin.contextEnhancement.findUniqueOrThrow({
|
|
197
|
+
where: { id: enhancementId },
|
|
198
|
+
});
|
|
199
|
+
const changes = record.proposedChanges ?? [];
|
|
200
|
+
const toApply = approvedChangeIndexes !== undefined
|
|
201
|
+
? changes.filter((_, i) => approvedChangeIndexes.includes(i))
|
|
202
|
+
: changes;
|
|
203
|
+
for (const change of toApply) {
|
|
204
|
+
switch (change.changeType) {
|
|
205
|
+
case 'ADD_LOCATOR':
|
|
206
|
+
case 'UPDATE_LOCATOR': {
|
|
207
|
+
const testCase = await database_1.prismaAdmin.testCase.findUniqueOrThrow({
|
|
208
|
+
where: { id: change.testCaseId },
|
|
209
|
+
});
|
|
210
|
+
const locatorsJson = { ...testCase.locatorsJson };
|
|
211
|
+
const incoming = JSON.parse(change.after);
|
|
212
|
+
Object.assign(locatorsJson, incoming);
|
|
213
|
+
await database_1.prismaAdmin.testCase.update({
|
|
214
|
+
where: { id: change.testCaseId },
|
|
215
|
+
data: { locatorsJson: locatorsJson },
|
|
216
|
+
});
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case 'ADD_STEP': {
|
|
220
|
+
const testCase = await database_1.prismaAdmin.testCase.findUniqueOrThrow({
|
|
221
|
+
where: { id: change.testCaseId },
|
|
222
|
+
});
|
|
223
|
+
const steps = Array.isArray(testCase.steps) ? [...testCase.steps] : [];
|
|
224
|
+
steps.push(JSON.parse(change.after));
|
|
225
|
+
await database_1.prismaAdmin.testCase.update({
|
|
226
|
+
where: { id: change.testCaseId },
|
|
227
|
+
data: { steps },
|
|
228
|
+
});
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'MODIFY_STEP': {
|
|
232
|
+
const testCase = await database_1.prismaAdmin.testCase.findUniqueOrThrow({
|
|
233
|
+
where: { id: change.testCaseId },
|
|
234
|
+
});
|
|
235
|
+
const steps = Array.isArray(testCase.steps) ? [...testCase.steps] : [];
|
|
236
|
+
const beforeStep = JSON.parse(change.before);
|
|
237
|
+
const afterStep = JSON.parse(change.after);
|
|
238
|
+
const idx = steps.findIndex((s) => JSON.stringify(s) === JSON.stringify(beforeStep));
|
|
239
|
+
if (idx !== -1)
|
|
240
|
+
steps[idx] = afterStep;
|
|
241
|
+
await database_1.prismaAdmin.testCase.update({
|
|
242
|
+
where: { id: change.testCaseId },
|
|
243
|
+
data: { steps },
|
|
244
|
+
});
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
case 'ADD_SCENARIO': {
|
|
248
|
+
const afterPayload = JSON.parse(change.after);
|
|
249
|
+
await database_1.prismaAdmin.testCase.create({
|
|
250
|
+
data: {
|
|
251
|
+
tenantId: record.tenantId,
|
|
252
|
+
projectId: record.projectId,
|
|
253
|
+
title: afterPayload.title ?? 'Untitled scenario',
|
|
254
|
+
status: 'PENDING_REVIEW',
|
|
255
|
+
kind: 'POSITIVE',
|
|
256
|
+
steps: [],
|
|
257
|
+
groundedOn: [],
|
|
258
|
+
suite: { connect: [] },
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
case 'ADD_TEST_DATA': {
|
|
264
|
+
const testCase = await database_1.prismaAdmin.testCase.findUniqueOrThrow({
|
|
265
|
+
where: { id: change.testCaseId },
|
|
266
|
+
});
|
|
267
|
+
const existing = testCase.testDataInline ?? {};
|
|
268
|
+
const incoming = JSON.parse(change.after);
|
|
269
|
+
await database_1.prismaAdmin.testCase.update({
|
|
270
|
+
where: { id: change.testCaseId },
|
|
271
|
+
data: { testDataInline: { ...existing, ...incoming } },
|
|
272
|
+
});
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
await database_1.prismaAdmin.contextEnhancement.update({
|
|
278
|
+
where: { id: enhancementId },
|
|
279
|
+
data: { status: 'APPLIED', appliedAt: new Date() },
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// ─── 5. updateProjectContextSources ──────────────────────────────────────────
|
|
283
|
+
const CONTEXT_THRESHOLDS = {
|
|
284
|
+
WEB: 80,
|
|
285
|
+
API: 40,
|
|
286
|
+
NATIVE: 60,
|
|
287
|
+
HYBRID: 70,
|
|
288
|
+
};
|
|
289
|
+
async function updateProjectContextSources(tenantId, projectId, sourceType, payload) {
|
|
290
|
+
await database_1.prismaAdmin.$transaction(async (tx) => {
|
|
291
|
+
const project = await tx.project.findUniqueOrThrow({
|
|
292
|
+
where: { id: projectId, tenantId },
|
|
293
|
+
select: {
|
|
294
|
+
contextSources: true,
|
|
295
|
+
contextScore: true,
|
|
296
|
+
contextPhase: true,
|
|
297
|
+
contextSourceRequirements: true,
|
|
298
|
+
projectKind: true,
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
const src = { ...(project.contextSources ?? {}) };
|
|
302
|
+
if (sourceType && payload) {
|
|
303
|
+
const key = sourceType.toLowerCase();
|
|
304
|
+
src[key] = { ...(src[key] ?? {}), ...payload };
|
|
305
|
+
}
|
|
306
|
+
// Recompute score
|
|
307
|
+
let score = 0;
|
|
308
|
+
if (src.screenshot?.approvedAt)
|
|
309
|
+
score += 20;
|
|
310
|
+
else if ((src.screenshot?.count ?? 0) > 0)
|
|
311
|
+
score += 5;
|
|
312
|
+
if (src.figma?.approvedAt)
|
|
313
|
+
score += 25;
|
|
314
|
+
else if (src.figma?.importedAt)
|
|
315
|
+
score += 8;
|
|
316
|
+
if (src.crawl?.approvedAt)
|
|
317
|
+
score += 40;
|
|
318
|
+
else if ((src.crawl?.screenCount ?? 0) > 0)
|
|
319
|
+
score += 12;
|
|
320
|
+
if (src.api_spec?.approvedAt)
|
|
321
|
+
score += 15;
|
|
322
|
+
else if (src.api_spec?.uploadedAt)
|
|
323
|
+
score += 5;
|
|
324
|
+
// Recompute phase — never regress a more advanced phase
|
|
325
|
+
const threshold = CONTEXT_THRESHOLDS[project.projectKind ?? 'WEB'] ?? 80;
|
|
326
|
+
void threshold; // used by caller (recomputeApprovalGateSummary) after this
|
|
327
|
+
let phase = 'PENDING_CONTEXT';
|
|
328
|
+
if (score > 0 && score < 30)
|
|
329
|
+
phase = 'CONTEXT_PARTIAL';
|
|
330
|
+
else if (score >= 30)
|
|
331
|
+
phase = 'CONTEXT_ENRICHED';
|
|
332
|
+
// Phase transitions to DIFF_PENDING_APPROVAL / ALL_DIFFS_APPROVED / READY_FOR_SCRIPT_GEN
|
|
333
|
+
// are driven by recomputeApprovalGateSummary in crawl-diff-agent.ts — don't regress here
|
|
334
|
+
const currentIdx = PHASE_ORDER.indexOf((project.contextPhase ?? 'PENDING_CONTEXT'));
|
|
335
|
+
const newIdx = PHASE_ORDER.indexOf(phase);
|
|
336
|
+
const finalPhase = newIdx > currentIdx ? phase : (project.contextPhase ?? 'PENDING_CONTEXT');
|
|
337
|
+
await tx.project.update({
|
|
338
|
+
where: { id: projectId, tenantId },
|
|
339
|
+
data: { contextSources: src, contextScore: score, contextPhase: finalPhase },
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZeTa-AI Field Detector Agent
|
|
3
|
+
*
|
|
4
|
+
* Drafts an AppField catalog from one of three inputs:
|
|
5
|
+
* (1) Rendered HTML — DOM detector reads form controls
|
|
6
|
+
* (2) OpenAPI/Swagger JSON/YAML — schema detector reads request bodies
|
|
7
|
+
* (3) Sample JSON payload — shape detector infers types from values
|
|
8
|
+
*
|
|
9
|
+
* Output is always FieldSchema[] in "unconfirmed" status.
|
|
10
|
+
* Persist via AppBrain.upsertAppField() after user review.
|
|
11
|
+
*/
|
|
12
|
+
import type { FieldSchema } from '@taodigital/core';
|
|
13
|
+
export declare function detectFieldsFromHtml(opts: {
|
|
14
|
+
tenantId: string;
|
|
15
|
+
projectId: string;
|
|
16
|
+
html: string;
|
|
17
|
+
}): Promise<FieldSchema[]>;
|
|
18
|
+
export declare function detectFieldsFromApi(opts: {
|
|
19
|
+
tenantId: string;
|
|
20
|
+
projectId: string;
|
|
21
|
+
input: string;
|
|
22
|
+
}): Promise<FieldSchema[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Merge auto-detected fields into the project catalog.
|
|
25
|
+
* Preserves human-edited piiClass, notes, and status on existing rows.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mergeIntoCatalog(opts: {
|
|
28
|
+
tenantId: string;
|
|
29
|
+
projectId: string;
|
|
30
|
+
detected: FieldSchema[];
|
|
31
|
+
}): Promise<{
|
|
32
|
+
added: number;
|
|
33
|
+
updated: number;
|
|
34
|
+
unchanged: number;
|
|
35
|
+
}>;
|