@mknrt/autotests-overkill 1.1.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/README.md +117 -0
- package/assets/overkill-logo.svg +8 -0
- package/assets/overkill-small.svg +8 -0
- package/dist/connectors/index.d.ts +4 -0
- package/dist/connectors/index.js +4 -0
- package/dist/indexer/index.d.ts +4 -0
- package/dist/indexer/index.js +4 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +2 -0
- package/dist/src/appContext.d.ts +21 -0
- package/dist/src/appContext.js +40 -0
- package/dist/src/cli/indexKnowledge.d.ts +1 -0
- package/dist/src/cli/indexKnowledge.js +11 -0
- package/dist/src/cli/main.d.ts +19 -0
- package/dist/src/cli/main.js +59 -0
- package/dist/src/cli/runAcceptance.d.ts +21 -0
- package/dist/src/cli/runAcceptance.js +131 -0
- package/dist/src/cli/runMcpServer.d.ts +1 -0
- package/dist/src/cli/runMcpServer.js +10 -0
- package/dist/src/cli/shared.d.ts +1 -0
- package/dist/src/cli/shared.js +7 -0
- package/dist/src/config/consumerConfig.d.ts +126 -0
- package/dist/src/config/consumerConfig.js +63 -0
- package/dist/src/connectors/autotests2Connector.d.ts +41 -0
- package/dist/src/connectors/autotests2Connector.js +94 -0
- package/dist/src/connectors/caseplatformConnector.d.ts +35 -0
- package/dist/src/connectors/caseplatformConnector.js +251 -0
- package/dist/src/connectors/ciArtifactsConnector.d.ts +23 -0
- package/dist/src/connectors/ciArtifactsConnector.js +14 -0
- package/dist/src/connectors/diffConnector.d.ts +1 -0
- package/dist/src/connectors/diffConnector.js +4 -0
- package/dist/src/connectors/filesystemConnector.d.ts +4 -0
- package/dist/src/connectors/filesystemConnector.js +15 -0
- package/dist/src/connectors/gitlabConnector.d.ts +14 -0
- package/dist/src/connectors/gitlabConnector.js +64 -0
- package/dist/src/connectors/reportPortalConnector.d.ts +14 -0
- package/dist/src/connectors/reportPortalConnector.js +84 -0
- package/dist/src/contracts/confidence.d.ts +8 -0
- package/dist/src/contracts/confidence.js +33 -0
- package/dist/src/contracts/errors.d.ts +6 -0
- package/dist/src/contracts/errors.js +18 -0
- package/dist/src/contracts/toolOutput.d.ts +113 -0
- package/dist/src/contracts/toolOutput.js +35 -0
- package/dist/src/domain/analyzeDiffImpact.d.ts +22 -0
- package/dist/src/domain/analyzeDiffImpact.js +52 -0
- package/dist/src/domain/analyzeTestGaps.d.ts +22 -0
- package/dist/src/domain/analyzeTestGaps.js +41 -0
- package/dist/src/domain/buildRerunScope.d.ts +23 -0
- package/dist/src/domain/buildRerunScope.js +17 -0
- package/dist/src/domain/buildSetupTeardownPlan.d.ts +23 -0
- package/dist/src/domain/buildSetupTeardownPlan.js +13 -0
- package/dist/src/domain/findExistingTestAssets.d.ts +22 -0
- package/dist/src/domain/findExistingTestAssets.js +50 -0
- package/dist/src/domain/findFrontendContract.d.ts +22 -0
- package/dist/src/domain/findFrontendContract.js +13 -0
- package/dist/src/domain/findFrontendRuntimeLogic.d.ts +22 -0
- package/dist/src/domain/findFrontendRuntimeLogic.js +28 -0
- package/dist/src/domain/findTestAreaContext.d.ts +23 -0
- package/dist/src/domain/findTestAreaContext.js +30 -0
- package/dist/src/domain/generateSpecBlueprint.d.ts +23 -0
- package/dist/src/domain/generateSpecBlueprint.js +213 -0
- package/dist/src/domain/mapFeatureToExistingCoverage.d.ts +22 -0
- package/dist/src/domain/mapFeatureToExistingCoverage.js +56 -0
- package/dist/src/domain/recommendSelectorStrategy.d.ts +22 -0
- package/dist/src/domain/recommendSelectorStrategy.js +13 -0
- package/dist/src/domain/shared.d.ts +5 -0
- package/dist/src/domain/shared.js +36 -0
- package/dist/src/domain/summarizeCiContext.d.ts +22 -0
- package/dist/src/domain/summarizeCiContext.js +35 -0
- package/dist/src/domain/triageFailedRun.d.ts +25 -0
- package/dist/src/domain/triageFailedRun.js +46 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/indexer/extractors/frontendContractExtractor.d.ts +17 -0
- package/dist/src/indexer/extractors/frontendContractExtractor.js +109 -0
- package/dist/src/indexer/extractors/frontendRuntimeExtractor.d.ts +14 -0
- package/dist/src/indexer/extractors/frontendRuntimeExtractor.js +110 -0
- package/dist/src/indexer/extractors/testAssetExtractor.d.ts +13 -0
- package/dist/src/indexer/extractors/testAssetExtractor.js +17 -0
- package/dist/src/indexer/refreshPipeline.d.ts +12 -0
- package/dist/src/indexer/refreshPipeline.js +156 -0
- package/dist/src/indexer/snapshotStore.d.ts +10 -0
- package/dist/src/indexer/snapshotStore.js +45 -0
- package/dist/src/knowledge/database.d.ts +3 -0
- package/dist/src/knowledge/database.js +8 -0
- package/dist/src/knowledge/migrations.d.ts +1 -0
- package/dist/src/knowledge/migrations.js +28 -0
- package/dist/src/knowledge/repositories.d.ts +28 -0
- package/dist/src/knowledge/repositories.js +78 -0
- package/dist/src/mcp/registerTools.d.ts +11 -0
- package/dist/src/mcp/registerTools.js +118 -0
- package/dist/src/mcp/server.d.ts +4 -0
- package/dist/src/mcp/server.js +29 -0
- package/dist/src/utils/fileUtils.d.ts +4 -0
- package/dist/src/utils/fileUtils.js +16 -0
- package/dist/src/utils/pathUtils.d.ts +7 -0
- package/dist/src/utils/pathUtils.js +59 -0
- package/dist/src/utils/textUtils.d.ts +3 -0
- package/dist/src/utils/textUtils.js +26 -0
- package/docs/architecture/overview.md +31 -0
- package/docs/consumer-integration.md +64 -0
- package/docs/operator-cookbook.md +39 -0
- package/docs/plugin-packaging.md +33 -0
- package/docs/superpowers/plans/2026-04-19-autotests-overkill-v1.md +600 -0
- package/docs/tool-catalog.md +34 -0
- package/overkill.config.example.json +16 -0
- package/package.json +73 -0
- package/skills/overkill-debug-failure/SKILL.md +13 -0
- package/skills/overkill-find-gaps/SKILL.md +13 -0
- package/skills/overkill-generate-setup/SKILL.md +12 -0
- package/skills/overkill-generate-test/SKILL.md +15 -0
- package/skills/overkill-impact-analysis/SKILL.md +12 -0
- package/skills/overkill-onboard/SKILL.md +16 -0
- package/skills/overkill-prompt-builder/SKILL.md +13 -0
- package/skills/overkill-reuse-project-patterns/SKILL.md +13 -0
- package/skills/overkill-review-test-draft/SKILL.md +10 -0
- package/templates/prompts/debug-failure.md +3 -0
- package/templates/prompts/find-gaps.md +3 -0
- package/templates/prompts/generate-test.md +3 -0
- package/templates/prompts/impact-analysis.md +3 -0
- package/templates/spec-blueprints/default.md +39 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export class ReportPortalConnector {
|
|
2
|
+
config;
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
async describeLaunch(stats) {
|
|
7
|
+
const launchId = stats.launch_id;
|
|
8
|
+
const launchLink = stats.launch_link;
|
|
9
|
+
if (!launchId && !launchLink) {
|
|
10
|
+
return {
|
|
11
|
+
source: 'unavailable',
|
|
12
|
+
warnings: this.config
|
|
13
|
+
? ['ReportPortal adapter is configured, but the stats file does not expose launch metadata.']
|
|
14
|
+
: [],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (!this.config) {
|
|
18
|
+
return {
|
|
19
|
+
launchId,
|
|
20
|
+
launchLink,
|
|
21
|
+
source: 'local-stats',
|
|
22
|
+
warnings: [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!this.config.project || !this.config.token) {
|
|
26
|
+
return {
|
|
27
|
+
launchId,
|
|
28
|
+
launchLink,
|
|
29
|
+
source: 'local-stats',
|
|
30
|
+
warnings: ['ReportPortal live lookup is disabled because token or project is missing. Using launch metadata from local stats only.'],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (!launchId) {
|
|
34
|
+
return {
|
|
35
|
+
launchLink,
|
|
36
|
+
source: 'local-stats',
|
|
37
|
+
warnings: ['ReportPortal launch ID is missing, so live enrichment was skipped.'],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timeout = setTimeout(() => controller.abort(), 1500);
|
|
43
|
+
const apiBase = normalizeReportPortalApiBase(this.config.endpoint);
|
|
44
|
+
const response = await fetch(`${apiBase}/api/v1/${encodeURIComponent(this.config.project)}/launch/${encodeURIComponent(launchId)}`, {
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `bearer ${this.config.token}`,
|
|
47
|
+
},
|
|
48
|
+
signal: controller.signal,
|
|
49
|
+
});
|
|
50
|
+
clearTimeout(timeout);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
return {
|
|
53
|
+
launchId,
|
|
54
|
+
launchLink,
|
|
55
|
+
source: 'local-stats',
|
|
56
|
+
warnings: [`ReportPortal live lookup failed with status ${response.status}. Using launch metadata from local stats only.`],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const payload = await response.json();
|
|
60
|
+
return {
|
|
61
|
+
launchId,
|
|
62
|
+
launchLink,
|
|
63
|
+
launchStatus: payload.status,
|
|
64
|
+
source: 'live-api',
|
|
65
|
+
warnings: [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
launchId,
|
|
71
|
+
launchLink,
|
|
72
|
+
source: 'local-stats',
|
|
73
|
+
warnings: [`ReportPortal live lookup failed: ${error instanceof Error ? error.message : 'unknown error'}. Using launch metadata from local stats only.`],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function normalizeReportPortalApiBase(endpoint) {
|
|
79
|
+
const normalized = endpoint.replace(/\/+$/, '');
|
|
80
|
+
if (normalized.includes('/api/')) {
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
return normalized.replace(/\/ui(?:\/#.*)?$/, '');
|
|
84
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ConfidenceLevel = 'low' | 'medium' | 'high';
|
|
2
|
+
export type ConfidenceScore = {
|
|
3
|
+
score: number;
|
|
4
|
+
level: ConfidenceLevel;
|
|
5
|
+
rationale: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function createConfidence(score: number, rationale: string[]): ConfidenceScore;
|
|
8
|
+
export declare function confidenceFromEvidence(evidenceCount: number, warnings: string[], modifiers?: string[]): ConfidenceScore;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function createConfidence(score, rationale) {
|
|
2
|
+
const bounded = Math.max(0, Math.min(1, score));
|
|
3
|
+
return {
|
|
4
|
+
score: Number(bounded.toFixed(2)),
|
|
5
|
+
level: bounded >= 0.75 ? 'high' : bounded >= 0.45 ? 'medium' : 'low',
|
|
6
|
+
rationale,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function confidenceFromEvidence(evidenceCount, warnings, modifiers = []) {
|
|
10
|
+
let score = Math.min(0.95, 0.25 + evidenceCount * 0.12);
|
|
11
|
+
score -= warnings.length * 0.08;
|
|
12
|
+
if (modifiers.length > 0) {
|
|
13
|
+
score += Math.min(0.1, modifiers.length * 0.03);
|
|
14
|
+
}
|
|
15
|
+
return createConfidence(score, [
|
|
16
|
+
`${evidenceCount} evidence item(s) contributed to the result.`,
|
|
17
|
+
...modifiers,
|
|
18
|
+
...warnings.map(classifyWarningRationale),
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
function classifyWarningRationale(warning) {
|
|
22
|
+
const normalized = warning.toLowerCase();
|
|
23
|
+
if (normalized.includes('disabled') || normalized.includes('failed') || normalized.includes('unavailable')) {
|
|
24
|
+
return `Unavailable source lowered confidence: ${warning}`;
|
|
25
|
+
}
|
|
26
|
+
if (normalized.includes('stale') || normalized.includes('outdated') || normalized.includes('refresh')) {
|
|
27
|
+
return `Potentially stale data lowered confidence: ${warning}`;
|
|
28
|
+
}
|
|
29
|
+
if (normalized.includes('no ') || normalized.includes('not found') || normalized.includes('missing')) {
|
|
30
|
+
return `Missing source lowered confidence: ${warning}`;
|
|
31
|
+
}
|
|
32
|
+
return `Warning lowered confidence: ${warning}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class OverkillError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
details;
|
|
4
|
+
constructor(code, message, details) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.details = details;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function toWarning(error) {
|
|
11
|
+
if (error instanceof OverkillError) {
|
|
12
|
+
return `${error.code}: ${error.message}`;
|
|
13
|
+
}
|
|
14
|
+
if (error instanceof Error) {
|
|
15
|
+
return error.message;
|
|
16
|
+
}
|
|
17
|
+
return String(error);
|
|
18
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ConfidenceScore } from './confidence.js';
|
|
3
|
+
export declare const evidenceSchema: z.ZodObject<{
|
|
4
|
+
type: z.ZodEnum<["file", "symbol", "test", "artifact", "report", "diff", "config"]>;
|
|
5
|
+
label: z.ZodString;
|
|
6
|
+
path: z.ZodOptional<z.ZodString>;
|
|
7
|
+
locator: z.ZodOptional<z.ZodString>;
|
|
8
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
9
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
12
|
+
label: string;
|
|
13
|
+
path?: string | undefined;
|
|
14
|
+
locator?: string | undefined;
|
|
15
|
+
snippet?: string | undefined;
|
|
16
|
+
metadata?: Record<string, unknown> | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
19
|
+
label: string;
|
|
20
|
+
path?: string | undefined;
|
|
21
|
+
locator?: string | undefined;
|
|
22
|
+
snippet?: string | undefined;
|
|
23
|
+
metadata?: Record<string, unknown> | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const toolOutputSchema: z.ZodObject<{
|
|
26
|
+
summary: z.ZodString;
|
|
27
|
+
evidence: z.ZodArray<z.ZodObject<{
|
|
28
|
+
type: z.ZodEnum<["file", "symbol", "test", "artifact", "report", "diff", "config"]>;
|
|
29
|
+
label: z.ZodString;
|
|
30
|
+
path: z.ZodOptional<z.ZodString>;
|
|
31
|
+
locator: z.ZodOptional<z.ZodString>;
|
|
32
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
33
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
36
|
+
label: string;
|
|
37
|
+
path?: string | undefined;
|
|
38
|
+
locator?: string | undefined;
|
|
39
|
+
snippet?: string | undefined;
|
|
40
|
+
metadata?: Record<string, unknown> | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
43
|
+
label: string;
|
|
44
|
+
path?: string | undefined;
|
|
45
|
+
locator?: string | undefined;
|
|
46
|
+
snippet?: string | undefined;
|
|
47
|
+
metadata?: Record<string, unknown> | undefined;
|
|
48
|
+
}>, "many">;
|
|
49
|
+
recommended_actions: z.ZodArray<z.ZodString, "many">;
|
|
50
|
+
repo_paths: z.ZodArray<z.ZodString, "many">;
|
|
51
|
+
confidence: z.ZodObject<{
|
|
52
|
+
score: z.ZodNumber;
|
|
53
|
+
level: z.ZodEnum<["low", "medium", "high"]>;
|
|
54
|
+
rationale: z.ZodArray<z.ZodString, "many">;
|
|
55
|
+
}, "strip", z.ZodTypeAny, {
|
|
56
|
+
score: number;
|
|
57
|
+
level: "low" | "medium" | "high";
|
|
58
|
+
rationale: string[];
|
|
59
|
+
}, {
|
|
60
|
+
score: number;
|
|
61
|
+
level: "low" | "medium" | "high";
|
|
62
|
+
rationale: string[];
|
|
63
|
+
}>;
|
|
64
|
+
warnings: z.ZodArray<z.ZodString, "many">;
|
|
65
|
+
}, "strip", z.ZodTypeAny, {
|
|
66
|
+
warnings: string[];
|
|
67
|
+
summary: string;
|
|
68
|
+
evidence: {
|
|
69
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
70
|
+
label: string;
|
|
71
|
+
path?: string | undefined;
|
|
72
|
+
locator?: string | undefined;
|
|
73
|
+
snippet?: string | undefined;
|
|
74
|
+
metadata?: Record<string, unknown> | undefined;
|
|
75
|
+
}[];
|
|
76
|
+
recommended_actions: string[];
|
|
77
|
+
repo_paths: string[];
|
|
78
|
+
confidence: {
|
|
79
|
+
score: number;
|
|
80
|
+
level: "low" | "medium" | "high";
|
|
81
|
+
rationale: string[];
|
|
82
|
+
};
|
|
83
|
+
}, {
|
|
84
|
+
warnings: string[];
|
|
85
|
+
summary: string;
|
|
86
|
+
evidence: {
|
|
87
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
88
|
+
label: string;
|
|
89
|
+
path?: string | undefined;
|
|
90
|
+
locator?: string | undefined;
|
|
91
|
+
snippet?: string | undefined;
|
|
92
|
+
metadata?: Record<string, unknown> | undefined;
|
|
93
|
+
}[];
|
|
94
|
+
recommended_actions: string[];
|
|
95
|
+
repo_paths: string[];
|
|
96
|
+
confidence: {
|
|
97
|
+
score: number;
|
|
98
|
+
level: "low" | "medium" | "high";
|
|
99
|
+
rationale: string[];
|
|
100
|
+
};
|
|
101
|
+
}>;
|
|
102
|
+
export type EvidenceItem = z.infer<typeof evidenceSchema>;
|
|
103
|
+
export type NormalizedToolOutput = z.infer<typeof toolOutputSchema>;
|
|
104
|
+
type BuildToolOutputInput = {
|
|
105
|
+
summary: string;
|
|
106
|
+
evidence: EvidenceItem[];
|
|
107
|
+
recommended_actions: string[];
|
|
108
|
+
repo_paths?: string[];
|
|
109
|
+
confidence?: ConfidenceScore;
|
|
110
|
+
warnings?: string[];
|
|
111
|
+
};
|
|
112
|
+
export declare function buildToolOutput(input: BuildToolOutputInput): NormalizedToolOutput;
|
|
113
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { confidenceFromEvidence } from './confidence.js';
|
|
3
|
+
export const evidenceSchema = z.object({
|
|
4
|
+
type: z.enum(['file', 'symbol', 'test', 'artifact', 'report', 'diff', 'config']),
|
|
5
|
+
label: z.string(),
|
|
6
|
+
path: z.string().optional(),
|
|
7
|
+
locator: z.string().optional(),
|
|
8
|
+
snippet: z.string().optional(),
|
|
9
|
+
metadata: z.record(z.unknown()).optional(),
|
|
10
|
+
});
|
|
11
|
+
export const toolOutputSchema = z.object({
|
|
12
|
+
summary: z.string(),
|
|
13
|
+
evidence: z.array(evidenceSchema),
|
|
14
|
+
recommended_actions: z.array(z.string()),
|
|
15
|
+
repo_paths: z.array(z.string()),
|
|
16
|
+
confidence: z.object({
|
|
17
|
+
score: z.number(),
|
|
18
|
+
level: z.enum(['low', 'medium', 'high']),
|
|
19
|
+
rationale: z.array(z.string()),
|
|
20
|
+
}),
|
|
21
|
+
warnings: z.array(z.string()),
|
|
22
|
+
});
|
|
23
|
+
export function buildToolOutput(input) {
|
|
24
|
+
const warnings = input.warnings ?? [];
|
|
25
|
+
const repoPaths = input.repo_paths ?? input.evidence.flatMap((item) => item.path ? [item.path] : []);
|
|
26
|
+
const uniquePaths = [...new Set(repoPaths)];
|
|
27
|
+
return {
|
|
28
|
+
summary: input.summary,
|
|
29
|
+
evidence: input.evidence,
|
|
30
|
+
recommended_actions: input.recommended_actions,
|
|
31
|
+
repo_paths: uniquePaths,
|
|
32
|
+
confidence: input.confidence ?? confidenceFromEvidence(input.evidence.length, warnings),
|
|
33
|
+
warnings,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function analyzeDiffImpact(input: {
|
|
3
|
+
changedFiles: string[];
|
|
4
|
+
}, context: AppContext): Promise<{
|
|
5
|
+
warnings: string[];
|
|
6
|
+
summary: string;
|
|
7
|
+
evidence: {
|
|
8
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
9
|
+
label: string;
|
|
10
|
+
path?: string | undefined;
|
|
11
|
+
locator?: string | undefined;
|
|
12
|
+
snippet?: string | undefined;
|
|
13
|
+
metadata?: Record<string, unknown> | undefined;
|
|
14
|
+
}[];
|
|
15
|
+
recommended_actions: string[];
|
|
16
|
+
repo_paths: string[];
|
|
17
|
+
confidence: {
|
|
18
|
+
score: number;
|
|
19
|
+
level: "low" | "medium" | "high";
|
|
20
|
+
rationale: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { normalizeChangedFiles } from '../connectors/diffConnector.js';
|
|
3
|
+
import { queryDocuments } from '../knowledge/repositories.js';
|
|
4
|
+
import { extractTestAssets } from '../indexer/extractors/testAssetExtractor.js';
|
|
5
|
+
import { basenameWithoutExt } from '../utils/pathUtils.js';
|
|
6
|
+
import { ensureIndexed, evidenceFile, output } from './shared.js';
|
|
7
|
+
export async function analyzeDiffImpact(input, context) {
|
|
8
|
+
const changedFiles = normalizeChangedFiles(input.changedFiles);
|
|
9
|
+
const featureHints = changedFiles.map((filePath) => deriveFeatureHint(filePath)).filter(Boolean);
|
|
10
|
+
const uniqueFeatures = [...new Set(featureHints)];
|
|
11
|
+
const evidence = changedFiles.map((filePath) => evidenceFile('changed file', filePath));
|
|
12
|
+
const impactedSpecs = new Set();
|
|
13
|
+
await ensureIndexed(context);
|
|
14
|
+
for (const feature of uniqueFeatures) {
|
|
15
|
+
const indexed = queryDocuments(context.database, feature, {
|
|
16
|
+
repoKind: 'autotests2',
|
|
17
|
+
sourceKinds: ['spec'],
|
|
18
|
+
limit: 12,
|
|
19
|
+
});
|
|
20
|
+
for (const item of indexed) {
|
|
21
|
+
impactedSpecs.add(item.path);
|
|
22
|
+
}
|
|
23
|
+
if (indexed.length > 0) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const assets = await extractTestAssets({
|
|
27
|
+
root: context.config.repos.autotests2,
|
|
28
|
+
featureHint: feature,
|
|
29
|
+
});
|
|
30
|
+
for (const spec of assets.specs.slice(0, 8)) {
|
|
31
|
+
impactedSpecs.add(spec.path);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return output(`Diff impact analysis mapped ${changedFiles.length} changed file(s) to ${impactedSpecs.size} affected spec candidate(s).`, [
|
|
35
|
+
...evidence,
|
|
36
|
+
...[...impactedSpecs].slice(0, 10).map((filePath) => evidenceFile('impacted spec', filePath)),
|
|
37
|
+
], [
|
|
38
|
+
'Rerun directly impacted specs before broader regression.',
|
|
39
|
+
'Inspect widget events and container variants when the diff touches shared widget files.',
|
|
40
|
+
], impactedSpecs.size === 0 ? ['No impacted specs were found via naming heuristics.'] : []);
|
|
41
|
+
}
|
|
42
|
+
function deriveFeatureHint(filePath) {
|
|
43
|
+
const base = basenameWithoutExt(filePath);
|
|
44
|
+
if (/caption/i.test(base)) {
|
|
45
|
+
return 'caption';
|
|
46
|
+
}
|
|
47
|
+
if (/date.*range/i.test(base) || /daterange/i.test(base)) {
|
|
48
|
+
return 'dateRange';
|
|
49
|
+
}
|
|
50
|
+
const parent = path.basename(path.dirname(filePath));
|
|
51
|
+
return parent;
|
|
52
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function analyzeTestGaps(input: {
|
|
3
|
+
widget: string;
|
|
4
|
+
}, context: AppContext): Promise<{
|
|
5
|
+
warnings: string[];
|
|
6
|
+
summary: string;
|
|
7
|
+
evidence: {
|
|
8
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
9
|
+
label: string;
|
|
10
|
+
path?: string | undefined;
|
|
11
|
+
locator?: string | undefined;
|
|
12
|
+
snippet?: string | undefined;
|
|
13
|
+
metadata?: Record<string, unknown> | undefined;
|
|
14
|
+
}[];
|
|
15
|
+
recommended_actions: string[];
|
|
16
|
+
repo_paths: string[];
|
|
17
|
+
confidence: {
|
|
18
|
+
score: number;
|
|
19
|
+
level: "low" | "medium" | "high";
|
|
20
|
+
rationale: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { extractFrontendContract } from '../indexer/extractors/frontendContractExtractor.js';
|
|
2
|
+
import { extractFrontendRuntimeLogic } from '../indexer/extractors/frontendRuntimeExtractor.js';
|
|
3
|
+
import { extractTestAssets } from '../indexer/extractors/testAssetExtractor.js';
|
|
4
|
+
import { unique } from '../utils/pathUtils.js';
|
|
5
|
+
import { evidenceFile, output } from './shared.js';
|
|
6
|
+
export async function analyzeTestGaps(input, context) {
|
|
7
|
+
const [contract, runtime, assets] = await Promise.all([
|
|
8
|
+
extractFrontendContract({
|
|
9
|
+
root: context.config.repos.caseplatformWeb,
|
|
10
|
+
widgetHint: input.widget,
|
|
11
|
+
}),
|
|
12
|
+
extractFrontendRuntimeLogic({
|
|
13
|
+
root: context.config.repos.caseplatformWeb,
|
|
14
|
+
widgetHint: input.widget,
|
|
15
|
+
}),
|
|
16
|
+
extractTestAssets({
|
|
17
|
+
root: context.config.repos.autotests2,
|
|
18
|
+
featureHint: input.widget,
|
|
19
|
+
}),
|
|
20
|
+
]);
|
|
21
|
+
const specText = assets.specs.map((spec) => spec.body).join('\n');
|
|
22
|
+
const uncoveredMethods = contract.methods.filter((method) => !specText.includes(method));
|
|
23
|
+
const uncoveredEvents = contract.events.filter((eventName) => !specText.includes(eventName));
|
|
24
|
+
const uncoveredBranches = runtime.runtimeSignals
|
|
25
|
+
.filter((signal) => signal.kind === 'branch')
|
|
26
|
+
.filter((signal) => !specText.includes(signal.name));
|
|
27
|
+
const repoPaths = unique([
|
|
28
|
+
...contract.files,
|
|
29
|
+
...uncoveredBranches.map((branch) => branch.path),
|
|
30
|
+
]);
|
|
31
|
+
const gapCount = uncoveredMethods.length + uncoveredEvents.length + uncoveredBranches.length;
|
|
32
|
+
const warnings = gapCount === 0 ? ['No obvious gaps detected; heuristics may be missing hidden branches.'] : [];
|
|
33
|
+
return output(`Gap analysis for ${input.widget}: ${gapCount} likely gap signal(s) detected.`, [
|
|
34
|
+
...uncoveredMethods.slice(0, 4).map((method) => ({ type: 'symbol', label: `uncovered method ${method}`, snippet: method })),
|
|
35
|
+
...uncoveredEvents.slice(0, 4).map((eventName) => ({ type: 'symbol', label: `uncovered event ${eventName}`, snippet: eventName })),
|
|
36
|
+
...uncoveredBranches.slice(0, 4).map((branch) => evidenceFile(`uncovered branch ${branch.name}`, branch.path, branch.detail)),
|
|
37
|
+
], [
|
|
38
|
+
'Add assertions around uncovered runtime signals before expanding new feature coverage.',
|
|
39
|
+
'Review container variants separately when only the base widget appears covered.',
|
|
40
|
+
], warnings, repoPaths);
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function buildRerunScope(input: {
|
|
3
|
+
changedFiles?: string[];
|
|
4
|
+
statsFile: string;
|
|
5
|
+
}, context: AppContext): Promise<{
|
|
6
|
+
warnings: string[];
|
|
7
|
+
summary: string;
|
|
8
|
+
evidence: {
|
|
9
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
10
|
+
label: string;
|
|
11
|
+
path?: string | undefined;
|
|
12
|
+
locator?: string | undefined;
|
|
13
|
+
snippet?: string | undefined;
|
|
14
|
+
metadata?: Record<string, unknown> | undefined;
|
|
15
|
+
}[];
|
|
16
|
+
recommended_actions: string[];
|
|
17
|
+
repo_paths: string[];
|
|
18
|
+
confidence: {
|
|
19
|
+
score: number;
|
|
20
|
+
level: "low" | "medium" | "high";
|
|
21
|
+
rationale: string[];
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { analyzeDiffImpact } from './analyzeDiffImpact.js';
|
|
2
|
+
import { output } from './shared.js';
|
|
3
|
+
export async function buildRerunScope(input, context) {
|
|
4
|
+
const stats = context.ciArtifacts.readStats(input.statsFile);
|
|
5
|
+
const diffImpact = input.changedFiles?.length
|
|
6
|
+
? await analyzeDiffImpact({ changedFiles: input.changedFiles }, context)
|
|
7
|
+
: null;
|
|
8
|
+
const rerunSpecs = [
|
|
9
|
+
...(stats.failed_specs ?? []),
|
|
10
|
+
...(diffImpact?.repo_paths.filter((repoPath) => repoPath.endsWith('.cy.js')) ?? []),
|
|
11
|
+
];
|
|
12
|
+
const uniqueSpecs = [...new Set(rerunSpecs)];
|
|
13
|
+
return output(`Built rerun scope with ${uniqueSpecs.length} spec candidate(s).`, uniqueSpecs.map((repoPath) => ({ type: 'test', label: 'rerun spec', path: repoPath })), [
|
|
14
|
+
'Run the union of failed specs and impacted specs first.',
|
|
15
|
+
'Keep the rerun pack minimal before escalating to a broader suite.',
|
|
16
|
+
], uniqueSpecs.length === 0 ? ['No explicit rerun spec candidates were discovered.'] : []);
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function buildSetupTeardownPlan(input: {
|
|
3
|
+
area: string;
|
|
4
|
+
feature: string;
|
|
5
|
+
}, context: AppContext): Promise<{
|
|
6
|
+
warnings: string[];
|
|
7
|
+
summary: string;
|
|
8
|
+
evidence: {
|
|
9
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
10
|
+
label: string;
|
|
11
|
+
path?: string | undefined;
|
|
12
|
+
locator?: string | undefined;
|
|
13
|
+
snippet?: string | undefined;
|
|
14
|
+
metadata?: Record<string, unknown> | undefined;
|
|
15
|
+
}[];
|
|
16
|
+
recommended_actions: string[];
|
|
17
|
+
repo_paths: string[];
|
|
18
|
+
confidence: {
|
|
19
|
+
score: number;
|
|
20
|
+
level: "low" | "medium" | "high";
|
|
21
|
+
rationale: string[];
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { evidenceFile, output } from './shared.js';
|
|
2
|
+
export async function buildSetupTeardownPlan(input, context) {
|
|
3
|
+
const docs = context.autotests2.getAreaDocs();
|
|
4
|
+
const initPattern = input.area === 'constructor' ? 'initConf()' : 'init()';
|
|
5
|
+
return output(`Setup/teardown for ${input.feature} in ${input.area}: bootstrap with ${initPattern}, reuse existing auth and wait helpers, and keep teardown inside existing commands.`, [
|
|
6
|
+
evidenceFile('base support', input.area === 'constructor' ? `${context.config.repos.autotests2}/cypress/support/baseConf.js` : `${context.config.repos.autotests2}/cypress/support/base.js`),
|
|
7
|
+
evidenceFile('testing rules', docs.testingPath, docs.testingText.slice(0, 220)),
|
|
8
|
+
], [
|
|
9
|
+
`Call ${initPattern} at the describe level.`,
|
|
10
|
+
'Use cy.waitFormsInFr / cy.waitVcmForm instead of ad-hoc sleeps.',
|
|
11
|
+
'Reuse auth helpers from cypress/support/auth.commands.js before adding setup code.',
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function findExistingTestAssets(input: {
|
|
3
|
+
query: string;
|
|
4
|
+
}, context: AppContext): Promise<{
|
|
5
|
+
warnings: string[];
|
|
6
|
+
summary: string;
|
|
7
|
+
evidence: {
|
|
8
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
9
|
+
label: string;
|
|
10
|
+
path?: string | undefined;
|
|
11
|
+
locator?: string | undefined;
|
|
12
|
+
snippet?: string | undefined;
|
|
13
|
+
metadata?: Record<string, unknown> | undefined;
|
|
14
|
+
}[];
|
|
15
|
+
recommended_actions: string[];
|
|
16
|
+
repo_paths: string[];
|
|
17
|
+
confidence: {
|
|
18
|
+
score: number;
|
|
19
|
+
level: "low" | "medium" | "high";
|
|
20
|
+
rationale: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { queryDocuments } from '../knowledge/repositories.js';
|
|
2
|
+
import { extractTestAssets } from '../indexer/extractors/testAssetExtractor.js';
|
|
3
|
+
import { truncate } from '../utils/textUtils.js';
|
|
4
|
+
import { ensureIndexed, evidenceFile, output } from './shared.js';
|
|
5
|
+
export async function findExistingTestAssets(input, context) {
|
|
6
|
+
await ensureIndexed(context);
|
|
7
|
+
const indexedPrimary = queryDocuments(context.database, input.query, {
|
|
8
|
+
repoKind: 'autotests2',
|
|
9
|
+
sourceKinds: ['spec', 'command', 'helper', 'selector'],
|
|
10
|
+
limit: 12,
|
|
11
|
+
});
|
|
12
|
+
const indexedReusable = queryDocuments(context.database, 'Cypress Commands helper selector getWidget selectForm waitVcmForm', {
|
|
13
|
+
repoKind: 'autotests2',
|
|
14
|
+
sourceKinds: ['command', 'helper', 'selector'],
|
|
15
|
+
limit: 6,
|
|
16
|
+
});
|
|
17
|
+
const indexed = dedupeByPath([...indexedPrimary, ...indexedReusable]);
|
|
18
|
+
const indexedEvidence = indexed.map((item) => evidenceFile(item.title || 'indexed result', item.path, truncate(item.body, 220), {
|
|
19
|
+
retrieval: 'knowledge-store',
|
|
20
|
+
}));
|
|
21
|
+
if (indexedEvidence.length > 0) {
|
|
22
|
+
return output(`Found ${indexedEvidence.length} indexed asset candidate(s) related to '${input.query}'.`, indexedEvidence, [
|
|
23
|
+
'Reuse indexed specs and commands before creating a new draft.',
|
|
24
|
+
'Fall back to direct scan only when indexed evidence is insufficient.',
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
const assets = await extractTestAssets({
|
|
28
|
+
root: context.config.repos.autotests2,
|
|
29
|
+
featureHint: input.query,
|
|
30
|
+
});
|
|
31
|
+
const evidence = [
|
|
32
|
+
...assets.specs.slice(0, 6).map((spec) => evidenceFile('matched spec', spec.path, truncate(spec.body, 220), { score: spec.score, retrieval: 'direct-scan' })),
|
|
33
|
+
...assets.commands.slice(0, 4).map((command) => evidenceFile(`command ${command.name}`, command.path, undefined, { retrieval: 'direct-scan' })),
|
|
34
|
+
...assets.helpers.slice(0, 4).map((helper) => evidenceFile(`helper ${helper.name}`, helper.path, undefined, { retrieval: 'direct-scan' })),
|
|
35
|
+
];
|
|
36
|
+
return output(`Found ${assets.specs.length} specs, ${assets.commands.length} commands, and ${assets.helpers.length} helper candidates related to '${input.query}'.`, evidence, [
|
|
37
|
+
'Reuse matching specs before creating a new draft.',
|
|
38
|
+
'Prefer existing Cypress commands and helpers over ad-hoc Cypress chains.',
|
|
39
|
+
], assets.warnings);
|
|
40
|
+
}
|
|
41
|
+
function dedupeByPath(items) {
|
|
42
|
+
const seen = new Set();
|
|
43
|
+
return items.filter((item) => {
|
|
44
|
+
if (seen.has(item.path)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
seen.add(item.path);
|
|
48
|
+
return true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function findFrontendContract(input: {
|
|
3
|
+
widget: string;
|
|
4
|
+
}, context: AppContext): Promise<{
|
|
5
|
+
warnings: string[];
|
|
6
|
+
summary: string;
|
|
7
|
+
evidence: {
|
|
8
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
9
|
+
label: string;
|
|
10
|
+
path?: string | undefined;
|
|
11
|
+
locator?: string | undefined;
|
|
12
|
+
snippet?: string | undefined;
|
|
13
|
+
metadata?: Record<string, unknown> | undefined;
|
|
14
|
+
}[];
|
|
15
|
+
recommended_actions: string[];
|
|
16
|
+
repo_paths: string[];
|
|
17
|
+
confidence: {
|
|
18
|
+
score: number;
|
|
19
|
+
level: "low" | "medium" | "high";
|
|
20
|
+
rationale: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { extractFrontendContract } from '../indexer/extractors/frontendContractExtractor.js';
|
|
2
|
+
import { evidenceFile, output } from './shared.js';
|
|
3
|
+
export async function findFrontendContract(input, context) {
|
|
4
|
+
const contract = await extractFrontendContract({
|
|
5
|
+
root: context.config.repos.caseplatformWeb,
|
|
6
|
+
widgetHint: input.widget,
|
|
7
|
+
});
|
|
8
|
+
const evidence = contract.snippets.map((snippet) => evidenceFile(`${input.widget} ${snippet.label}`, snippet.path, snippet.snippet, { label: snippet.label }));
|
|
9
|
+
return output(`${input.widget} exposes ${contract.properties.length} properties, ${contract.events.length} events, and ${contract.methods.length} methods.`, evidence, [
|
|
10
|
+
`Use ${contract.properties[0] ?? 'the extracted properties'} as the starting point for spec assertions.`,
|
|
11
|
+
'Cross-check runtime branches before concluding the widget is fully covered.',
|
|
12
|
+
], contract.warnings, contract.files);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AppContext } from '../appContext.js';
|
|
2
|
+
export declare function findFrontendRuntimeLogic(input: {
|
|
3
|
+
widget: string;
|
|
4
|
+
}, context: AppContext): Promise<{
|
|
5
|
+
warnings: string[];
|
|
6
|
+
summary: string;
|
|
7
|
+
evidence: {
|
|
8
|
+
type: "symbol" | "config" | "file" | "test" | "artifact" | "report" | "diff";
|
|
9
|
+
label: string;
|
|
10
|
+
path?: string | undefined;
|
|
11
|
+
locator?: string | undefined;
|
|
12
|
+
snippet?: string | undefined;
|
|
13
|
+
metadata?: Record<string, unknown> | undefined;
|
|
14
|
+
}[];
|
|
15
|
+
recommended_actions: string[];
|
|
16
|
+
repo_paths: string[];
|
|
17
|
+
confidence: {
|
|
18
|
+
score: number;
|
|
19
|
+
level: "low" | "medium" | "high";
|
|
20
|
+
rationale: string[];
|
|
21
|
+
};
|
|
22
|
+
}>;
|