@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,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SwarmOrchestrator = exports.DualContextOrchestrator = void 0;
|
|
4
|
+
const app_brain_1 = require("@taodigital/app-brain");
|
|
5
|
+
const test_case_agent_js_1 = require("./test-case-agent.js");
|
|
6
|
+
const llm_config_js_1 = require("./llm-config.js");
|
|
7
|
+
const core_1 = require("@taodigital/core");
|
|
8
|
+
const flow_agent_js_1 = require("./flow-agent.js");
|
|
9
|
+
/**
|
|
10
|
+
* Dual-Context Orchestrator — routes test generation to the right agent.
|
|
11
|
+
*
|
|
12
|
+
* ROUTE 1: targetScreenId → Pure CAG → generateTestCases() (21-pass)
|
|
13
|
+
* ROUTE 2: targetFlowStart + targetFlowEnd → KAG BFS + CAG → generateFlowTestCases()
|
|
14
|
+
*/
|
|
15
|
+
class DualContextOrchestrator {
|
|
16
|
+
static async execute(req) {
|
|
17
|
+
if (req.targetScreenId) {
|
|
18
|
+
console.log(`[Orchestrator] CAG — screen: ${req.targetScreenId}`);
|
|
19
|
+
const [tenantCfg, fastCfg, dbGlobalRules] = await Promise.all([
|
|
20
|
+
(0, llm_config_js_1.getTaskAgentLLMConfig)(req.tenantId, 'testGeneration'),
|
|
21
|
+
(0, llm_config_js_1.getFastPassLLMConfig)(req.tenantId),
|
|
22
|
+
(0, core_1.getAgentPrompt)('test-case-global-rules'),
|
|
23
|
+
]);
|
|
24
|
+
const globalRules = dbGlobalRules ?? test_case_agent_js_1.GLOBAL_RULES_FALLBACK;
|
|
25
|
+
return (0, llm_config_js_1.tryWithPlatformFallback)(tenantCfg, (cfg) => (0, test_case_agent_js_1.generateTestCases)({
|
|
26
|
+
tenantId: req.tenantId,
|
|
27
|
+
projectId: req.projectId,
|
|
28
|
+
screenId: req.targetScreenId,
|
|
29
|
+
jobId: req.jobId,
|
|
30
|
+
viewport: req.viewport,
|
|
31
|
+
suiteNameOverride: req.suiteNameOverride,
|
|
32
|
+
force: req.force,
|
|
33
|
+
mode: req.mode,
|
|
34
|
+
kinds: req.kinds,
|
|
35
|
+
llmCfg: cfg,
|
|
36
|
+
fastPassLlmCfg: fastCfg,
|
|
37
|
+
globalRules,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
if (req.targetFlowStartScreenId && req.targetFlowEndScreenId) {
|
|
41
|
+
console.log(`[Orchestrator] KAG+CAG — flow: ${req.targetFlowStartScreenId} → ${req.targetFlowEndScreenId}`);
|
|
42
|
+
const path = await app_brain_1.AppBrain.getScreenTransitionGraph(req.tenantId, req.projectId, req.targetFlowStartScreenId, req.targetFlowEndScreenId);
|
|
43
|
+
if (!path || path.length === 0) {
|
|
44
|
+
console.warn(`[Orchestrator] No KAG path found — falling back to CAG for start screen.`);
|
|
45
|
+
const [tenantCfg, fastCfg] = await Promise.all([
|
|
46
|
+
(0, llm_config_js_1.getTaskAgentLLMConfig)(req.tenantId, 'testGeneration'),
|
|
47
|
+
(0, llm_config_js_1.getFastPassLLMConfig)(req.tenantId),
|
|
48
|
+
]);
|
|
49
|
+
return (0, llm_config_js_1.tryWithPlatformFallback)(tenantCfg, (cfg) => (0, test_case_agent_js_1.generateTestCases)({
|
|
50
|
+
tenantId: req.tenantId,
|
|
51
|
+
projectId: req.projectId,
|
|
52
|
+
screenId: req.targetFlowStartScreenId,
|
|
53
|
+
viewport: req.viewport,
|
|
54
|
+
force: req.force,
|
|
55
|
+
mode: req.mode,
|
|
56
|
+
kinds: req.kinds,
|
|
57
|
+
llmCfg: cfg,
|
|
58
|
+
fastPassLlmCfg: fastCfg,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
console.log(`[Orchestrator] KAG path: ${path.length} hops`);
|
|
62
|
+
return (0, flow_agent_js_1.generateFlowTestCases)({
|
|
63
|
+
tenantId: req.tenantId,
|
|
64
|
+
projectId: req.projectId,
|
|
65
|
+
startScreenId: req.targetFlowStartScreenId,
|
|
66
|
+
endScreenId: req.targetFlowEndScreenId,
|
|
67
|
+
path,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
throw new Error('[Orchestrator] Must provide targetScreenId OR (targetFlowStartScreenId AND targetFlowEndScreenId).');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.DualContextOrchestrator = DualContextOrchestrator;
|
|
74
|
+
/**
|
|
75
|
+
* Swarm Orchestrator — runs multiple test-generation agents in parallel.
|
|
76
|
+
*
|
|
77
|
+
* Each agent reads shared skill files before generating (so they all share
|
|
78
|
+
* the same application knowledge). After all agents complete, COVERAGE_MATRIX
|
|
79
|
+
* skill file is updated with aggregated counts.
|
|
80
|
+
*
|
|
81
|
+
* Skill files act as the shared memory bus — any knowledge learned from
|
|
82
|
+
* artifacts (via learnFromArtifact) or from crawls (via skill-file-agent)
|
|
83
|
+
* is automatically available to every agent in the swarm.
|
|
84
|
+
*/
|
|
85
|
+
class SwarmOrchestrator {
|
|
86
|
+
static async execute(req) {
|
|
87
|
+
const { tenantId, projectId, screenIds, concurrency = parseInt(process.env.SWARM_CONCURRENCY_DEFAULT ?? '') || 8 } = req;
|
|
88
|
+
const result = { succeeded: [], failed: [], totalGenerated: 0 };
|
|
89
|
+
console.log(`[Swarm] Starting ${screenIds.length} agents (concurrency=${concurrency})`);
|
|
90
|
+
const tokenBudgetPerScreen = parseInt(process.env.AGENT_TOKEN_BUDGET_PER_SCREEN ?? '') || 50_000;
|
|
91
|
+
await (0, app_brain_1.enforceTokenBudget)(tenantId, screenIds.length * tokenBudgetPerScreen);
|
|
92
|
+
const [swarmLlmCfg, swarmFastCfg, dbGlobalRules] = await Promise.all([
|
|
93
|
+
(0, llm_config_js_1.getTaskAgentLLMConfig)(tenantId, 'testGeneration'),
|
|
94
|
+
(0, llm_config_js_1.getFastPassLLMConfig)(tenantId),
|
|
95
|
+
(0, core_1.getAgentPrompt)('test-case-global-rules'),
|
|
96
|
+
]);
|
|
97
|
+
// Pre-fetch once — all agents in this swarm share the same prompt version.
|
|
98
|
+
const globalRules = dbGlobalRules ?? test_case_agent_js_1.GLOBAL_RULES_FALLBACK;
|
|
99
|
+
// Process in batches to respect concurrency limit
|
|
100
|
+
for (let i = 0; i < screenIds.length; i += concurrency) {
|
|
101
|
+
const batch = screenIds.slice(i, i + concurrency);
|
|
102
|
+
console.log(`[Swarm] Batch ${Math.floor(i / concurrency) + 1}: screens ${batch.join(', ')}`);
|
|
103
|
+
const outcomes = await Promise.allSettled(batch.map((screenId) => (0, test_case_agent_js_1.generateTestCases)({
|
|
104
|
+
tenantId,
|
|
105
|
+
projectId,
|
|
106
|
+
screenId,
|
|
107
|
+
jobId: req.jobId,
|
|
108
|
+
llmCfg: swarmLlmCfg,
|
|
109
|
+
fastPassLlmCfg: swarmFastCfg,
|
|
110
|
+
globalRules,
|
|
111
|
+
})));
|
|
112
|
+
for (let j = 0; j < batch.length; j++) {
|
|
113
|
+
const screenId = batch[j];
|
|
114
|
+
const outcome = outcomes[j];
|
|
115
|
+
if (outcome.status === 'fulfilled') {
|
|
116
|
+
const generated = Array.isArray(outcome.value) ? outcome.value.length : 0;
|
|
117
|
+
result.succeeded.push(screenId);
|
|
118
|
+
result.totalGenerated += generated;
|
|
119
|
+
console.log(`[Swarm] ✓ Screen ${screenId}: ${generated} test cases`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
result.failed.push({ screenId, error: String(outcome.reason?.message ?? outcome.reason) });
|
|
123
|
+
console.error(`[Swarm] ✗ Screen ${screenId}:`, outcome.reason?.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log(`[Swarm] Done — ${result.succeeded.length} succeeded, ${result.failed.length} failed, ${result.totalGenerated} total TCs`);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
/** Run swarm over ALL crawled screens in a project */
|
|
131
|
+
static async executeForProject(req) {
|
|
132
|
+
const graph = await app_brain_1.AppBrain.getCoverageGraph(req.tenantId, req.projectId);
|
|
133
|
+
const screenIds = (graph?.nodes ?? []).map((s) => s.id);
|
|
134
|
+
if (screenIds.length === 0) {
|
|
135
|
+
console.warn('[Swarm] No screens found — run a crawl first.');
|
|
136
|
+
return { succeeded: [], failed: [], totalGenerated: 0 };
|
|
137
|
+
}
|
|
138
|
+
return SwarmOrchestrator.execute({ ...req, screenIds });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.SwarmOrchestrator = SwarmOrchestrator;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTestPlan = generateTestPlan;
|
|
4
|
+
const app_brain_1 = require("@taodigital/app-brain");
|
|
5
|
+
const core_1 = require("@taodigital/core");
|
|
6
|
+
const llm_js_1 = require("./llm.js");
|
|
7
|
+
const llm_config_js_1 = require("./llm-config.js");
|
|
8
|
+
const requirements_agent_js_1 = require("./requirements-agent.js");
|
|
9
|
+
/**
|
|
10
|
+
* The Plan Agent — ISO/IEC/IEEE 29119-3 Edition.
|
|
11
|
+
*
|
|
12
|
+
* Generates an ISO/IEC/IEEE 29119-3 structured test plan in Markdown format.
|
|
13
|
+
* Key upgrades over the original IEEE-829 implementation:
|
|
14
|
+
*
|
|
15
|
+
* 1. Per-section RAG — each of the 6 plan sections retrieves its own top-k
|
|
16
|
+
* knowledge chunks rather than a single fixed query (higher relevance per section).
|
|
17
|
+
* 2. Requirements-driven — loads project requirements and coverage gap analysis
|
|
18
|
+
* so §3 has a Traceability column and §7 leads with unbuilt requirements.
|
|
19
|
+
* 3. Standard corrected — ISO/IEC/IEEE 29119-3:2021.
|
|
20
|
+
* 4. Cache invalidation awareness — stale plan hint surface in comments.
|
|
21
|
+
* 5. Honest reranker comment — LLM-based rerank, not a cross-encoder model.
|
|
22
|
+
*/
|
|
23
|
+
// ─── Per-section RAG query map ───────────────────────────────────────────────
|
|
24
|
+
// Each section of the plan retrieves its own top-k skill-file chunks.
|
|
25
|
+
// This raises relevance far more than a single fixed query ever could.
|
|
26
|
+
const SECTION_QUERIES = {
|
|
27
|
+
scope: 'business domain user roles core journeys scope functional nonfunctional',
|
|
28
|
+
features: 'navigation map screens flows interaction patterns user journeys',
|
|
29
|
+
approach: 'tech stack framework api contracts locator strategy test automation tools',
|
|
30
|
+
criteria: 'coverage matrix error catalogue data model constraints pass fail threshold',
|
|
31
|
+
environment: 'tech stack browsers auth session test data strategy environments',
|
|
32
|
+
risk: 'error catalogue auth session security api contracts business rules risks',
|
|
33
|
+
suspension: 'blocker defect threshold build environment failure conditions resume criteria',
|
|
34
|
+
};
|
|
35
|
+
async function generateTestPlan(opts) {
|
|
36
|
+
const { tenantId, projectId } = opts;
|
|
37
|
+
// ── Parallel data fetch — project map + coverage + requirements + per-section RAG ──
|
|
38
|
+
const sectionEntries = Object.entries(SECTION_QUERIES);
|
|
39
|
+
const [projectMap, coverage, requirements, gapReport, ...sectionResults] = await Promise.all([
|
|
40
|
+
app_brain_1.AppBrain.getProjectMap(tenantId, projectId),
|
|
41
|
+
app_brain_1.AppBrain.getCoverageReport(tenantId, projectId),
|
|
42
|
+
app_brain_1.AppBrain.listRequirements(tenantId, projectId).catch(() => []),
|
|
43
|
+
(0, requirements_agent_js_1.generateCoverageGapReport)({ tenantId, projectId }).catch(() => null),
|
|
44
|
+
...sectionEntries.map(([, query]) => app_brain_1.AppBrain.searchKnowledge(tenantId, projectId, query, 4, 1200).catch(() => [])),
|
|
45
|
+
]);
|
|
46
|
+
if (!projectMap) {
|
|
47
|
+
throw new Error(`Project ${projectId} not found for tenant ${tenantId}.`);
|
|
48
|
+
}
|
|
49
|
+
// Build per-section knowledge map
|
|
50
|
+
const knowledgeBySec = {};
|
|
51
|
+
sectionEntries.forEach(([section], idx) => {
|
|
52
|
+
const chunks = sectionResults[idx];
|
|
53
|
+
knowledgeBySec[section] = chunks.length > 0
|
|
54
|
+
? chunks.map(k => `#### ${k.label}\n${k.content}`).join('\n\n')
|
|
55
|
+
: '(no relevant skill files yet)';
|
|
56
|
+
});
|
|
57
|
+
const projectName = projectMap.name;
|
|
58
|
+
const now = new Date();
|
|
59
|
+
const yyyymm = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
60
|
+
const planId = `TP-${yyyymm}-${projectName.replace(/\s+/g, '-').toUpperCase()}-v1.0`;
|
|
61
|
+
// Screen names only — never element data (causes 258k+ token overflow in prod)
|
|
62
|
+
const allScreens = projectMap.screens ?? [];
|
|
63
|
+
const screensSummary = allScreens.map((s) => `- ${s.name}`).join('\n') || '(none crawled yet)';
|
|
64
|
+
const flowsSummary = (projectMap.flows ?? []).map((f) => `- ${f.name}`).join('\n') || '(none mapped yet)';
|
|
65
|
+
// Requirements summary (compact: reqId, title, priority, status only to avoid overflow)
|
|
66
|
+
const reqList = requirements.slice(0, 40);
|
|
67
|
+
const requirementsSummary = reqList.length > 0
|
|
68
|
+
? reqList.map((r) => ` ${r.reqId} [${r.priority}/${r.status}] ${r.title}`).join('\n')
|
|
69
|
+
: '(no requirements extracted yet — upload a PRD to enable traceability)';
|
|
70
|
+
// Coverage gap data for §7
|
|
71
|
+
const untestedReqs = coverage.untestedRequirements ?? gapReport?.untested?.map((id) => ({ reqId: id })) ?? [];
|
|
72
|
+
const unbuiltReqs = coverage.unbuiltRequirements ?? gapReport?.unbuilt?.map((id) => ({ reqId: id })) ?? [];
|
|
73
|
+
const topGaps = gapReport?.topGaps ?? coverage.gaps ?? [];
|
|
74
|
+
const SYSTEM_DEFAULT = [
|
|
75
|
+
'You are a senior QA architect writing a formal ISO/IEC/IEEE 29119-3 test plan in Markdown.',
|
|
76
|
+
'Reference ISO/IEC/IEEE 29119-3:2021 only.',
|
|
77
|
+
'The plan must be professional, concise, lightweight, and tailorable — avoid ceremony.',
|
|
78
|
+
'Use only the data provided. Do not invent screens, requirements, or metrics.',
|
|
79
|
+
'Every section must be present. Tables must be valid Markdown.',
|
|
80
|
+
'Deterministic language only — no hedging words (possibly, may, might, could, should probably).',
|
|
81
|
+
].join(' ');
|
|
82
|
+
const system = await (0, core_1.getAgentPrompt)('test-plan', undefined, tenantId) ?? SYSTEM_DEFAULT;
|
|
83
|
+
const user = [
|
|
84
|
+
`Generate a full ISO/IEC/IEEE 29119-3 test plan for the project "${projectName}".`,
|
|
85
|
+
`Plan ID: ${planId}`,
|
|
86
|
+
`Standard: ISO/IEC/IEEE 29119-3:2021`,
|
|
87
|
+
'',
|
|
88
|
+
'## Project Screens',
|
|
89
|
+
`Total: ${allScreens.length}`,
|
|
90
|
+
screensSummary,
|
|
91
|
+
'',
|
|
92
|
+
'## Flows',
|
|
93
|
+
flowsSummary,
|
|
94
|
+
'',
|
|
95
|
+
'## Coverage Stats',
|
|
96
|
+
`- Screen coverage: ${coverage.coveragePercent}% (${coverage.screensWithTests}/${coverage.totalScreens} screens)`,
|
|
97
|
+
`- Total test cases: ${coverage.totalTestCases} (${coverage.approvedTestCases} approved, ${coverage.pendingReviewCases} pending)`,
|
|
98
|
+
`- Status: ${coverage.status}`,
|
|
99
|
+
...((coverage.requirementsTotal ?? 0) > 0 ? [
|
|
100
|
+
`- Requirements coverage: ${coverage.requirementCoveragePercent}% (${coverage.requirementsCovered}/${coverage.requirementsTotal} requirements)`,
|
|
101
|
+
`- Risk-weighted coverage: ${coverage.riskWeightedCoverage}%`,
|
|
102
|
+
] : []),
|
|
103
|
+
'',
|
|
104
|
+
'## Requirements (for traceability in §3 and risk assessment in §7)',
|
|
105
|
+
requirementsSummary,
|
|
106
|
+
...(untestedReqs.length > 0 ? [
|
|
107
|
+
'',
|
|
108
|
+
`UNTESTED REQUIREMENTS (${untestedReqs.length}) — include in §3 as coverage debt:`,
|
|
109
|
+
untestedReqs.slice(0, 10).map((r) => ` ${r.reqId} ${r.title ?? ''} [${r.priority ?? ''}]`).join('\n'),
|
|
110
|
+
] : []),
|
|
111
|
+
...(unbuiltReqs.length > 0 ? [
|
|
112
|
+
'',
|
|
113
|
+
`UNBUILT REQUIREMENTS (${unbuiltReqs.length}) — lead §7 Risk Areas with these:`,
|
|
114
|
+
unbuiltReqs.slice(0, 10).map((r) => ` ${r.reqId} ${r.title ?? ''} [${r.priority ?? ''}]`).join('\n'),
|
|
115
|
+
] : []),
|
|
116
|
+
...(topGaps.length > 0 ? [
|
|
117
|
+
'',
|
|
118
|
+
'TOP COVERAGE GAPS (priority order — use in §7):',
|
|
119
|
+
topGaps.slice(0, 5).map((g) => ` - ${g}`).join('\n'),
|
|
120
|
+
] : []),
|
|
121
|
+
'',
|
|
122
|
+
'## Section Knowledge (per-section retrieved context)',
|
|
123
|
+
`### For §1 Scope:\n${knowledgeBySec.scope}`,
|
|
124
|
+
'',
|
|
125
|
+
`### For §3 Features:\n${knowledgeBySec.features}`,
|
|
126
|
+
'',
|
|
127
|
+
`### For §4 Approach:\n${knowledgeBySec.approach}`,
|
|
128
|
+
'',
|
|
129
|
+
`### For §5 Criteria:\n${knowledgeBySec.criteria}`,
|
|
130
|
+
'',
|
|
131
|
+
`### For §6 Environment:\n${knowledgeBySec.environment}`,
|
|
132
|
+
'',
|
|
133
|
+
`### For §7 Risk:\n${knowledgeBySec.risk}`,
|
|
134
|
+
'',
|
|
135
|
+
`### For §8 Suspension Criteria:\n${knowledgeBySec.suspension}`,
|
|
136
|
+
'',
|
|
137
|
+
'Return ONLY the Markdown test plan using this exact structure:',
|
|
138
|
+
'',
|
|
139
|
+
`# Test Plan · ${planId}`,
|
|
140
|
+
`*Standard: ISO/IEC/IEEE 29119-3*`,
|
|
141
|
+
'',
|
|
142
|
+
'## 1. Scope',
|
|
143
|
+
'(what is in scope and out of scope — ground in business domain and crawled screens)',
|
|
144
|
+
'',
|
|
145
|
+
'## 2. Test Items',
|
|
146
|
+
'(what software artifacts are being tested)',
|
|
147
|
+
'',
|
|
148
|
+
'## 3. Features to Test',
|
|
149
|
+
'| Feature | Screens | Approach | Priority | Requirements (traceTo) |',
|
|
150
|
+
'|---|---|---|---|---|',
|
|
151
|
+
'(one row per feature/flow; Requirements column = reqIds from the requirements list that this feature covers)',
|
|
152
|
+
...(untestedReqs.length > 0 ? [
|
|
153
|
+
'',
|
|
154
|
+
'### Coverage Debt',
|
|
155
|
+
'(list untestedRequirements here as rows with no test case — these are explicit coverage gaps)',
|
|
156
|
+
] : []),
|
|
157
|
+
'',
|
|
158
|
+
'## 4. Approach',
|
|
159
|
+
'(testing strategy: types, tools, automation vs manual split, techniques — ground in tech stack knowledge)',
|
|
160
|
+
'',
|
|
161
|
+
'## 5. Entry / Exit & Pass / Fail Criteria',
|
|
162
|
+
'(entry conditions, exit conditions, pass/fail thresholds — ground in coverage stats above)',
|
|
163
|
+
'',
|
|
164
|
+
'## 6. Environment & Test Data Strategy',
|
|
165
|
+
'(browsers, devices, CI/CD pipeline, test data approach — ground in tech stack and auth session knowledge)',
|
|
166
|
+
'',
|
|
167
|
+
'## 7. Risk Areas',
|
|
168
|
+
...(unbuiltReqs.length > 0 ? [
|
|
169
|
+
'(LEAD with unbuiltRequirements — "Promised but not found in crawl" risks — THEN technical risks)',
|
|
170
|
+
] : [
|
|
171
|
+
'(identified risks and mitigations — lead with CRITICAL/HIGH requirement gaps if any)',
|
|
172
|
+
]),
|
|
173
|
+
'',
|
|
174
|
+
'## 8. Suspension Criteria and Resumption Requirements',
|
|
175
|
+
'(conditions under which testing is suspended — e.g. >30% CRITICAL failures in a single run, build environment unavailable, blocker defect density exceeds threshold — and the criteria required to resume)',
|
|
176
|
+
'',
|
|
177
|
+
'## 9. Schedule',
|
|
178
|
+
'(milestones and timeline)',
|
|
179
|
+
'',
|
|
180
|
+
'## 10. Approvals',
|
|
181
|
+
'(sign-off table with roles)',
|
|
182
|
+
]
|
|
183
|
+
.filter(line => line !== undefined)
|
|
184
|
+
.join('\n');
|
|
185
|
+
const llmCfg = await (0, llm_config_js_1.getTaskAgentLLMConfig)(tenantId, 'testPlan');
|
|
186
|
+
if (!llmCfg) {
|
|
187
|
+
throw new Error('LLM provider not configured. Add an AI provider in Workspace → Settings → AI Provider.');
|
|
188
|
+
}
|
|
189
|
+
// LLM-based rerank (not a cross-encoder model — uses the same LLM context for semantic scoring)
|
|
190
|
+
const reqIds = requirements.map((r) => r.reqId ?? r.id ?? '').filter(Boolean);
|
|
191
|
+
const casesKey = [...reqIds].sort().join(',').slice(0, 500);
|
|
192
|
+
const planSeed = (0, llm_js_1.deterministicSeed)(`${tenantId}:${projectId}:test-plan:${casesKey}`);
|
|
193
|
+
const planMaxTokens = parseInt(process.env.PLAN_MAX_TOKENS ?? '') || 16000;
|
|
194
|
+
let { text: markdown } = await (0, llm_js_1.askTracked)(system, user, llmCfg, planMaxTokens, undefined, { temperature: 0, seed: planSeed });
|
|
195
|
+
// Strip ```markdown ... ``` fences if present
|
|
196
|
+
markdown = markdown.trim()
|
|
197
|
+
.replace(/^```(?:markdown|md)?\s*\n/, '')
|
|
198
|
+
.replace(/\n```\s*$/, '');
|
|
199
|
+
return markdown.trim();
|
|
200
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub repo scanning agent for Framework Modernization.
|
|
3
|
+
*
|
|
4
|
+
* Handles: GitHub API calls (raw fetch), framework detection from config file paths,
|
|
5
|
+
* test ID extraction from test file content, and style extraction from sample files.
|
|
6
|
+
*/
|
|
7
|
+
export interface DetectedFramework {
|
|
8
|
+
framework: 'playwright-ts' | 'playwright-js' | 'playwright-py' | 'playwright-java' | 'cypress-ts' | 'cypress-js' | 'pytest' | 'junit' | 'selenium-java' | 'selenium-py' | 'jest-ts' | 'jest-js' | 'cucumber' | 'gherkin' | 'webdriverio' | 'unknown';
|
|
9
|
+
language: 'typescript' | 'javascript' | 'python' | 'java';
|
|
10
|
+
testGlob: string;
|
|
11
|
+
configPath: string;
|
|
12
|
+
confidence: 'HIGH' | 'MEDIUM' | 'LOW';
|
|
13
|
+
}
|
|
14
|
+
export interface ExtractedTest {
|
|
15
|
+
name: string;
|
|
16
|
+
normalizedName: string;
|
|
17
|
+
filePath: string;
|
|
18
|
+
lineNumber: number;
|
|
19
|
+
describeBlock?: string;
|
|
20
|
+
framework: string;
|
|
21
|
+
dynamic: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface StyleProfile {
|
|
24
|
+
framework: string;
|
|
25
|
+
language: string;
|
|
26
|
+
importLines: string[];
|
|
27
|
+
describePattern: string;
|
|
28
|
+
beforeEachPattern: string;
|
|
29
|
+
pageObjectPattern: string;
|
|
30
|
+
conventions: string;
|
|
31
|
+
folderPattern: string;
|
|
32
|
+
namingPattern: string;
|
|
33
|
+
/** Package/module root derived by LLM from repo paths. For Java: dot-separated (e.g. "tests", "com.crm.qa"). For TS/JS: directory. For Python: module. Overrides io.zeta default. */
|
|
34
|
+
javaBasePackage?: string;
|
|
35
|
+
/** Java source root directory derived by LLM (e.g. "src/test/java"). Used by assembler to place files correctly without hardcoding. */
|
|
36
|
+
javaRoot?: string;
|
|
37
|
+
/**
|
|
38
|
+
* How the repo organises its test code — detected by LLM from actual paths and content:
|
|
39
|
+
* - "flat": single test class per feature, no page object separation
|
|
40
|
+
* - "two-tier": page objects in one location + test classes in another (separate src/main vs src/test)
|
|
41
|
+
* - "zeta-v2": already uses Zeta v2 6-layer structure (locators/pages/actions/assertions/testdata/smoke)
|
|
42
|
+
* - "custom": multi-layer but different names — see testLayers
|
|
43
|
+
*/
|
|
44
|
+
testStructurePattern?: 'flat' | 'two-tier' | 'zeta-v2' | 'custom';
|
|
45
|
+
/** Layer subdirectory names found in the repo (e.g. ["pages","tests"] or ["locators","pages","actions","assertions","testdata"]). Empty for flat. */
|
|
46
|
+
testLayers?: string[];
|
|
47
|
+
/** Where page/component objects live, relative to repo root (e.g. "src/main/java/pages"). */
|
|
48
|
+
pageObjectLocation?: string;
|
|
49
|
+
/** Where test runner classes live, relative to repo root (e.g. "src/test/java/tests"). */
|
|
50
|
+
testLocation?: string;
|
|
51
|
+
/** Repo's base class for page objects (e.g. "Page", "BasePage"). */
|
|
52
|
+
basePageClass?: string;
|
|
53
|
+
/** Repo's driver/browser factory class (e.g. "Browser", "DriverFactory"). */
|
|
54
|
+
driverClass?: string;
|
|
55
|
+
/** Detected directory for test data / data provider files (e.g. "testdata/", "test-data/", "data/"). Null = repo has none. */
|
|
56
|
+
testDataDir?: string | null;
|
|
57
|
+
/** Detected directory for fixture files (e.g. "fixtures/", "tests/fixtures/", "cypress/fixtures/"). Null = repo has none. */
|
|
58
|
+
fixtureDir?: string | null;
|
|
59
|
+
/** Detected directory for locator/selector files (e.g. "locators/", "src/locators/", "selectors/"). Undefined = repo has none. */
|
|
60
|
+
locatorsDir?: string;
|
|
61
|
+
/** testDir extracted from playwright.config.ts / cypress.config.ts — highest-priority ground truth for test file location. */
|
|
62
|
+
configTestDir?: string;
|
|
63
|
+
/** fixturesFolder from cypress.config.ts. */
|
|
64
|
+
configFixturesFolder?: string;
|
|
65
|
+
/** TypeScript path aliases from tsconfig.json compilerOptions.paths (e.g. {"@pages/*": ["src/pages/*"]}). */
|
|
66
|
+
tsconfigPaths?: Record<string, string[]>;
|
|
67
|
+
/** baseUrl from tsconfig.json compilerOptions (e.g. "."). */
|
|
68
|
+
tsconfigBaseUrl?: string;
|
|
69
|
+
/** Distinct test subdirectories when repo splits tests by type (e.g. ["tests/ui", "tests/api"]). */
|
|
70
|
+
testSubDirs?: string[];
|
|
71
|
+
/** Component object directory when separate from pageObjectLocation (e.g. "src/components"). */
|
|
72
|
+
componentDir?: string;
|
|
73
|
+
/** True when fixtureDir points to a TypeScript fixture module (Playwright test.extend), not static data. */
|
|
74
|
+
isTestFixtureModule?: boolean;
|
|
75
|
+
/** Detected directory for utility/helper files (e.g. "utils/", "helpers/", "shared/"). Null = repo has none. */
|
|
76
|
+
utilsDir?: string | null;
|
|
77
|
+
/** Detected config directory (e.g. "config/", "src/config/"). Null = repo has none. */
|
|
78
|
+
configDir?: string | null;
|
|
79
|
+
/** File naming stem for page objects (e.g. "{PascalCase}Page.ts", "{camelCase}.page.ts"). */
|
|
80
|
+
pageFileStem?: string;
|
|
81
|
+
/** File naming stem for locator files (e.g. "{camelCase}Locators.ts"). */
|
|
82
|
+
locatorFileStem?: string;
|
|
83
|
+
/** File naming stem for test data files (e.g. "{camelCase}TestData.ts"). */
|
|
84
|
+
testDataFileStem?: string;
|
|
85
|
+
/** File naming stem for fixture files (e.g. "{camelCase}.fixture.ts"). */
|
|
86
|
+
fixtureFileStem?: string;
|
|
87
|
+
}
|
|
88
|
+
export declare function validateGitHubRepo(token: string, repoFullName: string): Promise<{
|
|
89
|
+
ok: boolean;
|
|
90
|
+
defaultBranch: string;
|
|
91
|
+
private: boolean;
|
|
92
|
+
error?: string;
|
|
93
|
+
}>;
|
|
94
|
+
export declare function fetchRepoTree(token: string, repoFullName: string, branch?: string): Promise<{
|
|
95
|
+
paths: string[];
|
|
96
|
+
sha: string;
|
|
97
|
+
truncated: boolean;
|
|
98
|
+
}>;
|
|
99
|
+
export declare function fetchFileContent(token: string, repoFullName: string, filePath: string): Promise<string | null>;
|
|
100
|
+
export declare function detectFramework(paths: string[]): DetectedFramework[];
|
|
101
|
+
/**
|
|
102
|
+
* G-07: Multi-file fixture tracing.
|
|
103
|
+
* For relative imports where specifier name does NOT match the name heuristic,
|
|
104
|
+
* fetch the imported file and check if it exports a test.extend() result.
|
|
105
|
+
* Returns confirmed alias names to merge into detectTestAliases.
|
|
106
|
+
* Max 3 fixture files fetched per spec file to stay within rate limits.
|
|
107
|
+
*/
|
|
108
|
+
export declare function resolveFixtureAliasesFromImports(content: string, token: string, repoFullName: string, specFilePath: string): Promise<Set<string>>;
|
|
109
|
+
export declare function extractTestIds(content: string, filePath: string, framework: string, extraAliases?: Set<string>): ExtractedTest[];
|
|
110
|
+
export declare function extractStyleProfile(sampleFiles: Array<{
|
|
111
|
+
path: string;
|
|
112
|
+
content: string;
|
|
113
|
+
}>, framework: string, language: string, tenantId: string, projectId?: string, jobId?: string,
|
|
114
|
+
/** Filtered code paths (test + source files) — used by LLM for convention/structure analysis */
|
|
115
|
+
allPaths?: string[],
|
|
116
|
+
/**
|
|
117
|
+
* Full repo tree paths (unfiltered) — used ONLY by heuristic detectDataDirs() so that
|
|
118
|
+
* testdata/*.json, fixtures/*.yaml, locators/ etc. are visible even when they contain no .ts files.
|
|
119
|
+
* When omitted, falls back to allPaths (same behaviour as before).
|
|
120
|
+
*/
|
|
121
|
+
fullTreePaths?: string[]): Promise<StyleProfile>;
|
|
122
|
+
/**
|
|
123
|
+
* Extract Java base package from folder pattern or file paths.
|
|
124
|
+
* e.g. "src/test/java/com/crm/qa/testc/{feature}Test.java" → "com.crm.qa"
|
|
125
|
+
*/
|
|
126
|
+
export declare function deriveJavaBasePackage(folderPattern: string, filePaths?: string[]): string | null;
|
|
127
|
+
export interface PageObjectMethod {
|
|
128
|
+
name: string;
|
|
129
|
+
params: string;
|
|
130
|
+
returnType: string;
|
|
131
|
+
}
|
|
132
|
+
export interface PageObjectSummary {
|
|
133
|
+
filePath: string;
|
|
134
|
+
className: string;
|
|
135
|
+
methods: PageObjectMethod[];
|
|
136
|
+
importPath: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Build the page object inventory from all non-test repo files.
|
|
140
|
+
* Pass up to 60 candidate files for efficiency.
|
|
141
|
+
*/
|
|
142
|
+
export declare function buildPageObjectInventory(token: string, repoFullName: string, allPaths: string[], framework: string): Promise<PageObjectSummary[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Format page object inventory as a prompt-friendly string for LLM injection.
|
|
145
|
+
*/
|
|
146
|
+
export declare function formatPageObjectInventoryForPrompt(inventory: PageObjectSummary[], urlHint?: string): string;
|
|
147
|
+
/**
|
|
148
|
+
* Given a screen URL and the repo's detected style profile, derive
|
|
149
|
+
* the canonical path where a new test script for this screen should live.
|
|
150
|
+
*
|
|
151
|
+
* Example:
|
|
152
|
+
* url: https://app.acme.com/reports/export
|
|
153
|
+
* folderPattern: tests/e2e/
|
|
154
|
+
* framework: playwright-ts
|
|
155
|
+
* → tests/e2e/reports-export.spec.ts
|
|
156
|
+
*/
|
|
157
|
+
export declare function deriveRepoTargetPath(screenUrl: string, styleProfile: StyleProfile, framework: string): string;
|
|
158
|
+
export interface FrameworkConfigPaths {
|
|
159
|
+
/** testDir — authoritative test directory from framework config */
|
|
160
|
+
testDir?: string;
|
|
161
|
+
/** fixturesFolder from cypress.config / wdio.conf */
|
|
162
|
+
fixturesFolder?: string;
|
|
163
|
+
/** baseURL from playwright.config / wdio.conf / cypress.config */
|
|
164
|
+
baseURL?: string;
|
|
165
|
+
/** TypeScript path aliases from tsconfig.json compilerOptions.paths */
|
|
166
|
+
tsconfigPaths?: Record<string, string[]>;
|
|
167
|
+
/** baseUrl from tsconfig.json compilerOptions */
|
|
168
|
+
tsconfigBaseUrl?: string;
|
|
169
|
+
/** Jest moduleNameMapper — alias map for import resolution */
|
|
170
|
+
jestModuleNameMapper?: Record<string, string>;
|
|
171
|
+
/** Java testSourceDirectory from pom.xml / build.gradle */
|
|
172
|
+
javaTestSourceDir?: string;
|
|
173
|
+
}
|
|
174
|
+
export type ConfigFileType = 'playwright' | 'cypress' | 'tsconfig' | 'jest' | 'package-json' | 'pytest-ini' | 'pyproject-toml' | 'setup-cfg' | 'pom-xml' | 'build-gradle' | 'testng-xml' | 'wdio' | 'cucumber';
|
|
175
|
+
/**
|
|
176
|
+
* Extract path-relevant settings from framework config files using regex/JSON.
|
|
177
|
+
* No LLM — these files have predictable, well-documented structure.
|
|
178
|
+
*/
|
|
179
|
+
export declare function parseFrameworkConfig(configType: ConfigFileType, content: string): FrameworkConfigPaths;
|
|
180
|
+
/**
|
|
181
|
+
* Given a repo's file tree paths and detected framework, return which config
|
|
182
|
+
* files to fetch and what parser to use for each.
|
|
183
|
+
* Covers all supported frameworks: playwright, cypress, jest, pytest, junit,
|
|
184
|
+
* selenium-java/py, webdriverio, cucumber.
|
|
185
|
+
*/
|
|
186
|
+
export declare function detectConfigFilesToFetch(paths: string[], framework: string): Array<{
|
|
187
|
+
path: string;
|
|
188
|
+
type: ConfigFileType;
|
|
189
|
+
}>;
|