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,313 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import { ApprovalService, ApprovalTokenPayload } from './approval-service.js';
5
+ import { WorktreeManager } from '../sandbox/worktree-manager.js';
6
+
7
+ export interface PromotionResult {
8
+ success: boolean;
9
+ targetFilePath: string;
10
+ bytesWritten: number;
11
+ finalHash: string;
12
+ error?: string | undefined;
13
+ }
14
+
15
+ interface LockFilePayload {
16
+ pid: number;
17
+ timestamp: number;
18
+ }
19
+
20
+ /**
21
+ * WorkspacePromoter handles safe, atomic, compare-before-write promotion of
22
+ * verified worktree repairs into the developer's active workspace.
23
+ *
24
+ * Enforces:
25
+ * 1. Canonical realpath containment inside the approved workspace.
26
+ * 2. Cross-process and in-process cooperative file locks.
27
+ * 3. Compare-And-Swap (CAS) hash verification of original source before mutation.
28
+ * 4. Patch diff and content hash validation against cryptographic approval token.
29
+ * 5. Safe temporary file write in the same directory.
30
+ * 6. Recoverable backup creation for fail-closed rollback.
31
+ * 7. Verification of post-promotion content hash.
32
+ * 8. Elimination of unvalidated direct-write fallbacks.
33
+ */
34
+ export class WorkspacePromoter {
35
+ private static activeLocks = new Set<string>();
36
+
37
+ /**
38
+ * Promotes an approved patch to the active workspace with CAS hash verification,
39
+ * filesystem locks, and atomic rename.
40
+ */
41
+ public static async promote(params: {
42
+ targetFilePath: string;
43
+ workspaceRoot: string;
44
+ patchedContent: string;
45
+ approvalTokenPayload: ApprovalTokenPayload;
46
+ }): Promise<PromotionResult> {
47
+ const { targetFilePath, workspaceRoot, patchedContent, approvalTokenPayload } = params;
48
+
49
+ // 1. Resolve canonical target path
50
+ if (!fs.existsSync(targetFilePath)) {
51
+ return {
52
+ success: false,
53
+ targetFilePath,
54
+ bytesWritten: 0,
55
+ finalHash: '',
56
+ error: `Target file does not exist: '${targetFilePath}'`,
57
+ };
58
+ }
59
+
60
+ const canonicalTarget = fs.realpathSync(targetFilePath);
61
+ const canonicalWorkspace = fs.realpathSync(workspaceRoot);
62
+
63
+ // 2. Verify workspace containment
64
+ const relative = path.relative(canonicalWorkspace, canonicalTarget);
65
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
66
+ return {
67
+ success: false,
68
+ targetFilePath: canonicalTarget,
69
+ bytesWritten: 0,
70
+ finalHash: '',
71
+ error: `Security Violation: Target path '${canonicalTarget}' escapes workspace root '${canonicalWorkspace}'.`,
72
+ };
73
+ }
74
+
75
+ // Verify target matches token binding
76
+ if (canonicalTarget !== approvalTokenPayload.targetFilePath) {
77
+ return {
78
+ success: false,
79
+ targetFilePath: canonicalTarget,
80
+ bytesWritten: 0,
81
+ finalHash: '',
82
+ error: `Approval Token Target Mismatch: Token bound to '${approvalTokenPayload.targetFilePath}', received '${canonicalTarget}'.`,
83
+ };
84
+ }
85
+
86
+ // 3. Acquire per-file in-process and cross-process lock
87
+ const lockKey = canonicalTarget.toLowerCase();
88
+ if (WorkspacePromoter.activeLocks.has(lockKey)) {
89
+ return {
90
+ success: false,
91
+ targetFilePath: canonicalTarget,
92
+ bytesWritten: 0,
93
+ finalHash: '',
94
+ error: `Conflict: Another promotion is currently active for '${canonicalTarget}'.`,
95
+ };
96
+ }
97
+
98
+ const lockFilePath = `${canonicalTarget}.pie-promote.lock`;
99
+ const lockAcquired = WorkspacePromoter.acquireCrossProcessLock(lockFilePath);
100
+ if (!lockAcquired.success) {
101
+ return {
102
+ success: false,
103
+ targetFilePath: canonicalTarget,
104
+ bytesWritten: 0,
105
+ finalHash: '',
106
+ error: lockAcquired.error,
107
+ };
108
+ }
109
+
110
+ WorkspacePromoter.activeLocks.add(lockKey);
111
+
112
+ const targetDir = path.dirname(canonicalTarget);
113
+ const tempFile = path.join(targetDir, `.pie-promo-${process.pid}-${crypto.randomBytes(8).toString('hex')}.tmp`);
114
+ const backupFile = path.join(targetDir, `.pie-backup-${process.pid}-${crypto.randomBytes(8).toString('hex')}.bak`);
115
+
116
+ try {
117
+ // 4 & 5. Read current file and calculate hash
118
+ const currentContent = await fs.promises.readFile(canonicalTarget, 'utf-8');
119
+ const currentHash = ApprovalService.hashContent(currentContent);
120
+
121
+ // 6 & 7. Compare hash (CAS check)
122
+ if (currentHash !== approvalTokenPayload.originalSourceHash) {
123
+ return {
124
+ success: false,
125
+ targetFilePath: canonicalTarget,
126
+ bytesWritten: 0,
127
+ finalHash: currentHash,
128
+ error: `Concurrent Modification Detected: File '${canonicalTarget}' has been modified since approval (current: ${currentHash.slice(0, 8)}, expected: ${approvalTokenPayload.originalSourceHash.slice(0, 8)}). Promotion aborted.`,
129
+ };
130
+ }
131
+
132
+ // Verify patchedContent matches token patchedSourceHash
133
+ const contentHash = ApprovalService.hashContent(patchedContent);
134
+ if (approvalTokenPayload.patchedSourceHash && contentHash !== approvalTokenPayload.patchedSourceHash) {
135
+ return {
136
+ success: false,
137
+ targetFilePath: canonicalTarget,
138
+ bytesWritten: 0,
139
+ finalHash: contentHash,
140
+ error: `Patch Content Corrupted: Content hash does not match approved patchedSourceHash.`,
141
+ };
142
+ }
143
+
144
+ // P0 TOCTOU Fix: Verify and atomically consume approval token strictly INSIDE the locked critical section
145
+ const tokenConsumption = ApprovalService.getInstance().verifyAndConsumeToken({
146
+ tokenId: approvalTokenPayload.tokenId,
147
+ targetFilePath: canonicalTarget,
148
+ currentSourceContent: currentContent,
149
+ targetClassName: approvalTokenPayload.targetClassName,
150
+ targetMemberName: approvalTokenPayload.targetMemberName,
151
+ patchHash: approvalTokenPayload.replacementAstHash,
152
+ fullDiffHash: approvalTokenPayload.fullDiffHash,
153
+ });
154
+
155
+ if (!tokenConsumption.isValid) {
156
+ return {
157
+ success: false,
158
+ targetFilePath: canonicalTarget,
159
+ bytesWritten: 0,
160
+ finalHash: currentHash,
161
+ error: `Security Violation: Token authorization denied under lock: ${tokenConsumption.reason}`,
162
+ };
163
+ }
164
+
165
+ // 8. Create safety rollback backup
166
+ await fs.promises.copyFile(canonicalTarget, backupFile);
167
+
168
+ // 9. Write to secure temporary file in the same directory
169
+ await fs.promises.writeFile(tempFile, patchedContent, 'utf-8');
170
+ const tempContent = await fs.promises.readFile(tempFile, 'utf-8');
171
+ const writtenHash = ApprovalService.hashContent(tempContent);
172
+ if (writtenHash !== contentHash) {
173
+ throw new Error(`Integrity check failed: written temporary content hash mismatch.`);
174
+ }
175
+
176
+ // 10. Atomic replace with retry for Windows transient scanner locks
177
+ let replaced = false;
178
+ for (let attempt = 0; attempt < 5; attempt++) {
179
+ try {
180
+ fs.renameSync(tempFile, canonicalTarget);
181
+ replaced = true;
182
+ break;
183
+ } catch {
184
+ // Brief backoff for Windows anti-virus or indexer lock
185
+ await new Promise((r) => setTimeout(r, 50));
186
+ }
187
+ }
188
+
189
+ if (!replaced) {
190
+ // Safe rollback from backup: never do unvalidated overwrite
191
+ await fs.promises.copyFile(backupFile, canonicalTarget).catch(() => {});
192
+ throw new Error(`Failed to atomically replace '${canonicalTarget}' after multiple attempts.`);
193
+ }
194
+
195
+ // 11. Verify resulting file hash
196
+ const verifiedContent = await fs.promises.readFile(canonicalTarget, 'utf-8');
197
+ const finalHash = ApprovalService.hashContent(verifiedContent);
198
+
199
+ if (finalHash !== contentHash) {
200
+ // Rollback immediately if final hash mismatches expected
201
+ await fs.promises.copyFile(backupFile, canonicalTarget).catch(() => {});
202
+ return {
203
+ success: false,
204
+ targetFilePath: canonicalTarget,
205
+ bytesWritten: 0,
206
+ finalHash,
207
+ error: `Post-promotion verification failed: final file hash did not match expected hash. Rolled back from backup.`,
208
+ };
209
+ }
210
+
211
+ // 12. Cleanup safety backup on success
212
+ if (fs.existsSync(backupFile)) {
213
+ await fs.promises.unlink(backupFile).catch(() => {});
214
+ }
215
+
216
+ return {
217
+ success: true,
218
+ targetFilePath: canonicalTarget,
219
+ bytesWritten: Buffer.byteLength(verifiedContent, 'utf-8'),
220
+ finalHash,
221
+ };
222
+ } catch (err: any) {
223
+ // Clean up temporary file and restore from backup if necessary
224
+ if (fs.existsSync(tempFile)) {
225
+ try { fs.unlinkSync(tempFile); } catch {}
226
+ }
227
+ if (fs.existsSync(backupFile)) {
228
+ try {
229
+ await fs.promises.copyFile(backupFile, canonicalTarget);
230
+ await fs.promises.unlink(backupFile);
231
+ } catch {}
232
+ }
233
+ return {
234
+ success: false,
235
+ targetFilePath: canonicalTarget,
236
+ bytesWritten: 0,
237
+ finalHash: '',
238
+ error: `Promotion I/O Error: ${err?.message ?? String(err)}`,
239
+ };
240
+ } finally {
241
+ // 13. Release in-process and cross-process lock
242
+ WorkspacePromoter.activeLocks.delete(lockKey);
243
+ WorkspacePromoter.releaseCrossProcessLock(lockFilePath);
244
+ if (fs.existsSync(tempFile)) {
245
+ try { fs.unlinkSync(tempFile); } catch {}
246
+ }
247
+ if (fs.existsSync(backupFile)) {
248
+ try { fs.unlinkSync(backupFile); } catch {}
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Acquires a cross-process lock using exclusive file creation ('wx').
255
+ * If a lock exists, verifies whether the owning process is still alive.
256
+ * If the owner is dead or the lock is older than 30s, the stale lock is safely reclaimed.
257
+ */
258
+ private static acquireCrossProcessLock(lockFilePath: string): { success: boolean; error?: string } {
259
+ const maxTries = 3;
260
+ for (let i = 0; i < maxTries; i++) {
261
+ try {
262
+ const payload: LockFilePayload = {
263
+ pid: process.pid,
264
+ timestamp: Date.now(),
265
+ };
266
+ fs.writeFileSync(lockFilePath, JSON.stringify(payload), { flag: 'wx' });
267
+ return { success: true };
268
+ } catch (err: any) {
269
+ if (err.code === 'EEXIST') {
270
+ // Check if lock is stale
271
+ try {
272
+ const raw = fs.readFileSync(lockFilePath, 'utf-8');
273
+ const existing: LockFilePayload = JSON.parse(raw);
274
+ const isAlive = WorktreeManager.isProcessAlive(existing.pid);
275
+
276
+ // P1: Never steal lock from a living process regardless of elapsed time
277
+ if (!isAlive) {
278
+ // Owning process has terminated: safely reclaim dead lock
279
+ try { fs.unlinkSync(lockFilePath); } catch {}
280
+ continue;
281
+ }
282
+
283
+ return {
284
+ success: false,
285
+ error: `Conflict: Another active PIE process (PID ${existing.pid}) holds the promotion lock for this file.`,
286
+ };
287
+ } catch {
288
+ // Malformed lock file: reclaim
289
+ try { fs.unlinkSync(lockFilePath); } catch {}
290
+ continue;
291
+ }
292
+ }
293
+ return {
294
+ success: false,
295
+ error: `Cross-process lock failure: ${err.message}`,
296
+ };
297
+ }
298
+ }
299
+
300
+ return {
301
+ success: false,
302
+ error: `Could not acquire promotion lock for '${lockFilePath}' after ${maxTries} attempts.`,
303
+ };
304
+ }
305
+
306
+ private static releaseCrossProcessLock(lockFilePath: string): void {
307
+ try {
308
+ if (fs.existsSync(lockFilePath)) {
309
+ fs.unlinkSync(lockFilePath);
310
+ }
311
+ } catch {}
312
+ }
313
+ }
@@ -0,0 +1,287 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import { WorktreeManager } from '../sandbox/worktree-manager.js';
4
+ import { ExecutionRunner } from '../sandbox/execution-runner.js';
5
+ import { ErrorCollector } from '../collectors/error-collector.js';
6
+ import { StaticAstCollector } from '../collectors/static-ast-collector.js';
7
+ import { GitDiffCollector } from '../collectors/git-diff-collector.js';
8
+ import { EnvironmentCollector } from '../collectors/environment-collector.js';
9
+ import { TraceCollector } from '../collectors/trace-collector.js';
10
+ import { EvidenceNormalizer } from '../evidence/evidence-normalizer.js';
11
+ import { EvidenceScorecard, UnifiedEvidenceRecord } from '../evidence/evidence-model.js';
12
+ import { EvidenceRedactor } from '../evidence/evidence-redactor.js';
13
+ import { DiagnosticContext } from '../core/types/diagnostic-context.js';
14
+ import { EvidenceCorrelator } from '../diagnostics/evidence-correlator.js';
15
+ import { DiagnosticReportGenerator } from '../diagnostics/diagnostic-report.js';
16
+ import { DirectBrowserInvestigator } from './direct-browser-investigator.js';
17
+ import { LocatorPlan } from '../selector/candidate-extractor.js';
18
+ import { BrowserOperationExecutionResult } from '../repair/browser-operation-executor.js';
19
+
20
+ export interface DiagnosticRerunOptions {
21
+ specFilePath: string;
22
+ workspaceRoot: string;
23
+ testNamePattern?: string;
24
+ timeoutMs?: number;
25
+ useWorktree?: boolean;
26
+ enableLiveEscalation?: boolean;
27
+ operation?: unknown;
28
+ operationTargetPlan?: LocatorPlan | undefined;
29
+ operationReplacementPlan?: LocatorPlan | undefined;
30
+ operationOverlayPlan?: LocatorPlan | undefined;
31
+ allowInteraction?: boolean | undefined;
32
+ }
33
+
34
+ export interface DiagnosticRerunResult {
35
+ passed: boolean;
36
+ exitCode: number;
37
+ stdout: string;
38
+ stderr: string;
39
+ scorecard: EvidenceScorecard;
40
+ unifiedEvidence: UnifiedEvidenceRecord[];
41
+ context: DiagnosticContext;
42
+ report: string;
43
+ executedInWorktree: boolean;
44
+ targetSpecPath: string;
45
+ browserOperationResult?: BrowserOperationExecutionResult | undefined;
46
+ }
47
+
48
+ export class DiagnosticRerunner {
49
+ /**
50
+ * Mode A: Executes the failing Playwright test inside an isolated Git worktree (AUDIT-11 / B2).
51
+ * Canonicalizes paths, guarantees worktree containment, captures multi-source evidence,
52
+ * enforces central redaction, and escalates to live browser investigation if trace is absent.
53
+ */
54
+ public static async rerunAndDiagnose(options: DiagnosticRerunOptions): Promise<DiagnosticRerunResult> {
55
+ const executionId = `rerun-${Date.now()}`;
56
+ const worktreeManager = new WorktreeManager(options.workspaceRoot);
57
+ let targetDir = options.workspaceRoot;
58
+ let worktreeId: string | undefined;
59
+
60
+ // 1. Resolve canonical workspace root (AUDIT-05)
61
+ if (!fs.existsSync(options.workspaceRoot)) {
62
+ throw new Error(`Workspace root does not exist: '${options.workspaceRoot}'`);
63
+ }
64
+ const canonicalWorkspace = fs.realpathSync(options.workspaceRoot);
65
+
66
+ // 2. Fix absolute spec path bug (AUDIT-11 / B2): Calculate worktree-relative spec path
67
+ let relativeSpec = options.specFilePath;
68
+ if (path.isAbsolute(options.specFilePath)) {
69
+ if (!fs.existsSync(options.specFilePath)) {
70
+ throw new Error(`Spec file not found: '${options.specFilePath}'`);
71
+ }
72
+ const canonicalSpec = fs.realpathSync(options.specFilePath);
73
+ const rel = path.relative(canonicalWorkspace, canonicalSpec);
74
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
75
+ throw new Error(
76
+ `Security Violation: Spec file '${options.specFilePath}' escapes workspace root '${canonicalWorkspace}'.`
77
+ );
78
+ }
79
+ relativeSpec = rel;
80
+ }
81
+
82
+ if (options.useWorktree !== false) {
83
+ worktreeId = `reproduce-${Date.now().toString(36)}`;
84
+ const info = await worktreeManager.createWorktree(worktreeId);
85
+ targetDir = info.worktreePath;
86
+ }
87
+
88
+ try {
89
+ const allRecords: UnifiedEvidenceRecord[] = [];
90
+ const addRedacted = (records: UnifiedEvidenceRecord[]) => {
91
+ for (const rec of records) {
92
+ allRecords.push(EvidenceRedactor.redactRecord(rec));
93
+ }
94
+ };
95
+
96
+ // 1. Environment Evidence
97
+ const envCollector = new EnvironmentCollector();
98
+ const envRecords = await envCollector.collect({
99
+ workspaceRoot: targetDir,
100
+ executionId,
101
+ });
102
+ addRedacted(envRecords);
103
+
104
+ // 2. Git Diff Evidence
105
+ const gitCollector = new GitDiffCollector();
106
+ const gitRecords = await gitCollector.collect({
107
+ workspaceRoot: targetDir,
108
+ executionId,
109
+ });
110
+ addRedacted(gitRecords);
111
+
112
+ // 3. Static AST Evidence of the failing spec strictly inside the worktree
113
+ const worktreeTargetSpec = path.resolve(targetDir, relativeSpec);
114
+ const astCollector = new StaticAstCollector();
115
+ const astRecords = await astCollector.collect({
116
+ targetFilePath: fs.existsSync(worktreeTargetSpec) ? worktreeTargetSpec : options.specFilePath,
117
+ executionId,
118
+ });
119
+ addRedacted(astRecords);
120
+
121
+ // 4. Execute Playwright test strictly targeting the worktree copy with --trace on (forward slashes for Windows regex matcher)
122
+ const playwrightArgs = ['playwright', 'test', relativeSpec.replace(/\\/g, '/'), '--trace', 'on'];
123
+ if (options.testNamePattern) {
124
+ playwrightArgs.push('-g', options.testNamePattern);
125
+ }
126
+
127
+ const runStartTime = Date.now();
128
+ const runResult = await ExecutionRunner.run('npx', {
129
+ cwd: targetDir,
130
+ args: playwrightArgs,
131
+ timeoutMs: options.timeoutMs ?? 60000,
132
+ env: {
133
+ PW_INTELLIGENCE_REPRODUCE: '1',
134
+ CI: '1',
135
+ },
136
+ });
137
+ const passed = runResult.exitCode === 0;
138
+
139
+ // 5. Ingest trace artifacts from test-results with freshness filtering (eliminates stale trace risk)
140
+ let traceCaptured = false;
141
+ const testResultsDir = path.join(targetDir, 'test-results');
142
+ if (fs.existsSync(testResultsDir)) {
143
+ const traceFiles = this.findTraceFiles(testResultsDir, runStartTime - 5000);
144
+ if (traceFiles.length > 0 && traceFiles[0]) {
145
+ const traceCollector = new TraceCollector();
146
+ const traceRecords = await traceCollector.collect({
147
+ archivePath: traceFiles[0],
148
+ executionId,
149
+ });
150
+ addRedacted(traceRecords);
151
+ traceCaptured = traceRecords.length > 0;
152
+ }
153
+ }
154
+
155
+ // 6. Collect error evidence on failure
156
+ if (!passed) {
157
+ const errorCollector = new ErrorCollector();
158
+ const errorRecords = await errorCollector.collect({
159
+ errorMessage: runResult.stderr || runResult.stdout || 'Playwright test execution failed',
160
+ errorStack: runResult.stderr,
161
+ testFile: relativeSpec,
162
+ executionId,
163
+ });
164
+ addRedacted(errorRecords);
165
+ }
166
+
167
+ // Check if trace records contain failure-point DOM snapshot (prevents Rule R-015 INCOMPLETE_EVIDENCE)
168
+ const hasDomSnapshot = allRecords.some((r) => r.evidenceType === 'dom-snapshot');
169
+
170
+ // Extract failing action selector if any from test output
171
+ let failingSelector: string | undefined;
172
+ let browserOperationResult: BrowserOperationExecutionResult | undefined;
173
+ const selectorMatch = (runResult.stdout + '\n' + runResult.stderr).match(/locator\(['"]([^'"]+)['"]\)/);
174
+ if (selectorMatch && selectorMatch[1]) {
175
+ failingSelector = selectorMatch[1];
176
+ }
177
+
178
+ // 7. Tiered Escalation: If execution failed and (trace was absent OR trace lacked DOM snapshot), trigger Mode B DirectBrowserInvestigator
179
+ if (!passed && (!traceCaptured || !hasDomSnapshot) && options.enableLiveEscalation !== false) {
180
+ try {
181
+ // Attempt to extract target URL from AST records or test stdout/stderr
182
+ let discoveredUrl: string | undefined;
183
+ const urlMatch = (runResult.stdout + '\n' + runResult.stderr).match(/https?:\/\/[^\s"'`)]+/);
184
+ if (urlMatch && urlMatch[0]) {
185
+ discoveredUrl = urlMatch[0];
186
+ } else {
187
+ // Check AST records for page.goto calls
188
+ for (const rec of astRecords) {
189
+ const strPayload = JSON.stringify(rec.payload ?? {});
190
+ const astUrlMatch = strPayload.match(/https?:\/\/[^\s"'`)]+/);
191
+ if (astUrlMatch && astUrlMatch[0]) {
192
+ discoveredUrl = astUrlMatch[0];
193
+ break;
194
+ }
195
+ }
196
+ }
197
+
198
+ if (discoveredUrl) {
199
+ const directRes = await DirectBrowserInvestigator.investigateUrl({
200
+ url: discoveredUrl,
201
+ actionSelector: failingSelector,
202
+ intendedAction: 'click',
203
+ executionId,
204
+ timeoutMs: 10000,
205
+ operation: options.operation,
206
+ operationTargetPlan: options.operationTargetPlan,
207
+ operationReplacementPlan: options.operationReplacementPlan,
208
+ operationOverlayPlan: options.operationOverlayPlan,
209
+ allowInteraction: options.allowInteraction,
210
+ });
211
+ browserOperationResult = directRes.browserOperationResult;
212
+
213
+ // AUDIT FIX: Only accept live browser evidence if action genuinely failed (REPRODUCED) or captured failure DOM
214
+ if (directRes.success && (directRes.status === 'REPRODUCED' || directRes.domSnapshotRecord)) {
215
+ if (directRes.domSnapshotRecord) {
216
+ addRedacted([directRes.domSnapshotRecord]);
217
+ }
218
+ if (directRes.ariaSnapshotRecord) {
219
+ addRedacted([directRes.ariaSnapshotRecord]);
220
+ }
221
+ if (directRes.status === 'REPRODUCED' && directRes.records.length > 0) {
222
+ addRedacted(directRes.records);
223
+ }
224
+ }
225
+ }
226
+ } catch {
227
+ // Graceful fallback if headless chromium cannot launch
228
+ }
229
+ }
230
+
231
+ // 8. Normalize multi-source evidence to DiagnosticContext
232
+ const context = EvidenceNormalizer.toDiagnosticContext(allRecords);
233
+ const scorecard = EvidenceNormalizer.calculateScorecard(allRecords);
234
+
235
+ // 9. Correlate and generate report
236
+ const correlated = EvidenceCorrelator.correlate(context);
237
+ const report = DiagnosticReportGenerator.generateTerminalReport(correlated, scorecard);
238
+
239
+ return {
240
+ passed,
241
+ exitCode: runResult.exitCode,
242
+ stdout: runResult.stdout,
243
+ stderr: runResult.stderr,
244
+ scorecard,
245
+ unifiedEvidence: allRecords,
246
+ context,
247
+ report,
248
+ executedInWorktree: options.useWorktree !== false,
249
+ targetSpecPath: worktreeTargetSpec,
250
+ browserOperationResult,
251
+ };
252
+ } finally {
253
+ if (worktreeId) {
254
+ await worktreeManager.destroyWorktree(worktreeId).catch(() => {});
255
+ }
256
+ }
257
+ }
258
+
259
+ private static findTraceFiles(dir: string, minMtimeMs?: number): string[] {
260
+ const results: Array<{ filePath: string; mtimeMs: number }> = [];
261
+ const scan = (currentDir: string) => {
262
+ try {
263
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
264
+ for (const entry of entries) {
265
+ const fullPath = path.join(currentDir, entry.name);
266
+ if (entry.isDirectory()) {
267
+ scan(fullPath);
268
+ } else if (entry.isFile() && entry.name.endsWith('trace.zip')) {
269
+ try {
270
+ const stat = fs.statSync(fullPath);
271
+ if (!minMtimeMs || stat.mtimeMs >= minMtimeMs) {
272
+ results.push({ filePath: fullPath, mtimeMs: stat.mtimeMs });
273
+ }
274
+ } catch {
275
+ results.push({ filePath: fullPath, mtimeMs: 0 });
276
+ }
277
+ }
278
+ }
279
+ } catch {
280
+ // Ignore directory read errors
281
+ }
282
+ };
283
+ scan(dir);
284
+ // Sort descending by modification time (freshest trace first)
285
+ return results.sort((a, b) => b.mtimeMs - a.mtimeMs).map((r) => r.filePath);
286
+ }
287
+ }