@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
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,10 @@ const path_1 = require("path");
|
|
|
8
8
|
const config_js_1 = require("./agent/config.js");
|
|
9
9
|
const anthropic_provider_js_1 = require("./anthropic_provider.js");
|
|
10
10
|
const provider_interface_js_1 = require("./provider_interface.js");
|
|
11
|
-
const runner_js_1 = require("./agent/runner.js");
|
|
12
11
|
const plan_js_1 = require("./agent/plan.js");
|
|
13
|
-
const
|
|
12
|
+
const impact_engine_js_1 = require("./engine/impact_engine.js");
|
|
13
|
+
const plan_builder_js_1 = require("./engine/plan_builder.js");
|
|
14
|
+
const git_js_1 = require("./agent/git.js");
|
|
14
15
|
const feedback_js_1 = require("./agent/feedback.js");
|
|
15
16
|
const handoff_js_1 = require("./agent/handoff.js");
|
|
16
17
|
const traceability_ingest_js_1 = require("./agent/traceability_ingest.js");
|
|
@@ -61,13 +62,9 @@ function printUsage() {
|
|
|
61
62
|
console.log([
|
|
62
63
|
'Usage:',
|
|
63
64
|
' e2e-ai-agents impact --path <app-root> [options]',
|
|
64
|
-
' e2e-ai-agents gap --path <app-root> [options]',
|
|
65
65
|
' e2e-ai-agents plan --path <app-root> [options]',
|
|
66
|
-
' e2e-ai-agents generate --path <app-root> [options]',
|
|
67
|
-
' e2e-ai-agents heal --path <app-root> --traceability-report <json> [options]',
|
|
68
66
|
' e2e-ai-agents suggest --path <app-root> [options]',
|
|
69
|
-
' e2e-ai-agents
|
|
70
|
-
' e2e-ai-agents auto-heal-pr --path <app-root> [options]',
|
|
67
|
+
' e2e-ai-agents heal --path <app-root> --traceability-report <json> [options]',
|
|
71
68
|
' e2e-ai-agents finalize-generated-tests --path <app-root> [options]',
|
|
72
69
|
' e2e-ai-agents feedback --path <app-root> --feedback-input <json>',
|
|
73
70
|
' e2e-ai-agents traceability-capture --path <app-root> --traceability-report <json>',
|
|
@@ -146,13 +143,9 @@ function parseArgs(argv) {
|
|
|
146
143
|
}
|
|
147
144
|
const command = argv[0];
|
|
148
145
|
if (command === 'impact'
|
|
149
|
-
|| command === 'gap'
|
|
150
146
|
|| command === 'plan'
|
|
151
|
-
|| command === 'generate'
|
|
152
147
|
|| command === 'heal'
|
|
153
148
|
|| command === 'suggest'
|
|
154
|
-
|| command === 'approve-and-generate'
|
|
155
|
-
|| command === 'auto-heal-pr'
|
|
156
149
|
|| command === 'finalize-generated-tests'
|
|
157
150
|
|| command === 'feedback'
|
|
158
151
|
|| command === 'traceability-capture'
|
|
@@ -761,117 +754,6 @@ async function main() {
|
|
|
761
754
|
}
|
|
762
755
|
return;
|
|
763
756
|
}
|
|
764
|
-
if (args.command === 'auto-heal-pr') {
|
|
765
|
-
if (!args.path && !autoConfig) {
|
|
766
|
-
// eslint-disable-next-line no-console
|
|
767
|
-
console.error('Error: --path is required for auto-heal-pr command');
|
|
768
|
-
process.exit(1);
|
|
769
|
-
}
|
|
770
|
-
const { config } = (0, config_js_1.resolveConfig)(process.cwd(), autoConfig, {
|
|
771
|
-
path: args.path,
|
|
772
|
-
profile: args.profile,
|
|
773
|
-
testsRoot: args.testsRoot,
|
|
774
|
-
mode: 'gap',
|
|
775
|
-
framework: args.framework,
|
|
776
|
-
timeLimitMinutes: args.timeLimitMinutes,
|
|
777
|
-
budget: {
|
|
778
|
-
maxUSD: args.budgetUSD,
|
|
779
|
-
maxTokens: args.budgetTokens,
|
|
780
|
-
},
|
|
781
|
-
testPatterns: args.testPatterns,
|
|
782
|
-
flowPatterns: args.flowPatterns,
|
|
783
|
-
flowExclude: args.flowExclude,
|
|
784
|
-
flowCatalogPath: args.flowCatalogPath,
|
|
785
|
-
specPDF: args.specPDF,
|
|
786
|
-
gitSince: args.gitSince,
|
|
787
|
-
pipeline: {
|
|
788
|
-
enabled: true,
|
|
789
|
-
scenarios: args.pipelineScenarios,
|
|
790
|
-
outputDir: args.pipelineOutput,
|
|
791
|
-
baseUrl: args.pipelineBaseUrl,
|
|
792
|
-
browser: args.pipelineBrowser,
|
|
793
|
-
headless: args.pipelineHeadless,
|
|
794
|
-
project: args.pipelineProject,
|
|
795
|
-
parallel: args.pipelineParallel,
|
|
796
|
-
dryRun: args.pipelineDryRun,
|
|
797
|
-
mcp: args.pipelineMcp,
|
|
798
|
-
mcpAllowFallback: args.pipelineMcpAllowFallback,
|
|
799
|
-
mcpOnly: args.pipelineMcpOnly,
|
|
800
|
-
},
|
|
801
|
-
llmProvider: args.llmProvider,
|
|
802
|
-
});
|
|
803
|
-
if (args.allowFallback) {
|
|
804
|
-
config.impact.allowFallback = true;
|
|
805
|
-
}
|
|
806
|
-
await (0, runner_js_1.runGap)(config, { apply: true });
|
|
807
|
-
const reportRoot = config.testsRoot || config.path;
|
|
808
|
-
if (args.traceabilityReportPath) {
|
|
809
|
-
const unstableSpecs = (0, playwright_report_js_1.extractPlaywrightUnstableSpecs)(args.traceabilityReportPath, [reportRoot, config.path]);
|
|
810
|
-
if (unstableSpecs.length > 0) {
|
|
811
|
-
const targetedSummary = (0, pipeline_js_1.runTargetedSpecHeal)(reportRoot, unstableSpecs.map((spec) => ({
|
|
812
|
-
specPath: spec.specPath,
|
|
813
|
-
status: spec.status,
|
|
814
|
-
reason: `Playwright report: failingTests=${spec.failingTests}, flakyTests=${spec.flakyTests}`,
|
|
815
|
-
})), {
|
|
816
|
-
...config.pipeline,
|
|
817
|
-
enabled: true,
|
|
818
|
-
heal: true,
|
|
819
|
-
});
|
|
820
|
-
const healedCount = targetedSummary.results.filter((result) => result.healStatus === 'success').length;
|
|
821
|
-
// eslint-disable-next-line no-console
|
|
822
|
-
console.log(`Auto-heal targeted unstable specs: ${unstableSpecs.length} (healed=${healedCount})`);
|
|
823
|
-
if (targetedSummary.warnings.length > 0) {
|
|
824
|
-
// eslint-disable-next-line no-console
|
|
825
|
-
console.log(`Auto-heal warnings: ${targetedSummary.warnings.join(' | ')}`);
|
|
826
|
-
}
|
|
827
|
-
const gapPath = (0, path_1.join)(reportRoot, '.e2e-ai-agents', 'gap.json');
|
|
828
|
-
if ((0, fs_1.existsSync)(gapPath)) {
|
|
829
|
-
const gap = JSON.parse((0, fs_1.readFileSync)(gapPath, 'utf-8'));
|
|
830
|
-
const existingResults = Array.isArray(gap.pipeline?.results) ? gap.pipeline?.results : [];
|
|
831
|
-
const existingWarnings = Array.isArray(gap.pipeline?.warnings) ? gap.pipeline?.warnings : [];
|
|
832
|
-
gap.pipeline = {
|
|
833
|
-
runner: gap.pipeline?.runner || targetedSummary.runner,
|
|
834
|
-
results: [...existingResults, ...targetedSummary.results],
|
|
835
|
-
warnings: Array.from(new Set([...(existingWarnings || []), ...targetedSummary.warnings])),
|
|
836
|
-
};
|
|
837
|
-
(0, fs_1.writeFileSync)(gapPath, `${JSON.stringify(gap, null, 2)}\n`, 'utf-8');
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
else {
|
|
841
|
-
// eslint-disable-next-line no-console
|
|
842
|
-
console.log('Auto-heal targeted unstable specs: 0');
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
const branchSuffix = new Date().toISOString().replace(/[:.]/g, '-');
|
|
846
|
-
const result = (0, handoff_js_1.finalizeGeneratedTests)({
|
|
847
|
-
appPath: config.path,
|
|
848
|
-
testsRoot: reportRoot,
|
|
849
|
-
branch: args.branch || `auto-heal-${branchSuffix}`,
|
|
850
|
-
commitMessage: args.commitMessage || 'test(e2e): auto-heal generated specs',
|
|
851
|
-
createPr: true,
|
|
852
|
-
prTitle: args.prTitle || 'test(e2e): auto-heal generated specs',
|
|
853
|
-
prBody: args.prBody || 'Automated e2e-heal run generated by @yasserkhanorg/e2e-agents.',
|
|
854
|
-
baseBranch: args.prBase || 'master',
|
|
855
|
-
dryRun: args.dryRun,
|
|
856
|
-
});
|
|
857
|
-
// eslint-disable-next-line no-console
|
|
858
|
-
console.log(`Auto-heal repo root: ${result.repoRoot}`);
|
|
859
|
-
// eslint-disable-next-line no-console
|
|
860
|
-
console.log(`Auto-heal branch: ${result.branch}`);
|
|
861
|
-
// eslint-disable-next-line no-console
|
|
862
|
-
console.log(`Auto-heal staged paths: ${result.stagedPaths.join(', ') || 'none'}`);
|
|
863
|
-
// eslint-disable-next-line no-console
|
|
864
|
-
console.log(`Auto-heal commit: ${result.committed ? 'created' : 'skipped'}`);
|
|
865
|
-
if (result.commitSha) {
|
|
866
|
-
// eslint-disable-next-line no-console
|
|
867
|
-
console.log(`Auto-heal commit sha: ${result.commitSha}`);
|
|
868
|
-
}
|
|
869
|
-
if (result.prUrl) {
|
|
870
|
-
// eslint-disable-next-line no-console
|
|
871
|
-
console.log(`Auto-heal PR: ${result.prUrl}`);
|
|
872
|
-
}
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
757
|
if (args.command === 'heal') {
|
|
876
758
|
if (!args.path && !autoConfig) {
|
|
877
759
|
// eslint-disable-next-line no-console
|
|
@@ -936,13 +818,11 @@ async function main() {
|
|
|
936
818
|
printUsage();
|
|
937
819
|
process.exit(1);
|
|
938
820
|
}
|
|
939
|
-
const
|
|
940
|
-
const forceAIPipelineFromApproval = args.command === 'approve-and-generate' || args.command === 'generate';
|
|
941
|
-
const { config, configPath } = (0, config_js_1.resolveConfig)(process.cwd(), autoConfig, {
|
|
821
|
+
const { config } = (0, config_js_1.resolveConfig)(process.cwd(), autoConfig, {
|
|
942
822
|
path: args.path,
|
|
943
823
|
profile: args.profile,
|
|
944
824
|
testsRoot: args.testsRoot,
|
|
945
|
-
mode:
|
|
825
|
+
mode: 'impact',
|
|
946
826
|
framework: args.framework,
|
|
947
827
|
timeLimitMinutes: args.timeLimitMinutes,
|
|
948
828
|
budget: {
|
|
@@ -956,7 +836,7 @@ async function main() {
|
|
|
956
836
|
specPDF: args.specPDF,
|
|
957
837
|
gitSince: args.gitSince,
|
|
958
838
|
llmProvider: args.llmProvider,
|
|
959
|
-
pipeline:
|
|
839
|
+
pipeline: args.pipeline
|
|
960
840
|
? {
|
|
961
841
|
enabled: true,
|
|
962
842
|
scenarios: args.pipelineScenarios,
|
|
@@ -967,9 +847,9 @@ async function main() {
|
|
|
967
847
|
project: args.pipelineProject,
|
|
968
848
|
parallel: args.pipelineParallel,
|
|
969
849
|
dryRun: args.pipelineDryRun,
|
|
970
|
-
mcp: args.pipelineMcp
|
|
850
|
+
mcp: args.pipelineMcp,
|
|
971
851
|
mcpAllowFallback: args.pipelineMcpAllowFallback,
|
|
972
|
-
mcpOnly: args.pipelineMcpOnly
|
|
852
|
+
mcpOnly: args.pipelineMcpOnly,
|
|
973
853
|
mcpCommandTimeoutMs: args.pipelineMcpTimeoutMs,
|
|
974
854
|
mcpRetries: args.pipelineMcpRetries,
|
|
975
855
|
}
|
|
@@ -990,45 +870,41 @@ async function main() {
|
|
|
990
870
|
}
|
|
991
871
|
: undefined,
|
|
992
872
|
});
|
|
993
|
-
if (args.allowFallback) {
|
|
994
|
-
config.impact.allowFallback = true;
|
|
995
|
-
}
|
|
996
873
|
if (args.command === 'impact') {
|
|
997
|
-
await (0, runner_js_1.runImpact)(config, { apply: args.apply });
|
|
998
|
-
return;
|
|
999
|
-
}
|
|
1000
|
-
if (args.command === 'suggest' || args.command === 'plan') {
|
|
1001
874
|
const reportRoot = config.testsRoot || config.path;
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
875
|
+
const gitResult = (0, git_js_1.getChangedFiles)(config.path, config.git.since, { includeUncommitted: config.git.includeUncommitted });
|
|
876
|
+
const impactResult = (0, impact_engine_js_1.analyzeImpact)(gitResult.files, {
|
|
877
|
+
testsRoot: reportRoot,
|
|
878
|
+
routeFamilies: config.routeFamilies,
|
|
879
|
+
});
|
|
880
|
+
// eslint-disable-next-line no-console
|
|
881
|
+
console.log(`Impact: ${impactResult.changedFiles.length} changed files → ${impactResult.impactedFeatures.length} features impacted`);
|
|
882
|
+
// eslint-disable-next-line no-console
|
|
883
|
+
console.log(`Unbound files: ${impactResult.unboundFiles.length}`);
|
|
884
|
+
for (const f of impactResult.impactedFeatures) {
|
|
885
|
+
const label = f.featureId || f.familyId;
|
|
886
|
+
// eslint-disable-next-line no-console
|
|
887
|
+
console.log(` [${f.priority}] ${label}: ${f.coverageStatus} (PW=${f.playwrightSpecs.length}, Cy=${f.cypressSpecs.length})`);
|
|
888
|
+
}
|
|
889
|
+
if (impactResult.warnings.length > 0) {
|
|
890
|
+
for (const w of impactResult.warnings) {
|
|
1010
891
|
// eslint-disable-next-line no-console
|
|
1011
|
-
console.warn(`
|
|
1012
|
-
}
|
|
1013
|
-
else {
|
|
1014
|
-
throw err;
|
|
892
|
+
console.warn(` Warning: ${w}`);
|
|
1015
893
|
}
|
|
1016
894
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
const
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
appPath: config.path,
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (args.command === 'suggest' || args.command === 'plan') {
|
|
898
|
+
const reportRoot = config.testsRoot || config.path;
|
|
899
|
+
const gitResult = (0, git_js_1.getChangedFiles)(config.path, config.git.since, { includeUncommitted: config.git.includeUncommitted });
|
|
900
|
+
const impactResult = (0, impact_engine_js_1.analyzeImpact)(gitResult.files, {
|
|
1024
901
|
testsRoot: reportRoot,
|
|
1025
|
-
|
|
1026
|
-
configPath,
|
|
902
|
+
routeFamilies: config.routeFamilies,
|
|
1027
903
|
});
|
|
1028
|
-
const plan = (0,
|
|
1029
|
-
const planPath = (0,
|
|
1030
|
-
const summaryMarkdown = (0,
|
|
1031
|
-
const summaryPath = (0,
|
|
904
|
+
const plan = (0, plan_builder_js_1.buildPlanFromImpact)(impactResult, config.policy);
|
|
905
|
+
const planPath = (0, plan_builder_js_1.writePlanReport)(reportRoot, plan);
|
|
906
|
+
const summaryMarkdown = (0, plan_builder_js_1.renderCiSummaryMarkdown)(plan);
|
|
907
|
+
const summaryPath = (0, plan_builder_js_1.writeCiSummary)(reportRoot, summaryMarkdown, args.ciCommentPath);
|
|
1032
908
|
const metrics = (0, plan_js_1.appendPlanMetrics)(reportRoot, plan);
|
|
1033
909
|
const ghaOutput = args.githubOutputPath || process.env.GITHUB_OUTPUT;
|
|
1034
910
|
if (ghaOutput) {
|
|
@@ -1056,25 +932,16 @@ async function main() {
|
|
|
1056
932
|
console.log(`CI summary: ${summaryPath}`);
|
|
1057
933
|
// eslint-disable-next-line no-console
|
|
1058
934
|
console.log(`Plan metrics: ${metrics.summaryPath}`);
|
|
1059
|
-
if (plan.nextActions) {
|
|
1060
|
-
// eslint-disable-next-line no-console
|
|
1061
|
-
console.log(`Next action (run existing): ${plan.nextActions.runRecommendedTests || plan.nextActions.runSmokeSuite}`);
|
|
1062
|
-
// eslint-disable-next-line no-console
|
|
1063
|
-
console.log(`Next action (approve + generate): ${plan.nextActions.approveAndGenerate || plan.nextActions.generateMissingTests}`);
|
|
1064
|
-
// eslint-disable-next-line no-console
|
|
1065
|
-
console.log(`Next action (heal): ${plan.nextActions.healGeneratedTests}`);
|
|
1066
|
-
}
|
|
1067
935
|
const failOnLegacyFlag = args.failOnMustAddTests && plan.decision.action === 'must-add-tests';
|
|
1068
936
|
if (failOnLegacyFlag || plan.enforcement.shouldFail) {
|
|
1069
937
|
process.exit(2);
|
|
1070
938
|
}
|
|
1071
939
|
return;
|
|
1072
940
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
await (0, runner_js_1.runGap)(config, { apply: args.apply });
|
|
941
|
+
// eslint-disable-next-line no-console
|
|
942
|
+
console.error(`Unknown command: ${args.command}`);
|
|
943
|
+
printUsage();
|
|
944
|
+
process.exit(1);
|
|
1078
945
|
}
|
|
1079
946
|
async function runLlmHealth() {
|
|
1080
947
|
if (!process.env.ANTHROPIC_API_KEY) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FeaturePriority } from '../knowledge/route_families.js';
|
|
2
|
+
import type { RouteFamiliesConfig } from '../agent/config.js';
|
|
3
|
+
export type CoverageStatus = 'covered' | 'partial' | 'uncovered';
|
|
4
|
+
export interface ImpactedFeature {
|
|
5
|
+
familyId: string;
|
|
6
|
+
featureId?: string;
|
|
7
|
+
priority: FeaturePriority;
|
|
8
|
+
changedFiles: string[];
|
|
9
|
+
playwrightSpecs: string[];
|
|
10
|
+
cypressSpecs: string[];
|
|
11
|
+
userFlows: string[];
|
|
12
|
+
coverageStatus: CoverageStatus;
|
|
13
|
+
}
|
|
14
|
+
export interface ImpactResult {
|
|
15
|
+
changedFiles: string[];
|
|
16
|
+
expandedFiles: string[];
|
|
17
|
+
impactedFeatures: ImpactedFeature[];
|
|
18
|
+
unboundFiles: string[];
|
|
19
|
+
warnings: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface ImpactEngineOptions {
|
|
22
|
+
testsRoot: string;
|
|
23
|
+
cypressRoot?: string;
|
|
24
|
+
routeFamilies?: RouteFamiliesConfig;
|
|
25
|
+
expandedFiles?: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function analyzeImpact(changedFiles: string[], options: ImpactEngineOptions): ImpactResult;
|
|
28
|
+
/**
|
|
29
|
+
* Get gaps: P0/P1 features with 'uncovered' status.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getGaps(result: ImpactResult): ImpactedFeature[];
|
|
32
|
+
/**
|
|
33
|
+
* Get partial gaps: P0/P1 features with 'partial' status (advisory).
|
|
34
|
+
*/
|
|
35
|
+
export declare function getPartialGaps(result: ImpactResult): ImpactedFeature[];
|
|
36
|
+
//# sourceMappingURL=impact_engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"impact_engine.d.ts","sourceRoot":"","sources":["../../src/engine/impact_engine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGR,eAAe,EAClB,MAAM,gCAAgC,CAAC;AASxC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,oBAAoB,CAAC;AAE5D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IACzB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAuGD,wBAAgB,aAAa,CACzB,YAAY,EAAE,MAAM,EAAE,EACtB,OAAO,EAAE,mBAAmB,GAC7B,YAAY,CA2Ed;AAYD;;GAEG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,EAAE,CAI/D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,eAAe,EAAE,CAItE"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.analyzeImpact = analyzeImpact;
|
|
6
|
+
exports.getGaps = getGaps;
|
|
7
|
+
exports.getPartialGaps = getPartialGaps;
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const route_families_js_1 = require("../knowledge/route_families.js");
|
|
11
|
+
function scanDirForSpecs(baseDir, specDir, extension) {
|
|
12
|
+
const fullDir = (0, path_1.join)(baseDir, specDir);
|
|
13
|
+
if (!(0, fs_1.existsSync)(fullDir)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const specs = [];
|
|
17
|
+
try {
|
|
18
|
+
const items = (0, fs_1.readdirSync)(fullDir, { withFileTypes: true });
|
|
19
|
+
for (const item of items) {
|
|
20
|
+
const itemPath = (0, path_1.join)(fullDir, item.name);
|
|
21
|
+
if (item.isDirectory()) {
|
|
22
|
+
specs.push(...scanDirForSpecsRecursive(itemPath, extension));
|
|
23
|
+
}
|
|
24
|
+
else if (item.name.endsWith(extension)) {
|
|
25
|
+
specs.push((0, path_1.join)(specDir, item.name));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Directory not readable
|
|
31
|
+
}
|
|
32
|
+
return specs;
|
|
33
|
+
}
|
|
34
|
+
function scanDirForSpecsRecursive(dir, extension) {
|
|
35
|
+
const specs = [];
|
|
36
|
+
try {
|
|
37
|
+
const items = (0, fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
const fullPath = (0, path_1.join)(dir, item.name);
|
|
40
|
+
if (item.isDirectory()) {
|
|
41
|
+
specs.push(...scanDirForSpecsRecursive(fullPath, extension));
|
|
42
|
+
}
|
|
43
|
+
else if (item.name.endsWith(extension)) {
|
|
44
|
+
specs.push(fullPath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Directory not readable
|
|
50
|
+
}
|
|
51
|
+
return specs;
|
|
52
|
+
}
|
|
53
|
+
function resolvePlaywrightSpecs(testsRoot, specDirs) {
|
|
54
|
+
const specs = [];
|
|
55
|
+
for (const dir of specDirs) {
|
|
56
|
+
specs.push(...scanDirForSpecs(testsRoot, dir, '.spec.ts'));
|
|
57
|
+
}
|
|
58
|
+
return specs;
|
|
59
|
+
}
|
|
60
|
+
function resolveCypressSpecs(cypressRoot, specDirs) {
|
|
61
|
+
const specs = [];
|
|
62
|
+
for (const dir of specDirs) {
|
|
63
|
+
// cypressSpecDirs are relative to testsRoot (e.g. ../cypress/tests/integration/channels/search/)
|
|
64
|
+
// Resolve them relative to the cypress root
|
|
65
|
+
const resolvedDir = (0, path_1.join)(cypressRoot, dir.replace(/^\.\.\/cypress\//, ''));
|
|
66
|
+
if (!(0, fs_1.existsSync)(resolvedDir)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const found = scanDirForSpecsRecursive(resolvedDir, '.js');
|
|
70
|
+
const tsFound = scanDirForSpecsRecursive(resolvedDir, '.ts');
|
|
71
|
+
specs.push(...found, ...tsFound);
|
|
72
|
+
}
|
|
73
|
+
return specs;
|
|
74
|
+
}
|
|
75
|
+
function computeCoverageStatus(pwSpecs, cySpecs) {
|
|
76
|
+
const hasPw = pwSpecs.length > 0;
|
|
77
|
+
const hasCy = cySpecs.length > 0;
|
|
78
|
+
if (hasPw && hasCy) {
|
|
79
|
+
return 'covered';
|
|
80
|
+
}
|
|
81
|
+
if (hasPw || hasCy) {
|
|
82
|
+
return 'partial';
|
|
83
|
+
}
|
|
84
|
+
return 'uncovered';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Group file bindings into a deduplicated map of family/feature → changed files.
|
|
88
|
+
*/
|
|
89
|
+
function groupBindings(fileBindings) {
|
|
90
|
+
const groups = new Map();
|
|
91
|
+
for (const fb of fileBindings) {
|
|
92
|
+
for (const binding of fb.bindings) {
|
|
93
|
+
const key = binding.feature || binding.family;
|
|
94
|
+
const existing = groups.get(key);
|
|
95
|
+
if (existing) {
|
|
96
|
+
if (!existing.files.includes(fb.file)) {
|
|
97
|
+
existing.files.push(fb.file);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
groups.set(key, {
|
|
102
|
+
familyId: binding.family,
|
|
103
|
+
featureId: binding.feature,
|
|
104
|
+
files: [fb.file],
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return groups;
|
|
110
|
+
}
|
|
111
|
+
function analyzeImpact(changedFiles, options) {
|
|
112
|
+
const { testsRoot, routeFamilies } = options;
|
|
113
|
+
const warnings = [];
|
|
114
|
+
// Load manifest
|
|
115
|
+
const manifest = (0, route_families_js_1.loadRouteFamilyManifest)(testsRoot, routeFamilies);
|
|
116
|
+
if (!manifest) {
|
|
117
|
+
return {
|
|
118
|
+
changedFiles,
|
|
119
|
+
expandedFiles: options.expandedFiles || [],
|
|
120
|
+
impactedFeatures: [],
|
|
121
|
+
unboundFiles: [...changedFiles],
|
|
122
|
+
warnings: ['Route family manifest not found. All files are unbound.'],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// Combine original + expanded files
|
|
126
|
+
const allFiles = [...new Set([...changedFiles, ...(options.expandedFiles || [])])];
|
|
127
|
+
// Bind files to families
|
|
128
|
+
const fileBindings = (0, route_families_js_1.bindFilesToFamilies)(allFiles, manifest);
|
|
129
|
+
// Find unbound files
|
|
130
|
+
const unboundFiles = fileBindings
|
|
131
|
+
.filter((fb) => fb.bindings.length === 0)
|
|
132
|
+
.map((fb) => fb.file);
|
|
133
|
+
// Group bindings into features
|
|
134
|
+
const groups = groupBindings(fileBindings.filter((fb) => fb.bindings.length > 0));
|
|
135
|
+
// Determine cypress root
|
|
136
|
+
const cypressRoot = options.cypressRoot || inferCypressRoot(testsRoot);
|
|
137
|
+
// Resolve specs and compute coverage for each feature
|
|
138
|
+
const impactedFeatures = [];
|
|
139
|
+
for (const group of groups.values()) {
|
|
140
|
+
const binding = { family: group.familyId, feature: group.featureId };
|
|
141
|
+
const specDirs = (0, route_families_js_1.getSpecDirsForBinding)(manifest, binding);
|
|
142
|
+
const cypressSpecDirs = (0, route_families_js_1.getCypressSpecDirsForBinding)(manifest, binding);
|
|
143
|
+
const priority = (0, route_families_js_1.getPriorityForBinding)(manifest, binding);
|
|
144
|
+
const userFlows = (0, route_families_js_1.getUserFlowsForBinding)(manifest, binding);
|
|
145
|
+
const playwrightSpecs = resolvePlaywrightSpecs(testsRoot, specDirs);
|
|
146
|
+
const cypressSpecs = cypressRoot ? resolveCypressSpecs(cypressRoot, cypressSpecDirs) : [];
|
|
147
|
+
const coverageStatus = computeCoverageStatus(playwrightSpecs, cypressSpecs);
|
|
148
|
+
impactedFeatures.push({
|
|
149
|
+
familyId: group.familyId,
|
|
150
|
+
featureId: group.featureId,
|
|
151
|
+
priority,
|
|
152
|
+
changedFiles: group.files,
|
|
153
|
+
playwrightSpecs,
|
|
154
|
+
cypressSpecs,
|
|
155
|
+
userFlows,
|
|
156
|
+
coverageStatus,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Sort by priority (P0 first, then P1, then P2)
|
|
160
|
+
const priorityOrder = { P0: 0, P1: 1, P2: 2 };
|
|
161
|
+
impactedFeatures.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
162
|
+
if (unboundFiles.length > 0 && unboundFiles.length <= 5) {
|
|
163
|
+
warnings.push(`${unboundFiles.length} file(s) not mapped to any route family: ${unboundFiles.join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
else if (unboundFiles.length > 5) {
|
|
166
|
+
warnings.push(`${unboundFiles.length} file(s) not mapped to any route family`);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
changedFiles,
|
|
170
|
+
expandedFiles: options.expandedFiles || [],
|
|
171
|
+
impactedFeatures,
|
|
172
|
+
unboundFiles,
|
|
173
|
+
warnings,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function inferCypressRoot(testsRoot) {
|
|
177
|
+
// testsRoot is typically the Playwright tests directory
|
|
178
|
+
// Cypress tests are at a sibling path: e2e-tests/cypress/tests/integration/channels/
|
|
179
|
+
const candidate = (0, path_1.join)(testsRoot, '..', 'cypress');
|
|
180
|
+
if ((0, fs_1.existsSync)(candidate)) {
|
|
181
|
+
return candidate;
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get gaps: P0/P1 features with 'uncovered' status.
|
|
187
|
+
*/
|
|
188
|
+
function getGaps(result) {
|
|
189
|
+
return result.impactedFeatures.filter((f) => (f.priority === 'P0' || f.priority === 'P1') && f.coverageStatus === 'uncovered');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get partial gaps: P0/P1 features with 'partial' status (advisory).
|
|
193
|
+
*/
|
|
194
|
+
function getPartialGaps(result) {
|
|
195
|
+
return result.impactedFeatures.filter((f) => (f.priority === 'P0' || f.priority === 'P1') && f.coverageStatus === 'partial');
|
|
196
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PolicyConfig } from '../agent/config.js';
|
|
2
|
+
import type { ImpactResult } from './impact_engine.js';
|
|
3
|
+
import type { PlanReport, GapDetail, CoveredFlowSummary } from '../agent/plan.js';
|
|
4
|
+
export type { PlanReport, GapDetail, CoveredFlowSummary };
|
|
5
|
+
export declare function buildPlanFromImpact(impact: ImpactResult, policyOverride?: Partial<PolicyConfig>): PlanReport;
|
|
6
|
+
export declare function writePlanReport(appRoot: string, plan: PlanReport): string;
|
|
7
|
+
export declare function renderCiSummaryMarkdown(plan: PlanReport): string;
|
|
8
|
+
export declare function writeCiSummary(appRoot: string, markdown: string, relativePath?: string): string;
|
|
9
|
+
//# sourceMappingURL=plan_builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan_builder.d.ts","sourceRoot":"","sources":["../../src/engine/plan_builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAC,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AAItE,OAAO,KAAK,EACR,UAAU,EACV,SAAS,EACT,kBAAkB,EAIrB,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAC,UAAU,EAAE,SAAS,EAAE,kBAAkB,EAAC,CAAC;AAqOxD,wBAAgB,mBAAmB,CAC/B,MAAM,EAAE,YAAY,EACpB,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GACvC,UAAU,CAiFZ;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAMzE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA8ChE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,SAAiC,GAAG,MAAM,CAMvH"}
|