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,198 @@
1
+ import { normalizeFailureCategory } from '../diagnostics/failure-category.js';
2
+
3
+ export interface EvaluationObservation {
4
+ id?: string | undefined;
5
+ repository?: string | undefined;
6
+ expectedCategory?: string | undefined;
7
+ rawPredictedCategory?: string | undefined;
8
+ predictedCategory?: string | undefined;
9
+ pieNormalizedCategory?: string | undefined;
10
+ isGroundTruthVerified?: boolean | undefined;
11
+ isPrimaryMutationTrace?: boolean | undefined;
12
+ testType?: string | undefined;
13
+ shouldAbstain?: boolean | undefined;
14
+ actionOutcome?: string | undefined;
15
+ isDeterministic?: boolean | undefined;
16
+ durationMs?: number | undefined;
17
+ meanDurationMs?: number | undefined;
18
+ evaluationRole?: 'primary' | 'negative-control' | 'baseline' | undefined;
19
+ repairExpected?: boolean | undefined;
20
+ repairSucceeded?: boolean | undefined;
21
+ falseRepair?: boolean | undefined;
22
+ }
23
+
24
+ export interface CategoryMetrics {
25
+ support: number;
26
+ truePositives: number;
27
+ falsePositives: number;
28
+ falseNegatives: number;
29
+ precision: number;
30
+ recall: number;
31
+ f1Score: number;
32
+ }
33
+
34
+ export interface EvaluationScorecard {
35
+ observationCount: number;
36
+ accuracyCaseCount: number;
37
+ exactMatches: number;
38
+ semanticAccuracy: number | null;
39
+ exactAccuracy: number | null;
40
+ confusionMatrix: Record<string, Record<string, number>>;
41
+ perCategory: Record<string, CategoryMetrics>;
42
+ negativeControlCount: number;
43
+ correctSafeAbstentions: number;
44
+ unsafeRepairRecommendations: number;
45
+ safeAbstentionRate: number | null;
46
+ falseRepairRate: number | null;
47
+ deterministicCount: number;
48
+ determinismRate: number | null;
49
+ latency: {
50
+ count: number;
51
+ minMs?: number | undefined;
52
+ meanMs?: number | undefined;
53
+ medianMs?: number | undefined;
54
+ p95Ms?: number | undefined;
55
+ maxMs?: number | undefined;
56
+ };
57
+ repair: {
58
+ eligible: number;
59
+ succeeded: number;
60
+ precision: number | null;
61
+ recall: number | null;
62
+ falseRepairs: number;
63
+ };
64
+ byRepository: Record<string, {
65
+ observations: number;
66
+ accuracyCases: number;
67
+ exactMatches: number;
68
+ semanticMatches: number;
69
+ semanticAccuracy: number | null;
70
+ exactAccuracy: number | null;
71
+ safeAbstentionRate: number | null;
72
+ meanLatencyMs?: number;
73
+ }>;
74
+ }
75
+
76
+ function round(value: number): number {
77
+ return Number(value.toFixed(3));
78
+ }
79
+
80
+ function percentile(values: number[], percentileValue: number): number | undefined {
81
+ if (values.length === 0) return undefined;
82
+ const sorted = [...values].sort((left, right) => left - right);
83
+ return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * percentileValue) - 1)];
84
+ }
85
+
86
+ function roleOf(observation: EvaluationObservation): 'primary' | 'negative-control' | 'baseline' | undefined {
87
+ if (observation.evaluationRole) return observation.evaluationRole;
88
+ if (observation.isPrimaryMutationTrace || observation.testType === 'BASELINE_FAILURE') return 'primary';
89
+ if (observation.testType === 'BASELINE_PASS') return 'baseline';
90
+ const expected = normalizeFailureCategory(observation.expectedCategory);
91
+ if (['BACKEND_500', 'AUTH_EXPIRED', 'ENVIRONMENT_FAILURE', 'DISABLED_ELEMENT', 'OBSTRUCTED_ELEMENT', 'UNSUPPORTED_TRACE_FORMAT'].includes(expected)) {
92
+ return 'negative-control';
93
+ }
94
+ return undefined;
95
+ }
96
+
97
+ function categoryOf(value: string | undefined): string {
98
+ return normalizeFailureCategory(value);
99
+ }
100
+
101
+ function metricFor(category: string, expected: string[], predicted: string[]): CategoryMetrics {
102
+ const support = expected.filter((value) => value === category).length;
103
+ const truePositives = expected.filter((value, index) => value === category && predicted[index] === category).length;
104
+ const falsePositives = expected.filter((value, index) => value !== category && predicted[index] === category).length;
105
+ const falseNegatives = expected.filter((value, index) => value === category && predicted[index] !== category).length;
106
+ const precision = truePositives + falsePositives > 0 ? truePositives / (truePositives + falsePositives) : 0;
107
+ const recall = truePositives + falseNegatives > 0 ? truePositives / (truePositives + falseNegatives) : 0;
108
+ return {
109
+ support,
110
+ truePositives,
111
+ falsePositives,
112
+ falseNegatives,
113
+ precision: round(precision),
114
+ recall: round(recall),
115
+ f1Score: round(precision + recall > 0 ? (2 * precision * recall) / (precision + recall) : 0),
116
+ };
117
+ }
118
+
119
+ export function buildEvaluationScorecard(observations: EvaluationObservation[]): EvaluationScorecard {
120
+ const accuracyObservations = observations.filter((observation) => observation.isGroundTruthVerified !== false && roleOf(observation) === 'primary');
121
+ const expected = accuracyObservations.map((observation) => categoryOf(observation.expectedCategory));
122
+ const predicted = accuracyObservations.map((observation) => categoryOf(observation.predictedCategory ?? observation.pieNormalizedCategory));
123
+ const rawExpected = accuracyObservations.map((observation) => (observation.expectedCategory ?? '').trim().toUpperCase());
124
+ const rawPredicted = accuracyObservations.map((observation) => (observation.rawPredictedCategory ?? observation.predictedCategory ?? observation.pieNormalizedCategory ?? '').trim().toUpperCase());
125
+ const categories = [...new Set([...expected, ...predicted])].sort();
126
+ const confusionMatrix: Record<string, Record<string, number>> = {};
127
+ for (const expectedCategory of categories) {
128
+ confusionMatrix[expectedCategory] = {};
129
+ for (const predictedCategory of categories) confusionMatrix[expectedCategory]![predictedCategory] = 0;
130
+ }
131
+ for (let index = 0; index < expected.length; index += 1) {
132
+ const row = confusionMatrix[expected[index]!];
133
+ if (row) row[predicted[index]!] = (row[predicted[index]!] ?? 0) + 1;
134
+ }
135
+
136
+ const negativeControls = observations.filter((observation) => roleOf(observation) === 'negative-control' || roleOf(observation) === 'baseline');
137
+ const correctSafeAbstentions = negativeControls.filter((observation) => observation.shouldAbstain === true && observation.actionOutcome !== 'REPAIR').length;
138
+ const unsafeRepairRecommendations = negativeControls.filter((observation) => observation.shouldAbstain === false || observation.actionOutcome === 'REPAIR' || observation.falseRepair === true).length;
139
+ const latencies = observations.map((observation) => observation.meanDurationMs ?? observation.durationMs).filter((value): value is number => typeof value === 'number' && Number.isFinite(value));
140
+ const deterministicCount = observations.filter((observation) => observation.isDeterministic !== false).length;
141
+ const repairObservations = observations.filter((observation) => observation.repairExpected === true || observation.repairSucceeded === true || observation.falseRepair === true);
142
+ const eligibleRepairs = repairObservations.filter((observation) => observation.repairExpected === true).length;
143
+ const successfulRepairs = repairObservations.filter((observation) => observation.repairExpected === true && observation.repairSucceeded === true).length;
144
+ const falseRepairs = repairObservations.filter((observation) => observation.falseRepair === true).length;
145
+
146
+ const byRepository: EvaluationScorecard['byRepository'] = {};
147
+ for (const observation of observations) {
148
+ const repository = observation.repository ?? 'unknown';
149
+ const current = byRepository[repository] ?? { observations: 0, accuracyCases: 0, exactMatches: 0, semanticMatches: 0, semanticAccuracy: null, exactAccuracy: null, safeAbstentionRate: null };
150
+ current.observations += 1;
151
+ if (roleOf(observation) === 'primary' && observation.isGroundTruthVerified !== false) {
152
+ current.accuracyCases += 1;
153
+ if ((observation.expectedCategory ?? '').trim().toUpperCase() === (observation.rawPredictedCategory ?? observation.predictedCategory ?? observation.pieNormalizedCategory ?? '').trim().toUpperCase()) current.exactMatches += 1;
154
+ if (categoryOf(observation.expectedCategory) === categoryOf(observation.predictedCategory ?? observation.pieNormalizedCategory)) current.semanticMatches += 1;
155
+ }
156
+ const latency = observation.meanDurationMs ?? observation.durationMs;
157
+ if (typeof latency === 'number') current.meanLatencyMs = ((current.meanLatencyMs ?? 0) * (current.observations - 1) + latency) / current.observations;
158
+ byRepository[repository] = current;
159
+ }
160
+ for (const summary of Object.values(byRepository)) {
161
+ summary.semanticAccuracy = summary.accuracyCases > 0 ? round(summary.semanticMatches / summary.accuracyCases) : null;
162
+ summary.exactAccuracy = summary.accuracyCases > 0 ? round(summary.exactMatches / summary.accuracyCases) : null;
163
+ if (summary.meanLatencyMs !== undefined) summary.meanLatencyMs = round(summary.meanLatencyMs);
164
+ }
165
+
166
+ return {
167
+ observationCount: observations.length,
168
+ accuracyCaseCount: accuracyObservations.length,
169
+ exactMatches: rawExpected.filter((value, index) => value === rawPredicted[index]).length,
170
+ semanticAccuracy: expected.length > 0 ? round(expected.filter((value, index) => value === predicted[index]).length / expected.length) : null,
171
+ exactAccuracy: rawExpected.length > 0 ? round(rawExpected.filter((value, index) => value === rawPredicted[index]).length / rawExpected.length) : null,
172
+ confusionMatrix,
173
+ perCategory: Object.fromEntries(categories.map((category) => [category, metricFor(category, expected, predicted)])),
174
+ negativeControlCount: negativeControls.length,
175
+ correctSafeAbstentions,
176
+ unsafeRepairRecommendations,
177
+ safeAbstentionRate: negativeControls.length > 0 ? round(correctSafeAbstentions / negativeControls.length) : null,
178
+ falseRepairRate: negativeControls.length > 0 ? round(unsafeRepairRecommendations / negativeControls.length) : null,
179
+ deterministicCount,
180
+ determinismRate: observations.length > 0 ? round(deterministicCount / observations.length) : null,
181
+ latency: {
182
+ count: latencies.length,
183
+ minMs: latencies.length > 0 ? Math.min(...latencies) : undefined,
184
+ meanMs: latencies.length > 0 ? round(latencies.reduce((sum, value) => sum + value, 0) / latencies.length) : undefined,
185
+ medianMs: percentile(latencies, 0.5),
186
+ p95Ms: percentile(latencies, 0.95),
187
+ maxMs: latencies.length > 0 ? Math.max(...latencies) : undefined,
188
+ },
189
+ repair: {
190
+ eligible: eligibleRepairs,
191
+ succeeded: successfulRepairs,
192
+ precision: successfulRepairs + falseRepairs > 0 ? round(successfulRepairs / (successfulRepairs + falseRepairs)) : null,
193
+ recall: eligibleRepairs > 0 ? round(successfulRepairs / eligibleRepairs) : null,
194
+ falseRepairs,
195
+ },
196
+ byRepository,
197
+ };
198
+ }
@@ -0,0 +1,143 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ export interface RepositoryManifest {
5
+ name: string;
6
+ root: string;
7
+ packageManager: 'npm' | 'pnpm' | 'yarn' | 'unknown';
8
+ license: string;
9
+ nodeVersion?: string | undefined;
10
+ playwrightVersion?: string | undefined;
11
+ hasPlaywrightConfig: boolean;
12
+ playwrightConfigs: string[];
13
+ testFiles: number;
14
+ pageObjectFiles: number;
15
+ fixtureFiles: number;
16
+ hasDocker: boolean;
17
+ hasLocalWebServerHint: boolean;
18
+ scripts: Record<string, string>;
19
+ status: 'DISCOVERED' | 'NOT_A_PLAYWRIGHT_REPOSITORY' | 'UNREADABLE';
20
+ limitations: string[];
21
+ }
22
+
23
+ async function exists(filePath: string): Promise<boolean> {
24
+ try {
25
+ await fs.access(filePath);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ async function listFiles(root: string, depth = 0): Promise<string[]> {
33
+ if (depth > 8) return [];
34
+ const results: string[] = [];
35
+ let entries;
36
+ try {
37
+ entries = await fs.readdir(root, { withFileTypes: true });
38
+ } catch {
39
+ return results;
40
+ }
41
+ for (const entry of entries) {
42
+ if (['node_modules', '.git', 'dist', 'build', 'coverage', 'playwright-report', 'test-results'].includes(entry.name)) continue;
43
+ const fullPath = path.join(root, entry.name);
44
+ if (entry.isDirectory()) results.push(...await listFiles(fullPath, depth + 1));
45
+ else results.push(fullPath);
46
+ }
47
+ return results;
48
+ }
49
+
50
+ export async function inspectRepository(root: string): Promise<RepositoryManifest> {
51
+ const absoluteRoot = path.resolve(root);
52
+ try {
53
+ const files = await listFiles(absoluteRoot);
54
+ const packageCandidates = [
55
+ path.join(absoluteRoot, 'package.json'),
56
+ ...files.filter((file) => path.basename(file) === 'package.json' && file !== path.join(absoluteRoot, 'package.json')),
57
+ ];
58
+ let packagePath: string | undefined;
59
+ let packageJson: {
60
+ license?: string;
61
+ engines?: { node?: string };
62
+ scripts?: Record<string, string>;
63
+ dependencies?: Record<string, string>;
64
+ devDependencies?: Record<string, string>;
65
+ } | undefined;
66
+ for (const candidate of packageCandidates) {
67
+ try {
68
+ const parsed = JSON.parse(await fs.readFile(candidate, 'utf8')) as typeof packageJson;
69
+ const dependencies = { ...parsed?.dependencies, ...parsed?.devDependencies };
70
+ if (!packageJson || Object.keys(dependencies).some((name) => name === '@playwright/test' || name === 'playwright')) {
71
+ packagePath = candidate;
72
+ packageJson = parsed;
73
+ }
74
+ if (Object.keys(dependencies).some((name) => name === '@playwright/test' || name === 'playwright')) break;
75
+ } catch {
76
+ // Continue searching nested packages in monorepos.
77
+ }
78
+ }
79
+ if (!packageJson || !packagePath) throw new Error('No readable package.json found');
80
+
81
+ const relative = files.map((file) => path.relative(absoluteRoot, file));
82
+ const configs = relative.filter((file) => /(^|[\\/])playwright(?:\.[^.]*)?\.config\.(?:ts|js|mjs|cjs)$/i.test(file));
83
+ const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
84
+ const hasPlaywright = Object.keys(allDependencies).some((name) => name === '@playwright/test' || name === 'playwright');
85
+ const packageRoot = path.dirname(packagePath);
86
+ const packageManager = await exists(path.join(packageRoot, 'pnpm-lock.yaml'))
87
+ ? 'pnpm'
88
+ : await exists(path.join(packageRoot, 'yarn.lock'))
89
+ ? 'yarn'
90
+ : await exists(path.join(packageRoot, 'package-lock.json'))
91
+ ? 'npm'
92
+ : 'unknown';
93
+
94
+ const limitations = packageRoot !== absoluteRoot ? [`Playwright package metadata discovered in nested project: ${path.relative(absoluteRoot, packageRoot)}`] : [];
95
+
96
+ return {
97
+ name: path.basename(absoluteRoot),
98
+ root: absoluteRoot,
99
+ packageManager,
100
+ license: packageJson.license ?? 'UNKNOWN',
101
+ nodeVersion: packageJson.engines?.node,
102
+ playwrightVersion: allDependencies['@playwright/test'] ?? allDependencies.playwright,
103
+ hasPlaywrightConfig: configs.length > 0,
104
+ playwrightConfigs: configs,
105
+ testFiles: relative.filter((file) => /(?:^|[\\/])(?:tests?|e2e)(?:[\\/]).*\.(?:spec|test)\.(?:ts|tsx|js|jsx|mjs|cjs)$/i.test(file)).length,
106
+ pageObjectFiles: relative.filter((file) => /(?:page|pages|pom|page-objects?)[\\/].*\.(?:ts|tsx|js|jsx)$/i.test(file)).length,
107
+ fixtureFiles: relative.filter((file) => /fixture/i.test(file) && /\.(?:ts|tsx|js|jsx)$/.test(file)).length,
108
+ hasDocker: relative.some((file) => /(?:^|[\\/])(?:docker-compose[^\\/]*|Dockerfile(?:\..*)?)$/i.test(file)),
109
+ hasLocalWebServerHint: Object.values(packageJson.scripts ?? {}).some((script) => /(?:next|vite|dev|start|serve)/i.test(script)),
110
+ scripts: packageJson.scripts ?? {},
111
+ status: hasPlaywright || configs.length > 0 ? 'DISCOVERED' : 'NOT_A_PLAYWRIGHT_REPOSITORY',
112
+ limitations,
113
+ };
114
+ } catch (error) {
115
+ return {
116
+ name: path.basename(absoluteRoot),
117
+ root: absoluteRoot,
118
+ packageManager: 'unknown',
119
+ license: 'UNKNOWN',
120
+ hasPlaywrightConfig: false,
121
+ playwrightConfigs: [],
122
+ testFiles: 0,
123
+ pageObjectFiles: 0,
124
+ fixtureFiles: 0,
125
+ hasDocker: false,
126
+ hasLocalWebServerHint: false,
127
+ scripts: {},
128
+ status: 'UNREADABLE',
129
+ limitations: [`Unable to inspect repository: ${error instanceof Error ? error.message : String(error)}`],
130
+ };
131
+ }
132
+ }
133
+
134
+ export async function inspectTestbed(testbedRoot: string): Promise<RepositoryManifest[]> {
135
+ const repositoriesRoot = path.join(path.resolve(testbedRoot), 'repositories');
136
+ const entries = await fs.readdir(repositoriesRoot, { withFileTypes: true });
137
+ const manifests: RepositoryManifest[] = [];
138
+ for (const entry of entries.filter((candidate) => candidate.isDirectory())) {
139
+ manifests.push(await inspectRepository(path.join(repositoriesRoot, entry.name)));
140
+ }
141
+ return manifests.sort((left, right) => left.name.localeCompare(right.name));
142
+ }
143
+
@@ -0,0 +1,197 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { RepositoryManifest } from './repository-inspector.js';
4
+
5
+ export interface RepositoryRunOptions {
6
+ script?: string | undefined;
7
+ timeoutMs?: number | undefined;
8
+ environment?: Record<string, string> | undefined;
9
+ }
10
+
11
+ export interface RepositoryRunResult {
12
+ repository: string;
13
+ command: string;
14
+ args: string[];
15
+ cwd: string;
16
+ status: 'PASS' | 'FAIL' | 'TIMEOUT' | 'BLOCKED';
17
+ exitCode: number | null;
18
+ signal?: string | undefined;
19
+ stdout: string;
20
+ stderr: string;
21
+ durationMs: number;
22
+ reason?: string | undefined;
23
+ }
24
+
25
+ const MAX_OUTPUT_BYTES = 16 * 1024 * 1024;
26
+ const SAFE_COMMANDS = new Set(['npm', 'pnpm', 'yarn', 'npx']);
27
+
28
+ function executableFor(manager: RepositoryManifest['packageManager']): string {
29
+ if (manager === 'pnpm') return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
30
+ if (manager === 'yarn') return process.platform === 'win32' ? 'yarn.cmd' : 'yarn';
31
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
32
+ }
33
+
34
+ function chooseScript(manifest: RepositoryManifest, requested?: string): string | undefined {
35
+ if (requested) return requested;
36
+ const candidates = ['test:e2e', 'test:smoke', 'test:sanity', 'test'];
37
+ return candidates.find((candidate) => manifest.scripts[candidate]);
38
+ }
39
+
40
+ export class RepositoryRunner {
41
+ public static async runBaseline(manifest: RepositoryManifest, options: RepositoryRunOptions = {}): Promise<RepositoryRunResult> {
42
+ if (manifest.status !== 'DISCOVERED') {
43
+ return {
44
+ repository: manifest.name,
45
+ command: '',
46
+ args: [],
47
+ cwd: manifest.root,
48
+ status: 'BLOCKED',
49
+ exitCode: null,
50
+ stdout: '',
51
+ stderr: '',
52
+ durationMs: 0,
53
+ reason: `Repository status is ${manifest.status}`,
54
+ };
55
+ }
56
+
57
+ const script = chooseScript(manifest, options.script);
58
+ const executable = executableFor(manifest.packageManager);
59
+ const args = script ? ['run', script] : ['exec', 'playwright', 'test'];
60
+ return this.runCommand(manifest, executable, args, options);
61
+ }
62
+
63
+ public static runCommand(
64
+ manifest: RepositoryManifest,
65
+ executable: string,
66
+ args: string[],
67
+ options: RepositoryRunOptions = {},
68
+ ): Promise<RepositoryRunResult> {
69
+ const normalizedExecutable = path.basename(executable).replace(/\.cmd$/i, '').toLowerCase();
70
+ if (!SAFE_COMMANDS.has(normalizedExecutable)) {
71
+ return Promise.resolve({
72
+ repository: manifest.name,
73
+ command: executable,
74
+ args,
75
+ cwd: manifest.root,
76
+ status: 'BLOCKED',
77
+ exitCode: null,
78
+ stdout: '',
79
+ stderr: '',
80
+ durationMs: 0,
81
+ reason: `Command '${executable}' is not in the repository evaluation allowlist`,
82
+ });
83
+ }
84
+
85
+ if (args.some((argument) => /[&|<>]/.test(argument))) {
86
+ return Promise.resolve({
87
+ repository: manifest.name,
88
+ command: executable,
89
+ args,
90
+ cwd: manifest.root,
91
+ status: 'BLOCKED',
92
+ exitCode: null,
93
+ stdout: '',
94
+ stderr: '',
95
+ durationMs: 0,
96
+ reason: 'Command arguments contain shell metacharacters',
97
+ });
98
+ }
99
+
100
+ const isWindows = process.platform === 'win32';
101
+ const command = isWindows ? (process.env.ComSpec ?? 'cmd.exe') : executable;
102
+ const spawnArgs = isWindows
103
+ ? ['/d', '/s', '/c', [executable, ...args].map((argument) => argument.includes(' ') ? `"${argument.replace(/"/g, '\\"')}"` : argument).join(' ')]
104
+ : args;
105
+
106
+ return new Promise((resolve) => {
107
+ const started = Date.now();
108
+ const child = spawn(command, spawnArgs, {
109
+ cwd: manifest.root,
110
+ shell: false,
111
+ windowsHide: true,
112
+ env: { ...process.env, CI: 'true', ...options.environment },
113
+ });
114
+ let stdout = '';
115
+ let stderr = '';
116
+ let truncated = false;
117
+ let timedOut = false;
118
+ let settled = false;
119
+ let timeout: ReturnType<typeof setTimeout> | undefined;
120
+ const finish = (result: RepositoryRunResult): void => {
121
+ if (settled) return;
122
+ settled = true;
123
+ if (timeout) clearTimeout(timeout);
124
+ resolve(result);
125
+ };
126
+ const append = (target: 'stdout' | 'stderr', chunk: Buffer): void => {
127
+ if (Buffer.byteLength(stdout) + Buffer.byteLength(stderr) >= MAX_OUTPUT_BYTES) {
128
+ truncated = true;
129
+ return;
130
+ }
131
+ if (target === 'stdout') stdout += chunk.toString('utf8');
132
+ else stderr += chunk.toString('utf8');
133
+ };
134
+ child.stdout?.on('data', (chunk: Buffer) => append('stdout', chunk));
135
+ child.stderr?.on('data', (chunk: Buffer) => append('stderr', chunk));
136
+
137
+ timeout = setTimeout(() => {
138
+ timedOut = true;
139
+ child.kill('SIGTERM');
140
+ if (process.platform === 'win32' && child.pid) {
141
+ spawnSync('taskkill.exe', ['/PID', String(child.pid), '/T', '/F'], {
142
+ windowsHide: true,
143
+ stdio: 'ignore',
144
+ shell: false,
145
+ timeout: 5_000,
146
+ });
147
+ child.stdout?.destroy();
148
+ child.stderr?.destroy();
149
+ }
150
+ finish({
151
+ repository: manifest.name,
152
+ command: executable,
153
+ args,
154
+ cwd: manifest.root,
155
+ status: 'TIMEOUT',
156
+ exitCode: null,
157
+ stdout,
158
+ stderr,
159
+ durationMs: Date.now() - started,
160
+ reason: 'Command exceeded the configured timeout',
161
+ });
162
+ }, options.timeoutMs ?? 15 * 60 * 1000);
163
+
164
+ child.on('error', (error) => {
165
+ finish({
166
+ repository: manifest.name,
167
+ command: executable,
168
+ args,
169
+ cwd: manifest.root,
170
+ status: 'BLOCKED',
171
+ exitCode: null,
172
+ stdout,
173
+ stderr,
174
+ durationMs: Date.now() - started,
175
+ reason: `Unable to start command: ${error.message}`,
176
+ });
177
+ });
178
+
179
+ child.on('close', (exitCode, signal) => {
180
+ finish({
181
+ repository: manifest.name,
182
+ command: executable,
183
+ args,
184
+ cwd: manifest.root,
185
+ status: timedOut ? 'TIMEOUT' : exitCode === 0 ? 'PASS' : 'FAIL',
186
+ exitCode,
187
+ signal: signal ?? undefined,
188
+ stdout,
189
+ stderr,
190
+ durationMs: Date.now() - started,
191
+ reason: truncated ? 'Output truncated at 16 MB' : undefined,
192
+ });
193
+ });
194
+ });
195
+ }
196
+ }
197
+
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+
3
+ export const EvidenceSourceSchema = z.enum([
4
+ 'trace',
5
+ 'live-browser',
6
+ 'console',
7
+ 'network',
8
+ 'static-ast',
9
+ 'git-diff',
10
+ 'environment',
11
+ 'error',
12
+ ]);
13
+
14
+ export type EvidenceSource = z.infer<typeof EvidenceSourceSchema>;
15
+
16
+ export const ReliabilityLevelSchema = z.enum([
17
+ 'HIGH',
18
+ 'MEDIUM',
19
+ 'LOW',
20
+ 'UNVERIFIED',
21
+ ]);
22
+
23
+ export type ReliabilityLevel = z.infer<typeof ReliabilityLevelSchema>;
24
+
25
+ export const ProvenanceMetadataSchema = z.object({
26
+ collector: z.string(),
27
+ collectedAt: z.number(),
28
+ captureMethod: z.string(),
29
+ sourceUri: z.string().optional(),
30
+ });
31
+
32
+ export type ProvenanceMetadata = z.infer<typeof ProvenanceMetadataSchema>;
33
+
34
+ export const RedactionStatusSchema = z.object({
35
+ isRedacted: z.boolean(),
36
+ redactedFields: z.array(z.string()).optional(),
37
+ redactionRulesApplied: z.array(z.string()).optional(),
38
+ });
39
+
40
+ export type RedactionStatus = z.infer<typeof RedactionStatusSchema>;
41
+
42
+ export const UnifiedEvidenceRecordSchema = z.object({
43
+ evidenceId: z.string(),
44
+ executionId: z.string(),
45
+ source: EvidenceSourceSchema,
46
+ timestamp: z.number(),
47
+ monotonicTimestamp: z.number().optional(),
48
+ workerId: z.string().optional(),
49
+ testId: z.string().optional(),
50
+ frameId: z.string().optional(),
51
+ actionId: z.string().optional(),
52
+ requestId: z.string().optional(),
53
+ evidenceType: z.string(), // e.g. 'dom-snapshot', 'console-error', 'http-response', 'ast-locator', 'git-commit'
54
+ payload: z.record(z.string(), z.any()),
55
+ reliability: ReliabilityLevelSchema,
56
+ provenance: ProvenanceMetadataSchema,
57
+ redactionStatus: RedactionStatusSchema,
58
+ limitations: z.array(z.string()).optional(),
59
+ });
60
+
61
+ export type UnifiedEvidenceRecord = z.infer<typeof UnifiedEvidenceRecordSchema>;
62
+
63
+ export interface EvidenceScorecard {
64
+ playwrightErrorAvailable: boolean;
65
+ testSourceAstAvailable: boolean;
66
+ domSnapshotAvailable: boolean;
67
+ consoleEventsAvailable: boolean;
68
+ networkEventsAvailable: boolean;
69
+ traceArchiveAvailable: boolean;
70
+ gitHistoryAvailable: boolean;
71
+ environmentMetadataAvailable: boolean;
72
+ totalRecordsCount: number;
73
+ availableSources: EvidenceSource[];
74
+ unavailableSources: EvidenceSource[];
75
+ }
76
+