playwright-intelligence-engine 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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,561 @@
1
+ import { z } from 'zod';
2
+ import {
3
+ ArchitecturePlan,
4
+ ArchitectureWriteResult,
5
+ planArchitecture,
6
+ toSlug,
7
+ writeArchitecturePlan,
8
+ } from '../generator/architecture-generator.js';
9
+ import { AssertionQualityGate } from '../generator/assertion-quality-gate.js';
10
+ import { CandidateWriter, CandidateWriteResult } from '../generator/candidate-writer.js';
11
+ import { GeneratedStep } from '../generator/test-generator.js';
12
+ import { buildRepositoryProfile, RepositoryConventionProfile } from './repository-profile.js';
13
+ import { buildTestIntelligencePlan, ExistingTestInput, RequirementInput, TestIntelligencePlan } from './test-intelligence.js';
14
+ import { ExecutionRunner, ExecutionResult } from '../sandbox/execution-runner.js';
15
+ import {
16
+ Author,
17
+ AuthorKind,
18
+ CandidateChange,
19
+ FullCodeGenerationProvider,
20
+ HybridAuthor,
21
+ LlmAuthor,
22
+ NamingLinter,
23
+ TemplateAuthor,
24
+ } from '../authoring/index.js';
25
+ import { QualityGate, QualityGateResult } from '../gate/index.js';
26
+
27
+ const GeneratedStepSchema = z.object({
28
+ action: z.enum(['click', 'fill', 'press', 'navigate', 'assert-visible', 'assert-text', 'assert-count', 'assert-value']),
29
+ role: z.string().min(1).optional(),
30
+ name: z.string().min(1).optional(),
31
+ selector: z.string().min(1).optional(),
32
+ value: z.string().optional(),
33
+ placeholder: z.string().optional(),
34
+ expectedText: z.string().optional(),
35
+ expectedCount: z.number().optional(),
36
+ expectedValue: z.string().optional(),
37
+ frame: z.string().optional(),
38
+ frameSelector: z.string().optional(),
39
+ }).strict();
40
+
41
+ const GeneratedScenarioSchema = z.object({
42
+ id: z.string().min(1),
43
+ title: z.string().min(1),
44
+ requirementId: z.string().min(1),
45
+ steps: z.array(GeneratedStepSchema).min(1).max(100),
46
+ reasoningSummary: z.string().min(1),
47
+ evidenceReferences: z.array(z.string()),
48
+ }).strict();
49
+
50
+ export const GenerationPlanSchema = z.object({
51
+ planVersion: z.literal('1'),
52
+ status: z.enum(['GENERATE', 'NEEDS_INPUT', 'ABSTAIN']),
53
+ reasoningSummary: z.string().min(1),
54
+ scenarios: z.array(GeneratedScenarioSchema).max(30),
55
+ requiredClarifications: z.array(z.string()),
56
+ evidenceReferences: z.array(z.string()),
57
+ }).strict();
58
+
59
+ export type GenerationPlan = z.infer<typeof GenerationPlanSchema>;
60
+
61
+ export interface GenerationPlanInput {
62
+ requirements: RequirementInput[];
63
+ existingTests?: ExistingTestInput[] | undefined;
64
+ mode: 'greenfield' | 'brownfield';
65
+ repositoryProfile?: RepositoryConventionProfile | undefined;
66
+ baseUrl?: string | undefined;
67
+ suggestedSteps?: GeneratedStep[] | undefined;
68
+ }
69
+
70
+ export interface GenerationPlanProvider {
71
+ propose(input: GenerationPlanInput, intelligence: TestIntelligencePlan): Promise<GenerationPlanParseResult>;
72
+ }
73
+
74
+ export interface GenerationPlanParseResult {
75
+ isValid: boolean;
76
+ plan?: GenerationPlan | undefined;
77
+ errors: string[];
78
+ }
79
+
80
+ export interface GenerationAgentInput extends GenerationPlanInput {
81
+ workspaceRoot?: string | undefined;
82
+ provider?: GenerationPlanProvider | undefined;
83
+ fullCodeProvider?: FullCodeGenerationProvider | undefined;
84
+ authorImpl?: Author | undefined;
85
+ write?: boolean | undefined;
86
+ allowModify?: boolean | undefined;
87
+ existingFiles?: string[] | undefined;
88
+ includeCi?: boolean | undefined;
89
+ verify?: boolean | undefined;
90
+ author?: AuthorKind | undefined;
91
+ maxSteps?: number | undefined;
92
+ maxDurationMs?: number | undefined;
93
+ contextPacket?: unknown;
94
+ workflowPlan?: unknown;
95
+ explorationBundle?: unknown;
96
+ allowedEvidenceIds?: string[] | undefined;
97
+ }
98
+
99
+ export interface GeneratedScenarioResult {
100
+ scenario: GenerationPlan['scenarios'][number];
101
+ architecture: ArchitecturePlan;
102
+ candidate?: CandidateChange | undefined;
103
+ writeResult?: ArchitectureWriteResult | undefined;
104
+ candidateWriteResult?: CandidateWriteResult | undefined;
105
+ verification?: ExecutionResult | undefined;
106
+ loopState?: 'SUCCESS' | 'AGENT_NO_PROGRESS' | 'AGENT_TIME_BUDGET' | 'MAX_STEPS_EXCEEDED' | undefined;
107
+ stepsCount?: number | undefined;
108
+ qualityGateResult?: QualityGateResult | undefined;
109
+ }
110
+
111
+ export interface GenerationAgentResult {
112
+ status: 'MODEL_NOT_CONFIGURED' | 'ABSTAINED' | 'NEEDS_INPUT' | 'PLANNED' | 'WRITTEN' | 'VERIFIED' | 'VERIFICATION_FAILED' | 'AGENT_NO_PROGRESS';
113
+ provider: 'llm' | 'deterministic-fallback';
114
+ authorKind?: AuthorKind | undefined;
115
+ intelligence: TestIntelligencePlan;
116
+ plan?: GenerationPlan | undefined;
117
+ scenarios: GeneratedScenarioResult[];
118
+ errors: string[];
119
+ }
120
+
121
+ function parseJson(input: unknown): unknown {
122
+ if (typeof input !== 'string') return input;
123
+ const fenced = input.trim().match(/```(?:json)?\s*([\s\S]*?)\s*```/i)?.[1];
124
+ const candidate = (fenced ?? input).trim();
125
+ const start = candidate.indexOf('{');
126
+ const end = candidate.lastIndexOf('}');
127
+ return JSON.parse(start >= 0 && end > start ? candidate.slice(start, end + 1) : candidate);
128
+ }
129
+
130
+ export function parseGenerationPlan(input: unknown): GenerationPlanParseResult {
131
+ try {
132
+ const parsed = GenerationPlanSchema.safeParse(parseJson(input));
133
+ if (!parsed.success) return { isValid: false, errors: parsed.error.issues.map((issue) => issue.message) };
134
+ if (parsed.data.status === 'GENERATE' && parsed.data.scenarios.length === 0) {
135
+ return { isValid: false, errors: ['GENERATE plans must contain at least one scenario'] };
136
+ }
137
+ if (parsed.data.status === 'NEEDS_INPUT' && parsed.data.requiredClarifications.length === 0) {
138
+ return { isValid: false, errors: ['NEEDS_INPUT plans must include requiredClarifications'] };
139
+ }
140
+ return { isValid: true, plan: parsed.data, errors: [] };
141
+ } catch (error) {
142
+ return { isValid: false, errors: [`Generation response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`] };
143
+ }
144
+ }
145
+
146
+ function generationSystemPrompt(): string {
147
+ return 'Return exactly one JSON object conforming to the schema:\n' +
148
+ '{\n' +
149
+ ' "planVersion": "1",\n' +
150
+ ' "status": "GENERATE",\n' +
151
+ ' "reasoningSummary": "string",\n' +
152
+ ' "scenarios": [\n' +
153
+ ' {\n' +
154
+ ' "id": "scenario-1",\n' +
155
+ ' "title": "string",\n' +
156
+ ' "requirementId": "string",\n' +
157
+ ' "steps": [\n' +
158
+ ' { "action": "navigate", "value": "/url" },\n' +
159
+ ' { "action": "fill", "role": "textbox", "name": "Field", "value": "val" },\n' +
160
+ ' { "action": "click", "role": "button", "name": "Button" },\n' +
161
+ ' { "action": "assert-text", "role": "status", "name": "Label", "expectedText": "Expected" }\n' +
162
+ ' ],\n' +
163
+ ' "reasoningSummary": "string",\n' +
164
+ ' "evidenceReferences": []\n' +
165
+ ' }\n' +
166
+ ' ],\n' +
167
+ ' "requiredClarifications": [],\n' +
168
+ ' "evidenceReferences": []\n' +
169
+ '}\n' +
170
+ 'Steps may only use click, fill, navigate, assert-visible, or assert-text. Every scenario MUST contain at least one step in its steps array. Do not return raw code, patches, or unsupported fields.';
171
+ }
172
+
173
+ export interface OpenAiCompatibleGenerationProviderOptions {
174
+ endpoint: string;
175
+ model: string;
176
+ apiKey?: string | undefined;
177
+ timeoutMs?: number | undefined;
178
+ fetchImpl?: typeof fetch | undefined;
179
+ }
180
+
181
+ /** Provider-neutral boundary with an OpenAI-compatible transport implementation. */
182
+ export class OpenAiCompatibleGenerationProvider implements GenerationPlanProvider {
183
+ public constructor(private readonly options: OpenAiCompatibleGenerationProviderOptions) {}
184
+
185
+ public async propose(input: GenerationPlanInput, intelligence: TestIntelligencePlan): Promise<GenerationPlanParseResult> {
186
+ const controller = new AbortController();
187
+ const timeout = setTimeout(() => controller.abort(), this.options.timeoutMs ?? 60_000);
188
+ try {
189
+ const response = await (this.options.fetchImpl ?? fetch)(this.options.endpoint, {
190
+ method: 'POST',
191
+ signal: controller.signal,
192
+ headers: {
193
+ 'content-type': 'application/json',
194
+ ...(this.options.apiKey ? { authorization: `Bearer ${this.options.apiKey}` } : {}),
195
+ },
196
+ body: JSON.stringify({
197
+ model: this.options.model,
198
+ temperature: 0,
199
+ response_format: { type: 'json_object' },
200
+ messages: [
201
+ { role: 'system', content: generationSystemPrompt() },
202
+ { role: 'user', content: JSON.stringify({ input, intelligence }) },
203
+ ],
204
+ }),
205
+ });
206
+ if (!response.ok) return { isValid: false, errors: [`Model endpoint returned HTTP ${response.status}`] };
207
+ const payload = await response.json() as { choices?: Array<{ message?: { content?: string | Array<{ text?: string }> | null } }>; output_text?: string };
208
+ const content = payload.output_text
209
+ ?? (typeof payload.choices?.[0]?.message?.content === 'string'
210
+ ? payload.choices[0].message.content
211
+ : payload.choices?.[0]?.message?.content?.map((part) => part.text ?? '').join(''));
212
+ if (!content) return { isValid: false, errors: ['Model endpoint returned no generation plan content'] };
213
+ return parseGenerationPlan(content);
214
+ } catch (error) {
215
+ return { isValid: false, errors: [error instanceof Error && error.name === 'AbortError' ? 'Generation model request timed out' : `Generation model request failed: ${error instanceof Error ? error.message : String(error)}`] };
216
+ } finally {
217
+ clearTimeout(timeout);
218
+ }
219
+ }
220
+ }
221
+
222
+ function configuredGenerationProvider(): GenerationPlanProvider | undefined {
223
+ const liveAllowed = !process.env.VITEST || process.env.PIE_LIVE_LLM_TESTS === '1';
224
+ const apiKey = liveAllowed ? (process.env.PIE_LLM_API_KEY ?? process.env.OPENAI_API_KEY ?? process.env.DEEPSEEK_API_KEY) : undefined;
225
+ const endpoint = process.env.PIE_LLM_ENDPOINT ?? (process.env.OPENAI_API_KEY ? 'https://api.openai.com/v1/chat/completions' : (apiKey ? 'https://api.deepseek.com/chat/completions' : undefined));
226
+ const model = process.env.PIE_LLM_MODEL ?? (process.env.OPENAI_API_KEY ? 'gpt-4o' : (apiKey ? 'deepseek-chat' : undefined));
227
+ if (!endpoint || !model) return undefined;
228
+ return new OpenAiCompatibleGenerationProvider({
229
+ endpoint,
230
+ model,
231
+ ...(apiKey ? { apiKey } : {}),
232
+ });
233
+ }
234
+
235
+ function validateGeneratedScenario(
236
+ scenario: GenerationPlan['scenarios'][number],
237
+ requirementIds: Set<string>,
238
+ ): string[] {
239
+ const errors: string[] = [];
240
+ if (!requirementIds.has(scenario.requirementId)) errors.push(`Scenario '${scenario.id}' references unknown requirement '${scenario.requirementId}'`);
241
+ for (const [index, step] of scenario.steps.entries()) {
242
+ const hasSemanticTarget = step.action === 'fill'
243
+ ? Boolean(step.name || (step.role && step.name))
244
+ : Boolean(step.role && step.name);
245
+ const hasExplicitTarget = Boolean(step.selector);
246
+ if ((step.action === 'click' || step.action === 'fill') && !hasSemanticTarget && !hasExplicitTarget) {
247
+ errors.push(`Scenario '${scenario.id}' step ${index + 1} requires role/name or an explicit selector`);
248
+ }
249
+ if (step.action === 'assert-visible' && !hasSemanticTarget && !hasExplicitTarget) {
250
+ errors.push(`Scenario '${scenario.id}' visibility assertion requires role/name or an explicit selector`);
251
+ }
252
+ if (step.action === 'assert-text') {
253
+ const text = step.expectedText?.trim() || step.value?.trim();
254
+ if (!text) {
255
+ errors.push(`Scenario '${scenario.id}' text assertion requires expectedText or value`);
256
+ } else if (!step.expectedText) {
257
+ step.expectedText = text;
258
+ }
259
+ }
260
+ }
261
+ return errors;
262
+ }
263
+
264
+ export async function runGenerationAgent(input: GenerationAgentInput): Promise<GenerationAgentResult> {
265
+ const intelligence = buildTestIntelligencePlan(input.requirements, input.existingTests ?? []);
266
+ const profile = input.repositoryProfile ?? (input.mode === 'brownfield' ? await buildRepositoryProfile(input.workspaceRoot ?? process.cwd()) : undefined);
267
+ const provider = input.provider ?? configuredGenerationProvider();
268
+ if (input.verify === true && input.write !== true) {
269
+ return { status: 'ABSTAINED', provider: provider ? 'llm' : 'deterministic-fallback', intelligence, scenarios: [], errors: ['Generation verification requires --write so the generated spec exists in the target workspace'] };
270
+ }
271
+
272
+ const authorKind: AuthorKind = input.author ?? 'hybrid';
273
+ const author: Author = input.authorImpl ?? (
274
+ authorKind === 'template'
275
+ ? new TemplateAuthor()
276
+ : authorKind === 'llm'
277
+ ? new LlmAuthor({ provider: input.fullCodeProvider })
278
+ : new HybridAuthor()
279
+ );
280
+
281
+ // If Tier-L2 fullCodeProvider is supplied with author='llm' and no custom step provider,
282
+ // allow direct full-code generation without forcing the 5-verb step planner first.
283
+ const effectiveProvider = (authorKind === 'llm' && input.fullCodeProvider && !input.provider) ? undefined : provider;
284
+
285
+ let generatedPlan: GenerationPlan;
286
+ let providerMode: 'llm' | 'deterministic-fallback' = 'llm';
287
+ if (effectiveProvider) {
288
+ const response = await effectiveProvider.propose({
289
+ requirements: input.requirements,
290
+ existingTests: input.existingTests,
291
+ mode: input.mode,
292
+ repositoryProfile: profile,
293
+ baseUrl: input.baseUrl,
294
+ suggestedSteps: input.suggestedSteps,
295
+ }, intelligence);
296
+ if (!response.isValid || !response.plan) return { status: 'ABSTAINED', provider: 'llm', intelligence, scenarios: [], errors: response.errors };
297
+ if (response.plan.status === 'ABSTAIN') return { status: 'ABSTAINED', provider: 'llm', intelligence, plan: response.plan, scenarios: [], errors: [response.plan.reasoningSummary] };
298
+ if (response.plan.status === 'NEEDS_INPUT') return { status: 'NEEDS_INPUT', provider: 'llm', intelligence, plan: response.plan, scenarios: [], errors: response.plan.requiredClarifications };
299
+ generatedPlan = response.plan;
300
+ } else {
301
+ providerMode = authorKind === 'llm' && input.fullCodeProvider ? 'llm' : 'deterministic-fallback';
302
+ generatedPlan = {
303
+ planVersion: '1',
304
+ status: 'GENERATE',
305
+ reasoningSummary: authorKind === 'llm' && input.fullCodeProvider
306
+ ? 'Tier-L2 full-code provider configured; synthesizing scenarios directly via LlmAuthor.'
307
+ : 'No LLM provider is configured; generated a conservative deterministic scaffold from the supplied requirements.',
308
+ scenarios: input.requirements.map((requirement) => ({
309
+ id: `scenario-${requirement.id}`,
310
+ title: requirement.title,
311
+ requirementId: requirement.id,
312
+ steps: input.suggestedSteps && input.suggestedSteps.length > 0
313
+ ? input.suggestedSteps
314
+ : [{ action: 'navigate' as const }, { action: 'assert-visible' as const, role: 'heading', name: requirement.title }],
315
+ reasoningSummary: 'Scenario derived directly from the supplied requirement; business assertions require review.',
316
+ evidenceReferences: [`requirement:${requirement.id}`],
317
+ })),
318
+ requiredClarifications: [],
319
+ evidenceReferences: input.requirements.map((requirement) => `requirement:${requirement.id}`),
320
+ };
321
+ }
322
+
323
+ const requirementIds = new Set(input.requirements.map((requirement) => requirement.id));
324
+ const scenarioIds = new Set<string>();
325
+ const scenarioValidationErrors = generatedPlan.scenarios.flatMap((scenario) => {
326
+ const errors = scenarioIds.has(scenario.id) ? [`Duplicate generated scenario id '${scenario.id}'`] : [];
327
+ scenarioIds.add(scenario.id);
328
+ return [...errors, ...validateGeneratedScenario(scenario, requirementIds)];
329
+ });
330
+ if (scenarioValidationErrors.length > 0) {
331
+ return { status: 'ABSTAINED', provider: providerMode, intelligence, plan: generatedPlan, scenarios: [], errors: scenarioValidationErrors };
332
+ }
333
+
334
+ const scenarios: GeneratedScenarioResult[] = [];
335
+ for (const scenario of generatedPlan.scenarios) {
336
+ const maxSteps = input.maxSteps ?? 3;
337
+ const maxDurationMs = input.maxDurationMs ?? 120_000;
338
+ const startTime = Date.now();
339
+ const seenSignatures = new Set<string>();
340
+
341
+ let candidate: CandidateChange | undefined;
342
+ let gateResult: QualityGateResult | undefined;
343
+ let loopState: 'SUCCESS' | 'AGENT_NO_PROGRESS' | 'AGENT_TIME_BUDGET' | 'MAX_STEPS_EXCEEDED' = 'SUCCESS';
344
+ let stepCount = 0;
345
+
346
+ for (let stepIndex = 0; stepIndex < maxSteps; stepIndex++) {
347
+ stepCount++;
348
+ if (Date.now() - startTime >= maxDurationMs) {
349
+ loopState = 'AGENT_TIME_BUDGET';
350
+ break;
351
+ }
352
+
353
+ candidate = await author.propose({
354
+ featureName: scenario.title,
355
+ mode: input.mode,
356
+ baseUrl: input.baseUrl,
357
+ requirements: input.requirements.find((r) => r.id === scenario.requirementId)?.description
358
+ ?? input.requirements.find((r) => r.id === scenario.requirementId)?.title,
359
+ steps: scenario.steps as GeneratedStep[],
360
+ profile,
361
+ existingFiles: input.existingFiles,
362
+ exemplars: profile?.exemplars,
363
+ contextPacket: input.contextPacket,
364
+ workflowPlan: input.workflowPlan,
365
+ explorationBundle: input.explorationBundle,
366
+ scenarios: generatedPlan.scenarios,
367
+ allowedEvidenceIds: input.allowedEvidenceIds,
368
+ fullCodeProvider: input.fullCodeProvider,
369
+ });
370
+
371
+ if (candidate.status === 'NEEDS_INPUT') {
372
+ return {
373
+ status: 'NEEDS_INPUT',
374
+ provider: providerMode,
375
+ authorKind,
376
+ intelligence,
377
+ plan: generatedPlan,
378
+ scenarios,
379
+ errors: candidate.requiredClarifications?.length ? candidate.requiredClarifications : [candidate.rationale],
380
+ };
381
+ }
382
+
383
+ if (candidate.status === 'ABSTAIN' && candidate.fullCodeAuthoring) {
384
+ gateResult = {
385
+ passed: false,
386
+ duplicateSelectors: [],
387
+ locatorPolicyViolations: [],
388
+ assertionStrengthPassed: false,
389
+ changedLines: 0,
390
+ violations: candidate.validationErrors?.length ? candidate.validationErrors : [candidate.rationale],
391
+ };
392
+ } else {
393
+ gateResult = QualityGate.evaluate({
394
+ symbols: candidate.symbols,
395
+ files: candidate.files,
396
+ locatorPolicy: profile?.identifierConventions?.recommendedLocatorPolicy,
397
+ });
398
+
399
+ if (candidate.fullCodeAuthoring) {
400
+ for (const spec of candidate.files.filter((f) => /\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs)$/i.test(f.relativePath))) {
401
+ const aqg = AssertionQualityGate.evaluate(spec.content, scenario.title);
402
+ if (!aqg.passed) {
403
+ gateResult.passed = false;
404
+ gateResult.assertionStrengthPassed = false;
405
+ gateResult.violations.push(...aqg.violations);
406
+ }
407
+ }
408
+ }
409
+ }
410
+
411
+ const signature = `${candidate.symbols.pageClassName}:${candidate.symbols.symbols.map((s) => `${s.property}:${s.method}`).join(',')}:${gateResult.violations.join(';')}`;
412
+
413
+ if (seenSignatures.has(signature)) {
414
+ loopState = 'AGENT_NO_PROGRESS';
415
+ break;
416
+ }
417
+ seenSignatures.add(signature);
418
+
419
+ if (gateResult.passed) {
420
+ loopState = 'SUCCESS';
421
+ break;
422
+ }
423
+
424
+ if (stepIndex === maxSteps - 1) {
425
+ loopState = 'MAX_STEPS_EXCEEDED';
426
+ }
427
+ }
428
+
429
+ const preserveCandidateFiles = Boolean(
430
+ candidate &&
431
+ candidate.files.length > 0 &&
432
+ (candidate.fullCodeAuthoring === true || candidate.authorKind === 'llm'),
433
+ );
434
+
435
+ const architecture: ArchitecturePlan = preserveCandidateFiles && candidate
436
+ ? {
437
+ mode: input.mode,
438
+ status: candidate.status === 'ABSTAIN' ? 'ABSTAIN' : 'READY',
439
+ featureName: scenario.title,
440
+ slug: toSlug(scenario.title),
441
+ conventions: {
442
+ testDirectory: profile?.paths?.testDirectory ?? 'tests',
443
+ pageObjectDirectory: profile?.paths?.pageObjectDirectory ?? 'tests/pages',
444
+ fixtureDirectory: profile?.paths?.fixtureDirectory ?? 'tests/fixtures',
445
+ preferredLocator: profile?.locatorConventions.preferred ?? 'getByRole',
446
+ usesCustomFixtures: profile?.structure.usesCustomFixtures ?? false,
447
+ usesStorageState: profile?.structure.usesStorageState ?? false,
448
+ },
449
+ assumptions: candidate.assumptions ?? [],
450
+ warnings: candidate.validationErrors ?? [],
451
+ files: candidate.files.map((file) => ({
452
+ relativePath: file.relativePath,
453
+ content: file.content,
454
+ purpose: file.purpose ?? file.rationale ?? 'Author-generated file',
455
+ action: file.action ?? 'CREATE',
456
+ })),
457
+ namePlan: candidate.symbols,
458
+ namingLintReport: NamingLinter.lint(candidate.symbols),
459
+ }
460
+ : planArchitecture({
461
+ featureName: scenario.title,
462
+ mode: input.mode,
463
+ baseUrl: input.baseUrl,
464
+ requirements: input.requirements.find((requirement) => requirement.id === scenario.requirementId)?.description
465
+ ?? input.requirements.find((requirement) => requirement.id === scenario.requirementId)?.title,
466
+ steps: scenario.steps as GeneratedStep[],
467
+ profile,
468
+ existingFiles: input.existingFiles,
469
+ includeCi: input.includeCi,
470
+ namePlan: candidate?.symbols,
471
+ });
472
+
473
+ let writeResult: ArchitectureWriteResult | undefined;
474
+ let candidateWriteResult: CandidateWriteResult | undefined;
475
+ if (input.write === true && loopState === 'SUCCESS') {
476
+ const workspaceRoot = input.workspaceRoot ?? process.cwd();
477
+ if (preserveCandidateFiles && candidate) {
478
+ candidateWriteResult = await CandidateWriter.apply(workspaceRoot, candidate, {
479
+ allowModify: input.allowModify ?? true,
480
+ validateLocalImports: false,
481
+ });
482
+ if (!candidateWriteResult.success) {
483
+ return {
484
+ status: 'ABSTAINED',
485
+ provider: providerMode,
486
+ authorKind,
487
+ intelligence,
488
+ plan: generatedPlan,
489
+ scenarios,
490
+ errors: candidateWriteResult.errors,
491
+ };
492
+ }
493
+ writeResult = {
494
+ written: candidateWriteResult.written,
495
+ skipped: candidateWriteResult.skipped,
496
+ modified: candidateWriteResult.modified,
497
+ };
498
+ } else {
499
+ writeResult = await writeArchitecturePlan(workspaceRoot, architecture);
500
+ }
501
+ }
502
+
503
+ let verification: ExecutionResult | undefined;
504
+ if (input.verify === true && loopState === 'SUCCESS') {
505
+ const spec = architecture.files.find((file) => /\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs)$/i.test(file.relativePath));
506
+ if (!spec) throw new Error(`Generated scenario '${scenario.id}' did not produce a spec file`);
507
+ verification = await ExecutionRunner.run('npx', {
508
+ cwd: input.workspaceRoot ?? process.cwd(),
509
+ args: ['playwright', 'test', spec.relativePath, '--reporter=line'],
510
+ timeoutMs: 120_000,
511
+ });
512
+ }
513
+
514
+ scenarios.push({
515
+ scenario,
516
+ architecture,
517
+ ...(candidate ? { candidate } : {}),
518
+ ...(writeResult ? { writeResult } : {}),
519
+ ...(candidateWriteResult ? { candidateWriteResult } : {}),
520
+ ...(verification ? { verification } : {}),
521
+ loopState,
522
+ stepsCount: stepCount,
523
+ ...(gateResult ? { qualityGateResult: gateResult } : {}),
524
+ });
525
+ }
526
+
527
+ const anyNoProgress = scenarios.some((s) => s.loopState === 'AGENT_NO_PROGRESS');
528
+ const anyGateFailed = scenarios.some((s) => s.qualityGateResult && !s.qualityGateResult.passed);
529
+ const anyVerificationFailed = scenarios.some((s) => s.verification && s.verification.exitCode !== 0);
530
+
531
+ const errors: string[] = [];
532
+ if (anyNoProgress) {
533
+ errors.push('Generation agent detected no progress: author proposed duplicate invalid states.');
534
+ }
535
+ if (anyGateFailed) {
536
+ errors.push(...scenarios.flatMap((s) => s.qualityGateResult?.violations ?? []));
537
+ }
538
+
539
+ let status: GenerationAgentResult['status'];
540
+ if (anyNoProgress) {
541
+ status = 'AGENT_NO_PROGRESS';
542
+ } else if (anyGateFailed) {
543
+ status = 'ABSTAINED';
544
+ } else if (input.verify === true) {
545
+ status = anyVerificationFailed ? 'VERIFICATION_FAILED' : 'VERIFIED';
546
+ } else if (input.write === true) {
547
+ status = 'WRITTEN';
548
+ } else {
549
+ status = 'PLANNED';
550
+ }
551
+
552
+ return {
553
+ status,
554
+ provider: providerMode,
555
+ authorKind,
556
+ intelligence,
557
+ plan: generatedPlan,
558
+ scenarios,
559
+ errors,
560
+ };
561
+ }