@yasserkhanorg/e2e-agents 0.3.2

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 (221) hide show
  1. package/LICENSE +168 -0
  2. package/README.md +620 -0
  3. package/dist/agent/analysis.d.ts +62 -0
  4. package/dist/agent/analysis.d.ts.map +1 -0
  5. package/dist/agent/analysis.js +292 -0
  6. package/dist/agent/blast_radius.d.ts +4 -0
  7. package/dist/agent/blast_radius.d.ts.map +1 -0
  8. package/dist/agent/blast_radius.js +37 -0
  9. package/dist/agent/cache_utils.d.ts +38 -0
  10. package/dist/agent/cache_utils.d.ts.map +1 -0
  11. package/dist/agent/cache_utils.js +67 -0
  12. package/dist/agent/config.d.ts +148 -0
  13. package/dist/agent/config.d.ts.map +1 -0
  14. package/dist/agent/config.js +640 -0
  15. package/dist/agent/dependency_graph.d.ts +14 -0
  16. package/dist/agent/dependency_graph.d.ts.map +1 -0
  17. package/dist/agent/dependency_graph.js +227 -0
  18. package/dist/agent/feedback.d.ts +55 -0
  19. package/dist/agent/feedback.d.ts.map +1 -0
  20. package/dist/agent/feedback.js +257 -0
  21. package/dist/agent/flags.d.ts +23 -0
  22. package/dist/agent/flags.d.ts.map +1 -0
  23. package/dist/agent/flags.js +171 -0
  24. package/dist/agent/flow_catalog.d.ts +25 -0
  25. package/dist/agent/flow_catalog.d.ts.map +1 -0
  26. package/dist/agent/flow_catalog.js +106 -0
  27. package/dist/agent/flow_mapping.d.ts +10 -0
  28. package/dist/agent/flow_mapping.d.ts.map +1 -0
  29. package/dist/agent/flow_mapping.js +84 -0
  30. package/dist/agent/framework.d.ts +13 -0
  31. package/dist/agent/framework.d.ts.map +1 -0
  32. package/dist/agent/framework.js +149 -0
  33. package/dist/agent/gap_suggestions.d.ts +14 -0
  34. package/dist/agent/gap_suggestions.d.ts.map +1 -0
  35. package/dist/agent/gap_suggestions.js +101 -0
  36. package/dist/agent/generator.d.ts +10 -0
  37. package/dist/agent/generator.d.ts.map +1 -0
  38. package/dist/agent/generator.js +115 -0
  39. package/dist/agent/git.d.ts +11 -0
  40. package/dist/agent/git.d.ts.map +1 -0
  41. package/dist/agent/git.js +90 -0
  42. package/dist/agent/handoff.d.ts +22 -0
  43. package/dist/agent/handoff.d.ts.map +1 -0
  44. package/dist/agent/handoff.js +180 -0
  45. package/dist/agent/impact-analyzer.d.ts +114 -0
  46. package/dist/agent/impact-analyzer.d.ts.map +1 -0
  47. package/dist/agent/impact-analyzer.js +557 -0
  48. package/dist/agent/index.d.ts +21 -0
  49. package/dist/agent/index.d.ts.map +1 -0
  50. package/dist/agent/index.js +38 -0
  51. package/dist/agent/model-router.d.ts +57 -0
  52. package/dist/agent/model-router.d.ts.map +1 -0
  53. package/dist/agent/model-router.js +154 -0
  54. package/dist/agent/operational_insights.d.ts +41 -0
  55. package/dist/agent/operational_insights.d.ts.map +1 -0
  56. package/dist/agent/operational_insights.js +126 -0
  57. package/dist/agent/pipeline.d.ts +23 -0
  58. package/dist/agent/pipeline.d.ts.map +1 -0
  59. package/dist/agent/pipeline.js +609 -0
  60. package/dist/agent/plan.d.ts +91 -0
  61. package/dist/agent/plan.d.ts.map +1 -0
  62. package/dist/agent/plan.js +331 -0
  63. package/dist/agent/playwright_report.d.ts +8 -0
  64. package/dist/agent/playwright_report.d.ts.map +1 -0
  65. package/dist/agent/playwright_report.js +126 -0
  66. package/dist/agent/report-generator.d.ts +24 -0
  67. package/dist/agent/report-generator.d.ts.map +1 -0
  68. package/dist/agent/report-generator.js +250 -0
  69. package/dist/agent/report.d.ts +81 -0
  70. package/dist/agent/report.d.ts.map +1 -0
  71. package/dist/agent/report.js +147 -0
  72. package/dist/agent/runner.d.ts +7 -0
  73. package/dist/agent/runner.d.ts.map +1 -0
  74. package/dist/agent/runner.js +576 -0
  75. package/dist/agent/selectors.d.ts +10 -0
  76. package/dist/agent/selectors.d.ts.map +1 -0
  77. package/dist/agent/selectors.js +75 -0
  78. package/dist/agent/spec-bridge.d.ts +101 -0
  79. package/dist/agent/spec-bridge.d.ts.map +1 -0
  80. package/dist/agent/spec-bridge.js +273 -0
  81. package/dist/agent/spec-builder.d.ts +102 -0
  82. package/dist/agent/spec-builder.d.ts.map +1 -0
  83. package/dist/agent/spec-builder.js +273 -0
  84. package/dist/agent/subsystem_risk.d.ts +23 -0
  85. package/dist/agent/subsystem_risk.d.ts.map +1 -0
  86. package/dist/agent/subsystem_risk.js +207 -0
  87. package/dist/agent/telemetry.d.ts +84 -0
  88. package/dist/agent/telemetry.d.ts.map +1 -0
  89. package/dist/agent/telemetry.js +220 -0
  90. package/dist/agent/test_path.d.ts +2 -0
  91. package/dist/agent/test_path.d.ts.map +1 -0
  92. package/dist/agent/test_path.js +23 -0
  93. package/dist/agent/tests.d.ts +18 -0
  94. package/dist/agent/tests.d.ts.map +1 -0
  95. package/dist/agent/tests.js +106 -0
  96. package/dist/agent/traceability.d.ts +22 -0
  97. package/dist/agent/traceability.d.ts.map +1 -0
  98. package/dist/agent/traceability.js +183 -0
  99. package/dist/agent/traceability_capture.d.ts +18 -0
  100. package/dist/agent/traceability_capture.d.ts.map +1 -0
  101. package/dist/agent/traceability_capture.js +313 -0
  102. package/dist/agent/traceability_ingest.d.ts +21 -0
  103. package/dist/agent/traceability_ingest.d.ts.map +1 -0
  104. package/dist/agent/traceability_ingest.js +237 -0
  105. package/dist/agent/utils.d.ts +13 -0
  106. package/dist/agent/utils.d.ts.map +1 -0
  107. package/dist/agent/utils.js +152 -0
  108. package/dist/agent/validators/selector-validator.d.ts +74 -0
  109. package/dist/agent/validators/selector-validator.d.ts.map +1 -0
  110. package/dist/agent/validators/selector-validator.js +165 -0
  111. package/dist/anthropic_provider.d.ts +65 -0
  112. package/dist/anthropic_provider.d.ts.map +1 -0
  113. package/dist/anthropic_provider.js +332 -0
  114. package/dist/api.d.ts +48 -0
  115. package/dist/api.d.ts.map +1 -0
  116. package/dist/api.js +113 -0
  117. package/dist/base_provider.d.ts +53 -0
  118. package/dist/base_provider.d.ts.map +1 -0
  119. package/dist/base_provider.js +81 -0
  120. package/dist/cli.d.ts +3 -0
  121. package/dist/cli.d.ts.map +1 -0
  122. package/dist/cli.js +843 -0
  123. package/dist/custom_provider.d.ts +20 -0
  124. package/dist/custom_provider.d.ts.map +1 -0
  125. package/dist/custom_provider.js +276 -0
  126. package/dist/e2e-test-gen/index.d.ts +51 -0
  127. package/dist/e2e-test-gen/index.d.ts.map +1 -0
  128. package/dist/e2e-test-gen/index.js +57 -0
  129. package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
  130. package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
  131. package/dist/e2e-test-gen/spec_parser.js +786 -0
  132. package/dist/e2e-test-gen/types.d.ts +185 -0
  133. package/dist/e2e-test-gen/types.d.ts.map +1 -0
  134. package/dist/e2e-test-gen/types.js +4 -0
  135. package/dist/esm/agent/analysis.js +287 -0
  136. package/dist/esm/agent/blast_radius.js +34 -0
  137. package/dist/esm/agent/cache_utils.js +63 -0
  138. package/dist/esm/agent/config.js +637 -0
  139. package/dist/esm/agent/dependency_graph.js +224 -0
  140. package/dist/esm/agent/feedback.js +253 -0
  141. package/dist/esm/agent/flags.js +160 -0
  142. package/dist/esm/agent/flow_catalog.js +103 -0
  143. package/dist/esm/agent/flow_mapping.js +81 -0
  144. package/dist/esm/agent/framework.js +145 -0
  145. package/dist/esm/agent/gap_suggestions.js +98 -0
  146. package/dist/esm/agent/generator.js +112 -0
  147. package/dist/esm/agent/git.js +87 -0
  148. package/dist/esm/agent/handoff.js +177 -0
  149. package/dist/esm/agent/impact-analyzer.js +548 -0
  150. package/dist/esm/agent/index.js +22 -0
  151. package/dist/esm/agent/model-router.js +150 -0
  152. package/dist/esm/agent/operational_insights.js +123 -0
  153. package/dist/esm/agent/pipeline.js +605 -0
  154. package/dist/esm/agent/plan.js +324 -0
  155. package/dist/esm/agent/playwright_report.js +123 -0
  156. package/dist/esm/agent/report-generator.js +247 -0
  157. package/dist/esm/agent/report.js +144 -0
  158. package/dist/esm/agent/runner.js +572 -0
  159. package/dist/esm/agent/selectors.js +71 -0
  160. package/dist/esm/agent/spec-bridge.js +267 -0
  161. package/dist/esm/agent/spec-builder.js +267 -0
  162. package/dist/esm/agent/subsystem_risk.js +204 -0
  163. package/dist/esm/agent/telemetry.js +216 -0
  164. package/dist/esm/agent/test_path.js +20 -0
  165. package/dist/esm/agent/tests.js +101 -0
  166. package/dist/esm/agent/traceability.js +180 -0
  167. package/dist/esm/agent/traceability_capture.js +310 -0
  168. package/dist/esm/agent/traceability_ingest.js +234 -0
  169. package/dist/esm/agent/utils.js +138 -0
  170. package/dist/esm/agent/validators/selector-validator.js +160 -0
  171. package/dist/esm/anthropic_provider.js +324 -0
  172. package/dist/esm/api.js +105 -0
  173. package/dist/esm/base_provider.js +77 -0
  174. package/dist/esm/cli.js +841 -0
  175. package/dist/esm/custom_provider.js +272 -0
  176. package/dist/esm/e2e-test-gen/index.js +50 -0
  177. package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
  178. package/dist/esm/e2e-test-gen/types.js +3 -0
  179. package/dist/esm/index.js +16 -0
  180. package/dist/esm/logger.js +89 -0
  181. package/dist/esm/mcp-server.js +465 -0
  182. package/dist/esm/ollama_provider.js +300 -0
  183. package/dist/esm/openai_provider.js +242 -0
  184. package/dist/esm/package.json +3 -0
  185. package/dist/esm/plan-and-test-constants.js +126 -0
  186. package/dist/esm/provider_factory.js +336 -0
  187. package/dist/esm/provider_interface.js +23 -0
  188. package/dist/esm/provider_utils.js +96 -0
  189. package/dist/index.d.ts +31 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +41 -0
  192. package/dist/logger.d.ts +23 -0
  193. package/dist/logger.d.ts.map +1 -0
  194. package/dist/logger.js +93 -0
  195. package/dist/mcp-server.d.ts +35 -0
  196. package/dist/mcp-server.d.ts.map +1 -0
  197. package/dist/mcp-server.js +469 -0
  198. package/dist/ollama_provider.d.ts +65 -0
  199. package/dist/ollama_provider.d.ts.map +1 -0
  200. package/dist/ollama_provider.js +308 -0
  201. package/dist/openai_provider.d.ts +23 -0
  202. package/dist/openai_provider.d.ts.map +1 -0
  203. package/dist/openai_provider.js +250 -0
  204. package/dist/plan-and-test-constants.d.ts +110 -0
  205. package/dist/plan-and-test-constants.d.ts.map +1 -0
  206. package/dist/plan-and-test-constants.js +132 -0
  207. package/dist/provider_factory.d.ts +99 -0
  208. package/dist/provider_factory.d.ts.map +1 -0
  209. package/dist/provider_factory.js +341 -0
  210. package/dist/provider_interface.d.ts +358 -0
  211. package/dist/provider_interface.d.ts.map +1 -0
  212. package/dist/provider_interface.js +28 -0
  213. package/dist/provider_utils.d.ts +39 -0
  214. package/dist/provider_utils.d.ts.map +1 -0
  215. package/dist/provider_utils.js +103 -0
  216. package/package.json +101 -0
  217. package/schemas/gap.schema.json +18 -0
  218. package/schemas/impact.schema.json +418 -0
  219. package/schemas/plan.schema.json +285 -0
  220. package/schemas/subsystem-risk-map.schema.json +62 -0
  221. package/schemas/traceability-input.schema.json +122 -0
@@ -0,0 +1,841 @@
1
+ #!/usr/bin/env node
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'fs';
5
+ import { dirname, join, resolve } from 'path';
6
+ import { resolveConfig } from './agent/config.js';
7
+ import { AnthropicProvider } from './anthropic_provider.js';
8
+ import { LLMProviderError } from './provider_interface.js';
9
+ import { runGap, runImpact } from './agent/runner.js';
10
+ import { attachDeveloperActions, buildPlanFromImpactReport, renderCiSummaryMarkdown, writeCiSummary, writePlanReport } from './agent/plan.js';
11
+ import { applyOperationalInsights } from './agent/operational_insights.js';
12
+ import { appendFeedbackAndRecompute } from './agent/feedback.js';
13
+ import { finalizeGeneratedTests } from './agent/handoff.js';
14
+ import { ingestTraceabilityInput } from './agent/traceability_ingest.js';
15
+ import { captureTraceabilityInput } from './agent/traceability_capture.js';
16
+ import { runTargetedSpecHeal } from './agent/pipeline.js';
17
+ import { extractPlaywrightUnstableSpecs } from './agent/playwright_report.js';
18
+ const CONFIG_CANDIDATES = ['e2e-ai-agents.config.json', '.e2e-ai-agents.config.json'];
19
+ function findConfigUpwards(startDir) {
20
+ if (!startDir) {
21
+ return undefined;
22
+ }
23
+ let current = resolve(startDir);
24
+ while (true) {
25
+ for (const candidate of CONFIG_CANDIDATES) {
26
+ const fullPath = join(current, candidate);
27
+ if (existsSync(fullPath)) {
28
+ return fullPath;
29
+ }
30
+ }
31
+ const parent = dirname(current);
32
+ if (parent === current) {
33
+ break;
34
+ }
35
+ current = parent;
36
+ }
37
+ return undefined;
38
+ }
39
+ function resolveAutoConfig(args) {
40
+ if (args.configPath) {
41
+ return args.configPath;
42
+ }
43
+ const searchRoots = [
44
+ process.cwd(),
45
+ args.testsRoot,
46
+ args.path,
47
+ ].filter(Boolean);
48
+ for (const root of searchRoots) {
49
+ const found = findConfigUpwards(root);
50
+ if (found) {
51
+ return found;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ function printUsage() {
57
+ // eslint-disable-next-line no-console
58
+ console.log([
59
+ 'Usage:',
60
+ ' e2e-ai-agents impact --path <app-root> [options]',
61
+ ' e2e-ai-agents gap --path <app-root> [options]',
62
+ ' e2e-ai-agents suggest --path <app-root> [options]',
63
+ ' e2e-ai-agents approve-and-generate --path <app-root> [options]',
64
+ ' e2e-ai-agents auto-heal-pr --path <app-root> [options]',
65
+ ' e2e-ai-agents finalize-generated-tests --path <app-root> [options]',
66
+ ' e2e-ai-agents feedback --path <app-root> --feedback-input <json>',
67
+ ' e2e-ai-agents traceability-capture --path <app-root> --traceability-report <json>',
68
+ ' e2e-ai-agents traceability-ingest --path <app-root> --traceability-input <json>',
69
+ ' e2e-ai-agents llm-health',
70
+ '',
71
+ 'Options:',
72
+ ' --config <path> Path to e2e-ai-agents.config.json (auto-discovered if present)',
73
+ ' --path <app-root> Path to the web app (required)',
74
+ ' --tests-root <path> Path to tests root (optional)',
75
+ ' --framework <name> auto | playwright | cypress | selenium',
76
+ ' --patterns <globs> Comma-separated test patterns',
77
+ ' --flow-patterns <g> Comma-separated flow discovery patterns',
78
+ ' --flow-exclude <g> Comma-separated flow exclude patterns',
79
+ ' --flow-catalog <path> Path to flow catalog JSON',
80
+ ' --allow-fallback Allow impact analysis without diff',
81
+ ' --pipeline Run Playwright AI pipeline for missing P0/P1 flows',
82
+ ' --pipeline-scenarios Number of scenarios per flow (default 3)',
83
+ ' --pipeline-output Output directory for generated tests',
84
+ ' --pipeline-base-url Base URL for Playwright runs',
85
+ ' --pipeline-browser Browser: chrome|chromium|firefox|webkit',
86
+ ' --pipeline-headless Run in headless mode',
87
+ ' --pipeline-project Playwright project name',
88
+ ' --pipeline-parallel Enable parallel mode in generator',
89
+ ' --pipeline-dry-run Do not execute pipeline (report only)',
90
+ ' --pipeline-mcp Use Playwright MCP server for exploration/healing',
91
+ ' --spec <path> Optional spec PDF for context',
92
+ ' --since <git-ref> Git ref for impact analysis (default HEAD~1)',
93
+ ' --time <minutes> Time limit in minutes',
94
+ ' --budget-usd <amount> Max LLM budget in USD',
95
+ ' --budget-tokens <n> Max LLM tokens',
96
+ ' --policy-min-confidence <n> Minimum confidence for targeted suite',
97
+ ' --policy-safe-merge-confidence <n> Confidence needed for safe-to-merge',
98
+ ' --policy-force-full-on-warnings <n> Escalate to full at warning count',
99
+ ' --policy-risky-patterns <globs> Comma-separated risky file globs',
100
+ ' --ci-comment-path <path> Write CI markdown summary',
101
+ ' --github-output <path> Write GitHub Actions outputs',
102
+ ' --fail-on-must-add-tests Exit non-zero on must-add-tests decision',
103
+ ' --feedback-input <path> Path to recommendation feedback JSON',
104
+ ' --traceability-report <path> Path to Playwright JSON report for traceability capture',
105
+ ' --traceability-capture-output <path> Output path for generated traceability ingest JSON',
106
+ ' --traceability-coverage-map <path> Optional coverage map (test<->files) to enrich traceability capture',
107
+ ' --traceability-changed-files <path> Optional changed-files list/JSON fallback for traceability capture',
108
+ ' --traceability-input <path> Path to traceability ingest JSON payload',
109
+ ' --traceability-min-hits <n> Minimum signal hits required per file mapping',
110
+ ' --traceability-max-files-per-test <n> Cap max mapped files per test',
111
+ ' --traceability-max-age-days <n> Drop stale mappings older than N days',
112
+ ' --branch <name> Optional handoff branch (prefixed with codex/)',
113
+ ' --commit-message <m> Commit message for finalize-generated-tests',
114
+ ' --create-pr Open PR with gh after commit',
115
+ ' --pr-title <title> PR title for finalize-generated-tests',
116
+ ' --pr-body <body> PR body for finalize-generated-tests',
117
+ ' --pr-base <branch> PR base branch for finalize-generated-tests',
118
+ ' (auto-heal-pr defaults to base=master)',
119
+ ' --dry-run Preview actions without mutating git state',
120
+ ' --apply Apply data-testid patches and generate tests',
121
+ ' (legacy shortcut; prefer approve-and-generate)',
122
+ ' --help Show help',
123
+ ].join('\n'));
124
+ }
125
+ function parseArgs(argv) {
126
+ const parsed = { apply: false, help: false };
127
+ if (argv.length === 0) {
128
+ return parsed;
129
+ }
130
+ const command = argv[0];
131
+ if (command === 'impact'
132
+ || command === 'gap'
133
+ || command === 'suggest'
134
+ || command === 'approve-and-generate'
135
+ || command === 'auto-heal-pr'
136
+ || command === 'finalize-generated-tests'
137
+ || command === 'feedback'
138
+ || command === 'traceability-capture'
139
+ || command === 'traceability-ingest'
140
+ || command === 'llm-health') {
141
+ parsed.command = command;
142
+ }
143
+ for (let i = 1; i < argv.length; i += 1) {
144
+ const arg = argv[i];
145
+ const next = argv[i + 1];
146
+ if (arg === '--help' || arg === '-h') {
147
+ parsed.help = true;
148
+ continue;
149
+ }
150
+ if (arg === '--apply') {
151
+ parsed.apply = true;
152
+ continue;
153
+ }
154
+ if (arg === '--config' && next) {
155
+ parsed.configPath = next;
156
+ i += 1;
157
+ continue;
158
+ }
159
+ if (arg === '--path' && next) {
160
+ parsed.path = next;
161
+ i += 1;
162
+ continue;
163
+ }
164
+ if (arg === '--tests-root' && next) {
165
+ parsed.testsRoot = next;
166
+ i += 1;
167
+ continue;
168
+ }
169
+ if (arg === '--framework' && next) {
170
+ parsed.framework = next;
171
+ i += 1;
172
+ continue;
173
+ }
174
+ if (arg === '--patterns' && next) {
175
+ parsed.testPatterns = next.split(',').map((value) => value.trim()).filter(Boolean);
176
+ i += 1;
177
+ continue;
178
+ }
179
+ if (arg === '--flow-patterns' && next) {
180
+ parsed.flowPatterns = next.split(',').map((value) => value.trim()).filter(Boolean);
181
+ i += 1;
182
+ continue;
183
+ }
184
+ if (arg === '--flow-exclude' && next) {
185
+ parsed.flowExclude = next.split(',').map((value) => value.trim()).filter(Boolean);
186
+ i += 1;
187
+ continue;
188
+ }
189
+ if (arg === '--flow-catalog' && next) {
190
+ parsed.flowCatalogPath = next;
191
+ i += 1;
192
+ continue;
193
+ }
194
+ if (arg === '--allow-fallback') {
195
+ parsed.allowFallback = true;
196
+ continue;
197
+ }
198
+ if (arg === '--pipeline') {
199
+ parsed.pipeline = true;
200
+ continue;
201
+ }
202
+ if (arg === '--pipeline-mcp') {
203
+ parsed.pipelineMcp = true;
204
+ continue;
205
+ }
206
+ if (arg === '--pipeline-scenarios' && next) {
207
+ const value = Number(next);
208
+ if (Number.isFinite(value)) {
209
+ parsed.pipelineScenarios = value;
210
+ }
211
+ i += 1;
212
+ continue;
213
+ }
214
+ if (arg === '--pipeline-output' && next) {
215
+ parsed.pipelineOutput = next;
216
+ i += 1;
217
+ continue;
218
+ }
219
+ if (arg === '--pipeline-base-url' && next) {
220
+ parsed.pipelineBaseUrl = next;
221
+ i += 1;
222
+ continue;
223
+ }
224
+ if (arg === '--pipeline-browser' && next) {
225
+ const value = next;
226
+ if (value === 'chrome' || value === 'chromium' || value === 'firefox' || value === 'webkit') {
227
+ parsed.pipelineBrowser = value;
228
+ }
229
+ i += 1;
230
+ continue;
231
+ }
232
+ if (arg === '--pipeline-headless') {
233
+ parsed.pipelineHeadless = true;
234
+ continue;
235
+ }
236
+ if (arg === '--pipeline-project' && next) {
237
+ parsed.pipelineProject = next;
238
+ i += 1;
239
+ continue;
240
+ }
241
+ if (arg === '--pipeline-parallel') {
242
+ parsed.pipelineParallel = true;
243
+ continue;
244
+ }
245
+ if (arg === '--pipeline-dry-run') {
246
+ parsed.pipelineDryRun = true;
247
+ continue;
248
+ }
249
+ if (arg === '--spec' && next) {
250
+ parsed.specPDF = next;
251
+ i += 1;
252
+ continue;
253
+ }
254
+ if (arg === '--since' && next) {
255
+ parsed.gitSince = next;
256
+ i += 1;
257
+ continue;
258
+ }
259
+ if (arg === '--time' && next) {
260
+ const value = Number(next);
261
+ if (Number.isFinite(value)) {
262
+ parsed.timeLimitMinutes = value;
263
+ }
264
+ i += 1;
265
+ continue;
266
+ }
267
+ if (arg === '--budget-usd' && next) {
268
+ const value = Number(next);
269
+ if (Number.isFinite(value)) {
270
+ parsed.budgetUSD = value;
271
+ }
272
+ i += 1;
273
+ continue;
274
+ }
275
+ if (arg === '--budget-tokens' && next) {
276
+ const value = Number(next);
277
+ if (Number.isFinite(value)) {
278
+ parsed.budgetTokens = value;
279
+ }
280
+ i += 1;
281
+ continue;
282
+ }
283
+ if (arg === '--policy-min-confidence' && next) {
284
+ const value = Number(next);
285
+ if (Number.isFinite(value)) {
286
+ parsed.policyMinConfidence = value;
287
+ }
288
+ i += 1;
289
+ continue;
290
+ }
291
+ if (arg === '--policy-safe-merge-confidence' && next) {
292
+ const value = Number(next);
293
+ if (Number.isFinite(value)) {
294
+ parsed.policySafeMergeConfidence = value;
295
+ }
296
+ i += 1;
297
+ continue;
298
+ }
299
+ if (arg === '--policy-force-full-on-warnings' && next) {
300
+ const value = Number(next);
301
+ if (Number.isFinite(value)) {
302
+ parsed.policyWarningsThreshold = value;
303
+ }
304
+ i += 1;
305
+ continue;
306
+ }
307
+ if (arg === '--policy-risky-patterns' && next) {
308
+ parsed.policyRiskyPatterns = next.split(',').map((value) => value.trim()).filter(Boolean);
309
+ i += 1;
310
+ continue;
311
+ }
312
+ if (arg === '--ci-comment-path' && next) {
313
+ parsed.ciCommentPath = next;
314
+ i += 1;
315
+ continue;
316
+ }
317
+ if (arg === '--github-output' && next) {
318
+ parsed.githubOutputPath = next;
319
+ i += 1;
320
+ continue;
321
+ }
322
+ if (arg === '--fail-on-must-add-tests') {
323
+ parsed.failOnMustAddTests = true;
324
+ continue;
325
+ }
326
+ if (arg === '--feedback-input' && next) {
327
+ parsed.feedbackInputPath = next;
328
+ i += 1;
329
+ continue;
330
+ }
331
+ if (arg === '--traceability-report' && next) {
332
+ parsed.traceabilityReportPath = next;
333
+ i += 1;
334
+ continue;
335
+ }
336
+ if (arg === '--traceability-capture-output' && next) {
337
+ parsed.traceabilityCaptureOutputPath = next;
338
+ i += 1;
339
+ continue;
340
+ }
341
+ if (arg === '--traceability-coverage-map' && next) {
342
+ parsed.traceabilityCoverageMapPath = next;
343
+ i += 1;
344
+ continue;
345
+ }
346
+ if (arg === '--traceability-changed-files' && next) {
347
+ parsed.traceabilityChangedFilesPath = next;
348
+ i += 1;
349
+ continue;
350
+ }
351
+ if (arg === '--traceability-input' && next) {
352
+ parsed.traceabilityInputPath = next;
353
+ i += 1;
354
+ continue;
355
+ }
356
+ if (arg === '--traceability-min-hits' && next) {
357
+ const value = Number(next);
358
+ if (Number.isFinite(value)) {
359
+ parsed.traceabilityMinHits = value;
360
+ }
361
+ i += 1;
362
+ continue;
363
+ }
364
+ if (arg === '--traceability-max-files-per-test' && next) {
365
+ const value = Number(next);
366
+ if (Number.isFinite(value)) {
367
+ parsed.traceabilityMaxFilesPerTest = value;
368
+ }
369
+ i += 1;
370
+ continue;
371
+ }
372
+ if (arg === '--traceability-max-age-days' && next) {
373
+ const value = Number(next);
374
+ if (Number.isFinite(value)) {
375
+ parsed.traceabilityMaxAgeDays = value;
376
+ }
377
+ i += 1;
378
+ continue;
379
+ }
380
+ if (arg === '--branch' && next) {
381
+ parsed.branch = next;
382
+ i += 1;
383
+ continue;
384
+ }
385
+ if (arg === '--commit-message' && next) {
386
+ parsed.commitMessage = next;
387
+ i += 1;
388
+ continue;
389
+ }
390
+ if (arg === '--create-pr') {
391
+ parsed.createPr = true;
392
+ continue;
393
+ }
394
+ if (arg === '--pr-title' && next) {
395
+ parsed.prTitle = next;
396
+ i += 1;
397
+ continue;
398
+ }
399
+ if (arg === '--pr-body' && next) {
400
+ parsed.prBody = next;
401
+ i += 1;
402
+ continue;
403
+ }
404
+ if (arg === '--pr-base' && next) {
405
+ parsed.prBase = next;
406
+ i += 1;
407
+ continue;
408
+ }
409
+ if (arg === '--dry-run') {
410
+ parsed.dryRun = true;
411
+ continue;
412
+ }
413
+ }
414
+ return parsed;
415
+ }
416
+ async function main() {
417
+ const args = parseArgs(process.argv.slice(2));
418
+ const autoConfig = resolveAutoConfig(args);
419
+ if (args.help || !args.command) {
420
+ printUsage();
421
+ process.exit(args.command ? 0 : 1);
422
+ }
423
+ if (args.command === 'llm-health') {
424
+ await runLlmHealth();
425
+ return;
426
+ }
427
+ if (args.command === 'feedback') {
428
+ if (!args.path && !autoConfig) {
429
+ // eslint-disable-next-line no-console
430
+ console.error('Error: --path is required for feedback command');
431
+ process.exit(1);
432
+ }
433
+ if (!args.feedbackInputPath) {
434
+ // eslint-disable-next-line no-console
435
+ console.error('Error: --feedback-input <path> is required for feedback command');
436
+ process.exit(1);
437
+ }
438
+ const { config } = resolveConfig(process.cwd(), autoConfig, {
439
+ path: args.path,
440
+ testsRoot: args.testsRoot,
441
+ mode: 'impact',
442
+ });
443
+ const reportRoot = config.testsRoot || config.path;
444
+ const raw = JSON.parse(readFileSync(args.feedbackInputPath, 'utf-8'));
445
+ const payload = {
446
+ timestamp: raw.timestamp || new Date().toISOString(),
447
+ runSet: raw.runSet || 'targeted',
448
+ recommendedTests: raw.recommendedTests || [],
449
+ executedTests: raw.executedTests || [],
450
+ failedTests: raw.failedTests || [],
451
+ escapedFailures: raw.escapedFailures || [],
452
+ };
453
+ const output = appendFeedbackAndRecompute(reportRoot, payload);
454
+ // eslint-disable-next-line no-console
455
+ console.log(`Feedback data: ${output.feedbackPath}`);
456
+ // eslint-disable-next-line no-console
457
+ console.log(`Calibration data: ${output.calibrationPath}`);
458
+ // eslint-disable-next-line no-console
459
+ console.log(`Calibration overall: precision=${output.calibration.overall.precision}, recall=${output.calibration.overall.recall}, fnr=${output.calibration.overall.falseNegativeRate}`);
460
+ return;
461
+ }
462
+ if (args.command === 'traceability-capture') {
463
+ if (!args.path && !autoConfig) {
464
+ // eslint-disable-next-line no-console
465
+ console.error('Error: --path is required for traceability-capture command');
466
+ process.exit(1);
467
+ }
468
+ if (!args.traceabilityReportPath) {
469
+ // eslint-disable-next-line no-console
470
+ console.error('Error: --traceability-report <path> is required for traceability-capture command');
471
+ process.exit(1);
472
+ }
473
+ const { config } = resolveConfig(process.cwd(), autoConfig, {
474
+ path: args.path,
475
+ testsRoot: args.testsRoot,
476
+ mode: 'impact',
477
+ gitSince: args.gitSince,
478
+ });
479
+ const reportRoot = config.testsRoot || config.path;
480
+ const output = captureTraceabilityInput({
481
+ appPath: config.path,
482
+ testsRoot: reportRoot,
483
+ reportPath: args.traceabilityReportPath,
484
+ sinceRef: args.gitSince || config.git.since,
485
+ outputPath: args.traceabilityCaptureOutputPath,
486
+ coverageMapPath: args.traceabilityCoverageMapPath,
487
+ changedFilesPath: args.traceabilityChangedFilesPath,
488
+ });
489
+ // eslint-disable-next-line no-console
490
+ console.log(`Traceability input: ${output.outputPath}`);
491
+ // eslint-disable-next-line no-console
492
+ console.log(`Traceability tests seen: ${output.testsSeen}`);
493
+ // eslint-disable-next-line no-console
494
+ console.log(`Traceability runs generated: ${output.runsGenerated}`);
495
+ // eslint-disable-next-line no-console
496
+ console.log(`Traceability changed files used: ${output.changedFilesUsed}`);
497
+ if (output.warnings.length > 0) {
498
+ // eslint-disable-next-line no-console
499
+ console.log(`Traceability warnings: ${output.warnings.join(' | ')}`);
500
+ }
501
+ return;
502
+ }
503
+ if (args.command === 'traceability-ingest') {
504
+ if (!args.path && !autoConfig) {
505
+ // eslint-disable-next-line no-console
506
+ console.error('Error: --path is required for traceability-ingest command');
507
+ process.exit(1);
508
+ }
509
+ if (!args.traceabilityInputPath) {
510
+ // eslint-disable-next-line no-console
511
+ console.error('Error: --traceability-input <path> is required for traceability-ingest command');
512
+ process.exit(1);
513
+ }
514
+ const { config } = resolveConfig(process.cwd(), autoConfig, {
515
+ path: args.path,
516
+ testsRoot: args.testsRoot,
517
+ mode: 'impact',
518
+ });
519
+ const reportRoot = config.testsRoot || config.path;
520
+ const raw = JSON.parse(readFileSync(args.traceabilityInputPath, 'utf-8'));
521
+ const output = ingestTraceabilityInput(reportRoot, config.impact.traceability, raw, {
522
+ minHits: args.traceabilityMinHits,
523
+ maxFilesPerTest: args.traceabilityMaxFilesPerTest,
524
+ maxAgeDays: args.traceabilityMaxAgeDays,
525
+ });
526
+ // eslint-disable-next-line no-console
527
+ console.log(`Traceability manifest: ${output.manifestPath}`);
528
+ // eslint-disable-next-line no-console
529
+ console.log(`Traceability state: ${output.statePath}`);
530
+ // eslint-disable-next-line no-console
531
+ console.log(`Traceability ingested entries: ${output.entriesIngested}`);
532
+ // eslint-disable-next-line no-console
533
+ console.log(`Traceability tracked tests: ${output.testsTracked}`);
534
+ // eslint-disable-next-line no-console
535
+ console.log(`Traceability tracked edges: ${output.edgesTracked}`);
536
+ if (output.warnings.length > 0) {
537
+ // eslint-disable-next-line no-console
538
+ console.log(`Traceability warnings: ${output.warnings.join(' | ')}`);
539
+ }
540
+ return;
541
+ }
542
+ if (args.command === 'finalize-generated-tests') {
543
+ if (!args.path && !autoConfig) {
544
+ // eslint-disable-next-line no-console
545
+ console.error('Error: --path is required for finalize-generated-tests command');
546
+ process.exit(1);
547
+ }
548
+ const { config } = resolveConfig(process.cwd(), autoConfig, {
549
+ path: args.path,
550
+ testsRoot: args.testsRoot,
551
+ mode: 'gap',
552
+ });
553
+ const result = finalizeGeneratedTests({
554
+ appPath: config.path,
555
+ testsRoot: config.testsRoot || config.path,
556
+ branch: args.branch,
557
+ commitMessage: args.commitMessage,
558
+ createPr: args.createPr,
559
+ prTitle: args.prTitle,
560
+ prBody: args.prBody,
561
+ baseBranch: args.prBase,
562
+ dryRun: args.dryRun,
563
+ });
564
+ // eslint-disable-next-line no-console
565
+ console.log(`Finalize repo root: ${result.repoRoot}`);
566
+ // eslint-disable-next-line no-console
567
+ console.log(`Finalize branch: ${result.branch}`);
568
+ // eslint-disable-next-line no-console
569
+ console.log(`Finalize staged paths: ${result.stagedPaths.join(', ') || 'none'}`);
570
+ // eslint-disable-next-line no-console
571
+ console.log(`Finalize commit: ${result.committed ? 'created' : 'skipped'}`);
572
+ if (result.commitSha) {
573
+ // eslint-disable-next-line no-console
574
+ console.log(`Finalize commit sha: ${result.commitSha}`);
575
+ }
576
+ if (result.prUrl) {
577
+ // eslint-disable-next-line no-console
578
+ console.log(`Finalize PR: ${result.prUrl}`);
579
+ }
580
+ return;
581
+ }
582
+ if (args.command === 'auto-heal-pr') {
583
+ if (!args.path && !autoConfig) {
584
+ // eslint-disable-next-line no-console
585
+ console.error('Error: --path is required for auto-heal-pr command');
586
+ process.exit(1);
587
+ }
588
+ const { config } = resolveConfig(process.cwd(), autoConfig, {
589
+ path: args.path,
590
+ testsRoot: args.testsRoot,
591
+ mode: 'gap',
592
+ framework: args.framework,
593
+ timeLimitMinutes: args.timeLimitMinutes,
594
+ budget: {
595
+ maxUSD: args.budgetUSD,
596
+ maxTokens: args.budgetTokens,
597
+ },
598
+ testPatterns: args.testPatterns,
599
+ flowPatterns: args.flowPatterns,
600
+ flowExclude: args.flowExclude,
601
+ flowCatalogPath: args.flowCatalogPath,
602
+ specPDF: args.specPDF,
603
+ gitSince: args.gitSince,
604
+ pipeline: {
605
+ enabled: true,
606
+ scenarios: args.pipelineScenarios,
607
+ outputDir: args.pipelineOutput,
608
+ baseUrl: args.pipelineBaseUrl,
609
+ browser: args.pipelineBrowser,
610
+ headless: args.pipelineHeadless,
611
+ project: args.pipelineProject,
612
+ parallel: args.pipelineParallel,
613
+ dryRun: args.pipelineDryRun,
614
+ mcp: args.pipelineMcp,
615
+ },
616
+ });
617
+ if (args.allowFallback) {
618
+ config.impact.allowFallback = true;
619
+ }
620
+ await runGap(config, { apply: true });
621
+ const reportRoot = config.testsRoot || config.path;
622
+ if (args.traceabilityReportPath) {
623
+ const unstableSpecs = extractPlaywrightUnstableSpecs(args.traceabilityReportPath, [reportRoot, config.path]);
624
+ if (unstableSpecs.length > 0) {
625
+ const targetedSummary = runTargetedSpecHeal(reportRoot, unstableSpecs.map((spec) => ({
626
+ specPath: spec.specPath,
627
+ status: spec.status,
628
+ reason: `Playwright report: failingTests=${spec.failingTests}, flakyTests=${spec.flakyTests}`,
629
+ })), {
630
+ ...config.pipeline,
631
+ enabled: true,
632
+ heal: true,
633
+ });
634
+ const healedCount = targetedSummary.results.filter((result) => result.healStatus === 'success').length;
635
+ // eslint-disable-next-line no-console
636
+ console.log(`Auto-heal targeted unstable specs: ${unstableSpecs.length} (healed=${healedCount})`);
637
+ if (targetedSummary.warnings.length > 0) {
638
+ // eslint-disable-next-line no-console
639
+ console.log(`Auto-heal warnings: ${targetedSummary.warnings.join(' | ')}`);
640
+ }
641
+ const gapPath = join(reportRoot, '.e2e-ai-agents', 'gap.json');
642
+ if (existsSync(gapPath)) {
643
+ const gap = JSON.parse(readFileSync(gapPath, 'utf-8'));
644
+ const existingResults = Array.isArray(gap.pipeline?.results) ? gap.pipeline?.results : [];
645
+ const existingWarnings = Array.isArray(gap.pipeline?.warnings) ? gap.pipeline?.warnings : [];
646
+ gap.pipeline = {
647
+ runner: gap.pipeline?.runner || targetedSummary.runner,
648
+ results: [...existingResults, ...targetedSummary.results],
649
+ warnings: Array.from(new Set([...(existingWarnings || []), ...targetedSummary.warnings])),
650
+ };
651
+ writeFileSync(gapPath, `${JSON.stringify(gap, null, 2)}\n`, 'utf-8');
652
+ }
653
+ }
654
+ else {
655
+ // eslint-disable-next-line no-console
656
+ console.log('Auto-heal targeted unstable specs: 0');
657
+ }
658
+ }
659
+ const branchSuffix = new Date().toISOString().replace(/[:.]/g, '-');
660
+ const result = finalizeGeneratedTests({
661
+ appPath: config.path,
662
+ testsRoot: reportRoot,
663
+ branch: args.branch || `auto-heal-${branchSuffix}`,
664
+ commitMessage: args.commitMessage || 'test(e2e): auto-heal generated specs',
665
+ createPr: true,
666
+ prTitle: args.prTitle || 'test(e2e): auto-heal generated specs',
667
+ prBody: args.prBody || 'Automated e2e-heal run generated by @yasserkhanorg/e2e-agents.',
668
+ baseBranch: args.prBase || 'master',
669
+ dryRun: args.dryRun,
670
+ });
671
+ // eslint-disable-next-line no-console
672
+ console.log(`Auto-heal repo root: ${result.repoRoot}`);
673
+ // eslint-disable-next-line no-console
674
+ console.log(`Auto-heal branch: ${result.branch}`);
675
+ // eslint-disable-next-line no-console
676
+ console.log(`Auto-heal staged paths: ${result.stagedPaths.join(', ') || 'none'}`);
677
+ // eslint-disable-next-line no-console
678
+ console.log(`Auto-heal commit: ${result.committed ? 'created' : 'skipped'}`);
679
+ if (result.commitSha) {
680
+ // eslint-disable-next-line no-console
681
+ console.log(`Auto-heal commit sha: ${result.commitSha}`);
682
+ }
683
+ if (result.prUrl) {
684
+ // eslint-disable-next-line no-console
685
+ console.log(`Auto-heal PR: ${result.prUrl}`);
686
+ }
687
+ return;
688
+ }
689
+ if (!args.path && !autoConfig) {
690
+ // eslint-disable-next-line no-console
691
+ console.error('Error: --path is required (or provide a config file with path set)');
692
+ printUsage();
693
+ process.exit(1);
694
+ }
695
+ const forcePipelineFromApproval = args.command === 'approve-and-generate';
696
+ const { config } = resolveConfig(process.cwd(), autoConfig, {
697
+ path: args.path,
698
+ testsRoot: args.testsRoot,
699
+ mode: (args.command === 'gap' || args.command === 'approve-and-generate') ? 'gap' : 'impact',
700
+ framework: args.framework,
701
+ timeLimitMinutes: args.timeLimitMinutes,
702
+ budget: {
703
+ maxUSD: args.budgetUSD,
704
+ maxTokens: args.budgetTokens,
705
+ },
706
+ testPatterns: args.testPatterns,
707
+ flowPatterns: args.flowPatterns,
708
+ flowExclude: args.flowExclude,
709
+ flowCatalogPath: args.flowCatalogPath,
710
+ specPDF: args.specPDF,
711
+ gitSince: args.gitSince,
712
+ pipeline: (args.pipeline || forcePipelineFromApproval)
713
+ ? {
714
+ enabled: true,
715
+ scenarios: args.pipelineScenarios,
716
+ outputDir: args.pipelineOutput,
717
+ baseUrl: args.pipelineBaseUrl,
718
+ browser: args.pipelineBrowser,
719
+ headless: args.pipelineHeadless,
720
+ project: args.pipelineProject,
721
+ parallel: args.pipelineParallel,
722
+ dryRun: args.pipelineDryRun,
723
+ mcp: args.pipelineMcp !== undefined ? args.pipelineMcp : forcePipelineFromApproval,
724
+ }
725
+ : undefined,
726
+ policy: args.policyMinConfidence !== undefined ||
727
+ args.policySafeMergeConfidence !== undefined ||
728
+ args.policyWarningsThreshold !== undefined ||
729
+ (args.policyRiskyPatterns && args.policyRiskyPatterns.length > 0)
730
+ ? {
731
+ minConfidenceForTargeted: args.policyMinConfidence,
732
+ safeMergeMinConfidence: args.policySafeMergeConfidence,
733
+ forceFullOnWarningsAtOrAbove: args.policyWarningsThreshold,
734
+ riskyFilePatterns: args.policyRiskyPatterns,
735
+ }
736
+ : undefined,
737
+ });
738
+ if (args.allowFallback) {
739
+ config.impact.allowFallback = true;
740
+ }
741
+ if (args.command === 'impact') {
742
+ await runImpact(config, { apply: args.apply });
743
+ return;
744
+ }
745
+ if (args.command === 'suggest') {
746
+ await runImpact(config, { apply: args.apply });
747
+ const reportRoot = config.testsRoot || config.path;
748
+ const impactPath = join(reportRoot, '.e2e-ai-agents', 'impact.json');
749
+ if (!existsSync(impactPath)) {
750
+ throw new Error(`Impact report not found at ${impactPath}`);
751
+ }
752
+ const impact = JSON.parse(readFileSync(impactPath, 'utf-8'));
753
+ const basePlan = buildPlanFromImpactReport(impact, config.policy);
754
+ const withActions = attachDeveloperActions(basePlan, {
755
+ appPath: config.path,
756
+ testsRoot: reportRoot,
757
+ sinceRef: config.git.since,
758
+ });
759
+ const plan = applyOperationalInsights(withActions, reportRoot);
760
+ const planPath = writePlanReport(reportRoot, plan);
761
+ const summaryMarkdown = renderCiSummaryMarkdown(plan);
762
+ const summaryPath = writeCiSummary(reportRoot, summaryMarkdown, args.ciCommentPath);
763
+ const ghaOutput = args.githubOutputPath || process.env.GITHUB_OUTPUT;
764
+ if (ghaOutput) {
765
+ appendFileSync(ghaOutput, `run_set=${plan.runSet}\n`);
766
+ appendFileSync(ghaOutput, `action=${plan.decision.action}\n`);
767
+ appendFileSync(ghaOutput, `confidence=${plan.confidence}\n`);
768
+ appendFileSync(ghaOutput, `recommended_tests_count=${plan.recommendedTests.length}\n`);
769
+ appendFileSync(ghaOutput, `required_new_tests_count=${plan.requiredNewTests.length}\n`);
770
+ appendFileSync(ghaOutput, `plan_path=${planPath}\n`);
771
+ appendFileSync(ghaOutput, `summary_path=${summaryPath}\n`);
772
+ }
773
+ // eslint-disable-next-line no-console
774
+ console.log(`Suggested run set: ${plan.runSet} (confidence ${plan.confidence})`);
775
+ // eslint-disable-next-line no-console
776
+ console.log(`Decision: ${plan.decision.action} - ${plan.decision.summary}`);
777
+ // eslint-disable-next-line no-console
778
+ console.log(`Plan data: ${planPath}`);
779
+ // eslint-disable-next-line no-console
780
+ console.log(`CI summary: ${summaryPath}`);
781
+ if (plan.nextActions) {
782
+ // eslint-disable-next-line no-console
783
+ console.log(`Next action (run existing): ${plan.nextActions.runRecommendedTests || plan.nextActions.runSmokeSuite}`);
784
+ // eslint-disable-next-line no-console
785
+ console.log(`Next action (approve + generate): ${plan.nextActions.approveAndGenerate || plan.nextActions.generateMissingTests}`);
786
+ // eslint-disable-next-line no-console
787
+ console.log(`Next action (heal): ${plan.nextActions.healGeneratedTests}`);
788
+ }
789
+ if (args.failOnMustAddTests && plan.decision.action === 'must-add-tests') {
790
+ process.exit(2);
791
+ }
792
+ return;
793
+ }
794
+ if (args.command === 'approve-and-generate') {
795
+ await runGap(config, { apply: true });
796
+ return;
797
+ }
798
+ await runGap(config, { apply: args.apply });
799
+ }
800
+ async function runLlmHealth() {
801
+ if (!process.env.ANTHROPIC_API_KEY) {
802
+ // eslint-disable-next-line no-console
803
+ console.error('ANTHROPIC_API_KEY is required for llm-health.');
804
+ process.exit(1);
805
+ }
806
+ const model = process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-5-20250929';
807
+ const provider = new AnthropicProvider({
808
+ apiKey: process.env.ANTHROPIC_API_KEY,
809
+ model,
810
+ });
811
+ try {
812
+ const response = await provider.generateText('Reply with OK.', { maxTokens: 8, timeout: 15000 });
813
+ const text = response.text.trim();
814
+ // eslint-disable-next-line no-console
815
+ console.log(`Anthropic OK (${model}) -> ${text}`);
816
+ }
817
+ catch (error) {
818
+ if (error instanceof LLMProviderError) {
819
+ // eslint-disable-next-line no-console
820
+ console.error(`Anthropic failed: ${error.message}`);
821
+ if (error.cause instanceof Error) {
822
+ // eslint-disable-next-line no-console
823
+ console.error(`Cause: ${error.cause.message}`);
824
+ }
825
+ }
826
+ else if (error instanceof Error) {
827
+ // eslint-disable-next-line no-console
828
+ console.error(`Anthropic failed: ${error.message}`);
829
+ }
830
+ else {
831
+ // eslint-disable-next-line no-console
832
+ console.error(`Anthropic failed: ${String(error)}`);
833
+ }
834
+ process.exit(1);
835
+ }
836
+ }
837
+ main().catch((error) => {
838
+ // eslint-disable-next-line no-console
839
+ console.error(error instanceof Error ? error.message : String(error));
840
+ process.exit(1);
841
+ });