@yasserkhanorg/e2e-agents 0.5.16 → 0.6.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.
- package/dist/agent/pipeline.d.ts +1 -1
- package/dist/agent/pipeline.d.ts.map +1 -1
- package/dist/agent/plan.d.ts +0 -12
- package/dist/agent/plan.d.ts.map +1 -1
- package/dist/agent/plan.js +0 -365
- package/dist/agent/types.d.ts +42 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +4 -0
- package/dist/api.d.ts +10 -14
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +29 -59
- package/dist/cli.js +41 -174
- package/dist/engine/impact_engine.d.ts +36 -0
- package/dist/engine/impact_engine.d.ts.map +1 -0
- package/dist/engine/impact_engine.js +196 -0
- package/dist/engine/plan_builder.d.ts +9 -0
- package/dist/engine/plan_builder.d.ts.map +1 -0
- package/dist/engine/plan_builder.js +329 -0
- package/dist/esm/agent/plan.js +1 -360
- package/dist/esm/agent/types.js +3 -0
- package/dist/esm/api.js +27 -56
- package/dist/esm/cli.js +40 -173
- package/dist/esm/engine/impact_engine.js +191 -0
- package/dist/esm/engine/plan_builder.js +323 -0
- package/dist/esm/index.js +6 -3
- package/dist/esm/knowledge/route_families.js +57 -0
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -5
- package/dist/knowledge/route_families.d.ts +19 -0
- package/dist/knowledge/route_families.d.ts.map +1 -1
- package/dist/knowledge/route_families.js +60 -0
- package/package.json +1 -1
- package/dist/agent/ai_flow_analysis.d.ts +0 -13
- package/dist/agent/ai_flow_analysis.d.ts.map +0 -1
- package/dist/agent/ai_flow_analysis.js +0 -334
- package/dist/agent/ai_mapping.d.ts +0 -14
- package/dist/agent/ai_mapping.d.ts.map +0 -1
- package/dist/agent/ai_mapping.js +0 -560
- package/dist/agent/analysis.d.ts +0 -64
- package/dist/agent/analysis.d.ts.map +0 -1
- package/dist/agent/analysis.js +0 -292
- package/dist/agent/blast_radius.d.ts +0 -4
- package/dist/agent/blast_radius.d.ts.map +0 -1
- package/dist/agent/blast_radius.js +0 -37
- package/dist/agent/dependency_graph.d.ts +0 -14
- package/dist/agent/dependency_graph.d.ts.map +0 -1
- package/dist/agent/dependency_graph.js +0 -227
- package/dist/agent/flags.d.ts +0 -23
- package/dist/agent/flags.d.ts.map +0 -1
- package/dist/agent/flags.js +0 -171
- package/dist/agent/flow_catalog.d.ts +0 -25
- package/dist/agent/flow_catalog.d.ts.map +0 -1
- package/dist/agent/flow_catalog.js +0 -115
- package/dist/agent/flow_mapping.d.ts +0 -10
- package/dist/agent/flow_mapping.d.ts.map +0 -1
- package/dist/agent/flow_mapping.js +0 -84
- package/dist/agent/framework.d.ts +0 -13
- package/dist/agent/framework.d.ts.map +0 -1
- package/dist/agent/framework.js +0 -149
- package/dist/agent/gap_suggestions.d.ts +0 -14
- package/dist/agent/gap_suggestions.d.ts.map +0 -1
- package/dist/agent/gap_suggestions.js +0 -101
- package/dist/agent/generator.d.ts +0 -10
- package/dist/agent/generator.d.ts.map +0 -1
- package/dist/agent/generator.js +0 -115
- package/dist/agent/operational_insights.d.ts +0 -41
- package/dist/agent/operational_insights.d.ts.map +0 -1
- package/dist/agent/operational_insights.js +0 -127
- package/dist/agent/report.d.ts +0 -97
- package/dist/agent/report.d.ts.map +0 -1
- package/dist/agent/report.js +0 -159
- package/dist/agent/runner.d.ts +0 -7
- package/dist/agent/runner.d.ts.map +0 -1
- package/dist/agent/runner.js +0 -898
- package/dist/agent/selectors.d.ts +0 -10
- package/dist/agent/selectors.d.ts.map +0 -1
- package/dist/agent/selectors.js +0 -75
- package/dist/agent/subsystem_risk.d.ts +0 -23
- package/dist/agent/subsystem_risk.d.ts.map +0 -1
- package/dist/agent/subsystem_risk.js +0 -207
- package/dist/agent/tests.d.ts +0 -19
- package/dist/agent/tests.d.ts.map +0 -1
- package/dist/agent/tests.js +0 -116
- package/dist/agent/traceability.d.ts +0 -22
- package/dist/agent/traceability.d.ts.map +0 -1
- package/dist/agent/traceability.js +0 -183
- package/dist/esm/agent/ai_flow_analysis.js +0 -331
- package/dist/esm/agent/ai_mapping.js +0 -557
- package/dist/esm/agent/analysis.js +0 -287
- package/dist/esm/agent/blast_radius.js +0 -34
- package/dist/esm/agent/dependency_graph.js +0 -224
- package/dist/esm/agent/flags.js +0 -160
- package/dist/esm/agent/flow_catalog.js +0 -112
- package/dist/esm/agent/flow_mapping.js +0 -81
- package/dist/esm/agent/framework.js +0 -145
- package/dist/esm/agent/gap_suggestions.js +0 -98
- package/dist/esm/agent/generator.js +0 -112
- package/dist/esm/agent/operational_insights.js +0 -124
- package/dist/esm/agent/report.js +0 -156
- package/dist/esm/agent/runner.js +0 -894
- package/dist/esm/agent/selectors.js +0 -71
- package/dist/esm/agent/subsystem_risk.js +0 -204
- package/dist/esm/agent/tests.js +0 -111
- package/dist/esm/agent/traceability.js +0 -180
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
-
// See LICENSE.txt for license information.
|
|
3
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
4
|
-
import { join, resolve } from 'path';
|
|
5
|
-
import { isPathWithinRoot } from './utils.js';
|
|
6
|
-
function inferTestDir(patterns) {
|
|
7
|
-
if (patterns.length === 0) {
|
|
8
|
-
return 'tests';
|
|
9
|
-
}
|
|
10
|
-
const pattern = patterns[0];
|
|
11
|
-
const wildcardIndex = pattern.search(/[*{]/);
|
|
12
|
-
const base = wildcardIndex === -1 ? pattern : pattern.slice(0, wildcardIndex);
|
|
13
|
-
const trimmed = base.replace(/\/+$/, '');
|
|
14
|
-
return trimmed || 'tests';
|
|
15
|
-
}
|
|
16
|
-
function inferTestExtension(patterns, framework) {
|
|
17
|
-
const joined = patterns.join(' ');
|
|
18
|
-
if (joined.includes('.ts') || joined.includes('.tsx')) {
|
|
19
|
-
return 'ts';
|
|
20
|
-
}
|
|
21
|
-
return 'js';
|
|
22
|
-
}
|
|
23
|
-
function createPlaywrightTest(flow, testIds) {
|
|
24
|
-
const idsComment = testIds.length > 0 ? `// Suggested data-testid: ${testIds.join(', ')}` : '// TODO: add data-testid selectors';
|
|
25
|
-
return [
|
|
26
|
-
"import {test, expect} from '@mattermost/playwright-lib';",
|
|
27
|
-
'',
|
|
28
|
-
'/**',
|
|
29
|
-
` * @objective Validate ${flow.name} flow`,
|
|
30
|
-
' */',
|
|
31
|
-
`test('${flow.priority}: ${flow.name} basic flow', {tag: '@ai-assisted'}, async ({pw}) => {`,
|
|
32
|
-
' const {user, team} = await pw.initSetup();',
|
|
33
|
-
' const {channelsPage} = await pw.testBrowser.login(user);',
|
|
34
|
-
" await channelsPage.goto(team.name);",
|
|
35
|
-
` ${idsComment}`,
|
|
36
|
-
' // # TODO: implement steps',
|
|
37
|
-
' // * TODO: implement assertions',
|
|
38
|
-
' await expect(channelsPage.page).toHaveURL(/.*/);',
|
|
39
|
-
'});',
|
|
40
|
-
'',
|
|
41
|
-
].join('\n');
|
|
42
|
-
}
|
|
43
|
-
function createCypressTest(flow, testIds) {
|
|
44
|
-
const idsComment = testIds.length > 0 ? `// Suggested data-testid: ${testIds.join(', ')}` : '// TODO: add data-testid selectors';
|
|
45
|
-
return [
|
|
46
|
-
`describe('Flow: ${flow.name}', () => {`,
|
|
47
|
-
` it('${flow.priority}: ${flow.name} basic flow', () => {`,
|
|
48
|
-
" cy.visit('/');",
|
|
49
|
-
` ${idsComment}`,
|
|
50
|
-
' // TODO: implement steps',
|
|
51
|
-
' cy.url().should(\'match\', /.*/);',
|
|
52
|
-
' });',
|
|
53
|
-
'});',
|
|
54
|
-
'',
|
|
55
|
-
].join('\n');
|
|
56
|
-
}
|
|
57
|
-
function createSeleniumTest(flow, testIds) {
|
|
58
|
-
const idsComment = testIds.length > 0 ? `// Suggested data-testid: ${testIds.join(', ')}` : '// TODO: add data-testid selectors';
|
|
59
|
-
return [
|
|
60
|
-
"const {Builder, By, until} = require('selenium-webdriver');",
|
|
61
|
-
'',
|
|
62
|
-
`(async () => {`,
|
|
63
|
-
" const driver = await new Builder().forBrowser('chrome').build();",
|
|
64
|
-
' try {',
|
|
65
|
-
" await driver.get('http://localhost:3000');",
|
|
66
|
-
` ${idsComment}`,
|
|
67
|
-
' // TODO: implement steps',
|
|
68
|
-
' await driver.wait(until.titleIs(\'\'), 5000);',
|
|
69
|
-
' } finally {',
|
|
70
|
-
' await driver.quit();',
|
|
71
|
-
' }',
|
|
72
|
-
'})();',
|
|
73
|
-
'',
|
|
74
|
-
].join('\n');
|
|
75
|
-
}
|
|
76
|
-
export function generateTests(appRoot, flows, framework, testPatterns, testIdsByFlow) {
|
|
77
|
-
const inferredTestDir = inferTestDir(testPatterns);
|
|
78
|
-
const safeTestDir = isPathWithinRoot(appRoot, resolve(appRoot, inferredTestDir)) ? inferredTestDir : 'tests';
|
|
79
|
-
const testDir = safeTestDir;
|
|
80
|
-
const extension = inferTestExtension(testPatterns, framework);
|
|
81
|
-
const generated = [];
|
|
82
|
-
for (const flow of flows) {
|
|
83
|
-
if (flow.priority !== 'P0' && flow.priority !== 'P1') {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const testIds = testIdsByFlow.get(flow.id) || [];
|
|
87
|
-
const fileName = framework === 'cypress' ? `${flow.id}.cy.${extension}` : `${flow.id}.spec.${extension}`;
|
|
88
|
-
const fullPath = resolve(appRoot, testDir, fileName);
|
|
89
|
-
if (!isPathWithinRoot(appRoot, fullPath)) {
|
|
90
|
-
generated.push({ path: fullPath, flowId: flow.id, created: false, reason: 'outside-root' });
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (existsSync(fullPath)) {
|
|
94
|
-
generated.push({ path: fullPath, flowId: flow.id, created: false, reason: 'exists' });
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
mkdirSync(join(appRoot, testDir), { recursive: true });
|
|
98
|
-
let content = '';
|
|
99
|
-
if (framework === 'cypress') {
|
|
100
|
-
content = createCypressTest(flow, testIds);
|
|
101
|
-
}
|
|
102
|
-
else if (framework === 'selenium') {
|
|
103
|
-
content = createSeleniumTest(flow, testIds);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
content = createPlaywrightTest(flow, testIds);
|
|
107
|
-
}
|
|
108
|
-
writeFileSync(fullPath, content, 'utf-8');
|
|
109
|
-
generated.push({ path: fullPath, flowId: flow.id, created: true });
|
|
110
|
-
}
|
|
111
|
-
return generated;
|
|
112
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
-
// See LICENSE.txt for license information.
|
|
3
|
-
import { existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { refreshPlanEnforcement } from './plan.js';
|
|
6
|
-
import { inferSubsystemFromTestPath } from './test_path.js';
|
|
7
|
-
function readJson(path) {
|
|
8
|
-
if (!existsSync(path)) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
try {
|
|
12
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function normalizeTestName(test) {
|
|
19
|
-
return test.replace(/ \(flags:.*\)$/, '').trim();
|
|
20
|
-
}
|
|
21
|
-
function subsystemForTest(test) {
|
|
22
|
-
return inferSubsystemFromTestPath(test);
|
|
23
|
-
}
|
|
24
|
-
function riskyRate(entry) {
|
|
25
|
-
if (entry.flakeRate30d !== undefined) {
|
|
26
|
-
return entry.flakeRate30d;
|
|
27
|
-
}
|
|
28
|
-
return entry.flakeRate;
|
|
29
|
-
}
|
|
30
|
-
function loadFlakyManifest(appRoot) {
|
|
31
|
-
const path = join(appRoot, '.e2e-ai-agents', 'flaky-tests.json');
|
|
32
|
-
return readJson(path);
|
|
33
|
-
}
|
|
34
|
-
function loadQualityGates(appRoot) {
|
|
35
|
-
const path = join(appRoot, '.e2e-ai-agents', 'quality-gates.json');
|
|
36
|
-
return readJson(path);
|
|
37
|
-
}
|
|
38
|
-
function loadCalibration(appRoot) {
|
|
39
|
-
const path = join(appRoot, '.e2e-ai-agents', 'calibration.json');
|
|
40
|
-
return readJson(path);
|
|
41
|
-
}
|
|
42
|
-
export function applyOperationalInsights(plan, appRoot) {
|
|
43
|
-
const enhanced = { ...plan };
|
|
44
|
-
const insights = {};
|
|
45
|
-
const flaky = loadFlakyManifest(appRoot);
|
|
46
|
-
if (flaky && Array.isArray(flaky.tests)) {
|
|
47
|
-
const recommended = new Set(plan.recommendedTests.map(normalizeTestName));
|
|
48
|
-
const risky = flaky.tests
|
|
49
|
-
.filter((entry) => recommended.has(normalizeTestName(entry.test)) && riskyRate(entry) >= 0.2)
|
|
50
|
-
.sort((a, b) => riskyRate(b) - riskyRate(a))
|
|
51
|
-
.slice(0, 10);
|
|
52
|
-
const quarantined = risky.filter((entry) => entry.quarantine).map((entry) => entry.test);
|
|
53
|
-
const owners = Array.from(new Set(risky
|
|
54
|
-
.flatMap((entry) => entry.owners || [])
|
|
55
|
-
.filter(Boolean)));
|
|
56
|
-
insights.flaky = {
|
|
57
|
-
highRiskRecommendedTests: risky,
|
|
58
|
-
quarantinedRecommendedTests: quarantined,
|
|
59
|
-
ownerMentions: owners,
|
|
60
|
-
};
|
|
61
|
-
if (quarantined.length > 0) {
|
|
62
|
-
enhanced.reasons = [...enhanced.reasons, `Quarantined flaky tests in recommendation: ${quarantined.join(', ')}`];
|
|
63
|
-
}
|
|
64
|
-
if (owners.length > 0) {
|
|
65
|
-
enhanced.reasons = [...enhanced.reasons, `Subsystem owners to notify for flaky risk: ${owners.join(', ')}`];
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const gates = loadQualityGates(appRoot);
|
|
69
|
-
if (gates && Array.isArray(gates.gates)) {
|
|
70
|
-
const failed = gates.gates.filter((gate) => gate.status === 'fail');
|
|
71
|
-
const warnings = gates.gates.filter((gate) => gate.status === 'warn');
|
|
72
|
-
insights.qualityGates = { failed, warnings };
|
|
73
|
-
if (failed.length > 0 && enhanced.runSet !== 'full') {
|
|
74
|
-
enhanced.runSet = 'full';
|
|
75
|
-
enhanced.reasons = [...enhanced.reasons, `Quality gates failed: ${failed.map((gate) => gate.name).join(', ')}`];
|
|
76
|
-
enhanced.policy.triggeredRules = [...new Set([...enhanced.policy.triggeredRules, 'quality-gate-failed'])];
|
|
77
|
-
enhanced.decision = {
|
|
78
|
-
action: 'run-now',
|
|
79
|
-
title: 'Run now',
|
|
80
|
-
summary: 'Quality gate failures detected. Full suite is required before merge.',
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const calibration = loadCalibration(appRoot);
|
|
85
|
-
if (calibration) {
|
|
86
|
-
insights.calibration = calibration.overall;
|
|
87
|
-
if (calibration.overall.falseNegativeRate >= 0.2 && enhanced.runSet !== 'full') {
|
|
88
|
-
enhanced.runSet = 'full';
|
|
89
|
-
enhanced.reasons = [...enhanced.reasons, 'Historical false-negative rate is high; escalating to full suite.'];
|
|
90
|
-
enhanced.policy.triggeredRules = [...new Set([...enhanced.policy.triggeredRules, 'historical-fnr-high'])];
|
|
91
|
-
}
|
|
92
|
-
const recommendedSubsystems = Array.from(new Set(plan.recommendedTests.map(subsystemForTest)));
|
|
93
|
-
const highRiskSubsystems = recommendedSubsystems
|
|
94
|
-
.map((subsystem) => {
|
|
95
|
-
const metric = calibration.bySubsystem[subsystem];
|
|
96
|
-
if (!metric) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
if (metric.samples < 5) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
if (metric.recent30d.falseNegativeRate >= 0.2 || metric.falseNegativeRate >= 0.25) {
|
|
103
|
-
return { subsystem, fnr: metric.recent30d.falseNegativeRate || metric.falseNegativeRate };
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
})
|
|
107
|
-
.filter(Boolean);
|
|
108
|
-
if (highRiskSubsystems.length > 0 && enhanced.runSet !== 'full') {
|
|
109
|
-
enhanced.runSet = 'full';
|
|
110
|
-
enhanced.reasons = [
|
|
111
|
-
...enhanced.reasons,
|
|
112
|
-
`Historical subsystem false-negative risk is high: ${highRiskSubsystems.map((entry) => `${entry.subsystem}(${entry.fnr})`).join(', ')}`,
|
|
113
|
-
];
|
|
114
|
-
enhanced.policy.triggeredRules = [...new Set([...enhanced.policy.triggeredRules, 'subsystem-fnr-high'])];
|
|
115
|
-
enhanced.decision = {
|
|
116
|
-
action: 'run-now',
|
|
117
|
-
title: 'Run now',
|
|
118
|
-
summary: 'Subsystem calibration risk is high. Full suite is required before merge.',
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
enhanced.insights = insights;
|
|
123
|
-
return refreshPlanEnforcement(enhanced);
|
|
124
|
-
}
|
package/dist/esm/agent/report.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
-
// See LICENSE.txt for license information.
|
|
3
|
-
import { mkdirSync, writeFileSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { formatFlags } from './flags.js';
|
|
6
|
-
function formatFlow(flow) {
|
|
7
|
-
const reasonText = flow.reasons.length > 0 ? flow.reasons.join('; ') : 'No specific reasons';
|
|
8
|
-
const audienceText = flow.audience && flow.audience.length > 0 ? `\n Audience: ${flow.audience.join(', ')}` : '';
|
|
9
|
-
const flagsText = flow.flags && flow.flags.length > 0 ? `\n Flags: ${formatFlags(flow.flags)}` : '';
|
|
10
|
-
const blastText = flow.blastRadius ? `\n Blast radius: ${flow.blastRadius.summary}` : '';
|
|
11
|
-
return `- [${flow.priority}] ${flow.name} (${flow.id})\n Score: ${flow.score}\n Reasons: ${reasonText}\n Files: ${flow.files.join(', ')}${audienceText}${flagsText}${blastText}`;
|
|
12
|
-
}
|
|
13
|
-
function formatGap(flow) {
|
|
14
|
-
return `- [${flow.priority}] ${flow.name} (${flow.id})`;
|
|
15
|
-
}
|
|
16
|
-
function formatSuggestion(suggestion) {
|
|
17
|
-
return `- ${suggestion.file}:${suggestion.line} -> ${suggestion.testId}\n ${suggestion.snippet}`;
|
|
18
|
-
}
|
|
19
|
-
function formatTestSuggestion(suggestion) {
|
|
20
|
-
const source = suggestion.sourceFiles.length > 0 ? suggestion.sourceFiles.join(', ') : 'N/A';
|
|
21
|
-
return `- [${suggestion.priority}] ${suggestion.flowName} (${suggestion.flowId})\n Path: ${suggestion.suggestedTestPath}\n Source files: ${source}\n Why: ${suggestion.rationale}`;
|
|
22
|
-
}
|
|
23
|
-
function flowCounts(flows) {
|
|
24
|
-
return flows.reduce((acc, flow) => {
|
|
25
|
-
if (flow.priority === 'P0')
|
|
26
|
-
acc.p0 += 1;
|
|
27
|
-
else if (flow.priority === 'P1')
|
|
28
|
-
acc.p1 += 1;
|
|
29
|
-
else
|
|
30
|
-
acc.p2 += 1;
|
|
31
|
-
return acc;
|
|
32
|
-
}, { p0: 0, p1: 0, p2: 0 });
|
|
33
|
-
}
|
|
34
|
-
export function writeReport(appRoot, config, data) {
|
|
35
|
-
const specsDir = join(appRoot, config.artifacts.specsDir);
|
|
36
|
-
const baseDir = join(appRoot, '.e2e-ai-agents');
|
|
37
|
-
if (config.artifacts.mode !== 'none') {
|
|
38
|
-
mkdirSync(specsDir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
mkdirSync(baseDir, { recursive: true });
|
|
41
|
-
const counts = flowCounts(data.flows);
|
|
42
|
-
const markdownLines = [];
|
|
43
|
-
markdownLines.push(`# ${data.mode === 'impact' ? 'Impact Analysis' : 'Gap Analysis'} Report`);
|
|
44
|
-
markdownLines.push('');
|
|
45
|
-
if (data.runMetadata) {
|
|
46
|
-
markdownLines.push(`Run ID: ${data.runMetadata.runId}`);
|
|
47
|
-
markdownLines.push(`Run window: ${data.runMetadata.startedAt} -> ${data.runMetadata.completedAt}`);
|
|
48
|
-
markdownLines.push(`Run duration (ms): ${data.runMetadata.durationMs}`);
|
|
49
|
-
markdownLines.push(`Since ref: ${data.runMetadata.sinceRef}`);
|
|
50
|
-
}
|
|
51
|
-
markdownLines.push(`Framework: ${data.framework}`);
|
|
52
|
-
markdownLines.push(`Test Patterns: ${data.testPatterns.join(', ') || 'None'}`);
|
|
53
|
-
if (data.flowCatalog) {
|
|
54
|
-
markdownLines.push(`Flow Catalog: ${data.flowCatalog}`);
|
|
55
|
-
}
|
|
56
|
-
if (data.impactModel) {
|
|
57
|
-
markdownLines.push(`Impact Model: flow=${data.impactModel.flowMapping} test=${data.impactModel.testMapping} confidence=${data.impactModel.confidenceClass}`);
|
|
58
|
-
if (data.impactModel.traceability) {
|
|
59
|
-
const traceability = data.impactModel.traceability;
|
|
60
|
-
markdownLines.push(`Traceability: enabled=${traceability.enabled} manifestFound=${traceability.manifestFound} matchedFlows=${traceability.matchedFlows}/${traceability.totalFlows} matchedTests=${traceability.matchedTests} coverageRatio=${traceability.coverageRatio}`);
|
|
61
|
-
}
|
|
62
|
-
if (data.impactModel.dependencyGraph) {
|
|
63
|
-
const graph = data.impactModel.dependencyGraph;
|
|
64
|
-
markdownLines.push(`Dependency Graph: enabled=${graph.enabled} seeds=${graph.seedFiles} expanded=${graph.expandedFiles} files=${graph.analyzedFiles} edges=${graph.analyzedEdges} depth=${graph.maxDepth}${graph.truncated ? ' (truncated)' : ''}`);
|
|
65
|
-
}
|
|
66
|
-
if (data.impactModel.subsystemRisk) {
|
|
67
|
-
const subsystemRisk = data.impactModel.subsystemRisk;
|
|
68
|
-
markdownLines.push(`Subsystem Risk: enabled=${subsystemRisk.enabled} mapFound=${subsystemRisk.mapFound} rules=${subsystemRisk.rulesLoaded} filesMatched=${subsystemRisk.filesMatched} ruleMatches=${subsystemRisk.ruleMatches} boostedFlows=${subsystemRisk.boostedFlows}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
markdownLines.push(`Changed Files: ${data.changedFiles.length}`);
|
|
72
|
-
markdownLines.push(`Flows: P0=${counts.p0} P1=${counts.p1} P2=${counts.p2}`);
|
|
73
|
-
if (data.specPDF) {
|
|
74
|
-
markdownLines.push(`Spec PDF: ${data.specPDF}`);
|
|
75
|
-
}
|
|
76
|
-
if (data.warnings.length > 0) {
|
|
77
|
-
markdownLines.push('');
|
|
78
|
-
markdownLines.push('Warnings:');
|
|
79
|
-
markdownLines.push(...data.warnings.map((warning) => `- ${warning}`));
|
|
80
|
-
}
|
|
81
|
-
if (data.flows.length > 0) {
|
|
82
|
-
markdownLines.push('');
|
|
83
|
-
markdownLines.push('Impacted Flows:');
|
|
84
|
-
markdownLines.push(...data.flows.map(formatFlow));
|
|
85
|
-
}
|
|
86
|
-
if (data.gaps.length > 0) {
|
|
87
|
-
markdownLines.push('');
|
|
88
|
-
markdownLines.push('Coverage Gaps (P0/P1 without tests):');
|
|
89
|
-
markdownLines.push(...data.gaps.map(formatGap));
|
|
90
|
-
}
|
|
91
|
-
if (data.recommendedTests && data.recommendedTests.length > 0) {
|
|
92
|
-
markdownLines.push('');
|
|
93
|
-
markdownLines.push('Recommended Tests to Run:');
|
|
94
|
-
markdownLines.push(...data.recommendedTests.map((test) => `- ${test}`));
|
|
95
|
-
}
|
|
96
|
-
if (data.pipeline) {
|
|
97
|
-
markdownLines.push('');
|
|
98
|
-
markdownLines.push('Pipeline Results:');
|
|
99
|
-
markdownLines.push(`- Runner: ${data.pipeline.runner}`);
|
|
100
|
-
if (data.pipeline.mcp) {
|
|
101
|
-
markdownLines.push(`- MCP: requested=${data.pipeline.mcp.requested} active=${data.pipeline.mcp.active} backend=${data.pipeline.mcp.backend}`);
|
|
102
|
-
}
|
|
103
|
-
for (const result of data.pipeline.results) {
|
|
104
|
-
const status = result.healStatus ? `${result.generateStatus}/${result.healStatus}` : result.generateStatus;
|
|
105
|
-
markdownLines.push(`- ${result.flowId} (${result.flowName}): ${status} -> ${result.generatedDir}`);
|
|
106
|
-
if (result.error) {
|
|
107
|
-
markdownLines.push(` Error: ${result.error}`);
|
|
108
|
-
}
|
|
109
|
-
if (result.failureCategory || result.failureCode) {
|
|
110
|
-
markdownLines.push(` Failure taxonomy: category=${result.failureCategory || 'unknown'} code=${result.failureCode || 'unknown'}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (data.pipeline.warnings.length > 0) {
|
|
114
|
-
markdownLines.push('Pipeline warnings:');
|
|
115
|
-
markdownLines.push(...data.pipeline.warnings.map((warning) => `- ${warning}`));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (data.dataTestIds.length > 0) {
|
|
119
|
-
markdownLines.push('');
|
|
120
|
-
markdownLines.push('data-testid Suggestions:');
|
|
121
|
-
markdownLines.push(...data.dataTestIds.map(formatSuggestion));
|
|
122
|
-
}
|
|
123
|
-
if (data.testSuggestions && data.testSuggestions.length > 0) {
|
|
124
|
-
markdownLines.push('');
|
|
125
|
-
markdownLines.push('Suggested New Tests (Actionable):');
|
|
126
|
-
markdownLines.push(...data.testSuggestions.map(formatTestSuggestion));
|
|
127
|
-
}
|
|
128
|
-
if (data.applied) {
|
|
129
|
-
markdownLines.push('');
|
|
130
|
-
markdownLines.push('Applied Changes:');
|
|
131
|
-
if (data.applied.patchedFiles.length > 0) {
|
|
132
|
-
markdownLines.push(`- Patched files: ${data.applied.patchedFiles.join(', ')}`);
|
|
133
|
-
}
|
|
134
|
-
if (data.applied.generatedTests.length > 0) {
|
|
135
|
-
markdownLines.push(`- Generated tests: ${data.applied.generatedTests.join(', ')}`);
|
|
136
|
-
}
|
|
137
|
-
if (data.applied.skippedTests.length > 0) {
|
|
138
|
-
markdownLines.push(`- Skipped test files: ${data.applied.skippedTests.join(', ')}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const markdownContent = markdownLines.join('\n');
|
|
142
|
-
const reportName = data.mode === 'impact' ? 'impact-plan.md' : 'gap-report.md';
|
|
143
|
-
const markdownPath = join(specsDir, reportName);
|
|
144
|
-
if (config.artifacts.mode !== 'none') {
|
|
145
|
-
writeFileSync(markdownPath, markdownContent, 'utf-8');
|
|
146
|
-
}
|
|
147
|
-
const jsonPath = join(baseDir, data.mode === 'impact' ? 'impact.json' : 'gap.json');
|
|
148
|
-
const jsonData = data.mode === 'gap'
|
|
149
|
-
? {
|
|
150
|
-
...data,
|
|
151
|
-
suggestedNewTests: data.suggestedNewTests || data.testSuggestions || [],
|
|
152
|
-
}
|
|
153
|
-
: data;
|
|
154
|
-
writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2), 'utf-8');
|
|
155
|
-
return { markdownPath, jsonPath };
|
|
156
|
-
}
|