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,612 @@
1
+ import { Project, Node, SyntaxKind, CallExpression } from 'ts-morph';
2
+ import { UnauthorizedAstModificationError } from '../core/errors/engine-errors.js';
3
+
4
+ export interface DiffAllowlistTarget {
5
+ originalSource: string;
6
+ patchedSource: string;
7
+ targetClassName: string;
8
+ targetMemberName: string;
9
+ }
10
+
11
+ export interface DiffAllowlistResult {
12
+ isAllowed: boolean;
13
+ reason?: string | undefined;
14
+ modifiedMember?: string | undefined;
15
+ }
16
+
17
+ export class DiffAllowlistValidator {
18
+ private static readonly APPROVED_LOCATOR_METHODS = new Set([
19
+ 'locator',
20
+ 'frameLocator',
21
+ 'getByRole',
22
+ 'getByText',
23
+ 'getByTestId',
24
+ 'getByLabel',
25
+ 'getByPlaceholder',
26
+ 'getByAltText',
27
+ 'getByTitle',
28
+ 'filter',
29
+ 'first',
30
+ 'last',
31
+ 'nth',
32
+ ]);
33
+
34
+ /**
35
+ * Validates that ONLY the authorized locator member within the specified Page Object was modified.
36
+ * Compares the COMPLETE AST of the source file:
37
+ * - Prohibits modifying or adding imports.
38
+ * - Prohibits adding, removing, or modifying top-level functions, variables, interfaces, enums, type aliases.
39
+ * - Prohibits adding or removing class members (properties, methods, getters, setters, constructors).
40
+ * - Prohibits modifying class inheritance, decorators, or modifiers.
41
+ * - Validates that the replacement expression conforms strictly to the approved Playwright locator grammar.
42
+ */
43
+ public static validate(target: DiffAllowlistTarget): DiffAllowlistResult {
44
+ const origProject = new Project({ skipAddingFilesFromTsConfig: true });
45
+ const patchProject = new Project({ skipAddingFilesFromTsConfig: true });
46
+
47
+ const origFile = origProject.createSourceFile('orig.ts', target.originalSource);
48
+ const patchFile = patchProject.createSourceFile('patched.ts', target.patchedSource);
49
+
50
+ // 1. Verify Imports: Exact match
51
+ const origImports = origFile.getImportDeclarations().map((i) => i.getText().trim());
52
+ const patchImports = patchFile.getImportDeclarations().map((i) => i.getText().trim());
53
+ if (JSON.stringify(origImports) !== JSON.stringify(patchImports)) {
54
+ return {
55
+ isAllowed: false,
56
+ reason: 'Diff allowlist violated: Import declarations were modified, added, or deleted.',
57
+ };
58
+ }
59
+
60
+ // 2. Verify Top-Level Non-Class Declarations (Functions, Variables, Interfaces, Type Aliases, Enums)
61
+ const origFunctions = origFile.getFunctions().map((f) => f.getText().trim());
62
+ const patchFunctions = patchFile.getFunctions().map((f) => f.getText().trim());
63
+ if (JSON.stringify(origFunctions) !== JSON.stringify(patchFunctions)) {
64
+ return {
65
+ isAllowed: false,
66
+ reason: 'Diff allowlist violated: Top-level function declarations were added, removed, or modified.',
67
+ };
68
+ }
69
+
70
+ const origInterfaces = origFile.getInterfaces().map((i) => i.getText().trim());
71
+ const patchInterfaces = patchFile.getInterfaces().map((i) => i.getText().trim());
72
+ if (JSON.stringify(origInterfaces) !== JSON.stringify(patchInterfaces)) {
73
+ return {
74
+ isAllowed: false,
75
+ reason: 'Diff allowlist violated: Interface declarations were altered.',
76
+ };
77
+ }
78
+
79
+ const origTypeAliases = origFile.getTypeAliases().map((t) => t.getText().trim());
80
+ const patchTypeAliases = patchFile.getTypeAliases().map((t) => t.getText().trim());
81
+ if (JSON.stringify(origTypeAliases) !== JSON.stringify(patchTypeAliases)) {
82
+ return {
83
+ isAllowed: false,
84
+ reason: 'Diff allowlist violated: Type alias declarations were altered.',
85
+ };
86
+ }
87
+
88
+ const origEnums = origFile.getEnums().map((e) => e.getText().trim());
89
+ const patchEnums = patchFile.getEnums().map((e) => e.getText().trim());
90
+ if (JSON.stringify(origEnums) !== JSON.stringify(patchEnums)) {
91
+ return {
92
+ isAllowed: false,
93
+ reason: 'Diff allowlist violated: Enum declarations were altered.',
94
+ };
95
+ }
96
+
97
+ // 3. Verify Classes: Exact count and names
98
+ const origClasses = origFile.getClasses();
99
+ const patchClasses = patchFile.getClasses();
100
+ if (origClasses.length !== patchClasses.length) {
101
+ return {
102
+ isAllowed: false,
103
+ reason: `Diff allowlist violated: Class structure or declarations were altered (Class count changed ${origClasses.length} -> ${patchClasses.length}).`,
104
+ };
105
+ }
106
+
107
+ for (let idx = 0; idx < origClasses.length; idx++) {
108
+ if (origClasses[idx]?.getName() !== patchClasses[idx]?.getName()) {
109
+ return {
110
+ isAllowed: false,
111
+ reason: 'Diff allowlist violated: Class structure or declarations were altered.',
112
+ };
113
+ }
114
+ }
115
+
116
+ // 4. Locate Target Class
117
+ const origClass = origFile.getClass(target.targetClassName);
118
+ const patchClass = patchFile.getClass(target.targetClassName);
119
+
120
+ if (!origClass || !patchClass) {
121
+ // Check top-level constant pattern if class is not used
122
+ const origVar = origFile.getVariableDeclaration(target.targetMemberName);
123
+ const patchVar = patchFile.getVariableDeclaration(target.targetMemberName);
124
+ if (origVar && patchVar) {
125
+ const replacement = patchVar.getInitializer()?.getText() ?? '';
126
+ const grammarCheck = DiffAllowlistValidator.validateLocatorGrammar(replacement);
127
+ if (!grammarCheck.isValid) {
128
+ return { isAllowed: false, reason: grammarCheck.reason };
129
+ }
130
+ return { isAllowed: true, modifiedMember: target.targetMemberName };
131
+ }
132
+
133
+ return {
134
+ isAllowed: false,
135
+ reason: `Target class '${target.targetClassName}' not found in source AST.`,
136
+ };
137
+ }
138
+
139
+ // 5. Verify Target Class Structure & Members
140
+ // Total member counts must match
141
+ if (origClass.getProperties().length !== patchClass.getProperties().length) {
142
+ return {
143
+ isAllowed: false,
144
+ reason: `Diff allowlist violated: Class property count changed (${origClass.getProperties().length} -> ${patchClass.getProperties().length}). Unauthorized property added or removed.`,
145
+ };
146
+ }
147
+
148
+ if (origClass.getMethods().length !== patchClass.getMethods().length) {
149
+ return {
150
+ isAllowed: false,
151
+ reason: `Diff allowlist violated: Class method count changed (${origClass.getMethods().length} -> ${patchClass.getMethods().length}). Unauthorized method added or removed.`,
152
+ };
153
+ }
154
+
155
+ if (origClass.getGetAccessors().length !== patchClass.getGetAccessors().length) {
156
+ return {
157
+ isAllowed: false,
158
+ reason: `Diff allowlist violated: Getter accessor count changed in class '${target.targetClassName}'.`,
159
+ };
160
+ }
161
+
162
+ if (origClass.getSetAccessors().length !== patchClass.getSetAccessors().length) {
163
+ return {
164
+ isAllowed: false,
165
+ reason: `Diff allowlist violated: Setter accessor count changed in class '${target.targetClassName}'.`,
166
+ };
167
+ }
168
+
169
+ if (origClass.getConstructors().length !== patchClass.getConstructors().length) {
170
+ return {
171
+ isAllowed: false,
172
+ reason: `Diff allowlist violated: Constructor count changed in class '${target.targetClassName}'.`,
173
+ };
174
+ }
175
+
176
+ // Check heritage, modifiers, decorators
177
+ if (origClass.getExtends()?.getText() !== patchClass.getExtends()?.getText()) {
178
+ return {
179
+ isAllowed: false,
180
+ reason: `Diff allowlist violated: Class 'extends' heritage was altered.`,
181
+ };
182
+ }
183
+
184
+ // 6. Verify all other properties are unchanged
185
+ for (const prop of origClass.getProperties()) {
186
+ const name = prop.getName();
187
+ if (name !== target.targetMemberName) {
188
+ const patchProp = patchClass.getProperty(name);
189
+ if (!patchProp || patchProp.getText().trim() !== prop.getText().trim()) {
190
+ return {
191
+ isAllowed: false,
192
+ reason: `Diff allowlist violated: Unauthorized change to property '${name}' in class '${target.targetClassName}'.`,
193
+ };
194
+ }
195
+ }
196
+ }
197
+
198
+ // 7. Verify all other methods are unchanged
199
+ for (const method of origClass.getMethods()) {
200
+ const name = method.getName();
201
+ if (name !== target.targetMemberName) {
202
+ const patchMethod = patchClass.getMethod(name);
203
+ if (!patchMethod || patchMethod.getText().trim() !== method.getText().trim()) {
204
+ return {
205
+ isAllowed: false,
206
+ reason: `Diff allowlist violated: Unauthorized change to method '${name}' in class '${target.targetClassName}'.`,
207
+ };
208
+ }
209
+ }
210
+ }
211
+
212
+ // 8. Extract and validate replacement expression
213
+ const patchProp = patchClass.getProperty(target.targetMemberName);
214
+ const patchGetter = patchClass.getGetAccessor(target.targetMemberName);
215
+ const patchMethod = patchClass.getMethod(target.targetMemberName);
216
+ let locatorText = '';
217
+
218
+ if (patchProp && patchProp.getInitializer()) {
219
+ locatorText = patchProp.getInitializer()!.getText();
220
+ } else if (patchGetter) {
221
+ const returns = patchGetter.getDescendantsOfKind(SyntaxKind.ReturnStatement);
222
+ locatorText = returns[0]?.getExpression()?.getText() ?? '';
223
+ } else if (patchMethod) {
224
+ const returns = patchMethod.getDescendantsOfKind(SyntaxKind.ReturnStatement);
225
+ locatorText = returns[0]?.getExpression()?.getText() ?? '';
226
+ } else {
227
+ // Check constructor assignments
228
+ for (const ctor of patchClass.getConstructors()) {
229
+ for (const stmt of ctor.getStatements()) {
230
+ if (Node.isExpressionStatement(stmt)) {
231
+ const expr = stmt.getExpression();
232
+ if (Node.isBinaryExpression(expr) && expr.getOperatorToken().getKind() === SyntaxKind.EqualsToken) {
233
+ if (expr.getLeft().getText() === `this.${target.targetMemberName}`) {
234
+ locatorText = expr.getRight().getText();
235
+ break;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+ }
242
+
243
+ if (!locatorText) {
244
+ return {
245
+ isAllowed: false,
246
+ reason: `Diff allowlist violated: Target member '${target.targetMemberName}' has no replacement expression.`,
247
+ };
248
+ }
249
+
250
+ // 9. Semantic AST Locator Grammar Validation
251
+ const grammarCheck = DiffAllowlistValidator.validateLocatorGrammar(locatorText);
252
+ if (!grammarCheck.isValid) {
253
+ return {
254
+ isAllowed: false,
255
+ reason: grammarCheck.reason,
256
+ };
257
+ }
258
+
259
+ return {
260
+ isAllowed: true,
261
+ modifiedMember: target.targetMemberName,
262
+ };
263
+ }
264
+
265
+ private static readonly APPROVED_OPTION_KEYS = new Set([
266
+ 'name',
267
+ 'exact',
268
+ 'hasText',
269
+ 'hasNotText',
270
+ 'has',
271
+ 'hasNot',
272
+ 'timeout',
273
+ 'checked',
274
+ 'disabled',
275
+ 'expanded',
276
+ 'includeHidden',
277
+ 'level',
278
+ 'pressed',
279
+ 'selected',
280
+ ]);
281
+
282
+ private static isLiteralValue(node: Node): boolean {
283
+ return (
284
+ Node.isStringLiteral(node) ||
285
+ Node.isNumericLiteral(node) ||
286
+ node.getKind() === SyntaxKind.TrueKeyword ||
287
+ node.getKind() === SyntaxKind.FalseKeyword ||
288
+ Node.isRegularExpressionLiteral(node)
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Recursively validates an argument node passed into a locator method call.
294
+ * Strictly disallows identifiers (process.env), arbitrary function calls,
295
+ * arrow functions, dynamic property accesses, and executable code.
296
+ */
297
+ private static validateArgumentNode(arg: Node): { isValid: boolean; reason?: string } {
298
+ if (DiffAllowlistValidator.isLiteralValue(arg)) {
299
+ return { isValid: true };
300
+ }
301
+
302
+ if (Node.isObjectLiteralExpression(arg)) {
303
+ for (const prop of arg.getProperties()) {
304
+ if (!Node.isPropertyAssignment(prop)) {
305
+ return {
306
+ isValid: false,
307
+ reason: `Disallowed object property structure: only standard key-value assignments are allowed, received ${prop.getKindName()}`,
308
+ };
309
+ }
310
+
311
+ const keyName = prop.getName();
312
+ if (!DiffAllowlistValidator.APPROVED_OPTION_KEYS.has(keyName)) {
313
+ return {
314
+ isValid: false,
315
+ reason: `Disallowed option key '${keyName}'. Only approved Playwright locator options are allowed.`,
316
+ };
317
+ }
318
+
319
+ const val = prop.getInitializer();
320
+ if (!val) {
321
+ return { isValid: false, reason: `Missing value for option key '${keyName}'` };
322
+ }
323
+
324
+ if (keyName === 'has' || keyName === 'hasNot') {
325
+ // Recursive locator call validation for nested locators
326
+ if (!Node.isCallExpression(val)) {
327
+ return {
328
+ isValid: false,
329
+ reason: `Option '${keyName}' must be a CallExpression resolving to a locator, received ${val.getKindName()}`,
330
+ };
331
+ }
332
+ const nestedCheck = DiffAllowlistValidator.validateLocatorCallChain(val);
333
+ if (!nestedCheck.isValid) return nestedCheck;
334
+ } else {
335
+ // All other options must be literal values
336
+ if (!DiffAllowlistValidator.isLiteralValue(val)) {
337
+ return {
338
+ isValid: false,
339
+ reason: `Disallowed argument in option '${keyName}': ${val.getKindName()} ('${val.getText()}'). Dynamic expressions, environment variables, and function calls are prohibited.`,
340
+ };
341
+ }
342
+ }
343
+ }
344
+ return { isValid: true };
345
+ }
346
+
347
+ return {
348
+ isValid: false,
349
+ reason: `Disallowed argument expression: ${arg.getKindName()} ('${arg.getText()}'). Only literals and safe option objects are permitted.`,
350
+ };
351
+ }
352
+
353
+ /**
354
+ * Validates a locator call chain expression recursively, including all arguments.
355
+ */
356
+ private static validateLocatorCallChain(callExpr: CallExpression): { isValid: boolean; reason?: string } {
357
+ let current: Node = callExpr;
358
+ while (Node.isCallExpression(current)) {
359
+ const expr = current.getExpression();
360
+ if (Node.isPropertyAccessExpression(expr)) {
361
+ const methodName = expr.getName();
362
+ if (!DiffAllowlistValidator.APPROVED_LOCATOR_METHODS.has(methodName)) {
363
+ return {
364
+ isValid: false,
365
+ reason: `Replacement expression contains disallowed statements: disallowed method call '${methodName}'. Only Playwright locator methods are permitted.`,
366
+ };
367
+ }
368
+
369
+ // Recursively validate every argument passed to this method
370
+ for (const arg of current.getArguments()) {
371
+ const argCheck = DiffAllowlistValidator.validateArgumentNode(arg);
372
+ if (!argCheck.isValid) {
373
+ return {
374
+ isValid: false,
375
+ reason: `Replacement expression contains disallowed statements: ${argCheck.reason}`,
376
+ };
377
+ }
378
+ }
379
+
380
+ current = expr.getExpression();
381
+ } else {
382
+ break;
383
+ }
384
+ }
385
+
386
+ const rootText = current.getText().trim();
387
+ const approvedRoots = new Set(['this.page', 'page', 'this', 'this._page', 'this.browserPage']);
388
+ if (!approvedRoots.has(rootText)) {
389
+ return {
390
+ isValid: false,
391
+ reason: `Replacement expression contains disallowed statements: locator chain root must be 'this.page' or 'page', received '${rootText}'`,
392
+ };
393
+ }
394
+
395
+ return { isValid: true };
396
+ }
397
+
398
+ /**
399
+ * Validates that a replacement expression is strictly a safe Playwright locator call AST.
400
+ * Rejects arbitrary statements, process calls, functions, or unapproved methods.
401
+ */
402
+ public static validateLocatorGrammar(expressionText: string): { isValid: boolean; reason?: string } {
403
+ // 1. Basic sanitization: No statements separated by semicolons
404
+ const trimmed = expressionText.trim().replace(/;$/, '');
405
+ if (trimmed.includes(';')) {
406
+ return {
407
+ isValid: false,
408
+ reason: `Replacement expression contains disallowed statements: multiple statements in '${expressionText}'`,
409
+ };
410
+ }
411
+
412
+ // 2. Reject obvious dangerous keywords upfront
413
+ const lower = trimmed.toLowerCase();
414
+ if (
415
+ lower.includes('process.') ||
416
+ lower.includes('require(') ||
417
+ lower.includes('import(') ||
418
+ lower.includes('eval(') ||
419
+ lower.includes('exec(') ||
420
+ lower.includes('spawn(')
421
+ ) {
422
+ return {
423
+ isValid: false,
424
+ reason: `Replacement expression contains disallowed statements: arbitrary code execution detected in '${expressionText}'`,
425
+ };
426
+ }
427
+
428
+ // 3. Parse into AST expression
429
+ const tempProject = new Project({ skipAddingFilesFromTsConfig: true });
430
+ const tempFile = tempProject.createSourceFile('temp_expr.ts', `const _x = ${trimmed};`);
431
+ const varDecl = tempFile.getVariableDeclaration('_x');
432
+ const initializer = varDecl?.getInitializer();
433
+
434
+ if (!initializer) {
435
+ return {
436
+ isValid: false,
437
+ reason: `Replacement expression contains disallowed statements: failed to parse '${expressionText}'`,
438
+ };
439
+ }
440
+
441
+ // String literals are valid for locator constants (e.g. '[data-test=submit]')
442
+ if (Node.isStringLiteral(initializer)) {
443
+ return { isValid: true };
444
+ }
445
+
446
+ // Must be a CallExpression or PropertyAccessExpression resolving to a locator
447
+ if (!Node.isCallExpression(initializer)) {
448
+ return {
449
+ isValid: false,
450
+ reason: `Replacement expression contains disallowed statements: must be a CallExpression (e.g. this.page.getByRole(...)), received ${initializer.getKindName()}`,
451
+ };
452
+ }
453
+
454
+ // Validate the call chain and its arguments recursively
455
+ return DiffAllowlistValidator.validateLocatorCallChain(initializer);
456
+ }
457
+
458
+ /**
459
+ * Asserts diff allowlist compliance, throwing UnauthorizedAstModificationError if violated.
460
+ */
461
+ public static assertAllowed(target: DiffAllowlistTarget): void {
462
+ const result = DiffAllowlistValidator.validate(target);
463
+ if (!result.isAllowed) {
464
+ throw new UnauthorizedAstModificationError(result.reason ?? 'Unknown violation');
465
+ }
466
+ }
467
+ }
468
+
469
+ export interface StatementDiffAllowlistTarget {
470
+ originalSource: string;
471
+ patchedSource: string;
472
+ operation?: string | undefined;
473
+ targetLine?: number | undefined;
474
+ }
475
+
476
+ export interface StatementDiffAllowlistResult {
477
+ isAllowed: boolean;
478
+ reason?: string | undefined;
479
+ }
480
+
481
+ export class StatementDiffAllowlistValidator {
482
+ private static readonly DISALLOWED_SECURITY_TOKENS = [
483
+ 'process.env',
484
+ 'process.exit',
485
+ 'child_process',
486
+ 'require(',
487
+ 'eval(',
488
+ 'exec(',
489
+ 'spawn(',
490
+ 'fs.',
491
+ 'Function(',
492
+ ];
493
+
494
+ /**
495
+ * Validates that ONLY approved statement-level modifications (timing synchronizations,
496
+ * event-driven wait replacements, readiness checks, or inline locator repairs) were applied to the test spec AST.
497
+ *
498
+ * Guarantees:
499
+ * - No arbitrary code execution or dangerous APIs.
500
+ * - No unauthorized third-party module imports.
501
+ * - Top-level declarations (functions, classes, interfaces, types) remain invariant.
502
+ * - Test block structure and test descriptions remain invariant.
503
+ */
504
+ public static validate(target: StatementDiffAllowlistTarget): StatementDiffAllowlistResult {
505
+ // 1. Prohibit dangerous tokens
506
+ for (const token of StatementDiffAllowlistValidator.DISALLOWED_SECURITY_TOKENS) {
507
+ if (target.patchedSource.includes(token) && !target.originalSource.includes(token)) {
508
+ return {
509
+ isAllowed: false,
510
+ reason: `Statement diff allowlist violated: Prohibited security token '${token}' introduced.`,
511
+ };
512
+ }
513
+ }
514
+
515
+ const origProject = new Project({ skipAddingFilesFromTsConfig: true });
516
+ const patchProject = new Project({ skipAddingFilesFromTsConfig: true });
517
+
518
+ const origFile = origProject.createSourceFile('orig.spec.ts', target.originalSource);
519
+ const patchFile = patchProject.createSourceFile('patched.spec.ts', target.patchedSource);
520
+
521
+ // 2. Verify Imports: Only allow adding expect from @playwright/test
522
+ const origImports = origFile.getImportDeclarations();
523
+ const patchImports = patchFile.getImportDeclarations();
524
+
525
+ for (const patchImp of patchImports) {
526
+ const spec = patchImp.getModuleSpecifierValue();
527
+ const origImp = origImports.find((i) => i.getModuleSpecifierValue() === spec);
528
+ if (!origImp) {
529
+ // Only @playwright/test import may be added
530
+ if (spec !== '@playwright/test') {
531
+ return {
532
+ isAllowed: false,
533
+ reason: `Statement diff allowlist violated: Unauthorized new import of module '${spec}'.`,
534
+ };
535
+ }
536
+ } else {
537
+ // If module exists, ensure no unauthorized named imports were added
538
+ const origNamed = new Set(origImp.getNamedImports().map((n) => n.getName()));
539
+ for (const named of patchImp.getNamedImports()) {
540
+ const name = named.getName();
541
+ if (!origNamed.has(name) && name !== 'expect') {
542
+ return {
543
+ isAllowed: false,
544
+ reason: `Statement diff allowlist violated: Unauthorized import '${name}' added from '${spec}'.`,
545
+ };
546
+ }
547
+ }
548
+ }
549
+ }
550
+
551
+ // 3. Prohibit adding or removing top-level declarations
552
+ if (origFile.getFunctions().length !== patchFile.getFunctions().length) {
553
+ return {
554
+ isAllowed: false,
555
+ reason: 'Statement diff allowlist violated: Top-level function declarations modified or added.',
556
+ };
557
+ }
558
+ if (origFile.getClasses().length !== patchFile.getClasses().length) {
559
+ return {
560
+ isAllowed: false,
561
+ reason: 'Statement diff allowlist violated: Top-level class declarations modified or added.',
562
+ };
563
+ }
564
+ if (origFile.getInterfaces().length !== patchFile.getInterfaces().length) {
565
+ return {
566
+ isAllowed: false,
567
+ reason: 'Statement diff allowlist violated: Top-level interface declarations modified or added.',
568
+ };
569
+ }
570
+ if (origFile.getTypeAliases().length !== patchFile.getTypeAliases().length) {
571
+ return {
572
+ isAllowed: false,
573
+ reason: 'Statement diff allowlist violated: Top-level type aliases modified or added.',
574
+ };
575
+ }
576
+
577
+ // 4. Verify test blocks: Count and descriptions of test() and test.describe() must match
578
+ const extractTestCalls = (sourceFile: import('ts-morph').SourceFile) => {
579
+ const calls: string[] = [];
580
+ sourceFile.forEachDescendant((node) => {
581
+ if (Node.isCallExpression(node)) {
582
+ const text = node.getExpression().getText();
583
+ if (text === 'test' || text === 'test.describe' || text === 'test.only' || text === 'test.skip') {
584
+ const firstArg = node.getArguments()[0];
585
+ calls.push(`${text}:${firstArg ? firstArg.getText() : ''}`);
586
+ }
587
+ }
588
+ });
589
+ return calls;
590
+ };
591
+
592
+ const origTestCalls = extractTestCalls(origFile);
593
+ const patchTestCalls = extractTestCalls(patchFile);
594
+ if (JSON.stringify(origTestCalls) !== JSON.stringify(patchTestCalls)) {
595
+ return {
596
+ isAllowed: false,
597
+ reason: 'Statement diff allowlist violated: Test suite structure, test names, or test block declarations altered.',
598
+ };
599
+ }
600
+
601
+ return {
602
+ isAllowed: true,
603
+ };
604
+ }
605
+
606
+ public static assertAllowed(target: StatementDiffAllowlistTarget): void {
607
+ const res = StatementDiffAllowlistValidator.validate(target);
608
+ if (!res.isAllowed) {
609
+ throw new UnauthorizedAstModificationError(res.reason ?? 'Unknown violation');
610
+ }
611
+ }
612
+ }