@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.
Files changed (105) hide show
  1. package/dist/agent/pipeline.d.ts +1 -1
  2. package/dist/agent/pipeline.d.ts.map +1 -1
  3. package/dist/agent/plan.d.ts +0 -12
  4. package/dist/agent/plan.d.ts.map +1 -1
  5. package/dist/agent/plan.js +0 -365
  6. package/dist/agent/types.d.ts +42 -0
  7. package/dist/agent/types.d.ts.map +1 -0
  8. package/dist/agent/types.js +4 -0
  9. package/dist/api.d.ts +10 -14
  10. package/dist/api.d.ts.map +1 -1
  11. package/dist/api.js +29 -59
  12. package/dist/cli.js +41 -174
  13. package/dist/engine/impact_engine.d.ts +36 -0
  14. package/dist/engine/impact_engine.d.ts.map +1 -0
  15. package/dist/engine/impact_engine.js +196 -0
  16. package/dist/engine/plan_builder.d.ts +9 -0
  17. package/dist/engine/plan_builder.d.ts.map +1 -0
  18. package/dist/engine/plan_builder.js +329 -0
  19. package/dist/esm/agent/plan.js +1 -360
  20. package/dist/esm/agent/types.js +3 -0
  21. package/dist/esm/api.js +27 -56
  22. package/dist/esm/cli.js +40 -173
  23. package/dist/esm/engine/impact_engine.js +191 -0
  24. package/dist/esm/engine/plan_builder.js +323 -0
  25. package/dist/esm/index.js +6 -3
  26. package/dist/esm/knowledge/route_families.js +57 -0
  27. package/dist/index.d.ts +9 -4
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +14 -5
  30. package/dist/knowledge/route_families.d.ts +19 -0
  31. package/dist/knowledge/route_families.d.ts.map +1 -1
  32. package/dist/knowledge/route_families.js +60 -0
  33. package/package.json +1 -1
  34. package/dist/agent/ai_flow_analysis.d.ts +0 -13
  35. package/dist/agent/ai_flow_analysis.d.ts.map +0 -1
  36. package/dist/agent/ai_flow_analysis.js +0 -334
  37. package/dist/agent/ai_mapping.d.ts +0 -14
  38. package/dist/agent/ai_mapping.d.ts.map +0 -1
  39. package/dist/agent/ai_mapping.js +0 -560
  40. package/dist/agent/analysis.d.ts +0 -64
  41. package/dist/agent/analysis.d.ts.map +0 -1
  42. package/dist/agent/analysis.js +0 -292
  43. package/dist/agent/blast_radius.d.ts +0 -4
  44. package/dist/agent/blast_radius.d.ts.map +0 -1
  45. package/dist/agent/blast_radius.js +0 -37
  46. package/dist/agent/dependency_graph.d.ts +0 -14
  47. package/dist/agent/dependency_graph.d.ts.map +0 -1
  48. package/dist/agent/dependency_graph.js +0 -227
  49. package/dist/agent/flags.d.ts +0 -23
  50. package/dist/agent/flags.d.ts.map +0 -1
  51. package/dist/agent/flags.js +0 -171
  52. package/dist/agent/flow_catalog.d.ts +0 -25
  53. package/dist/agent/flow_catalog.d.ts.map +0 -1
  54. package/dist/agent/flow_catalog.js +0 -115
  55. package/dist/agent/flow_mapping.d.ts +0 -10
  56. package/dist/agent/flow_mapping.d.ts.map +0 -1
  57. package/dist/agent/flow_mapping.js +0 -84
  58. package/dist/agent/framework.d.ts +0 -13
  59. package/dist/agent/framework.d.ts.map +0 -1
  60. package/dist/agent/framework.js +0 -149
  61. package/dist/agent/gap_suggestions.d.ts +0 -14
  62. package/dist/agent/gap_suggestions.d.ts.map +0 -1
  63. package/dist/agent/gap_suggestions.js +0 -101
  64. package/dist/agent/generator.d.ts +0 -10
  65. package/dist/agent/generator.d.ts.map +0 -1
  66. package/dist/agent/generator.js +0 -115
  67. package/dist/agent/operational_insights.d.ts +0 -41
  68. package/dist/agent/operational_insights.d.ts.map +0 -1
  69. package/dist/agent/operational_insights.js +0 -127
  70. package/dist/agent/report.d.ts +0 -97
  71. package/dist/agent/report.d.ts.map +0 -1
  72. package/dist/agent/report.js +0 -159
  73. package/dist/agent/runner.d.ts +0 -7
  74. package/dist/agent/runner.d.ts.map +0 -1
  75. package/dist/agent/runner.js +0 -898
  76. package/dist/agent/selectors.d.ts +0 -10
  77. package/dist/agent/selectors.d.ts.map +0 -1
  78. package/dist/agent/selectors.js +0 -75
  79. package/dist/agent/subsystem_risk.d.ts +0 -23
  80. package/dist/agent/subsystem_risk.d.ts.map +0 -1
  81. package/dist/agent/subsystem_risk.js +0 -207
  82. package/dist/agent/tests.d.ts +0 -19
  83. package/dist/agent/tests.d.ts.map +0 -1
  84. package/dist/agent/tests.js +0 -116
  85. package/dist/agent/traceability.d.ts +0 -22
  86. package/dist/agent/traceability.d.ts.map +0 -1
  87. package/dist/agent/traceability.js +0 -183
  88. package/dist/esm/agent/ai_flow_analysis.js +0 -331
  89. package/dist/esm/agent/ai_mapping.js +0 -557
  90. package/dist/esm/agent/analysis.js +0 -287
  91. package/dist/esm/agent/blast_radius.js +0 -34
  92. package/dist/esm/agent/dependency_graph.js +0 -224
  93. package/dist/esm/agent/flags.js +0 -160
  94. package/dist/esm/agent/flow_catalog.js +0 -112
  95. package/dist/esm/agent/flow_mapping.js +0 -81
  96. package/dist/esm/agent/framework.js +0 -145
  97. package/dist/esm/agent/gap_suggestions.js +0 -98
  98. package/dist/esm/agent/generator.js +0 -112
  99. package/dist/esm/agent/operational_insights.js +0 -124
  100. package/dist/esm/agent/report.js +0 -156
  101. package/dist/esm/agent/runner.js +0 -894
  102. package/dist/esm/agent/selectors.js +0 -71
  103. package/dist/esm/agent/subsystem_risk.js +0 -204
  104. package/dist/esm/agent/tests.js +0 -111
  105. 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 operational_insights_js_1 = require("./agent/operational_insights.js");
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 approve-and-generate --path <app-root> [options]',
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 forcePipelineFromApproval = args.command === 'approve-and-generate' || args.command === 'generate';
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: (args.command === 'gap' || args.command === 'approve-and-generate' || args.command === 'generate') ? 'gap' : 'impact',
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: (args.pipeline || forcePipelineFromApproval)
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 !== undefined ? args.pipelineMcp : forceAIPipelineFromApproval,
850
+ mcp: args.pipelineMcp,
971
851
  mcpAllowFallback: args.pipelineMcpAllowFallback,
972
- mcpOnly: args.pipelineMcpOnly !== undefined ? args.pipelineMcpOnly : forceAIPipelineFromApproval,
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 impactPath = (0, path_1.join)(reportRoot, '.e2e-ai-agents', 'impact.json');
1003
- try {
1004
- await (0, runner_js_1.runImpact)(config, { apply: args.apply });
1005
- }
1006
- catch (err) {
1007
- // If impact analysis already ran (e.g. a prior CI step wrote impact.json),
1008
- // fall back to that data rather than failing the plan step.
1009
- if ((0, fs_1.existsSync)(impactPath)) {
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(`Impact re-run failed (${err instanceof Error ? err.message : String(err)}); using existing impact.json.`);
1012
- }
1013
- else {
1014
- throw err;
892
+ console.warn(` Warning: ${w}`);
1015
893
  }
1016
894
  }
1017
- if (!(0, fs_1.existsSync)(impactPath)) {
1018
- throw new Error(`Impact report not found at ${impactPath}`);
1019
- }
1020
- const impact = JSON.parse((0, fs_1.readFileSync)(impactPath, 'utf-8'));
1021
- const basePlan = (0, plan_js_1.buildPlanFromImpactReport)(impact, config.policy);
1022
- const withActions = (0, plan_js_1.attachDeveloperActions)(basePlan, {
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
- sinceRef: config.git.since,
1026
- configPath,
902
+ routeFamilies: config.routeFamilies,
1027
903
  });
1028
- const plan = (0, operational_insights_js_1.applyOperationalInsights)(withActions, reportRoot);
1029
- const planPath = (0, plan_js_1.writePlanReport)(reportRoot, plan);
1030
- const summaryMarkdown = (0, plan_js_1.renderCiSummaryMarkdown)(plan);
1031
- const summaryPath = (0, plan_js_1.writeCiSummary)(reportRoot, summaryMarkdown, args.ciCommentPath);
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
- if (args.command === 'approve-and-generate' || args.command === 'generate') {
1074
- await (0, runner_js_1.runGap)(config, { apply: args.apply });
1075
- return;
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"}