@cleocode/core 2026.10.27 → 2026.11.1-canary.3

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 (520) hide show
  1. package/README.md +2 -0
  2. package/binaries/README.md +30 -10
  3. package/dist/.source-stamp.json +4 -0
  4. package/dist/audit/reconstruct.d.ts.map +1 -1
  5. package/dist/audit/reconstruct.js +3 -13
  6. package/dist/audit/reconstruct.js.map +1 -1
  7. package/dist/build-provenance.json +2 -2
  8. package/dist/cloud/nexus-cloud.d.ts.map +1 -1
  9. package/dist/cloud/nexus-cloud.js +3 -3
  10. package/dist/cloud/nexus-cloud.js.map +1 -1
  11. package/dist/cloud/nexus-device.d.ts +1 -1
  12. package/dist/cloud/nexus-link.d.ts +18 -2
  13. package/dist/cloud/nexus-link.d.ts.map +1 -1
  14. package/dist/cloud/nexus-link.js +26 -6
  15. package/dist/cloud/nexus-link.js.map +1 -1
  16. package/dist/cloud/nexus-vault.d.ts.map +1 -1
  17. package/dist/cloud/nexus-vault.js +3 -9
  18. package/dist/cloud/nexus-vault.js.map +1 -1
  19. package/dist/decide/sites/registry.d.ts +40 -0
  20. package/dist/decide/sites/registry.d.ts.map +1 -1
  21. package/dist/decide/sites/registry.js +38 -0
  22. package/dist/decide/sites/registry.js.map +1 -1
  23. package/dist/docs/adr-allocator.d.ts +3 -3
  24. package/dist/docs/adr-allocator.d.ts.map +1 -1
  25. package/dist/docs/adr-allocator.js +8 -5
  26. package/dist/docs/adr-allocator.js.map +1 -1
  27. package/dist/docs/blob-consolidation.d.ts.map +1 -1
  28. package/dist/docs/blob-consolidation.js +3 -2
  29. package/dist/docs/blob-consolidation.js.map +1 -1
  30. package/dist/docs/derive-links.d.ts +12 -0
  31. package/dist/docs/derive-links.d.ts.map +1 -1
  32. package/dist/docs/derive-links.js +19 -0
  33. package/dist/docs/derive-links.js.map +1 -1
  34. package/dist/docs/doc-name-rule.d.ts +105 -0
  35. package/dist/docs/doc-name-rule.d.ts.map +1 -0
  36. package/dist/docs/doc-name-rule.js +212 -0
  37. package/dist/docs/doc-name-rule.js.map +1 -0
  38. package/dist/docs/docs-inconsistency-detector.d.ts.map +1 -1
  39. package/dist/docs/docs-inconsistency-detector.js +4 -3
  40. package/dist/docs/docs-inconsistency-detector.js.map +1 -1
  41. package/dist/docs/docs-ops.d.ts +27 -5
  42. package/dist/docs/docs-ops.d.ts.map +1 -1
  43. package/dist/docs/docs-ops.js +149 -17
  44. package/dist/docs/docs-ops.js.map +1 -1
  45. package/dist/docs/docs-read-model.d.ts +45 -15
  46. package/dist/docs/docs-read-model.d.ts.map +1 -1
  47. package/dist/docs/docs-read-model.js +22 -102
  48. package/dist/docs/docs-read-model.js.map +1 -1
  49. package/dist/docs/docs-update.d.ts.map +1 -1
  50. package/dist/docs/docs-update.js +8 -2
  51. package/dist/docs/docs-update.js.map +1 -1
  52. package/dist/docs/doctor.d.ts +71 -4
  53. package/dist/docs/doctor.d.ts.map +1 -1
  54. package/dist/docs/doctor.js +324 -23
  55. package/dist/docs/doctor.js.map +1 -1
  56. package/dist/docs/duplicate-check.d.ts +138 -0
  57. package/dist/docs/duplicate-check.d.ts.map +1 -0
  58. package/dist/docs/duplicate-check.js +286 -0
  59. package/dist/docs/duplicate-check.js.map +1 -0
  60. package/dist/docs/fetch-result.d.ts.map +1 -1
  61. package/dist/docs/fetch-result.js +27 -0
  62. package/dist/docs/fetch-result.js.map +1 -1
  63. package/dist/docs/import/scanner.d.ts +2 -0
  64. package/dist/docs/import/scanner.d.ts.map +1 -1
  65. package/dist/docs/import/scanner.js +3 -1
  66. package/dist/docs/import/scanner.js.map +1 -1
  67. package/dist/docs/lineage.d.ts +66 -0
  68. package/dist/docs/lineage.d.ts.map +1 -0
  69. package/dist/docs/lineage.js +190 -0
  70. package/dist/docs/lineage.js.map +1 -0
  71. package/dist/docs/modernize/apply.d.ts +139 -0
  72. package/dist/docs/modernize/apply.d.ts.map +1 -0
  73. package/dist/docs/modernize/apply.js +760 -0
  74. package/dist/docs/modernize/apply.js.map +1 -0
  75. package/dist/docs/modernize/guard.d.ts +92 -0
  76. package/dist/docs/modernize/guard.d.ts.map +1 -0
  77. package/dist/docs/modernize/guard.js +214 -0
  78. package/dist/docs/modernize/guard.js.map +1 -0
  79. package/dist/docs/modernize/index.d.ts +68 -0
  80. package/dist/docs/modernize/index.d.ts.map +1 -0
  81. package/dist/docs/modernize/index.js +97 -0
  82. package/dist/docs/modernize/index.js.map +1 -0
  83. package/dist/docs/modernize/judge.d.ts +85 -0
  84. package/dist/docs/modernize/judge.d.ts.map +1 -0
  85. package/dist/docs/modernize/judge.js +186 -0
  86. package/dist/docs/modernize/judge.js.map +1 -0
  87. package/dist/docs/modernize/plan.d.ts +77 -0
  88. package/dist/docs/modernize/plan.d.ts.map +1 -0
  89. package/dist/docs/modernize/plan.js +510 -0
  90. package/dist/docs/modernize/plan.js.map +1 -0
  91. package/dist/docs/modernize/restore.d.ts +41 -0
  92. package/dist/docs/modernize/restore.d.ts.map +1 -0
  93. package/dist/docs/modernize/restore.js +103 -0
  94. package/dist/docs/modernize/restore.js.map +1 -0
  95. package/dist/docs/modernize/rules.d.ts +197 -0
  96. package/dist/docs/modernize/rules.d.ts.map +1 -0
  97. package/dist/docs/modernize/rules.js +510 -0
  98. package/dist/docs/modernize/rules.js.map +1 -0
  99. package/dist/docs/modernize/store.d.ts +64 -0
  100. package/dist/docs/modernize/store.d.ts.map +1 -0
  101. package/dist/docs/modernize/store.js +390 -0
  102. package/dist/docs/modernize/store.js.map +1 -0
  103. package/dist/docs/publications-store.d.ts +16 -2
  104. package/dist/docs/publications-store.d.ts.map +1 -1
  105. package/dist/docs/publications-store.js +23 -2
  106. package/dist/docs/publications-store.js.map +1 -1
  107. package/dist/docs/publish-pr.d.ts.map +1 -1
  108. package/dist/docs/publish-pr.js +12 -1
  109. package/dist/docs/publish-pr.js.map +1 -1
  110. package/dist/docs/rot.d.ts +94 -0
  111. package/dist/docs/rot.d.ts.map +1 -0
  112. package/dist/docs/rot.js +298 -0
  113. package/dist/docs/rot.js.map +1 -0
  114. package/dist/docs/similarity-check.d.ts +2 -0
  115. package/dist/docs/similarity-check.d.ts.map +1 -1
  116. package/dist/docs/similarity-check.js +6 -49
  117. package/dist/docs/similarity-check.js.map +1 -1
  118. package/dist/docs/slug-rename.d.ts +84 -0
  119. package/dist/docs/slug-rename.d.ts.map +1 -0
  120. package/dist/docs/slug-rename.js +166 -0
  121. package/dist/docs/slug-rename.js.map +1 -0
  122. package/dist/docs/slug-similarity.d.ts +24 -0
  123. package/dist/docs/slug-similarity.d.ts.map +1 -0
  124. package/dist/docs/slug-similarity.js +63 -0
  125. package/dist/docs/slug-similarity.js.map +1 -0
  126. package/dist/docs/wikilinks.d.ts +20 -4
  127. package/dist/docs/wikilinks.d.ts.map +1 -1
  128. package/dist/docs/wikilinks.js +52 -7
  129. package/dist/docs/wikilinks.js.map +1 -1
  130. package/dist/docs/writer-registry.d.ts.map +1 -1
  131. package/dist/docs/writer-registry.js +33 -0
  132. package/dist/docs/writer-registry.js.map +1 -1
  133. package/dist/doctor/type-matrix-repair.d.ts +194 -0
  134. package/dist/doctor/type-matrix-repair.d.ts.map +1 -0
  135. package/dist/doctor/type-matrix-repair.js +662 -0
  136. package/dist/doctor/type-matrix-repair.js.map +1 -0
  137. package/dist/gateway-client/generated/index.d.ts +2 -2
  138. package/dist/gateway-client/generated/index.d.ts.map +1 -1
  139. package/dist/gateway-client/generated/index.js +1 -1
  140. package/dist/gateway-client/generated/index.js.map +1 -1
  141. package/dist/gateway-client/generated/namespaces.gen.d.ts +5 -0
  142. package/dist/gateway-client/generated/namespaces.gen.d.ts.map +1 -1
  143. package/dist/gateway-client/generated/namespaces.gen.js +6 -1
  144. package/dist/gateway-client/generated/namespaces.gen.js.map +1 -1
  145. package/dist/gateway-client/generated/sdk.gen.d.ts +21 -1
  146. package/dist/gateway-client/generated/sdk.gen.d.ts.map +1 -1
  147. package/dist/gateway-client/generated/sdk.gen.js +55 -0
  148. package/dist/gateway-client/generated/sdk.gen.js.map +1 -1
  149. package/dist/gateway-client/generated/types.gen.d.ts +222 -0
  150. package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
  151. package/dist/gc/gc-subsystem.d.ts +4 -0
  152. package/dist/gc/gc-subsystem.d.ts.map +1 -1
  153. package/dist/gc/gc-subsystem.js +16 -0
  154. package/dist/gc/gc-subsystem.js.map +1 -1
  155. package/dist/git/work-tree.d.ts +48 -0
  156. package/dist/git/work-tree.d.ts.map +1 -1
  157. package/dist/git/work-tree.js +63 -1
  158. package/dist/git/work-tree.js.map +1 -1
  159. package/dist/hooks/handlers/session-hooks.d.ts +5 -4
  160. package/dist/hooks/handlers/session-hooks.d.ts.map +1 -1
  161. package/dist/hooks/handlers/session-hooks.js +5 -21
  162. package/dist/hooks/handlers/session-hooks.js.map +1 -1
  163. package/dist/internal.d.ts +5 -1
  164. package/dist/internal.d.ts.map +1 -1
  165. package/dist/internal.js +5 -1
  166. package/dist/internal.js.map +1 -1
  167. package/dist/memory/brain-doctor.d.ts.map +1 -1
  168. package/dist/memory/brain-doctor.js +6 -1
  169. package/dist/memory/brain-doctor.js.map +1 -1
  170. package/dist/memory/engine-compat.d.ts.map +1 -1
  171. package/dist/memory/engine-compat.js +6 -0
  172. package/dist/memory/engine-compat.js.map +1 -1
  173. package/dist/nexus/analyze-orchestrator.d.ts +24 -0
  174. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  175. package/dist/nexus/analyze-orchestrator.js +43 -5
  176. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  177. package/dist/nexus/user-profile.d.ts +2 -1
  178. package/dist/nexus/user-profile.d.ts.map +1 -1
  179. package/dist/nexus/user-profile.js +13 -5
  180. package/dist/nexus/user-profile.js.map +1 -1
  181. package/dist/orchestrate/admission.d.ts +8 -0
  182. package/dist/orchestrate/admission.d.ts.map +1 -1
  183. package/dist/orchestrate/admission.js +3 -1
  184. package/dist/orchestrate/admission.js.map +1 -1
  185. package/dist/orchestrate/query-ops.d.ts.map +1 -1
  186. package/dist/orchestrate/query-ops.js +15 -4
  187. package/dist/orchestrate/query-ops.js.map +1 -1
  188. package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
  189. package/dist/orchestrate/spawn-ops.js +43 -2
  190. package/dist/orchestrate/spawn-ops.js.map +1 -1
  191. package/dist/orchestration/index.d.ts +6 -0
  192. package/dist/orchestration/index.d.ts.map +1 -1
  193. package/dist/orchestration/index.js +3 -0
  194. package/dist/orchestration/index.js.map +1 -1
  195. package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
  196. package/dist/orchestration/spawn-prompt.js +3 -1
  197. package/dist/orchestration/spawn-prompt.js.map +1 -1
  198. package/dist/paths.d.ts +43 -1
  199. package/dist/paths.d.ts.map +1 -1
  200. package/dist/paths.js +51 -1
  201. package/dist/paths.js.map +1 -1
  202. package/dist/project-lifecycle.d.ts.map +1 -1
  203. package/dist/project-lifecycle.js +31 -0
  204. package/dist/project-lifecycle.js.map +1 -1
  205. package/dist/release/authorization.d.ts +156 -0
  206. package/dist/release/authorization.d.ts.map +1 -0
  207. package/dist/release/authorization.js +460 -0
  208. package/dist/release/authorization.js.map +1 -0
  209. package/dist/release/ci-evidence.d.ts +55 -4
  210. package/dist/release/ci-evidence.d.ts.map +1 -1
  211. package/dist/release/ci-evidence.js +251 -22
  212. package/dist/release/ci-evidence.js.map +1 -1
  213. package/dist/release/ci-run-evidence.d.ts +107 -0
  214. package/dist/release/ci-run-evidence.d.ts.map +1 -0
  215. package/dist/release/ci-run-evidence.js +234 -0
  216. package/dist/release/ci-run-evidence.js.map +1 -0
  217. package/dist/release/cut.d.ts +231 -0
  218. package/dist/release/cut.d.ts.map +1 -0
  219. package/dist/release/cut.js +775 -0
  220. package/dist/release/cut.js.map +1 -0
  221. package/dist/release/deploy-evidence.d.ts +173 -0
  222. package/dist/release/deploy-evidence.d.ts.map +1 -0
  223. package/dist/release/deploy-evidence.js +381 -0
  224. package/dist/release/deploy-evidence.js.map +1 -0
  225. package/dist/release/engine-ops.d.ts.map +1 -1
  226. package/dist/release/engine-ops.js +10 -4
  227. package/dist/release/engine-ops.js.map +1 -1
  228. package/dist/release/index.d.ts +3 -0
  229. package/dist/release/index.d.ts.map +1 -1
  230. package/dist/release/index.js +2 -0
  231. package/dist/release/index.js.map +1 -1
  232. package/dist/release/invariants/archive-reason-invariant.d.ts.map +1 -1
  233. package/dist/release/invariants/archive-reason-invariant.js +4 -1
  234. package/dist/release/invariants/archive-reason-invariant.js.map +1 -1
  235. package/dist/release/open.d.ts.map +1 -1
  236. package/dist/release/open.js +2 -6
  237. package/dist/release/open.js.map +1 -1
  238. package/dist/release/plan.d.ts +34 -2
  239. package/dist/release/plan.d.ts.map +1 -1
  240. package/dist/release/plan.js +30 -6
  241. package/dist/release/plan.js.map +1 -1
  242. package/dist/release/preflight-skip.d.ts +4 -19
  243. package/dist/release/preflight-skip.d.ts.map +1 -1
  244. package/dist/release/preflight-skip.js +5 -110
  245. package/dist/release/preflight-skip.js.map +1 -1
  246. package/dist/release/reconcile-latest.d.ts +29 -0
  247. package/dist/release/reconcile-latest.d.ts.map +1 -0
  248. package/dist/release/reconcile-latest.js +71 -0
  249. package/dist/release/reconcile-latest.js.map +1 -0
  250. package/dist/release/reconcile.d.ts +3 -1
  251. package/dist/release/reconcile.d.ts.map +1 -1
  252. package/dist/release/reconcile.js +18 -6
  253. package/dist/release/reconcile.js.map +1 -1
  254. package/dist/release/release-published.d.ts +27 -0
  255. package/dist/release/release-published.d.ts.map +1 -0
  256. package/dist/release/release-published.js +35 -0
  257. package/dist/release/release-published.js.map +1 -0
  258. package/dist/release/tty-confirm.d.ts +23 -0
  259. package/dist/release/tty-confirm.d.ts.map +1 -0
  260. package/dist/release/tty-confirm.js +50 -0
  261. package/dist/release/tty-confirm.js.map +1 -0
  262. package/dist/remote/index.js +1 -1
  263. package/dist/remote/index.js.map +1 -1
  264. package/dist/resources/admission-ledger.d.ts +103 -1
  265. package/dist/resources/admission-ledger.d.ts.map +1 -1
  266. package/dist/resources/admission-ledger.js +200 -17
  267. package/dist/resources/admission-ledger.js.map +1 -1
  268. package/dist/resources/agent-lease.d.ts +82 -0
  269. package/dist/resources/agent-lease.d.ts.map +1 -0
  270. package/dist/resources/agent-lease.js +167 -0
  271. package/dist/resources/agent-lease.js.map +1 -0
  272. package/dist/resources/governor.d.ts +27 -1
  273. package/dist/resources/governor.d.ts.map +1 -1
  274. package/dist/resources/governor.js +88 -4
  275. package/dist/resources/governor.js.map +1 -1
  276. package/dist/resources/hook-footprint.d.ts +13 -1
  277. package/dist/resources/hook-footprint.d.ts.map +1 -1
  278. package/dist/resources/hook-footprint.js +33 -7
  279. package/dist/resources/hook-footprint.js.map +1 -1
  280. package/dist/resources/run-governed.d.ts +25 -0
  281. package/dist/resources/run-governed.d.ts.map +1 -1
  282. package/dist/resources/run-governed.js +44 -3
  283. package/dist/resources/run-governed.js.map +1 -1
  284. package/dist/sagas/reconcile.d.ts.map +1 -1
  285. package/dist/sagas/reconcile.js +11 -0
  286. package/dist/sagas/reconcile.js.map +1 -1
  287. package/dist/scaffold/ensure-dirs.d.ts.map +1 -1
  288. package/dist/scaffold/ensure-dirs.js +8 -6
  289. package/dist/scaffold/ensure-dirs.js.map +1 -1
  290. package/dist/security/override-cap-setting.d.ts +132 -0
  291. package/dist/security/override-cap-setting.d.ts.map +1 -1
  292. package/dist/security/override-cap-setting.js +88 -10
  293. package/dist/security/override-cap-setting.js.map +1 -1
  294. package/dist/security/owner-override-authority.d.ts.map +1 -1
  295. package/dist/security/owner-override-authority.js +5 -1
  296. package/dist/security/owner-override-authority.js.map +1 -1
  297. package/dist/selfimprove/draft-pr.d.ts.map +1 -1
  298. package/dist/selfimprove/draft-pr.js +4 -1
  299. package/dist/selfimprove/draft-pr.js.map +1 -1
  300. package/dist/session/engine-ops.d.ts.map +1 -1
  301. package/dist/session/engine-ops.js +4 -16
  302. package/dist/session/engine-ops.js.map +1 -1
  303. package/dist/sessions/briefing.d.ts +6 -0
  304. package/dist/sessions/briefing.d.ts.map +1 -1
  305. package/dist/sessions/briefing.js +34 -0
  306. package/dist/sessions/briefing.js.map +1 -1
  307. package/dist/sessions/session-end-snapshot-entry.js +2 -2
  308. package/dist/sessions/session-end-snapshot-entry.js.map +1 -1
  309. package/dist/sessions/session-end-snapshot.js +4 -4
  310. package/dist/sessions/session-end-snapshot.js.map +1 -1
  311. package/dist/skills/injection/token.d.ts.map +1 -1
  312. package/dist/skills/injection/token.js +3 -6
  313. package/dist/skills/injection/token.js.map +1 -1
  314. package/dist/skills/orchestrator/spawn.d.ts.map +1 -1
  315. package/dist/skills/orchestrator/spawn.js +3 -4
  316. package/dist/skills/orchestrator/spawn.js.map +1 -1
  317. package/dist/skills/propose-patch.d.ts.map +1 -1
  318. package/dist/skills/propose-patch.js +4 -1
  319. package/dist/skills/propose-patch.js.map +1 -1
  320. package/dist/spawn/branch-integration.d.ts.map +1 -1
  321. package/dist/spawn/branch-integration.js +5 -2
  322. package/dist/spawn/branch-integration.js.map +1 -1
  323. package/dist/store/attachment-repair.d.ts.map +1 -1
  324. package/dist/store/attachment-repair.js +5 -4
  325. package/dist/store/attachment-repair.js.map +1 -1
  326. package/dist/store/attachment-store.d.ts +6 -0
  327. package/dist/store/attachment-store.d.ts.map +1 -1
  328. package/dist/store/attachment-store.js +19 -2
  329. package/dist/store/attachment-store.js.map +1 -1
  330. package/dist/store/blob-ops.d.ts +1 -1
  331. package/dist/store/blob-ops.d.ts.map +1 -1
  332. package/dist/store/blob-ops.js +7 -3
  333. package/dist/store/blob-ops.js.map +1 -1
  334. package/dist/store/conduit-doc-history.d.ts +15 -1
  335. package/dist/store/conduit-doc-history.d.ts.map +1 -1
  336. package/dist/store/conduit-doc-history.js +16 -10
  337. package/dist/store/conduit-doc-history.js.map +1 -1
  338. package/dist/store/db-helpers.d.ts +29 -0
  339. package/dist/store/db-helpers.d.ts.map +1 -1
  340. package/dist/store/db-helpers.js +89 -1
  341. package/dist/store/db-helpers.js.map +1 -1
  342. package/dist/store/decision-adr-links.d.ts.map +1 -1
  343. package/dist/store/decision-adr-links.js +5 -2
  344. package/dist/store/decision-adr-links.js.map +1 -1
  345. package/dist/store/git-checkpoint.d.ts +11 -1
  346. package/dist/store/git-checkpoint.d.ts.map +1 -1
  347. package/dist/store/git-checkpoint.js +14 -3
  348. package/dist/store/git-checkpoint.js.map +1 -1
  349. package/dist/store/llmtxt-blob-adapter.d.ts.map +1 -1
  350. package/dist/store/llmtxt-blob-adapter.js +4 -1
  351. package/dist/store/llmtxt-blob-adapter.js.map +1 -1
  352. package/dist/store/schema/attachments.d.ts +7 -5
  353. package/dist/store/schema/attachments.d.ts.map +1 -1
  354. package/dist/store/schema/attachments.js +5 -2
  355. package/dist/store/schema/attachments.js.map +1 -1
  356. package/dist/store/session-store.d.ts.map +1 -1
  357. package/dist/store/session-store.js +6 -0
  358. package/dist/store/session-store.js.map +1 -1
  359. package/dist/store/sqlite-backup.d.ts +6 -1
  360. package/dist/store/sqlite-backup.d.ts.map +1 -1
  361. package/dist/store/sqlite-backup.js +9 -5
  362. package/dist/store/sqlite-backup.js.map +1 -1
  363. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  364. package/dist/store/sqlite-data-accessor.js +63 -36
  365. package/dist/store/sqlite-data-accessor.js.map +1 -1
  366. package/dist/system/backup.d.ts +29 -0
  367. package/dist/system/backup.d.ts.map +1 -1
  368. package/dist/system/backup.js +240 -37
  369. package/dist/system/backup.js.map +1 -1
  370. package/dist/system/dependencies.d.ts +22 -0
  371. package/dist/system/dependencies.d.ts.map +1 -1
  372. package/dist/system/dependencies.js +49 -2
  373. package/dist/system/dependencies.js.map +1 -1
  374. package/dist/system/health.d.ts.map +1 -1
  375. package/dist/system/health.js +23 -1
  376. package/dist/system/health.js.map +1 -1
  377. package/dist/task-work/claims.d.ts.map +1 -1
  378. package/dist/task-work/claims.js +14 -0
  379. package/dist/task-work/claims.js.map +1 -1
  380. package/dist/task-work/index.d.ts.map +1 -1
  381. package/dist/task-work/index.js +22 -0
  382. package/dist/task-work/index.js.map +1 -1
  383. package/dist/tasks/ac-coverage-gate.d.ts +27 -0
  384. package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
  385. package/dist/tasks/ac-coverage-gate.js +63 -28
  386. package/dist/tasks/ac-coverage-gate.js.map +1 -1
  387. package/dist/tasks/affected-packages.d.ts +26 -30
  388. package/dist/tasks/affected-packages.d.ts.map +1 -1
  389. package/dist/tasks/affected-packages.js +27 -50
  390. package/dist/tasks/affected-packages.js.map +1 -1
  391. package/dist/tasks/affected-scope.d.ts +13 -3
  392. package/dist/tasks/affected-scope.d.ts.map +1 -1
  393. package/dist/tasks/affected-scope.js +29 -11
  394. package/dist/tasks/affected-scope.js.map +1 -1
  395. package/dist/tasks/change-set.d.ts.map +1 -1
  396. package/dist/tasks/change-set.js +39 -20
  397. package/dist/tasks/change-set.js.map +1 -1
  398. package/dist/tasks/command-gate-cmd.d.ts +33 -0
  399. package/dist/tasks/command-gate-cmd.d.ts.map +1 -0
  400. package/dist/tasks/command-gate-cmd.js +45 -0
  401. package/dist/tasks/command-gate-cmd.js.map +1 -0
  402. package/dist/tasks/complete-ci.d.ts +9 -0
  403. package/dist/tasks/complete-ci.d.ts.map +1 -1
  404. package/dist/tasks/complete-ci.js +21 -4
  405. package/dist/tasks/complete-ci.js.map +1 -1
  406. package/dist/tasks/complete.d.ts.map +1 -1
  407. package/dist/tasks/complete.js +14 -0
  408. package/dist/tasks/complete.js.map +1 -1
  409. package/dist/tasks/component-pr.d.ts +37 -0
  410. package/dist/tasks/component-pr.d.ts.map +1 -1
  411. package/dist/tasks/component-pr.js +106 -14
  412. package/dist/tasks/component-pr.js.map +1 -1
  413. package/dist/tasks/done-plan.d.ts +14 -1
  414. package/dist/tasks/done-plan.d.ts.map +1 -1
  415. package/dist/tasks/done-plan.js +125 -15
  416. package/dist/tasks/done-plan.js.map +1 -1
  417. package/dist/tasks/done-record.d.ts.map +1 -1
  418. package/dist/tasks/done-record.js +4 -0
  419. package/dist/tasks/done-record.js.map +1 -1
  420. package/dist/tasks/engine-converters.d.ts.map +1 -1
  421. package/dist/tasks/engine-converters.js +2 -0
  422. package/dist/tasks/engine-converters.js.map +1 -1
  423. package/dist/tasks/evidence-tree.d.ts +28 -1
  424. package/dist/tasks/evidence-tree.d.ts.map +1 -1
  425. package/dist/tasks/evidence-tree.js +66 -2
  426. package/dist/tasks/evidence-tree.js.map +1 -1
  427. package/dist/tasks/evidence.d.ts +39 -27
  428. package/dist/tasks/evidence.d.ts.map +1 -1
  429. package/dist/tasks/evidence.js +382 -188
  430. package/dist/tasks/evidence.js.map +1 -1
  431. package/dist/tasks/gate-notes.d.ts +17 -0
  432. package/dist/tasks/gate-notes.d.ts.map +1 -1
  433. package/dist/tasks/gate-notes.js.map +1 -1
  434. package/dist/tasks/gate-preview.d.ts +7 -1
  435. package/dist/tasks/gate-preview.d.ts.map +1 -1
  436. package/dist/tasks/gate-preview.js +30 -4
  437. package/dist/tasks/gate-preview.js.map +1 -1
  438. package/dist/tasks/gate-runner.d.ts +42 -0
  439. package/dist/tasks/gate-runner.d.ts.map +1 -1
  440. package/dist/tasks/gate-runner.js +234 -94
  441. package/dist/tasks/gate-runner.js.map +1 -1
  442. package/dist/tasks/manifest-scope.d.ts +115 -0
  443. package/dist/tasks/manifest-scope.d.ts.map +1 -0
  444. package/dist/tasks/manifest-scope.js +352 -0
  445. package/dist/tasks/manifest-scope.js.map +1 -0
  446. package/dist/tasks/merge-close.d.ts +376 -0
  447. package/dist/tasks/merge-close.d.ts.map +1 -0
  448. package/dist/tasks/merge-close.js +1497 -0
  449. package/dist/tasks/merge-close.js.map +1 -0
  450. package/dist/tasks/qa-run-binding.d.ts +5 -0
  451. package/dist/tasks/qa-run-binding.d.ts.map +1 -1
  452. package/dist/tasks/qa-run-binding.js +6 -1
  453. package/dist/tasks/qa-run-binding.js.map +1 -1
  454. package/dist/tasks/req.d.ts.map +1 -1
  455. package/dist/tasks/req.js +29 -1
  456. package/dist/tasks/req.js.map +1 -1
  457. package/dist/tasks/review-verdict.d.ts +159 -0
  458. package/dist/tasks/review-verdict.d.ts.map +1 -0
  459. package/dist/tasks/review-verdict.js +442 -0
  460. package/dist/tasks/review-verdict.js.map +1 -0
  461. package/dist/tasks/task-reparent.d.ts.map +1 -1
  462. package/dist/tasks/task-reparent.js +22 -3
  463. package/dist/tasks/task-reparent.js.map +1 -1
  464. package/dist/tasks/test-report-counts.d.ts +274 -0
  465. package/dist/tasks/test-report-counts.d.ts.map +1 -0
  466. package/dist/tasks/test-report-counts.js +746 -0
  467. package/dist/tasks/test-report-counts.js.map +1 -0
  468. package/dist/tasks/test-run-binding.d.ts +20 -45
  469. package/dist/tasks/test-run-binding.d.ts.map +1 -1
  470. package/dist/tasks/test-run-binding.js +34 -119
  471. package/dist/tasks/test-run-binding.js.map +1 -1
  472. package/dist/tasks/update.d.ts.map +1 -1
  473. package/dist/tasks/update.js +42 -0
  474. package/dist/tasks/update.js.map +1 -1
  475. package/dist/tasks/verification-policy.d.ts +12 -0
  476. package/dist/tasks/verification-policy.d.ts.map +1 -1
  477. package/dist/tasks/verification-policy.js +13 -0
  478. package/dist/tasks/verification-policy.js.map +1 -1
  479. package/dist/tasks/workspace-resolution.d.ts +225 -0
  480. package/dist/tasks/workspace-resolution.d.ts.map +1 -0
  481. package/dist/tasks/workspace-resolution.js +497 -0
  482. package/dist/tasks/workspace-resolution.js.map +1 -0
  483. package/dist/validation/doctor/checks.d.ts +20 -0
  484. package/dist/validation/doctor/checks.d.ts.map +1 -1
  485. package/dist/validation/doctor/checks.js +112 -0
  486. package/dist/validation/doctor/checks.js.map +1 -1
  487. package/dist/validation/engine-ops.d.ts.map +1 -1
  488. package/dist/validation/engine-ops.js +37 -3
  489. package/dist/validation/engine-ops.js.map +1 -1
  490. package/dist/validation/validate-ops.d.ts +4 -0
  491. package/dist/validation/validate-ops.d.ts.map +1 -1
  492. package/dist/validation/validate-ops.js +79 -2
  493. package/dist/validation/validate-ops.js.map +1 -1
  494. package/dist/worktree/list.d.ts +4 -21
  495. package/dist/worktree/list.d.ts.map +1 -1
  496. package/dist/worktree/list.js +14 -44
  497. package/dist/worktree/list.js.map +1 -1
  498. package/dist/worktree/merged-cleanup.d.ts +63 -0
  499. package/dist/worktree/merged-cleanup.d.ts.map +1 -0
  500. package/dist/worktree/merged-cleanup.js +571 -0
  501. package/dist/worktree/merged-cleanup.js.map +1 -0
  502. package/dist/worktree/prune.d.ts +64 -2
  503. package/dist/worktree/prune.d.ts.map +1 -1
  504. package/dist/worktree/prune.js +151 -6
  505. package/dist/worktree/prune.js.map +1 -1
  506. package/dist/worktree/sentinel-index.d.ts +14 -0
  507. package/dist/worktree/sentinel-index.d.ts.map +1 -1
  508. package/dist/worktree/sentinel-index.js +17 -0
  509. package/dist/worktree/sentinel-index.js.map +1 -1
  510. package/dist/worktree/worktree-adopt.d.ts +1 -13
  511. package/dist/worktree/worktree-adopt.d.ts.map +1 -1
  512. package/dist/worktree/worktree-adopt.js +2 -17
  513. package/dist/worktree/worktree-adopt.js.map +1 -1
  514. package/package.json +12 -12
  515. package/schemas/project-context.schema.json +33 -0
  516. package/templates/CLEO-INJECTION.md +6 -16
  517. package/templates/CLEO-REFERENCE.md +27 -4
  518. package/templates/git-hooks/legacy-hashes.json +2 -1
  519. package/templates/git-hooks/pre-commit +43 -11
  520. package/templates/workflows/release-prepare.yml.tmpl +9 -88
@@ -0,0 +1,746 @@
1
+ /**
2
+ * One reader for test-report counts, shared by `test-run:` evidence and typed
3
+ * test gates' `minCount` (T14124).
4
+ *
5
+ * The two paths used to parse reports separately: a typed gate refused a
6
+ * node:test event report with node-style counters (`tests`/`pass`/`fail`,
7
+ * no `numTotalTests`; VidaPeps T2373) whose shape `test-run:` read, and
8
+ * `test-run:` read JUnit, libtest and Playwright through adapters the gate
9
+ * never saw. {@link readTestReport}
10
+ * tries every format in {@link TEST_REPORT_FORMATS} order, adapts it to the
11
+ * vitest shape, and returns its counts under one set of integrity rules:
12
+ *
13
+ * - counts are non-negative integers that add up to the total;
14
+ * - when the report also lists per-test results, its counters must equal
15
+ * them ({@link assertionDisagreement}) — a report whose counters contradict
16
+ * its own assertions is not evidence.
17
+ *
18
+ * What each caller does with failures differs: `test-run:` refuses a report
19
+ * with any failure; a typed gate reads the counts and fails the gate.
20
+ *
21
+ * @module tasks/test-report-counts
22
+ * @task T14124
23
+ */
24
+ import { statSync } from 'node:fs';
25
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
26
+ /** Playwright's per-test outcome, as a vitest assertion status. */
27
+ const PLAYWRIGHT_STATUS = {
28
+ expected: 'passed',
29
+ flaky: 'passed',
30
+ unexpected: 'failed',
31
+ skipped: 'skipped',
32
+ };
33
+ /** Every {@link PlaywrightStatus}, in `stats` key order. */
34
+ const PLAYWRIGHT_STATUSES = [
35
+ 'expected',
36
+ 'skipped',
37
+ 'unexpected',
38
+ 'flaky',
39
+ ];
40
+ function isPlaywrightStatus(value) {
41
+ return PLAYWRIGHT_STATUSES.some((status) => status === value);
42
+ }
43
+ function isRecord(value) {
44
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
45
+ }
46
+ /**
47
+ * Adapt a Playwright JSON report (`--reporter=json`) to the vitest shape, so
48
+ * an e2e spec can be test-run evidence (T14031); null when `report` is not
49
+ * one (no `suites` array and numeric `stats.expected`).
50
+ *
51
+ * The files run are the suites' `file`s (each spec's own `file` when set),
52
+ * resolved against `config.rootDir`, which Playwright makes them relative to.
53
+ * Counts come from the spec results, one per test and project: `expected`
54
+ * and `flaky` (passed on retry) pass, `unexpected` fails, `skipped` is
55
+ * pending. They must equal `stats`, and the run time is `stats.startTime`. A
56
+ * report listing top-level `errors` (no tests found, a config or global setup
57
+ * failure) is refused; the rest — vacuous, skipped-only or failing runs — is
58
+ * refused by the same checks as a vitest report.
59
+ *
60
+ * @param report - A parsed test-run report.
61
+ * @returns The adapted report, a refusal, or null when it is not Playwright's.
62
+ * @task T14031
63
+ */
64
+ export function playwrightTestRun(report) {
65
+ const { stats, suites, config, errors } = report;
66
+ if (!Array.isArray(suites) || !isRecord(stats) || typeof stats.expected !== 'number')
67
+ return null;
68
+ if (Array.isArray(errors) && errors.length > 0) {
69
+ const first = isRecord(errors[0]) && typeof errors[0].message === 'string' ? errors[0].message : '';
70
+ return {
71
+ refusal: `Playwright report lists ${errors.length} error(s)${first ? `: ${first.split('\n')[0]}` : ''}`,
72
+ codeName: 'E_EVIDENCE_TESTS_FAILED',
73
+ };
74
+ }
75
+ const files = new Map();
76
+ let badStatus;
77
+ const walk = (suite, inherited) => {
78
+ if (!isRecord(suite))
79
+ return;
80
+ const file = typeof suite.file === 'string' && suite.file !== '' ? suite.file : inherited;
81
+ for (const spec of Array.isArray(suite.specs) ? suite.specs : []) {
82
+ if (!isRecord(spec) || !Array.isArray(spec.tests))
83
+ continue;
84
+ const specFile = typeof spec.file === 'string' && spec.file !== '' ? spec.file : file;
85
+ if (specFile === null)
86
+ continue;
87
+ const statuses = files.get(specFile) ?? [];
88
+ for (const test of spec.tests) {
89
+ const status = isRecord(test) ? test.status : undefined;
90
+ if (isPlaywrightStatus(status))
91
+ statuses.push(status);
92
+ else
93
+ badStatus ??= status ?? null;
94
+ }
95
+ files.set(specFile, statuses);
96
+ }
97
+ for (const child of Array.isArray(suite.suites) ? suite.suites : [])
98
+ walk(child, file);
99
+ };
100
+ for (const suite of suites)
101
+ walk(suite, null);
102
+ if (badStatus !== undefined)
103
+ return {
104
+ refusal: `Playwright report has a test with status ${JSON.stringify(badStatus)}; expected one of ${PLAYWRIGHT_STATUSES.join(', ')}`,
105
+ codeName: 'E_EVIDENCE_INVALID',
106
+ };
107
+ const all = [...files.values()].flat();
108
+ const count = (status) => all.filter((s) => s === status).length;
109
+ for (const key of PLAYWRIGHT_STATUSES) {
110
+ if (stats[key] !== count(key))
111
+ return {
112
+ refusal: `Playwright report's stats.${key} is ${JSON.stringify(stats[key])}, but its spec results count ${count(key)}`,
113
+ codeName: 'E_EVIDENCE_INVALID',
114
+ };
115
+ }
116
+ const rootDir = isRecord(config) && typeof config.rootDir === 'string' ? config.rootDir : null;
117
+ const startTime = typeof stats.startTime === 'string' ? Date.parse(stats.startTime) : Number.NaN;
118
+ return {
119
+ ...(Number.isFinite(startTime) ? { startTime } : {}),
120
+ numTotalTests: all.length,
121
+ numPassedTests: count('expected') + count('flaky'),
122
+ numFailedTests: count('unexpected'),
123
+ numPendingTests: count('skipped'),
124
+ numTodoTests: 0,
125
+ testResults: [...files].map(([file, statuses]) => ({
126
+ name: rootDir === null ? file : resolve(rootDir, file),
127
+ status: statuses.includes('unexpected') ? 'failed' : 'passed',
128
+ assertionResults: statuses.map((s) => ({ status: PLAYWRIGHT_STATUS[s] })),
129
+ })),
130
+ };
131
+ }
132
+ /** Decode the five predefined XML entities and numeric character references. */
133
+ function xmlText(value) {
134
+ return value
135
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
136
+ .replace(/&#([0-9]+);/g, (_, dec) => String.fromCodePoint(Number.parseInt(dec, 10)))
137
+ .replace(/"/g, '"')
138
+ .replace(/'/g, "'")
139
+ .replace(/&lt;/g, '<')
140
+ .replace(/&gt;/g, '>')
141
+ .replace(/&amp;/g, '&');
142
+ }
143
+ /** The attributes of one XML start tag's attribute text. */
144
+ function xmlAttributes(text) {
145
+ const attrs = new Map();
146
+ for (const match of text.matchAll(/([A-Za-z_][\w:.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/g))
147
+ attrs.set(match[1], xmlText(match[2] ?? match[3] ?? ''));
148
+ return attrs;
149
+ }
150
+ /** A count attribute, when present and a non-negative integer. */
151
+ function countAttribute(attrs, name) {
152
+ const raw = attrs.get(name);
153
+ if (raw === undefined || !/^\d+$/.test(raw.trim()))
154
+ return null;
155
+ return Number(raw.trim());
156
+ }
157
+ /**
158
+ * Adapt a JUnit XML report to the vitest shape, so a runner without a JSON
159
+ * reporter can be test-run evidence (T14084): cargo nextest
160
+ * (`[profile.*.junit]`), pytest `--junitxml`, and most other runners; null
161
+ * when `text` is not JUnit XML (no `<testsuite` element).
162
+ *
163
+ * Each `<testcase>` is one test: a `<failure>` or `<error>` child fails it, a
164
+ * `<skipped>` child makes it pending, otherwise it passed. Its file is the
165
+ * testcase's `file` attribute, else its suite's, else the suite name (pytest
166
+ * xunit1 and Jest record files; nextest and pytest xunit2 name binaries and
167
+ * classes, which then cover no file). A `tests`, `failures`, `errors` or
168
+ * `skipped` total the report states must equal its testcases, and the run
169
+ * time is the earliest suite `timestamp`. Nested `<testsuite>` elements are
170
+ * refused rather than undercounted.
171
+ *
172
+ * @param text - The report file's text.
173
+ * @returns The adapted report, a refusal, or null when it is not JUnit XML.
174
+ * @task T14084
175
+ */
176
+ export function junitTestRun(text) {
177
+ const body = text.replace(/^/, '').replace(/<!--[\s\S]*?-->/g, '');
178
+ if (!/^\s*</.test(body) || !/<testsuite\b/.test(body))
179
+ return null;
180
+ const files = new Map();
181
+ const stated = { tests: 0, failed: 0, skipped: 0, any: false };
182
+ let startTime = Number.POSITIVE_INFINITY;
183
+ const suites = body.matchAll(/<testsuite\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testsuite>)/g);
184
+ for (const suite of suites) {
185
+ // A nested suite would be cut at its first closing tag and undercounted.
186
+ if (/<testsuite\b/.test(suite[2] ?? ''))
187
+ return {
188
+ refusal: 'JUnit report nests <testsuite> elements, which CLEO does not read; write one level of ' +
189
+ '<testsuite> under <testsuites> (pytest --junitxml, cargo nextest and jest-junit do)',
190
+ codeName: 'E_EVIDENCE_INVALID',
191
+ };
192
+ const attrs = xmlAttributes(suite[1] ?? '');
193
+ const tests = countAttribute(attrs, 'tests');
194
+ if (tests !== null) {
195
+ stated.any = true;
196
+ stated.tests += tests;
197
+ stated.failed +=
198
+ (countAttribute(attrs, 'failures') ?? 0) + (countAttribute(attrs, 'errors') ?? 0);
199
+ stated.skipped += countAttribute(attrs, 'skipped') ?? countAttribute(attrs, 'disabled') ?? 0;
200
+ }
201
+ const at = Date.parse(attrs.get('timestamp') ?? '');
202
+ if (Number.isFinite(at))
203
+ startTime = Math.min(startTime, at);
204
+ const suiteFile = attrs.get('file') || attrs.get('name') || 'junit';
205
+ const cases = (suite[2] ?? '').matchAll(/<testcase\b([^>]*?)(?:\/>|>([\s\S]*?)<\/testcase>)/g);
206
+ for (const testcase of cases) {
207
+ const caseAttrs = xmlAttributes(testcase[1] ?? '');
208
+ const inner = testcase[2] ?? '';
209
+ const outcome = /<(?:failure|error)\b/.test(inner)
210
+ ? 'failed'
211
+ : /<skipped\b/.test(inner)
212
+ ? 'skipped'
213
+ : 'passed';
214
+ const file = caseAttrs.get('file') || suiteFile;
215
+ files.set(file, [...(files.get(file) ?? []), outcome]);
216
+ }
217
+ }
218
+ const all = [...files.values()].flat();
219
+ const count = (outcome) => all.filter((o) => o === outcome).length;
220
+ if (stated.any &&
221
+ (stated.tests !== all.length ||
222
+ stated.failed !== count('failed') ||
223
+ stated.skipped !== count('skipped')))
224
+ return {
225
+ refusal: `JUnit report states ${stated.tests} tests (${stated.failed} failed or errored, ${stated.skipped} skipped), ` +
226
+ `but its testcases count ${all.length} (${count('failed')} failed, ${count('skipped')} skipped)`,
227
+ codeName: 'E_EVIDENCE_INVALID',
228
+ };
229
+ return {
230
+ ...(Number.isFinite(startTime) ? { startTime } : {}),
231
+ numTotalTests: all.length,
232
+ numPassedTests: count('passed'),
233
+ numFailedTests: count('failed'),
234
+ numPendingTests: count('skipped'),
235
+ numTodoTests: 0,
236
+ testResults: [...files].map(([file, outcomes]) => ({
237
+ name: file,
238
+ status: outcomes.includes('failed') ? 'failed' : 'passed',
239
+ assertionResults: outcomes.map((status) => ({ status })),
240
+ })),
241
+ };
242
+ }
243
+ /** libtest's per-test events, as vitest assertion statuses. */
244
+ const LIBTEST_STATUS = { ok: 'passed', failed: 'failed', ignored: 'skipped' };
245
+ function isLibtestEvent(value) {
246
+ return value === 'ok' || value === 'failed' || value === 'ignored';
247
+ }
248
+ /**
249
+ * Adapt libtest JSON lines to the vitest shape (T14084): `cargo test --
250
+ * -Z unstable-options --format json --report-time`, or cargo nextest
251
+ * `--message-format libtest-json`; null when `text` is not libtest JSON (a
252
+ * line that is not a JSON object with `type` `suite` or `test`).
253
+ *
254
+ * Each finished `test` event is one test (`ok` passes, `failed` fails,
255
+ * `ignored` is pending). Tests are grouped by their binary, the name before a
256
+ * `$` (nextest) or `::`. Libtest names no files, so a report covers none. Each
257
+ * suite's `passed`/`failed`/`ignored` summary must equal its test events, and
258
+ * every `started` suite must finish with as many test events as its
259
+ * `test_count`: a killed run is refused as truncated, never read as complete.
260
+ * Suites are tracked per binary when nextest's `libtest-json-plus` names them
261
+ * (its suites of parallel binaries interleave); otherwise one at a time.
262
+ *
263
+ * @param text - The report file's text.
264
+ * @returns The adapted report, a refusal, or null when it is not libtest JSON.
265
+ * @task T14084
266
+ */
267
+ export function libtestTestRun(text) {
268
+ const lines = text
269
+ .split('\n')
270
+ .map((line) => line.trim())
271
+ .filter(Boolean);
272
+ if (lines.length === 0)
273
+ return null;
274
+ const events = [];
275
+ for (const line of lines) {
276
+ let event;
277
+ try {
278
+ event = JSON.parse(line);
279
+ }
280
+ catch {
281
+ return null;
282
+ }
283
+ if (!isRecord(event) || (event.type !== 'suite' && event.type !== 'test'))
284
+ return null;
285
+ events.push(event);
286
+ }
287
+ const groups = new Map();
288
+ const summary = { passed: 0, failed: 0, ignored: 0, any: false };
289
+ // A killed run leaves a suite started and never finished; its partial
290
+ // counts must not read as a complete run.
291
+ const truncated = (detail) => ({
292
+ refusal: `the libtest report is truncated (a suite started but never finished): the run did not complete; ${detail}`,
293
+ codeName: 'E_EVIDENCE_INVALID',
294
+ });
295
+ // cargo test and nextest's libtest-json write one suite at a time, but
296
+ // nextest's libtest-json-plus streams suites of parallel binaries
297
+ // interleaved; its suite events then name the binary (`nextest.crate` and
298
+ // `nextest.test_binary`), as its test names do (`<crate>::<binary>$<test>`).
299
+ const suiteKey = (event) => {
300
+ const meta = event.nextest;
301
+ return isRecord(meta) && typeof meta.crate === 'string' && typeof meta.test_binary === 'string'
302
+ ? `${meta.crate}::${meta.test_binary}`
303
+ : '';
304
+ };
305
+ const keyed = events.some((event) => event.type === 'suite' && suiteKey(event) !== '');
306
+ const open = new Map();
307
+ for (const event of events) {
308
+ if (event.type === 'suite') {
309
+ const key = keyed ? suiteKey(event) : '';
310
+ if (event.event === 'started') {
311
+ if (open.has(key))
312
+ return truncated(`a suite${key ? ` of ${key}` : ''} started before the previous one finished`);
313
+ const expected = event.test_count;
314
+ open.set(key, {
315
+ expected: typeof expected === 'number' && Number.isInteger(expected) ? expected : null,
316
+ seen: 0,
317
+ });
318
+ continue;
319
+ }
320
+ if (event.event !== 'ok' && event.event !== 'failed')
321
+ continue;
322
+ const suite = open.get(key);
323
+ if (suite !== undefined && suite.expected !== null && suite.seen !== suite.expected)
324
+ return truncated(`a suite${key ? ` of ${key}` : ''} started with test_count ${suite.expected} but reported ${suite.seen} finished tests`);
325
+ open.delete(key);
326
+ summary.any = true;
327
+ for (const field of ['passed', 'failed', 'ignored']) {
328
+ const value = event[field];
329
+ if (typeof value === 'number' && Number.isInteger(value) && value >= 0)
330
+ summary[field] += value;
331
+ }
332
+ continue;
333
+ }
334
+ if (!isLibtestEvent(event.event))
335
+ continue;
336
+ const name = typeof event.name === 'string' ? event.name : '';
337
+ const suite = open.get(keyed && name.includes('$') ? name.split('$')[0] : '');
338
+ if (suite !== undefined)
339
+ suite.seen += 1;
340
+ const binary = name.includes('$') ? name.split('$')[0] : name.split('::')[0] || 'libtest';
341
+ groups.set(binary, [...(groups.get(binary) ?? []), event.event]);
342
+ }
343
+ if (open.size > 0)
344
+ return truncated(`${[...open.keys()].map((key) => key || 'the last suite').join(', ')} has no ok or failed event`);
345
+ const all = [...groups.values()].flat();
346
+ const count = (event) => all.filter((e) => e === event).length;
347
+ if (summary.any &&
348
+ (summary.passed !== count('ok') ||
349
+ summary.failed !== count('failed') ||
350
+ summary.ignored !== count('ignored')))
351
+ return {
352
+ refusal: `libtest summary states ${summary.passed} passed, ${summary.failed} failed, ${summary.ignored} ignored, ` +
353
+ `but its test events count ${count('ok')}, ${count('failed')}, ${count('ignored')}`,
354
+ codeName: 'E_EVIDENCE_INVALID',
355
+ };
356
+ return {
357
+ numTotalTests: all.length,
358
+ numPassedTests: count('ok'),
359
+ numFailedTests: count('failed'),
360
+ numPendingTests: count('ignored'),
361
+ numTodoTests: 0,
362
+ testResults: [...groups].map(([binary, outcomes]) => ({
363
+ name: binary,
364
+ status: outcomes.includes('failed') ? 'failed' : 'passed',
365
+ assertionResults: outcomes.map((event) => ({ status: LIBTEST_STATUS[event] })),
366
+ })),
367
+ };
368
+ }
369
+ /**
370
+ * The counter sets a `test-run:` report may carry (gh#1804), tried in order;
371
+ * the first whose total key is present is read. `failed` and `notRun` sum
372
+ * their keys, and an absent count is 0.
373
+ *
374
+ * | Shape | Total | Passed | Failed | Skipped / todo |
375
+ * |---|---|---|---|---|
376
+ * | vitest `--reporter=json`, jest `--json` | `numTotalTests` | `numPassedTests` | `numFailedTests` | `numPendingTests`, `numTodoTests` |
377
+ * | a runner's summary output (bun test, tsx --test) | `total` | `passed` | `failed` | `skipped`, `todo` |
378
+ * | node --test summary (`# tests`, `# pass`, ...) | `tests` | `pass` | `fail`, `cancelled` | `skipped`, `todo` |
379
+ *
380
+ * Integrity (T13136 review): every count is a non-negative integer;
381
+ * passed + failed + skipped/todo equals the total; a failure under ANY key
382
+ * ({@link TEST_RUN_FAILURE_KEYS}) is reported as `failure`, whichever set
383
+ * supplied the total, and `test-run:` refuses it;
384
+ * a report's `exit` / `exitCode` must be a number, and a non-zero one is
385
+ * refused. A `test-run:` atom proves only what its file says; `tool:test` and
386
+ * `ci:<pr>` are what prove the run. `cleo verify --help` documents the same
387
+ * sets.
388
+ *
389
+ * @task T13136
390
+ */
391
+ export const TEST_RUN_COUNTER_SHAPES = [
392
+ {
393
+ name: 'vitest/jest',
394
+ total: 'numTotalTests',
395
+ passed: 'numPassedTests',
396
+ failed: ['numFailedTests'],
397
+ notRun: ['numPendingTests', 'numTodoTests'],
398
+ },
399
+ {
400
+ name: 'summary',
401
+ total: 'total',
402
+ passed: 'passed',
403
+ failed: ['failed'],
404
+ notRun: ['skipped', 'todo'],
405
+ },
406
+ {
407
+ name: 'node --test',
408
+ total: 'tests',
409
+ passed: 'pass',
410
+ failed: ['fail', 'cancelled'],
411
+ notRun: ['skipped', 'todo'],
412
+ },
413
+ ];
414
+ /** The keys of each counter set, as the refusals name them. */
415
+ export const TEST_RUN_SHAPE_HINT = TEST_RUN_COUNTER_SHAPES.map((s) => `${[s.total, s.passed, ...s.failed, ...s.notRun].join('/')} (${s.name})`).join(', ');
416
+ /**
417
+ * Keys that name failures in any counter set, plus jest/vitest suite failures
418
+ * and the mocha / JUnit spellings: a positive value under any of them refuses
419
+ * the report, whichever set supplied the total.
420
+ */
421
+ const TEST_RUN_FAILURE_KEYS = [
422
+ 'numFailedTests',
423
+ 'numFailedTestSuites',
424
+ 'numRuntimeErrorTestSuites',
425
+ 'failed',
426
+ 'fail',
427
+ 'cancelled',
428
+ 'failures',
429
+ 'errors',
430
+ ];
431
+ /** A present, non-null JSON value. */
432
+ export const present = (value) => value !== undefined && value !== null;
433
+ /** A non-negative integer, or `undefined` when the value is anything else. */
434
+ const countOf = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : undefined;
435
+ /**
436
+ * Read a report's counts from the first counter set it carries, enforcing
437
+ * their integrity; `null` when it carries none.
438
+ *
439
+ * @param report - A report in the vitest shape (adapted when it was another format).
440
+ * @returns The counts, an integrity refusal, or null when no counter set is present.
441
+ * @task T13136
442
+ * @task T14124
443
+ */
444
+ export function reportCounts(report) {
445
+ const shape = TEST_RUN_COUNTER_SHAPES.find((s) => present(report[s.total]));
446
+ if (shape === undefined)
447
+ return null;
448
+ const invalid = (key) => ({
449
+ reason: `test-run report's "${key}" is ${JSON.stringify(report[key])}; every count must be a non-negative integer`,
450
+ codeName: 'E_EVIDENCE_INVALID',
451
+ });
452
+ const read = (key) => {
453
+ if (!present(report[key]))
454
+ return 0;
455
+ return countOf(report[key]) ?? invalid(key);
456
+ };
457
+ const total = read(shape.total);
458
+ if (typeof total !== 'number')
459
+ return total;
460
+ const keys = [shape.passed, ...shape.failed, ...shape.notRun];
461
+ const values = [];
462
+ for (const key of keys) {
463
+ const v = read(key);
464
+ if (typeof v !== 'number')
465
+ return v;
466
+ values.push(v);
467
+ }
468
+ let failure;
469
+ for (const key of TEST_RUN_FAILURE_KEYS) {
470
+ if (!present(report[key]))
471
+ continue;
472
+ const v = countOf(report[key]);
473
+ if (v === undefined)
474
+ return invalid(key);
475
+ if (v > 0)
476
+ failure ??= `${key} = ${v}`;
477
+ }
478
+ const [passed = 0, ...rest] = values;
479
+ const failed = rest.slice(0, shape.failed.length).reduce((a, b) => a + b, 0);
480
+ const notRun = rest.slice(shape.failed.length).reduce((a, b) => a + b, 0);
481
+ // A reported failure decides the report: it is failing, whatever its other
482
+ // counters add up to (T13136 review).
483
+ if (failure !== undefined)
484
+ return { shape, total, passed, failed, notRun, failure };
485
+ // T14124: a vitest/jest report carries all five test counters, as their JSON
486
+ // reporters always emit them; one missing means a trimmed or hand-made report.
487
+ if (shape.name === 'vitest/jest') {
488
+ const missing = [shape.total, ...keys].find((key) => !present(report[key]));
489
+ if (missing !== undefined)
490
+ return {
491
+ reason: `the report's "${missing}" is missing; a vitest/jest JSON report carries all five ` +
492
+ `test counters (${[shape.total, ...keys].join(', ')})`,
493
+ codeName: 'E_EVIDENCE_INVALID',
494
+ };
495
+ }
496
+ if (passed + failed + notRun !== total) {
497
+ return {
498
+ reason: `test-run report's counts do not add up: ${shape.total} is ${total}, but ` +
499
+ `${shape.passed} + ${[...shape.failed, ...shape.notRun].join(' + ')} = ${passed + failed + notRun}`,
500
+ codeName: 'E_EVIDENCE_INVALID',
501
+ };
502
+ }
503
+ return { shape, total, passed, failed, notRun };
504
+ }
505
+ /**
506
+ * Adapt node:test TAP output (`node --test --test-reporter=tap`, also via
507
+ * `tsx --test`), which ships no JSON reporter (T14051), to the vitest shape. The run's LAST
508
+ * top-level plan (`1..N` at column 0) must be followed by its `# tests N`,
509
+ * `# pass N` and `# fail N` summary; `# cancelled N` counts as failed. Test
510
+ * console output is re-emitted as indented or `# ` comment lines before that
511
+ * plan, so it cannot forge the summary.
512
+ *
513
+ * TAP names no files, so the adapted report covers none.
514
+ *
515
+ * T14161: a file that declares no tests is reported as one passing top-level
516
+ * test named after the file (`ok 1 - smoke/empty.test.mjs`, `type: 'test'`,
517
+ * nothing nested), and its `# tests 1` passed `minCount: 1`. Node 22, 24 and
518
+ * 26 print that entry exactly like a real top-level test, so nothing in the
519
+ * TAP tells them apart. Given the run's `cwd`, an entry like that whose name
520
+ * is an existing file under `cwd` counts as zero tests. A file that failed to
521
+ * load is `not ok` and still counts as a failure.
522
+ *
523
+ * @param stdout - A run's captured stdout, or a TAP report file's text.
524
+ * @param options - `cwd`: the directory the run's file names resolve against.
525
+ * @returns The adapted report, a refusal, or null when the text is not TAP.
526
+ * @task T14051
527
+ * @task T14124
528
+ * @task T14161
529
+ */
530
+ export function tapTestRun(stdout, options = {}) {
531
+ const lines = stdout.split(/\r?\n/);
532
+ if (!lines.some((line) => /^TAP version \d+$/.test(line.trim())))
533
+ return null;
534
+ if (lines.some((line) => /^\s*Bail out!/.test(line)))
535
+ return { refusal: 'its TAP output contains "Bail out!"', codeName: 'E_EVIDENCE_TESTS_FAILED' };
536
+ const plan = lines.findLastIndex((line) => /^1\.\.\d+(?:\s|$)/.test(line));
537
+ if (plan === -1)
538
+ return { refusal: 'its TAP output has no top-level 1..N plan', codeName: 'E_EVIDENCE_INVALID' };
539
+ const summary = new Map();
540
+ for (const line of lines.slice(plan + 1)) {
541
+ const match = line.match(/^# (tests|pass|fail|cancelled) (\d+)$/);
542
+ if (match)
543
+ summary.set(match[1], Number(match[2]));
544
+ }
545
+ const tests = summary.get('tests');
546
+ const pass = summary.get('pass');
547
+ const fail = summary.get('fail');
548
+ if (tests === undefined || pass === undefined || fail === undefined)
549
+ return {
550
+ refusal: 'its TAP output has no "# tests", "# pass" and "# fail" summary after the plan',
551
+ codeName: 'E_EVIDENCE_INVALID',
552
+ };
553
+ // Without a cwd no entry can be proven to be a file, so the summary is
554
+ // trusted as before: guessing from the name would zero real tests.
555
+ const emptyFiles = options.cwd === undefined ? [] : tapEmptyFiles(lines, plan, options.cwd);
556
+ if (tests - emptyFiles.length <= 0 || /^1\.\.0(?:\s|$)/.test(lines[plan]))
557
+ return {
558
+ refusal: emptyFiles.length === 0
559
+ ? 'its TAP output reports zero tests'
560
+ : `its TAP output reports zero tests (test files with no tests: ${emptyFiles.join(', ')})`,
561
+ codeName: 'E_EVIDENCE_TESTS_FAILED',
562
+ };
563
+ const failed = fail + (summary.get('cancelled') ?? 0);
564
+ return {
565
+ numTotalTests: tests - emptyFiles.length,
566
+ numPassedTests: pass - emptyFiles.length,
567
+ numFailedTests: failed,
568
+ numPendingTests: tests - pass - failed,
569
+ numTodoTests: 0,
570
+ testResults: [],
571
+ };
572
+ }
573
+ /**
574
+ * The passing top-level TAP entries before `plan` that stand for a test file
575
+ * with no tests (T14161): `type: 'test'`, no nested test points, and a name
576
+ * that is an existing file under `cwd`. A name escaping `cwd` (`..`) is never one.
577
+ */
578
+ function tapEmptyFiles(lines, plan, cwd) {
579
+ const files = [];
580
+ let nested = false;
581
+ for (let i = 0; i < plan; i++) {
582
+ const line = lines[i];
583
+ if (line.startsWith('# Subtest: '))
584
+ nested = false;
585
+ else if (/^\s+(?:not )?ok \d+\b/.test(line))
586
+ nested = true;
587
+ const entry = line.match(/^ok \d+ - (.*)$/);
588
+ if (entry === null)
589
+ continue;
590
+ const wasNested = nested;
591
+ nested = false;
592
+ // node escapes `\` and `#` in names; an unescaped `#` starts a directive.
593
+ if (wasNested || /(?<!\\)#/.test(entry[1]))
594
+ continue;
595
+ let type;
596
+ for (let j = i + 1; j < plan && /^\s/.test(lines[j]); j++) {
597
+ const yaml = lines[j].match(/^ {2}type: '(\w+)'$/);
598
+ if (yaml)
599
+ type = yaml[1];
600
+ if (lines[j] === ' ...')
601
+ break;
602
+ }
603
+ const name = entry[1].replace(/\\([\\#])/g, '$1');
604
+ if (type !== 'test')
605
+ continue;
606
+ const path = resolve(cwd, name);
607
+ const inside = relative(cwd, path);
608
+ if (inside === '' || inside.split(sep)[0] === '..' || isAbsolute(inside))
609
+ continue;
610
+ if (statSync(path, { throwIfNoEntry: false })?.isFile())
611
+ files.push(name);
612
+ }
613
+ return files;
614
+ }
615
+ /**
616
+ * Why a report's counters contradict its own per-test results, or null when
617
+ * they agree or it lists none (T14124). Counted over `testResults[].
618
+ * assertionResults[].status`: `passed`, `failed`, and every other status as
619
+ * not run. Applies to every counter set.
620
+ *
621
+ * @param report - A report in the vitest shape.
622
+ * @param counts - The counts read from its counter set.
623
+ * @returns The disagreement, or null.
624
+ * @task T14124
625
+ */
626
+ export function assertionDisagreement(report, counts) {
627
+ const files = report.testResults;
628
+ if (!Array.isArray(files))
629
+ return null;
630
+ const assertions = files.flatMap((f) => isRecord(f) && Array.isArray(f.assertionResults) ? f.assertionResults : []);
631
+ if (assertions.length === 0)
632
+ return null;
633
+ const status = (a) => (isRecord(a) ? a.status : undefined);
634
+ const passed = assertions.filter((a) => status(a) === 'passed').length;
635
+ const failed = assertions.filter((a) => status(a) === 'failed').length;
636
+ if (assertions.length === counts.total && passed === counts.passed && failed === counts.failed)
637
+ return null;
638
+ return (`its counters (${counts.shape.total} ${counts.total}, ${counts.shape.passed} ${counts.passed}, ` +
639
+ `failed ${counts.failed}) contradict its own test results (${assertions.length} tests, ` +
640
+ `${passed} passed, ${failed} failed)`);
641
+ }
642
+ /** The report formats {@link readTestReport} tries, in order, as refusals name them. */
643
+ export const TEST_REPORT_FORMATS = [
644
+ 'JUnit XML',
645
+ 'Playwright JSON',
646
+ 'vitest/jest JSON',
647
+ 'runner summary JSON',
648
+ 'node --test summary JSON',
649
+ 'libtest JSON lines',
650
+ 'node:test TAP',
651
+ ];
652
+ /** Format of a vitest-shaped report by the counter set that supplied its counts. */
653
+ const FORMAT_BY_SHAPE = {
654
+ 'vitest/jest': 'vitest/jest JSON',
655
+ summary: 'runner summary JSON',
656
+ 'node --test': 'node --test summary JSON',
657
+ };
658
+ /** Counts and cross-check for a report already in the vitest shape. */
659
+ function counted(report, adaptedAs) {
660
+ const counts = reportCounts(report);
661
+ if (counts === null)
662
+ return null;
663
+ const format = adaptedAs ??
664
+ ('shape' in counts ? FORMAT_BY_SHAPE[counts.shape.name] : null) ??
665
+ 'vitest/jest JSON';
666
+ if (!('shape' in counts))
667
+ return { ok: false, format, reason: counts.reason, codeName: counts.codeName };
668
+ const disagreement = counts.failure === undefined ? assertionDisagreement(report, counts) : null;
669
+ if (disagreement !== null)
670
+ return {
671
+ ok: false,
672
+ format,
673
+ reason: `the report is not evidence: ${disagreement}`,
674
+ codeName: 'E_EVIDENCE_INVALID',
675
+ };
676
+ return { ok: true, format, report, counts };
677
+ }
678
+ /** An adapter's result, counted. */
679
+ function adapted(result, format) {
680
+ if ('refusal' in result)
681
+ return { ok: false, format, reason: result.refusal, codeName: result.codeName };
682
+ return counted(result, format);
683
+ }
684
+ /**
685
+ * Read a parsed JSON report: Playwright, then a counter set (vitest/jest,
686
+ * runner summary, node --test summary; T14124).
687
+ *
688
+ * @param parsed - The parsed JSON value.
689
+ * @returns The read report, a refusal, or null when it is none of these.
690
+ * @task T14124
691
+ */
692
+ export function readParsedTestReport(parsed) {
693
+ if (!isRecord(parsed))
694
+ return null;
695
+ if (present(parsed.testResults) &&
696
+ (!Array.isArray(parsed.testResults) || parsed.testResults.some((tr) => !isRecord(tr))))
697
+ return {
698
+ ok: false,
699
+ format: 'vitest/jest JSON',
700
+ reason: 'the report\'s "testResults" must be an array of objects',
701
+ codeName: 'E_EVIDENCE_INVALID',
702
+ };
703
+ const playwright = playwrightTestRun(parsed);
704
+ if (playwright !== null)
705
+ return adapted(playwright, 'Playwright JSON');
706
+ return counted(parsed, null);
707
+ }
708
+ /**
709
+ * Read a test report's text in any supported format (T14124): JUnit XML, a
710
+ * JSON report ({@link readParsedTestReport}), libtest JSON lines, or node:test
711
+ * TAP. Each is adapted to the vitest shape and counted under the same rules.
712
+ *
713
+ * @param text - The report's text (a file, or a run's captured output).
714
+ * @param options - `cwd`: where a TAP run's file names resolve (T14161).
715
+ * @returns The read report, a refusal, or null when no format matches.
716
+ * @example
717
+ * ```ts
718
+ * const read = readTestReport(readFileSync('report.json', 'utf-8'));
719
+ * if (read?.ok) console.log(read.format, read.counts.passed);
720
+ * ```
721
+ * @task T14124
722
+ */
723
+ export function readTestReport(text, options = {}) {
724
+ const junit = junitTestRun(text);
725
+ if (junit !== null)
726
+ return adapted(junit, 'JUnit XML');
727
+ let parsed;
728
+ let isJson = true;
729
+ try {
730
+ parsed = JSON.parse(text);
731
+ }
732
+ catch {
733
+ isJson = false;
734
+ }
735
+ // A one-line libtest report is a JSON object too; only a recognised JSON
736
+ // report stops the search.
737
+ const json = isJson ? readParsedTestReport(parsed) : null;
738
+ if (json !== null)
739
+ return json;
740
+ const libtest = libtestTestRun(text);
741
+ if (libtest !== null)
742
+ return adapted(libtest, 'libtest JSON lines');
743
+ const tap = tapTestRun(text, options);
744
+ return tap === null ? null : adapted(tap, 'node:test TAP');
745
+ }
746
+ //# sourceMappingURL=test-report-counts.js.map