playwright-intelligence-engine 0.0.0-stage → 2.0.1

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 (353) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +229 -3
  3. package/bin/pw-intel.js +37 -0
  4. package/dist/authoring/author.d.ts +83 -0
  5. package/dist/authoring/author.d.ts.map +1 -0
  6. package/dist/authoring/generation-contract.d.ts +90 -0
  7. package/dist/authoring/generation-contract.d.ts.map +1 -0
  8. package/dist/authoring/hybrid-author.d.ts +6 -0
  9. package/dist/authoring/hybrid-author.d.ts.map +1 -0
  10. package/dist/authoring/index.d.ts +7 -0
  11. package/dist/authoring/index.d.ts.map +1 -0
  12. package/dist/authoring/llm-author.d.ts +28 -0
  13. package/dist/authoring/llm-author.d.ts.map +1 -0
  14. package/dist/authoring/naming/index.d.ts +4 -0
  15. package/dist/authoring/naming/index.d.ts.map +1 -0
  16. package/dist/authoring/naming/name-plan.d.ts +45 -0
  17. package/dist/authoring/naming/name-plan.d.ts.map +1 -0
  18. package/dist/authoring/naming/name-planner.d.ts +25 -0
  19. package/dist/authoring/naming/name-planner.d.ts.map +1 -0
  20. package/dist/authoring/naming/naming-linter.d.ts +20 -0
  21. package/dist/authoring/naming/naming-linter.d.ts.map +1 -0
  22. package/dist/authoring/template-author.d.ts +6 -0
  23. package/dist/authoring/template-author.d.ts.map +1 -0
  24. package/dist/cli/index.d.ts +2 -0
  25. package/dist/cli/index.d.ts.map +1 -0
  26. package/dist/collectors/collector-interface.d.ts +7 -0
  27. package/dist/collectors/collector-interface.d.ts.map +1 -0
  28. package/dist/collectors/environment-collector.d.ts +12 -0
  29. package/dist/collectors/environment-collector.d.ts.map +1 -0
  30. package/dist/collectors/error-collector.d.ts +18 -0
  31. package/dist/collectors/error-collector.d.ts.map +1 -0
  32. package/dist/collectors/git-diff-collector.d.ts +20 -0
  33. package/dist/collectors/git-diff-collector.d.ts.map +1 -0
  34. package/dist/collectors/live-browser-collector.d.ts +45 -0
  35. package/dist/collectors/live-browser-collector.d.ts.map +1 -0
  36. package/dist/collectors/network-collector.d.ts +28 -0
  37. package/dist/collectors/network-collector.d.ts.map +1 -0
  38. package/dist/collectors/static-ast-collector.d.ts +32 -0
  39. package/dist/collectors/static-ast-collector.d.ts.map +1 -0
  40. package/dist/collectors/trace-collector.d.ts +12 -0
  41. package/dist/collectors/trace-collector.d.ts.map +1 -0
  42. package/dist/config/project-config.d.ts +33 -0
  43. package/dist/config/project-config.d.ts.map +1 -0
  44. package/dist/core/errors/engine-errors.d.ts +34 -0
  45. package/dist/core/errors/engine-errors.d.ts.map +1 -0
  46. package/dist/core/types/diagnostic-context.d.ts +151 -0
  47. package/dist/core/types/diagnostic-context.d.ts.map +1 -0
  48. package/dist/core/types/repair-evidence.d.ts +56 -0
  49. package/dist/core/types/repair-evidence.d.ts.map +1 -0
  50. package/dist/core/version.d.ts +13 -0
  51. package/dist/core/version.d.ts.map +1 -0
  52. package/dist/diagnostics/diagnostic-contract.d.ts +52 -0
  53. package/dist/diagnostics/diagnostic-contract.d.ts.map +1 -0
  54. package/dist/diagnostics/diagnostic-report.d.ts +11 -0
  55. package/dist/diagnostics/diagnostic-report.d.ts.map +1 -0
  56. package/dist/diagnostics/evidence-correlator.d.ts +22 -0
  57. package/dist/diagnostics/evidence-correlator.d.ts.map +1 -0
  58. package/dist/diagnostics/external-failure-evidence.d.ts +18 -0
  59. package/dist/diagnostics/external-failure-evidence.d.ts.map +1 -0
  60. package/dist/diagnostics/failure-category.d.ts +4 -0
  61. package/dist/diagnostics/failure-category.d.ts.map +1 -0
  62. package/dist/diagnostics/failure-classifier.d.ts +58 -0
  63. package/dist/diagnostics/failure-classifier.d.ts.map +1 -0
  64. package/dist/evaluation/evaluation-scorecard.d.ts +72 -0
  65. package/dist/evaluation/evaluation-scorecard.d.ts.map +1 -0
  66. package/dist/evaluation/repository-inspector.d.ts +21 -0
  67. package/dist/evaluation/repository-inspector.d.ts.map +1 -0
  68. package/dist/evaluation/repository-runner.d.ts +24 -0
  69. package/dist/evaluation/repository-runner.d.ts.map +1 -0
  70. package/dist/evidence/evidence-model.d.ts +88 -0
  71. package/dist/evidence/evidence-model.d.ts.map +1 -0
  72. package/dist/evidence/evidence-normalizer.d.ts +19 -0
  73. package/dist/evidence/evidence-normalizer.d.ts.map +1 -0
  74. package/dist/evidence/evidence-redactor.d.ts +37 -0
  75. package/dist/evidence/evidence-redactor.d.ts.map +1 -0
  76. package/dist/framework/config-adapter.d.ts +28 -0
  77. package/dist/framework/config-adapter.d.ts.map +1 -0
  78. package/dist/gate/index.d.ts +21 -0
  79. package/dist/gate/index.d.ts.map +1 -0
  80. package/dist/gate/oracle.d.ts +26 -0
  81. package/dist/gate/oracle.d.ts.map +1 -0
  82. package/dist/gate/quality.d.ts +27 -0
  83. package/dist/gate/quality.d.ts.map +1 -0
  84. package/dist/gate/safety.d.ts +28 -0
  85. package/dist/gate/safety.d.ts.map +1 -0
  86. package/dist/generator/architecture-generator.d.ts +55 -0
  87. package/dist/generator/architecture-generator.d.ts.map +1 -0
  88. package/dist/generator/assertion-quality-gate.d.ts +18 -0
  89. package/dist/generator/assertion-quality-gate.d.ts.map +1 -0
  90. package/dist/generator/candidate-writer.d.ts +34 -0
  91. package/dist/generator/candidate-writer.d.ts.map +1 -0
  92. package/dist/generator/test-generator.d.ts +26 -0
  93. package/dist/generator/test-generator.d.ts.map +1 -0
  94. package/dist/governance/audit-log.d.ts +23 -0
  95. package/dist/governance/audit-log.d.ts.map +1 -0
  96. package/dist/governance/governance-policy.d.ts +24 -0
  97. package/dist/governance/governance-policy.d.ts.map +1 -0
  98. package/dist/governance/team-dashboard.d.ts +51 -0
  99. package/dist/governance/team-dashboard.d.ts.map +1 -0
  100. package/dist/governance/workflow-audit.d.ts +13 -0
  101. package/dist/governance/workflow-audit.d.ts.map +1 -0
  102. package/dist/index.d.ts +90 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +20318 -0
  105. package/dist/integrations/ci-summary.d.ts +14 -0
  106. package/dist/integrations/ci-summary.d.ts.map +1 -0
  107. package/dist/integrations/playwright-report-adapter.d.ts +22 -0
  108. package/dist/integrations/playwright-report-adapter.d.ts.map +1 -0
  109. package/dist/integrations/quality-gate.d.ts +35 -0
  110. package/dist/integrations/quality-gate.d.ts.map +1 -0
  111. package/dist/integrations/sarif-report.d.ts +48 -0
  112. package/dist/integrations/sarif-report.d.ts.map +1 -0
  113. package/dist/intelligence/autonomous-repair-workflow.d.ts +31 -0
  114. package/dist/intelligence/autonomous-repair-workflow.d.ts.map +1 -0
  115. package/dist/intelligence/business-intent.d.ts +58 -0
  116. package/dist/intelligence/business-intent.d.ts.map +1 -0
  117. package/dist/intelligence/evidence-bundle.d.ts +364 -0
  118. package/dist/intelligence/evidence-bundle.d.ts.map +1 -0
  119. package/dist/intelligence/failure-clustering.d.ts +38 -0
  120. package/dist/intelligence/failure-clustering.d.ts.map +1 -0
  121. package/dist/intelligence/generation-agent.d.ts +120 -0
  122. package/dist/intelligence/generation-agent.d.ts.map +1 -0
  123. package/dist/intelligence/generation-verifier.d.ts +151 -0
  124. package/dist/intelligence/generation-verifier.d.ts.map +1 -0
  125. package/dist/intelligence/llm-proposal.d.ts +377 -0
  126. package/dist/intelligence/llm-proposal.d.ts.map +1 -0
  127. package/dist/intelligence/locator-disambiguation-engine.d.ts +38 -0
  128. package/dist/intelligence/locator-disambiguation-engine.d.ts.map +1 -0
  129. package/dist/intelligence/model-contract.d.ts +86 -0
  130. package/dist/intelligence/model-contract.d.ts.map +1 -0
  131. package/dist/intelligence/model-provider.d.ts +75 -0
  132. package/dist/intelligence/model-provider.d.ts.map +1 -0
  133. package/dist/intelligence/project-intelligence.d.ts +22 -0
  134. package/dist/intelligence/project-intelligence.d.ts.map +1 -0
  135. package/dist/intelligence/repair-agent.d.ts +46 -0
  136. package/dist/intelligence/repair-agent.d.ts.map +1 -0
  137. package/dist/intelligence/repository-profile.d.ts +131 -0
  138. package/dist/intelligence/repository-profile.d.ts.map +1 -0
  139. package/dist/intelligence/suite-intelligence.d.ts +59 -0
  140. package/dist/intelligence/suite-intelligence.d.ts.map +1 -0
  141. package/dist/intelligence/test-intelligence.d.ts +44 -0
  142. package/dist/intelligence/test-intelligence.d.ts.map +1 -0
  143. package/dist/intelligence/trace-proposal-workflow.d.ts +30 -0
  144. package/dist/intelligence/trace-proposal-workflow.d.ts.map +1 -0
  145. package/dist/intelligence/verified-llm-repair-workflow.d.ts +21 -0
  146. package/dist/intelligence/verified-llm-repair-workflow.d.ts.map +1 -0
  147. package/dist/intelligence/workflow-planner.d.ts +278 -0
  148. package/dist/intelligence/workflow-planner.d.ts.map +1 -0
  149. package/dist/mcp/server.d.ts +32 -0
  150. package/dist/mcp/server.d.ts.map +1 -0
  151. package/dist/memory/stability-ledger.d.ts +46 -0
  152. package/dist/memory/stability-ledger.d.ts.map +1 -0
  153. package/dist/perception/app-model.d.ts +55 -0
  154. package/dist/perception/app-model.d.ts.map +1 -0
  155. package/dist/perception/crawler.d.ts +18 -0
  156. package/dist/perception/crawler.d.ts.map +1 -0
  157. package/dist/perception/index.d.ts +3 -0
  158. package/dist/perception/index.d.ts.map +1 -0
  159. package/dist/repair/approval-service.d.ts +139 -0
  160. package/dist/repair/approval-service.d.ts.map +1 -0
  161. package/dist/repair/assertion-hasher.d.ts +62 -0
  162. package/dist/repair/assertion-hasher.d.ts.map +1 -0
  163. package/dist/repair/ast-patcher.d.ts +36 -0
  164. package/dist/repair/ast-patcher.d.ts.map +1 -0
  165. package/dist/repair/browser-operation-executor.d.ts +27 -0
  166. package/dist/repair/browser-operation-executor.d.ts.map +1 -0
  167. package/dist/repair/diff-allowlist.d.ts +74 -0
  168. package/dist/repair/diff-allowlist.d.ts.map +1 -0
  169. package/dist/repair/intent-validator.d.ts +157 -0
  170. package/dist/repair/intent-validator.d.ts.map +1 -0
  171. package/dist/repair/repair-operations.d.ts +40 -0
  172. package/dist/repair/repair-operations.d.ts.map +1 -0
  173. package/dist/repair/repair-planner.d.ts +98 -0
  174. package/dist/repair/repair-planner.d.ts.map +1 -0
  175. package/dist/repair/repair-workflow.d.ts +42 -0
  176. package/dist/repair/repair-workflow.d.ts.map +1 -0
  177. package/dist/repair/safe-operation-compiler.d.ts +15 -0
  178. package/dist/repair/safe-operation-compiler.d.ts.map +1 -0
  179. package/dist/repair/source-mapper.d.ts +19 -0
  180. package/dist/repair/source-mapper.d.ts.map +1 -0
  181. package/dist/repair/statement-patcher.d.ts +75 -0
  182. package/dist/repair/statement-patcher.d.ts.map +1 -0
  183. package/dist/repair/verification-gates.d.ts +25 -0
  184. package/dist/repair/verification-gates.d.ts.map +1 -0
  185. package/dist/repair/workflow-plan.d.ts +35 -0
  186. package/dist/repair/workflow-plan.d.ts.map +1 -0
  187. package/dist/repair/workspace-promoter.d.ts +43 -0
  188. package/dist/repair/workspace-promoter.d.ts.map +1 -0
  189. package/dist/reproduce/diagnostic-rerunner.d.ts +40 -0
  190. package/dist/reproduce/diagnostic-rerunner.d.ts.map +1 -0
  191. package/dist/reproduce/direct-browser-investigator.d.ts +41 -0
  192. package/dist/reproduce/direct-browser-investigator.d.ts.map +1 -0
  193. package/dist/reproduce/interactive-workflow-runner.d.ts +43 -0
  194. package/dist/reproduce/interactive-workflow-runner.d.ts.map +1 -0
  195. package/dist/sandbox/dependency-strategy.d.ts +45 -0
  196. package/dist/sandbox/dependency-strategy.d.ts.map +1 -0
  197. package/dist/sandbox/execution-runner.d.ts +81 -0
  198. package/dist/sandbox/execution-runner.d.ts.map +1 -0
  199. package/dist/sandbox/worktree-manager.d.ts +51 -0
  200. package/dist/sandbox/worktree-manager.d.ts.map +1 -0
  201. package/dist/selector/abstention-policy.d.ts +34 -0
  202. package/dist/selector/abstention-policy.d.ts.map +1 -0
  203. package/dist/selector/candidate-extractor.d.ts +71 -0
  204. package/dist/selector/candidate-extractor.d.ts.map +1 -0
  205. package/dist/selector/live-locator-verifier.d.ts +28 -0
  206. package/dist/selector/live-locator-verifier.d.ts.map +1 -0
  207. package/dist/selector/repair-candidate-ranker.d.ts +10 -0
  208. package/dist/selector/repair-candidate-ranker.d.ts.map +1 -0
  209. package/dist/selector/selector-scorer.d.ts +43 -0
  210. package/dist/selector/selector-scorer.d.ts.map +1 -0
  211. package/dist/trace/adapters/adapter-registry.d.ts +18 -0
  212. package/dist/trace/adapters/adapter-registry.d.ts.map +1 -0
  213. package/dist/trace/adapters/trace-adapter.d.ts +21 -0
  214. package/dist/trace/adapters/trace-adapter.d.ts.map +1 -0
  215. package/dist/trace/adapters/unknown-adapter.d.ts +10 -0
  216. package/dist/trace/adapters/unknown-adapter.d.ts.map +1 -0
  217. package/dist/trace/adapters/v10-adapter.d.ts +10 -0
  218. package/dist/trace/adapters/v10-adapter.d.ts.map +1 -0
  219. package/dist/trace/adapters/v4-adapter.d.ts +11 -0
  220. package/dist/trace/adapters/v4-adapter.d.ts.map +1 -0
  221. package/dist/trace/adapters/v8-adapter.d.ts +10 -0
  222. package/dist/trace/adapters/v8-adapter.d.ts.map +1 -0
  223. package/dist/trace/adapters/v9-adapter.d.ts +10 -0
  224. package/dist/trace/adapters/v9-adapter.d.ts.map +1 -0
  225. package/dist/trace/archive-reader.d.ts +44 -0
  226. package/dist/trace/archive-reader.d.ts.map +1 -0
  227. package/dist/trace/snapshot-resolver.d.ts +55 -0
  228. package/dist/trace/snapshot-resolver.d.ts.map +1 -0
  229. package/dist/trace/trace-parser.d.ts +50 -0
  230. package/dist/trace/trace-parser.d.ts.map +1 -0
  231. package/dist/trace/version-detector.d.ts +20 -0
  232. package/dist/trace/version-detector.d.ts.map +1 -0
  233. package/package.json +76 -6
  234. package/skills/README.md +20 -0
  235. package/skills/pie-failure-investigation/SKILL.md +16 -0
  236. package/skills/pie-quality-engineer/SKILL.md +66 -0
  237. package/skills/pie-release-readiness/SKILL.md +22 -0
  238. package/skills/pie-repair-review/SKILL.md +21 -0
  239. package/skills/pie-trace-to-repair/SKILL.md +35 -0
  240. package/src/authoring/author.ts +81 -0
  241. package/src/authoring/generation-contract.ts +283 -0
  242. package/src/authoring/hybrid-author.ts +115 -0
  243. package/src/authoring/index.ts +7 -0
  244. package/src/authoring/llm-author.ts +282 -0
  245. package/src/authoring/naming/index.ts +4 -0
  246. package/src/authoring/naming/name-plan.ts +28 -0
  247. package/src/authoring/naming/name-planner.ts +339 -0
  248. package/src/authoring/naming/naming-linter.ts +292 -0
  249. package/src/authoring/template-author.ts +39 -0
  250. package/src/cli/index.ts +1172 -0
  251. package/src/collectors/collector-interface.ts +8 -0
  252. package/src/collectors/environment-collector.ts +103 -0
  253. package/src/collectors/error-collector.ts +177 -0
  254. package/src/collectors/git-diff-collector.ts +190 -0
  255. package/src/collectors/live-browser-collector.ts +412 -0
  256. package/src/collectors/network-collector.ts +90 -0
  257. package/src/collectors/static-ast-collector.ts +226 -0
  258. package/src/collectors/trace-collector.ts +66 -0
  259. package/src/config/project-config.ts +114 -0
  260. package/src/core/errors/engine-errors.ts +90 -0
  261. package/src/core/types/diagnostic-context.ts +94 -0
  262. package/src/core/types/repair-evidence.ts +45 -0
  263. package/src/core/version.ts +25 -0
  264. package/src/diagnostics/diagnostic-contract.ts +79 -0
  265. package/src/diagnostics/diagnostic-report.ts +204 -0
  266. package/src/diagnostics/evidence-correlator.ts +117 -0
  267. package/src/diagnostics/external-failure-evidence.ts +72 -0
  268. package/src/diagnostics/failure-category.ts +40 -0
  269. package/src/diagnostics/failure-classifier.ts +701 -0
  270. package/src/evaluation/evaluation-scorecard.ts +198 -0
  271. package/src/evaluation/repository-inspector.ts +143 -0
  272. package/src/evaluation/repository-runner.ts +197 -0
  273. package/src/evidence/evidence-model.ts +76 -0
  274. package/src/evidence/evidence-normalizer.ts +322 -0
  275. package/src/evidence/evidence-redactor.ts +218 -0
  276. package/src/framework/config-adapter.ts +131 -0
  277. package/src/gate/index.ts +43 -0
  278. package/src/gate/oracle.ts +69 -0
  279. package/src/gate/quality.ts +101 -0
  280. package/src/gate/safety.ts +94 -0
  281. package/src/generator/architecture-generator.ts +582 -0
  282. package/src/generator/assertion-quality-gate.ts +88 -0
  283. package/src/generator/candidate-writer.ts +288 -0
  284. package/src/generator/test-generator.ts +52 -0
  285. package/src/governance/audit-log.ts +115 -0
  286. package/src/governance/governance-policy.ts +72 -0
  287. package/src/governance/team-dashboard.ts +189 -0
  288. package/src/governance/workflow-audit.ts +72 -0
  289. package/src/index.ts +107 -0
  290. package/src/integrations/ci-summary.ts +37 -0
  291. package/src/integrations/playwright-report-adapter.ts +173 -0
  292. package/src/integrations/quality-gate.ts +95 -0
  293. package/src/integrations/sarif-report.ts +110 -0
  294. package/src/intelligence/autonomous-repair-workflow.ts +139 -0
  295. package/src/intelligence/business-intent.ts +163 -0
  296. package/src/intelligence/evidence-bundle.ts +300 -0
  297. package/src/intelligence/failure-clustering.ts +134 -0
  298. package/src/intelligence/generation-agent.ts +561 -0
  299. package/src/intelligence/generation-verifier.ts +1115 -0
  300. package/src/intelligence/llm-proposal.ts +519 -0
  301. package/src/intelligence/locator-disambiguation-engine.ts +331 -0
  302. package/src/intelligence/model-contract.ts +205 -0
  303. package/src/intelligence/model-provider.ts +348 -0
  304. package/src/intelligence/project-intelligence.ts +140 -0
  305. package/src/intelligence/repair-agent.ts +297 -0
  306. package/src/intelligence/repository-profile.ts +796 -0
  307. package/src/intelligence/suite-intelligence.ts +305 -0
  308. package/src/intelligence/test-intelligence.ts +136 -0
  309. package/src/intelligence/trace-proposal-workflow.ts +318 -0
  310. package/src/intelligence/verified-llm-repair-workflow.ts +48 -0
  311. package/src/intelligence/workflow-planner.ts +966 -0
  312. package/src/mcp/server.ts +970 -0
  313. package/src/memory/stability-ledger.ts +243 -0
  314. package/src/perception/app-model.ts +32 -0
  315. package/src/perception/crawler.ts +263 -0
  316. package/src/perception/index.ts +3 -0
  317. package/src/repair/approval-service.ts +541 -0
  318. package/src/repair/assertion-hasher.ts +386 -0
  319. package/src/repair/ast-patcher.ts +223 -0
  320. package/src/repair/browser-operation-executor.ts +263 -0
  321. package/src/repair/diff-allowlist.ts +612 -0
  322. package/src/repair/intent-validator.ts +761 -0
  323. package/src/repair/repair-operations.ts +65 -0
  324. package/src/repair/repair-planner.ts +467 -0
  325. package/src/repair/repair-workflow.ts +241 -0
  326. package/src/repair/safe-operation-compiler.ts +68 -0
  327. package/src/repair/source-mapper.ts +156 -0
  328. package/src/repair/statement-patcher.ts +544 -0
  329. package/src/repair/verification-gates.ts +46 -0
  330. package/src/repair/workflow-plan.ts +59 -0
  331. package/src/repair/workspace-promoter.ts +313 -0
  332. package/src/reproduce/diagnostic-rerunner.ts +287 -0
  333. package/src/reproduce/direct-browser-investigator.ts +208 -0
  334. package/src/reproduce/interactive-workflow-runner.ts +828 -0
  335. package/src/sandbox/dependency-strategy.ts +215 -0
  336. package/src/sandbox/execution-runner.ts +408 -0
  337. package/src/sandbox/worktree-manager.ts +287 -0
  338. package/src/selector/abstention-policy.ts +175 -0
  339. package/src/selector/candidate-extractor.ts +343 -0
  340. package/src/selector/live-locator-verifier.ts +139 -0
  341. package/src/selector/repair-candidate-ranker.ts +92 -0
  342. package/src/selector/selector-scorer.ts +344 -0
  343. package/src/trace/adapters/adapter-registry.ts +49 -0
  344. package/src/trace/adapters/trace-adapter.ts +28 -0
  345. package/src/trace/adapters/unknown-adapter.ts +50 -0
  346. package/src/trace/adapters/v10-adapter.ts +39 -0
  347. package/src/trace/adapters/v4-adapter.ts +31 -0
  348. package/src/trace/adapters/v8-adapter.ts +42 -0
  349. package/src/trace/adapters/v9-adapter.ts +39 -0
  350. package/src/trace/archive-reader.ts +246 -0
  351. package/src/trace/snapshot-resolver.ts +308 -0
  352. package/src/trace/trace-parser.ts +467 -0
  353. package/src/trace/version-detector.ts +81 -0
@@ -0,0 +1,544 @@
1
+ import { Project, SyntaxKind, Node, Statement } from 'ts-morph';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { AbstentionError } from '../core/errors/engine-errors.js';
5
+ import { AstPatcher } from './ast-patcher.js';
6
+ import { AssertionHasher } from './assertion-hasher.js';
7
+
8
+ export type StatementPatchOperation =
9
+ | 'replace-timeout-with-wait'
10
+ | 'wrap-action-with-response'
11
+ | 'insert-readiness-check'
12
+ | 'replace-inline-locator'
13
+ | 'replace-statement';
14
+
15
+ export interface BaseStatementPatchPlan {
16
+ kind: StatementPatchOperation;
17
+ /** 1-based line number in the source file. If omitted, matching is done by text pattern. */
18
+ targetLine?: number;
19
+ }
20
+
21
+ export interface ReplaceTimeoutPlan extends BaseStatementPatchPlan {
22
+ kind: 'replace-timeout-with-wait';
23
+ /** Replacement wait statement, e.g. "await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible({ timeout: 5000 });" */
24
+ replacementWaitStatement: string;
25
+ }
26
+
27
+ export interface WrapActionWithResponsePlan extends BaseStatementPatchPlan {
28
+ kind: 'wrap-action-with-response';
29
+ /** Target action substring to locate if targetLine is omitted, e.g. "click()" or locator name */
30
+ targetActionSubstring?: string;
31
+ /** Predicate string for page.waitForResponse, e.g. "resp => resp.url().includes('/api/auth') && resp.status() === 200" */
32
+ responsePredicate: string;
33
+ /** Optional custom timeout in ms for waitForResponse */
34
+ timeoutMs?: number;
35
+ /** Optional explicit page variable name, defaults to auto-detected or "page" */
36
+ pageVarName?: string;
37
+ }
38
+
39
+ export interface InsertReadinessCheckPlan extends BaseStatementPatchPlan {
40
+ kind: 'insert-readiness-check';
41
+ /** Target action substring to locate if targetLine is omitted */
42
+ targetActionSubstring?: string;
43
+ /** Statement to insert before action, e.g. "await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();" */
44
+ readinessStatement: string;
45
+ }
46
+
47
+ export interface ReplaceInlineLocatorPlan extends BaseStatementPatchPlan {
48
+ kind: 'replace-inline-locator';
49
+ /** Old locator text in statement, e.g. "page.locator('.btn-login')" */
50
+ oldLocatorText: string;
51
+ /** New locator expression, e.g. "page.getByRole('button', { name: 'Log In' })" */
52
+ newLocatorExpression: string;
53
+ }
54
+
55
+ export interface ReplaceStatementPlan extends BaseStatementPatchPlan {
56
+ kind: 'replace-statement';
57
+ /** Target substring to locate statement if targetLine is omitted */
58
+ targetSubstring?: string;
59
+ /** Complete new statement to replace existing one */
60
+ newStatement: string;
61
+ }
62
+
63
+ export type StatementPatchPlan =
64
+ | ReplaceTimeoutPlan
65
+ | WrapActionWithResponsePlan
66
+ | InsertReadinessCheckPlan
67
+ | ReplaceInlineLocatorPlan
68
+ | ReplaceStatementPlan;
69
+
70
+ export interface StatementPatchResult {
71
+ success: boolean;
72
+ operation: StatementPatchOperation;
73
+ originalStatement: string;
74
+ replacementStatement: string;
75
+ patchedSource: string;
76
+ diff: string;
77
+ importAdded?: boolean;
78
+ }
79
+
80
+ export class StatementPatcher {
81
+ private project: Project;
82
+
83
+ constructor(tsConfigFilePath?: string) {
84
+ if (tsConfigFilePath) {
85
+ this.project = new Project({
86
+ tsConfigFilePath,
87
+ skipAddingFilesFromTsConfig: false,
88
+ });
89
+ } else {
90
+ this.project = new Project({
91
+ skipAddingFilesFromTsConfig: true,
92
+ });
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Patches a test source string in-memory without touching disk.
98
+ */
99
+ public patchStatementInMemory(
100
+ sourceText: string,
101
+ plan: StatementPatchPlan,
102
+ fileName = 'test.spec.ts'
103
+ ): StatementPatchResult {
104
+ const tempProject = new Project({ skipAddingFilesFromTsConfig: true });
105
+ const sourceFile = tempProject.createSourceFile(fileName, sourceText);
106
+
107
+ let importAdded = false;
108
+
109
+ // Check if expect import is needed
110
+ if (
111
+ plan.kind === 'replace-timeout-with-wait' ||
112
+ plan.kind === 'insert-readiness-check' ||
113
+ plan.kind === 'replace-statement'
114
+ ) {
115
+ const codeToCheck =
116
+ plan.kind === 'replace-timeout-with-wait'
117
+ ? plan.replacementWaitStatement
118
+ : plan.kind === 'insert-readiness-check'
119
+ ? plan.readinessStatement
120
+ : plan.newStatement;
121
+
122
+ if (codeToCheck.includes('expect(')) {
123
+ importAdded = this.ensurePlaywrightExpectImport(sourceFile);
124
+ }
125
+ }
126
+
127
+ let result: {
128
+ originalStatement: string;
129
+ replacementStatement: string;
130
+ };
131
+
132
+ switch (plan.kind) {
133
+ case 'replace-timeout-with-wait':
134
+ result = this.executeReplaceTimeout(sourceFile, plan);
135
+ break;
136
+ case 'wrap-action-with-response':
137
+ result = this.executeWrapActionWithResponse(sourceFile, plan);
138
+ break;
139
+ case 'insert-readiness-check':
140
+ result = this.executeInsertReadinessCheck(sourceFile, plan);
141
+ break;
142
+ case 'replace-inline-locator':
143
+ result = this.executeReplaceInlineLocator(sourceFile, plan);
144
+ break;
145
+ case 'replace-statement':
146
+ result = this.executeReplaceStatement(sourceFile, plan);
147
+ break;
148
+ default:
149
+ throw new AbstentionError(
150
+ `Unsupported statement patch operation: ${(plan as BaseStatementPatchPlan).kind}`,
151
+ 'AST_UNSUPPORTED_STATEMENT_OPERATION'
152
+ );
153
+ }
154
+
155
+ // Validate that syntax remains valid after patching
156
+ const diagnostics = tempProject.getProgram().getSyntacticDiagnostics(sourceFile);
157
+ if (diagnostics.length > 0) {
158
+ const firstError = diagnostics[0]?.getMessageText();
159
+ const errorMsg = typeof firstError === 'string' ? firstError : firstError?.getMessageText();
160
+ throw new AbstentionError(
161
+ `Syntax error after applying statement patch: ${errorMsg ?? 'unknown syntax error'}`,
162
+ 'AST_SYNTAX_ERROR'
163
+ );
164
+ }
165
+
166
+ const patchedSource = sourceFile.getFullText();
167
+ const diff = AstPatcher.generateDiff(sourceText, patchedSource, fileName);
168
+
169
+ return {
170
+ success: true,
171
+ operation: plan.kind,
172
+ originalStatement: result.originalStatement,
173
+ replacementStatement: result.replacementStatement,
174
+ patchedSource,
175
+ diff,
176
+ importAdded,
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Patches a test file on disk atomically.
182
+ */
183
+ public patchStatementInFile(filePath: string, plan: StatementPatchPlan): StatementPatchResult {
184
+ const resolvedPath = path.resolve(filePath);
185
+ if (!fs.existsSync(resolvedPath)) {
186
+ throw new AbstentionError(`Target file not found at ${resolvedPath}`, 'AST_FILE_NOT_FOUND');
187
+ }
188
+
189
+ const sourceFile = this.project.getSourceFile(resolvedPath) ?? this.project.addSourceFileAtPath(resolvedPath);
190
+ const sourceText = sourceFile.getFullText();
191
+ const fileName = path.basename(resolvedPath);
192
+ const result = this.patchStatementInMemory(sourceText, plan, fileName);
193
+
194
+ const targetDir = path.dirname(resolvedPath);
195
+ const tempFile = path.join(
196
+ targetDir,
197
+ `.${path.basename(resolvedPath)}.${Date.now()}.${Math.random().toString(36).substring(2, 6)}.tmp`
198
+ );
199
+
200
+ try {
201
+ fs.writeFileSync(tempFile, result.patchedSource, 'utf8');
202
+ fs.renameSync(tempFile, resolvedPath);
203
+ sourceFile.refreshFromFileSystemSync();
204
+ return result;
205
+ } catch (err: any) {
206
+ if (fs.existsSync(tempFile)) {
207
+ try {
208
+ fs.unlinkSync(tempFile);
209
+ } catch {
210
+ // ignore cleanup failure
211
+ }
212
+ }
213
+ throw err;
214
+ }
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Execution Handlers
219
+ // ---------------------------------------------------------------------------
220
+
221
+ private executeReplaceTimeout(
222
+ sourceFile: import('ts-morph').SourceFile,
223
+ plan: ReplaceTimeoutPlan
224
+ ): { originalStatement: string; replacementStatement: string } {
225
+ const candidateStatements = this.findStatements(sourceFile, plan.targetLine, (stmt) => {
226
+ const text = stmt.getText();
227
+ return text.includes('waitForTimeout');
228
+ });
229
+
230
+ const targetStatement = this.selectSingleTargetStatement(candidateStatements, plan, 'waitForTimeout');
231
+ const originalStatement = targetStatement.getText();
232
+ const replacement = plan.replacementWaitStatement.trim().endsWith(';')
233
+ ? plan.replacementWaitStatement.trim()
234
+ : `${plan.replacementWaitStatement.trim()};`;
235
+
236
+ targetStatement.replaceWithText(replacement);
237
+
238
+ return {
239
+ originalStatement,
240
+ replacementStatement: replacement,
241
+ };
242
+ }
243
+
244
+ private executeWrapActionWithResponse(
245
+ sourceFile: import('ts-morph').SourceFile,
246
+ plan: WrapActionWithResponsePlan
247
+ ): { originalStatement: string; replacementStatement: string } {
248
+ const candidateStatements = this.findStatements(sourceFile, plan.targetLine, (stmt) => {
249
+ const text = stmt.getText();
250
+ if (plan.targetActionSubstring) {
251
+ return text.includes(plan.targetActionSubstring);
252
+ }
253
+ return (
254
+ text.includes('.click(') ||
255
+ text.includes('.fill(') ||
256
+ text.includes('.press(') ||
257
+ text.includes('.selectOption(') ||
258
+ text.includes('.check(')
259
+ );
260
+ });
261
+
262
+ const targetStatement = this.selectSingleTargetStatement(
263
+ candidateStatements,
264
+ plan,
265
+ plan.targetActionSubstring ?? 'action'
266
+ );
267
+ const originalStatement = targetStatement.getText();
268
+
269
+ // Extract bare expression without leading await and trailing semicolon
270
+ let bareAction = originalStatement.trim();
271
+ if (bareAction.startsWith('await ')) {
272
+ bareAction = bareAction.substring(6).trim();
273
+ }
274
+ if (bareAction.endsWith(';')) {
275
+ bareAction = bareAction.substring(0, bareAction.length - 1).trim();
276
+ }
277
+
278
+ // Auto-detect page variable
279
+ let pageVar = plan.pageVarName ?? 'page';
280
+ if (!plan.pageVarName) {
281
+ const pageMatch = bareAction.match(/(\b\w*[pP]age\w*)\./);
282
+ if (pageMatch && pageMatch[1]) {
283
+ pageVar = pageMatch[1];
284
+ }
285
+ }
286
+
287
+ const timeoutArg = plan.timeoutMs ? `, { timeout: ${plan.timeoutMs} }` : '';
288
+ const replacementStatement = `await Promise.all([\n ${pageVar}.waitForResponse(${plan.responsePredicate}${timeoutArg}),\n ${bareAction},\n]);`;
289
+
290
+ targetStatement.replaceWithText(replacementStatement);
291
+
292
+ return {
293
+ originalStatement,
294
+ replacementStatement,
295
+ };
296
+ }
297
+
298
+ private executeInsertReadinessCheck(
299
+ sourceFile: import('ts-morph').SourceFile,
300
+ plan: InsertReadinessCheckPlan
301
+ ): { originalStatement: string; replacementStatement: string } {
302
+ const candidateStatements = this.findStatements(sourceFile, plan.targetLine, (stmt) => {
303
+ const text = stmt.getText();
304
+ if (plan.targetActionSubstring) {
305
+ return text.includes(plan.targetActionSubstring);
306
+ }
307
+ return text.includes('.click(') || text.includes('.fill(');
308
+ });
309
+
310
+ const targetStatement = this.selectSingleTargetStatement(
311
+ candidateStatements,
312
+ plan,
313
+ plan.targetActionSubstring ?? 'click/fill'
314
+ );
315
+ const originalStatement = targetStatement.getText();
316
+ const parent = targetStatement.getParent();
317
+
318
+ const readinessCode = plan.readinessStatement.trim().endsWith(';')
319
+ ? plan.readinessStatement.trim()
320
+ : `${plan.readinessStatement.trim()};`;
321
+
322
+ if (Node.isBlock(parent)) {
323
+ const index = targetStatement.getChildIndex();
324
+ parent.insertStatements(index, readinessCode);
325
+ } else {
326
+ // Fallback: replace target statement with readiness + target
327
+ targetStatement.replaceWithText(`${readinessCode}\n${originalStatement}`);
328
+ }
329
+
330
+ return {
331
+ originalStatement,
332
+ replacementStatement: `${readinessCode}\n${originalStatement}`,
333
+ };
334
+ }
335
+
336
+ private executeReplaceInlineLocator(
337
+ sourceFile: import('ts-morph').SourceFile,
338
+ plan: ReplaceInlineLocatorPlan
339
+ ): { originalStatement: string; replacementStatement: string } {
340
+ const candidateStatements = this.findStatements(sourceFile, plan.targetLine, (stmt) => {
341
+ return stmt.getText().includes(plan.oldLocatorText);
342
+ });
343
+
344
+ const targetStatement = this.selectSingleTargetStatement(
345
+ candidateStatements,
346
+ plan,
347
+ `'${plan.oldLocatorText}'`
348
+ );
349
+ const originalStatement = targetStatement.getText();
350
+
351
+ // AST-level node replacement
352
+ let replacedInAst = false;
353
+ const calls = targetStatement.getDescendantsOfKind(SyntaxKind.CallExpression);
354
+ for (const call of calls) {
355
+ if (call.getText().trim() === plan.oldLocatorText.trim()) {
356
+ call.replaceWithText(plan.newLocatorExpression);
357
+ replacedInAst = true;
358
+ break;
359
+ }
360
+ }
361
+
362
+ if (!replacedInAst) {
363
+ targetStatement.forEachDescendant((node) => {
364
+ if (!replacedInAst && node.getText().trim() === plan.oldLocatorText.trim()) {
365
+ node.replaceWithText(plan.newLocatorExpression);
366
+ replacedInAst = true;
367
+ }
368
+ });
369
+ }
370
+
371
+ if (!replacedInAst) {
372
+ const patchedStatementText = originalStatement.replace(plan.oldLocatorText, plan.newLocatorExpression);
373
+ if (patchedStatementText === originalStatement) {
374
+ throw new AbstentionError(
375
+ `Failed to replace '${plan.oldLocatorText}' with '${plan.newLocatorExpression}'`,
376
+ 'AST_REPLACEMENT_FAILED'
377
+ );
378
+ }
379
+ targetStatement.replaceWithText(patchedStatementText);
380
+ }
381
+
382
+ return {
383
+ originalStatement,
384
+ replacementStatement: targetStatement.getText(),
385
+ };
386
+ }
387
+
388
+ private executeReplaceStatement(
389
+ sourceFile: import('ts-morph').SourceFile,
390
+ plan: ReplaceStatementPlan
391
+ ): { originalStatement: string; replacementStatement: string } {
392
+ const candidateStatements = this.findStatements(sourceFile, plan.targetLine, (stmt) => {
393
+ if (plan.targetSubstring) {
394
+ return stmt.getText().includes(plan.targetSubstring);
395
+ }
396
+ return true;
397
+ });
398
+
399
+ const targetStatement = this.selectSingleTargetStatement(
400
+ candidateStatements,
401
+ plan,
402
+ plan.targetSubstring ?? 'target'
403
+ );
404
+ const originalStatement = targetStatement.getText();
405
+ const replacement = plan.newStatement.trim().endsWith(';')
406
+ ? plan.newStatement.trim()
407
+ : `${plan.newStatement.trim()};`;
408
+
409
+ targetStatement.replaceWithText(replacement);
410
+
411
+ return {
412
+ originalStatement,
413
+ replacementStatement: replacement,
414
+ };
415
+ }
416
+
417
+ private selectSingleTargetStatement(
418
+ candidates: Statement[],
419
+ plan: BaseStatementPatchPlan,
420
+ label: string
421
+ ): Statement {
422
+ if (candidates.length === 0) {
423
+ throw new AbstentionError(
424
+ plan.targetLine
425
+ ? `No ${label} statement found at line ${plan.targetLine}`
426
+ : `No ${label} statement found in source file`,
427
+ 'AST_STATEMENT_NOT_FOUND'
428
+ );
429
+ }
430
+ if (candidates.length > 1) {
431
+ throw new AbstentionError(
432
+ plan.targetLine
433
+ ? `Ambiguous statement match on line ${plan.targetLine}: found ${candidates.length} matching ${label} statements. Provide a more specific targetSubstring or unique locator to disambiguate.`
434
+ : `Ambiguous statement match: found ${candidates.length} ${label} statements. Provide targetLine to specify which statement to transform.`,
435
+ 'AST_AMBIGUOUS_STATEMENT_TARGET'
436
+ );
437
+ }
438
+ const stmt = candidates[0]!;
439
+ this.assertNotAssertion(stmt);
440
+ return stmt;
441
+ }
442
+
443
+ // ---------------------------------------------------------------------------
444
+ // Helper Methods
445
+ // ---------------------------------------------------------------------------
446
+
447
+ private findStatements(
448
+ sourceFile: import('ts-morph').SourceFile,
449
+ targetLine?: number,
450
+ predicate?: (stmt: Statement) => boolean
451
+ ): Statement[] {
452
+ const statements: Statement[] = [];
453
+
454
+ // Traverse all expression statements and variable statements in tests/blocks
455
+ const allStatements = [
456
+ ...sourceFile.getDescendantsOfKind(SyntaxKind.ExpressionStatement),
457
+ ...sourceFile.getDescendantsOfKind(SyntaxKind.VariableStatement),
458
+ ];
459
+
460
+ for (const stmt of allStatements) {
461
+ // Exclude outer test wrapper calls (e.g. test('...', () => { ... }))
462
+ if (Node.isExpressionStatement(stmt)) {
463
+ const expr = stmt.getExpression();
464
+ if (Node.isCallExpression(expr)) {
465
+ const callText = expr.getExpression().getText();
466
+ if (
467
+ callText === 'test' ||
468
+ callText === 'test.describe' ||
469
+ callText === 'test.step' ||
470
+ callText === 'describe' ||
471
+ callText === 'it'
472
+ ) {
473
+ continue;
474
+ }
475
+ }
476
+ }
477
+
478
+ if (targetLine !== undefined) {
479
+ const startLine = stmt.getStartLineNumber();
480
+ const endLine = stmt.getEndLineNumber();
481
+ if (targetLine < startLine || targetLine > endLine) {
482
+ continue;
483
+ }
484
+ }
485
+
486
+ if (predicate && !predicate(stmt)) {
487
+ continue;
488
+ }
489
+
490
+ statements.push(stmt);
491
+ }
492
+
493
+ return statements;
494
+ }
495
+
496
+ private assertNotAssertion(stmt: Statement): void {
497
+ const identifiers = AssertionHasher.discoverAssertionIdentifiers(stmt);
498
+
499
+ // Guard against removing or tampering with assertions (standard or aliased)
500
+ const callExpressions = stmt.getDescendantsOfKind(SyntaxKind.CallExpression);
501
+ for (const call of callExpressions) {
502
+ if (AssertionHasher.isAssertionCall(call, identifiers)) {
503
+ throw new AbstentionError(
504
+ 'Modifying or removing test assertions via StatementPatcher is strictly prohibited',
505
+ 'AST_ASSERTION_TAMPERING_PROHIBITED'
506
+ );
507
+ }
508
+ }
509
+
510
+ const trimmed = stmt.getText().trim();
511
+ for (const id of identifiers) {
512
+ if (trimmed.startsWith(`${id}(`) || trimmed.startsWith(`await ${id}(`)) {
513
+ throw new AbstentionError(
514
+ 'Modifying or removing test assertions via StatementPatcher is strictly prohibited',
515
+ 'AST_ASSERTION_TAMPERING_PROHIBITED'
516
+ );
517
+ }
518
+ }
519
+ }
520
+
521
+ private ensurePlaywrightExpectImport(sourceFile: import('ts-morph').SourceFile): boolean {
522
+ const importDecls = sourceFile.getImportDeclarations();
523
+ const pwImport = importDecls.find((decl) => {
524
+ const spec = decl.getModuleSpecifierValue();
525
+ return spec === '@playwright/test';
526
+ });
527
+
528
+ if (pwImport) {
529
+ const namedImports = pwImport.getNamedImports().map((i) => i.getName());
530
+ if (!namedImports.includes('expect')) {
531
+ pwImport.addNamedImport('expect');
532
+ return true;
533
+ }
534
+ return false;
535
+ }
536
+
537
+ // No @playwright/test import found; add one at the top of the file
538
+ sourceFile.insertImportDeclaration(0, {
539
+ moduleSpecifier: '@playwright/test',
540
+ namedImports: ['expect'],
541
+ });
542
+ return true;
543
+ }
544
+ }
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod';
2
+ import { BusinessOracleStatus } from '../core/types/repair-evidence.js';
3
+
4
+ export const VerificationGateInputSchema = z.object({
5
+ executionPassed: z.boolean(),
6
+ targetIdentityPreserved: z.boolean(),
7
+ assertionsIntact: z.boolean(),
8
+ fixturesIntact: z.boolean(),
9
+ diffAllowlistPassed: z.boolean(),
10
+ regressionPassed: z.boolean(),
11
+ operationValidated: z.boolean(),
12
+ businessOracleStatus: z.enum(['verified', 'contradicted', 'unavailable']),
13
+ requireBusinessOracle: z.boolean().default(false),
14
+ });
15
+
16
+ export type VerificationGateInput = z.input<typeof VerificationGateInputSchema>;
17
+
18
+ export interface VerificationGateResult {
19
+ isPromotable: boolean;
20
+ failedGates: string[];
21
+ businessOracleStatus: BusinessOracleStatus;
22
+ }
23
+
24
+ export function evaluateVerificationGates(input: VerificationGateInput): VerificationGateResult {
25
+ const normalized = VerificationGateInputSchema.parse(input);
26
+ const failedGates: string[] = [];
27
+
28
+ if (!normalized.executionPassed) failedGates.push('executionPassed');
29
+ if (!normalized.targetIdentityPreserved) failedGates.push('targetIdentityPreserved');
30
+ if (!normalized.assertionsIntact) failedGates.push('assertionsIntact');
31
+ if (!normalized.fixturesIntact) failedGates.push('fixturesIntact');
32
+ if (!normalized.diffAllowlistPassed) failedGates.push('diffAllowlistPassed');
33
+ if (!normalized.regressionPassed) failedGates.push('regressionPassed');
34
+ if (!normalized.operationValidated) failedGates.push('operationValidated');
35
+ if (normalized.businessOracleStatus === 'contradicted') failedGates.push('businessOracleContradicted');
36
+ if (normalized.requireBusinessOracle && normalized.businessOracleStatus !== 'verified') {
37
+ failedGates.push('businessOracleRequired');
38
+ }
39
+
40
+ return {
41
+ isPromotable: failedGates.length === 0,
42
+ failedGates,
43
+ businessOracleStatus: normalized.businessOracleStatus,
44
+ };
45
+ }
46
+
@@ -0,0 +1,59 @@
1
+ import { DiffAllowlistValidator } from './diff-allowlist.js';
2
+ import { RepairOperation, validateRepairOperation } from './repair-operations.js';
3
+
4
+ export type WorkflowPlanStep =
5
+ | { kind: 'replace_locator'; target: string; locator: string }
6
+ | { kind: 'add_frame_scope'; target: string; frameLocator: string }
7
+ | { kind: 'add_shadow_scope'; target: string; shadowPath: string[] }
8
+ | { kind: 'dismiss_overlay'; locator: string }
9
+ | { kind: 'satisfy_prerequisite'; prerequisite: string }
10
+ | { kind: 'replace_select_with_combobox_flow'; target: string; steps: string[] }
11
+ | { kind: 'replace_unstable_wait_with_state_wait'; target: string; steps: string[] };
12
+
13
+ export interface WorkflowPlan {
14
+ status: 'EXECUTABLE' | 'ABSTAIN';
15
+ steps: WorkflowPlanStep[];
16
+ errors: string[];
17
+ }
18
+
19
+ export function compileWorkflowPlan(operations: RepairOperation[]): WorkflowPlan {
20
+ const errors: string[] = [];
21
+ const steps: WorkflowPlanStep[] = [];
22
+
23
+ for (const operation of operations) {
24
+ const validation = validateRepairOperation(operation);
25
+ if (!validation.isValid || !validation.operation) {
26
+ errors.push(validation.reason ?? 'Invalid repair operation');
27
+ continue;
28
+ }
29
+
30
+ const valid = validation.operation;
31
+ if (valid.kind === 'abstain') {
32
+ if (operations.length !== 1) errors.push('An abstention must be the only operation in a workflow plan');
33
+ continue;
34
+ }
35
+
36
+ if (valid.kind === 'replace_locator') {
37
+ const grammar = DiffAllowlistValidator.validateLocatorGrammar(valid.locator!);
38
+ if (!grammar.isValid) errors.push(grammar.reason ?? 'Locator grammar rejected');
39
+ else steps.push({ kind: valid.kind, target: valid.target!, locator: valid.locator! });
40
+ } else if (valid.kind === 'add_frame_scope') {
41
+ steps.push({ kind: valid.kind, target: valid.target!, frameLocator: valid.frameLocator! });
42
+ } else if (valid.kind === 'add_shadow_scope') {
43
+ steps.push({ kind: valid.kind, target: valid.target!, shadowPath: valid.shadowPath! });
44
+ } else if (valid.kind === 'dismiss_overlay') {
45
+ steps.push({ kind: valid.kind, locator: valid.locator! });
46
+ } else if (valid.kind === 'satisfy_prerequisite') {
47
+ steps.push({ kind: valid.kind, prerequisite: valid.prerequisite! });
48
+ } else if (valid.kind === 'replace_select_with_combobox_flow') {
49
+ steps.push({ kind: valid.kind, target: valid.target!, steps: valid.steps! });
50
+ } else if (valid.kind === 'replace_unstable_wait_with_state_wait') {
51
+ steps.push({ kind: valid.kind, target: valid.target!, steps: valid.steps! });
52
+ }
53
+ }
54
+
55
+ if (errors.length > 0) return { status: 'ABSTAIN', steps: [], errors };
56
+ if (operations.some((operation) => operation.kind === 'abstain')) return { status: 'ABSTAIN', steps: [], errors: ['Proposal requested abstention'] };
57
+ return { status: 'EXECUTABLE', steps, errors: [] };
58
+ }
59
+