@hominis/fireforge 0.49.2 → 0.51.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 (400) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/bin/fireforge.js +6 -0
  4. package/dist/build-info.json +4 -4
  5. package/dist/src/commands/build-prelock.d.ts +20 -0
  6. package/dist/src/commands/build-prelock.js +50 -0
  7. package/dist/src/commands/build.d.ts +3 -1
  8. package/dist/src/commands/build.js +13 -6
  9. package/dist/src/commands/check-report.d.ts +41 -0
  10. package/dist/src/commands/check-report.js +91 -0
  11. package/dist/src/commands/check-run.d.ts +33 -0
  12. package/dist/src/commands/check-run.js +112 -0
  13. package/dist/src/commands/check-steps.d.ts +75 -0
  14. package/dist/src/commands/check-steps.js +200 -0
  15. package/dist/src/commands/check.d.ts +32 -0
  16. package/dist/src/commands/check.js +160 -0
  17. package/dist/src/commands/config.js +5 -6
  18. package/dist/src/commands/declarations-report.d.ts +43 -0
  19. package/dist/src/commands/declarations-report.js +76 -0
  20. package/dist/src/commands/doctor-engine-eslint.js +6 -1
  21. package/dist/src/commands/doctor-source-pin.js +4 -1
  22. package/dist/src/commands/export-adopt-check.d.ts +38 -0
  23. package/dist/src/commands/export-adopt-check.js +98 -0
  24. package/dist/src/commands/export-flow.js +29 -14
  25. package/dist/src/commands/export-placement-gate.d.ts +6 -2
  26. package/dist/src/commands/export-placement-gate.js +8 -0
  27. package/dist/src/commands/export-shared.d.ts +2 -1
  28. package/dist/src/commands/export-staged-registration.d.ts +46 -0
  29. package/dist/src/commands/export-staged-registration.js +91 -0
  30. package/dist/src/commands/forward-import-remedy.d.ts +32 -0
  31. package/dist/src/commands/forward-import-remedy.js +63 -0
  32. package/dist/src/commands/furnace/apply.js +3 -3
  33. package/dist/src/commands/furnace/chrome-doc-remove.d.ts +2 -1
  34. package/dist/src/commands/furnace/chrome-doc-remove.js +2 -2
  35. package/dist/src/commands/furnace/chrome-doc.d.ts +2 -1
  36. package/dist/src/commands/furnace/chrome-doc.js +2 -2
  37. package/dist/src/commands/furnace/create.js +2 -2
  38. package/dist/src/commands/furnace/deploy-named.d.ts +48 -0
  39. package/dist/src/commands/furnace/deploy-named.js +107 -0
  40. package/dist/src/commands/furnace/deploy.d.ts +3 -2
  41. package/dist/src/commands/furnace/deploy.js +50 -86
  42. package/dist/src/commands/furnace/diff.js +26 -3
  43. package/dist/src/commands/furnace/index.js +66 -41
  44. package/dist/src/commands/furnace/init.js +13 -6
  45. package/dist/src/commands/furnace/override.js +4 -2
  46. package/dist/src/commands/furnace/preview.js +4 -3
  47. package/dist/src/commands/furnace/refresh.js +2 -2
  48. package/dist/src/commands/furnace/remove.js +4 -3
  49. package/dist/src/commands/furnace/rename-dry-run.d.ts +29 -0
  50. package/dist/src/commands/furnace/rename-dry-run.js +127 -0
  51. package/dist/src/commands/furnace/rename-helpers.d.ts +7 -0
  52. package/dist/src/commands/furnace/rename-helpers.js +15 -1
  53. package/dist/src/commands/furnace/rename-registrations.d.ts +41 -0
  54. package/dist/src/commands/furnace/rename-registrations.js +98 -0
  55. package/dist/src/commands/furnace/rename.d.ts +2 -1
  56. package/dist/src/commands/furnace/rename.js +28 -66
  57. package/dist/src/commands/furnace/scan.d.ts +9 -4
  58. package/dist/src/commands/furnace/scan.js +11 -9
  59. package/dist/src/commands/furnace/sync.js +3 -2
  60. package/dist/src/commands/furnace/validate-report.d.ts +45 -0
  61. package/dist/src/commands/furnace/validate-report.js +91 -0
  62. package/dist/src/commands/furnace/validate.d.ts +1 -1
  63. package/dist/src/commands/furnace/validate.js +151 -113
  64. package/dist/src/commands/furnace/validation-output.js +14 -3
  65. package/dist/src/commands/lint-per-patch.js +36 -61
  66. package/dist/src/commands/lint-per-run-checkjs.js +2 -2
  67. package/dist/src/commands/lint-report.d.ts +8 -4
  68. package/dist/src/commands/lint-report.js +20 -5
  69. package/dist/src/commands/lint.js +15 -34
  70. package/dist/src/commands/manifest.js +4 -0
  71. package/dist/src/commands/park.d.ts +22 -0
  72. package/dist/src/commands/park.js +232 -0
  73. package/dist/src/commands/patch/delete-gap-notice.js +3 -1
  74. package/dist/src/commands/patch/delete.d.ts +1 -1
  75. package/dist/src/commands/patch/delete.js +24 -104
  76. package/dist/src/commands/patch/index.d.ts +3 -3
  77. package/dist/src/commands/patch/index.js +10 -4
  78. package/dist/src/commands/patch/lint-ignore-from.d.ts +44 -0
  79. package/dist/src/commands/patch/lint-ignore-from.js +142 -0
  80. package/dist/src/commands/patch/lint-ignore.d.ts +5 -1
  81. package/dist/src/commands/patch/lint-ignore.js +78 -31
  82. package/dist/src/commands/patch/move-files-create.js +34 -18
  83. package/dist/src/commands/patch/move-files-into.js +15 -7
  84. package/dist/src/commands/patch/move-files.js +6 -2
  85. package/dist/src/commands/patch/removal-created-files.d.ts +48 -0
  86. package/dist/src/commands/patch/removal-created-files.js +83 -0
  87. package/dist/src/commands/patch/removal-dependents.d.ts +63 -0
  88. package/dist/src/commands/patch/removal-dependents.js +133 -0
  89. package/dist/src/commands/patch/rename.js +20 -1
  90. package/dist/src/commands/patch/reserve.d.ts +33 -0
  91. package/dist/src/commands/patch/reserve.js +152 -0
  92. package/dist/src/commands/patch/retire.d.ts +34 -0
  93. package/dist/src/commands/patch/retire.js +133 -0
  94. package/dist/src/commands/patch/split-plan.d.ts +17 -8
  95. package/dist/src/commands/patch/split-plan.js +60 -28
  96. package/dist/src/commands/patch/split-staged.d.ts +108 -0
  97. package/dist/src/commands/patch/split-staged.js +202 -0
  98. package/dist/src/commands/patch/split.d.ts +5 -0
  99. package/dist/src/commands/patch/split.js +62 -17
  100. package/dist/src/commands/patch/stage.d.ts +34 -0
  101. package/dist/src/commands/patch/stage.js +153 -0
  102. package/dist/src/commands/patch/staged-dependency-batch.d.ts +48 -0
  103. package/dist/src/commands/patch/staged-dependency-batch.js +176 -0
  104. package/dist/src/commands/patch/staged-dependency-entries.d.ts +36 -0
  105. package/dist/src/commands/patch/staged-dependency-entries.js +52 -0
  106. package/dist/src/commands/patch/staged-dependency-from.d.ts +14 -0
  107. package/dist/src/commands/patch/staged-dependency-from.js +108 -0
  108. package/dist/src/commands/patch/staged-dependency-remove.d.ts +31 -0
  109. package/dist/src/commands/patch/staged-dependency-remove.js +80 -0
  110. package/dist/src/commands/patch/staged-dependency.js +54 -139
  111. package/dist/src/commands/re-export-adjacent-registrations.d.ts +20 -0
  112. package/dist/src/commands/re-export-adjacent-registrations.js +75 -0
  113. package/dist/src/commands/re-export-adjacent.d.ts +50 -2
  114. package/dist/src/commands/re-export-adjacent.js +160 -40
  115. package/dist/src/commands/re-export-bulk-scan.d.ts +2 -1
  116. package/dist/src/commands/re-export-bulk-scan.js +22 -7
  117. package/dist/src/commands/re-export-drift.d.ts +36 -8
  118. package/dist/src/commands/re-export-drift.js +73 -49
  119. package/dist/src/commands/re-export-files.js +11 -8
  120. package/dist/src/commands/re-export-hunks.d.ts +35 -0
  121. package/dist/src/commands/re-export-hunks.js +220 -0
  122. package/dist/src/commands/re-export-lint.d.ts +2 -1
  123. package/dist/src/commands/re-export-lint.js +23 -10
  124. package/dist/src/commands/re-export-options.js +74 -6
  125. package/dist/src/commands/re-export-preflight.d.ts +23 -0
  126. package/dist/src/commands/re-export-preflight.js +160 -0
  127. package/dist/src/commands/re-export-purity-scope.d.ts +18 -0
  128. package/dist/src/commands/re-export-purity-scope.js +131 -0
  129. package/dist/src/commands/re-export-register.js +56 -17
  130. package/dist/src/commands/re-export-removed.d.ts +13 -2
  131. package/dist/src/commands/re-export-removed.js +57 -12
  132. package/dist/src/commands/re-export-scan-owner.d.ts +44 -0
  133. package/dist/src/commands/re-export-scan-owner.js +116 -0
  134. package/dist/src/commands/re-export-scan.d.ts +19 -9
  135. package/dist/src/commands/re-export-scan.js +60 -24
  136. package/dist/src/commands/re-export-single.js +122 -46
  137. package/dist/src/commands/re-export-stale-registrations.d.ts +18 -0
  138. package/dist/src/commands/re-export-stale-registrations.js +62 -0
  139. package/dist/src/commands/re-export-summary.d.ts +28 -0
  140. package/dist/src/commands/re-export-summary.js +36 -0
  141. package/dist/src/commands/re-export.js +71 -57
  142. package/dist/src/commands/rebase/index.js +17 -17
  143. package/dist/src/commands/run.js +16 -2
  144. package/dist/src/commands/status-check.d.ts +55 -16
  145. package/dist/src/commands/status-check.js +92 -37
  146. package/dist/src/commands/status-json.d.ts +22 -9
  147. package/dist/src/commands/status-json.js +47 -32
  148. package/dist/src/commands/status-output.js +3 -1
  149. package/dist/src/commands/status-scope.d.ts +37 -0
  150. package/dist/src/commands/status-scope.js +54 -0
  151. package/dist/src/commands/status.js +66 -23
  152. package/dist/src/commands/test-browser-preflight.js +19 -3
  153. package/dist/src/commands/test-build-furnace.d.ts +22 -0
  154. package/dist/src/commands/test-build-furnace.js +54 -0
  155. package/dist/src/commands/test-build-phase.js +64 -41
  156. package/dist/src/commands/test-checkjs.js +10 -2
  157. package/dist/src/commands/test-diagnose.js +7 -0
  158. package/dist/src/commands/test-dispatch.d.ts +39 -0
  159. package/dist/src/commands/test-dispatch.js +211 -0
  160. package/dist/src/commands/test-furnace-reach.d.ts +37 -0
  161. package/dist/src/commands/test-furnace-reach.js +132 -0
  162. package/dist/src/commands/test-modes.d.ts +16 -1
  163. package/dist/src/commands/test-modes.js +24 -1
  164. package/dist/src/commands/test-multi-verdict.d.ts +61 -0
  165. package/dist/src/commands/test-multi-verdict.js +111 -0
  166. package/dist/src/commands/test-orchestrate.d.ts +52 -0
  167. package/dist/src/commands/test-orchestrate.js +240 -0
  168. package/dist/src/commands/test-perf-scope.d.ts +50 -0
  169. package/dist/src/commands/test-perf-scope.js +138 -0
  170. package/dist/src/commands/test-prelock.d.ts +51 -0
  171. package/dist/src/commands/test-prelock.js +273 -0
  172. package/dist/src/commands/test-register.js +69 -10
  173. package/dist/src/commands/test-results.d.ts +62 -0
  174. package/dist/src/commands/test-results.js +62 -0
  175. package/dist/src/commands/test-run.d.ts +13 -55
  176. package/dist/src/commands/test-run.js +74 -99
  177. package/dist/src/commands/test-shards.d.ts +110 -0
  178. package/dist/src/commands/test-shards.js +255 -0
  179. package/dist/src/commands/test-stale-gate.d.ts +5 -1
  180. package/dist/src/commands/test-stale-gate.js +60 -28
  181. package/dist/src/commands/test-verdict.d.ts +11 -1
  182. package/dist/src/commands/test-verdict.js +28 -1
  183. package/dist/src/commands/test.d.ts +6 -1
  184. package/dist/src/commands/test.js +120 -124
  185. package/dist/src/commands/token-lint.d.ts +14 -0
  186. package/dist/src/commands/token-lint.js +151 -0
  187. package/dist/src/commands/token.js +10 -0
  188. package/dist/src/commands/verify-scope.d.ts +28 -0
  189. package/dist/src/commands/verify-scope.js +67 -0
  190. package/dist/src/commands/verify.d.ts +10 -2
  191. package/dist/src/commands/verify.js +67 -36
  192. package/dist/src/core/build-baseline-types.d.ts +46 -0
  193. package/dist/src/core/build-baseline-validate.js +19 -0
  194. package/dist/src/core/build-baseline.d.ts +5 -1
  195. package/dist/src/core/build-baseline.js +3 -1
  196. package/dist/src/core/build-overwrite-guard.d.ts +50 -0
  197. package/dist/src/core/build-overwrite-guard.js +84 -4
  198. package/dist/src/core/build-prepare.d.ts +13 -0
  199. package/dist/src/core/build-prepare.js +42 -10
  200. package/dist/src/core/build-run-record.d.ts +74 -0
  201. package/dist/src/core/build-run-record.js +136 -0
  202. package/dist/src/core/checkjs-support-heads.d.ts +41 -0
  203. package/dist/src/core/checkjs-support-heads.js +104 -0
  204. package/dist/src/core/config-paths.d.ts +2 -2
  205. package/dist/src/core/config-paths.js +10 -0
  206. package/dist/src/core/config-validate-check.d.ts +8 -0
  207. package/dist/src/core/config-validate-check.js +219 -0
  208. package/dist/src/core/config-validate-file-size.d.ts +9 -6
  209. package/dist/src/core/config-validate-file-size.js +91 -2
  210. package/dist/src/core/config-validate-history.d.ts +11 -0
  211. package/dist/src/core/config-validate-history.js +38 -0
  212. package/dist/src/core/config-validate-patch-policy.js +100 -0
  213. package/dist/src/core/config-validate-test-toolchains.js +21 -0
  214. package/dist/src/core/config-validate-token-scales.d.ts +9 -0
  215. package/dist/src/core/config-validate-token-scales.js +92 -0
  216. package/dist/src/core/config-validate.js +20 -5
  217. package/dist/src/core/config.d.ts +5 -0
  218. package/dist/src/core/config.js +16 -0
  219. package/dist/src/core/coverage-dispatch.d.ts +24 -0
  220. package/dist/src/core/coverage-dispatch.js +44 -0
  221. package/dist/src/core/coverage-extend.d.ts +70 -45
  222. package/dist/src/core/coverage-extend.js +199 -144
  223. package/dist/src/core/coverage-records.d.ts +108 -0
  224. package/dist/src/core/coverage-records.js +384 -0
  225. package/dist/src/core/destructive.d.ts +7 -4
  226. package/dist/src/core/destructive.js +10 -10
  227. package/dist/src/core/engine-session-lock.d.ts +16 -1
  228. package/dist/src/core/engine-session-lock.js +38 -10
  229. package/dist/src/core/export-moved-code-shape.d.ts +49 -11
  230. package/dist/src/core/export-moved-code-shape.js +135 -44
  231. package/dist/src/core/file-lock-queue.d.ts +67 -0
  232. package/dist/src/core/file-lock-queue.js +221 -0
  233. package/dist/src/core/file-lock-shared.d.ts +57 -0
  234. package/dist/src/core/file-lock-shared.js +117 -0
  235. package/dist/src/core/file-lock.d.ts +13 -25
  236. package/dist/src/core/file-lock.js +163 -140
  237. package/dist/src/core/furnace-apply-output.d.ts +10 -0
  238. package/dist/src/core/furnace-apply-output.js +31 -1
  239. package/dist/src/core/furnace-apply.js +22 -12
  240. package/dist/src/core/furnace-composed-source.d.ts +76 -0
  241. package/dist/src/core/furnace-composed-source.js +167 -0
  242. package/dist/src/core/furnace-config-validate-exceptions.d.ts +16 -0
  243. package/dist/src/core/furnace-config-validate-exceptions.js +102 -0
  244. package/dist/src/core/furnace-config-validate.js +14 -0
  245. package/dist/src/core/furnace-css-fragments.d.ts +10 -0
  246. package/dist/src/core/furnace-css-fragments.js +26 -0
  247. package/dist/src/core/furnace-operation.d.ts +3 -3
  248. package/dist/src/core/furnace-registration-jar-inplace.d.ts +12 -0
  249. package/dist/src/core/furnace-registration-jar-inplace.js +80 -0
  250. package/dist/src/core/furnace-registration-remove.d.ts +22 -1
  251. package/dist/src/core/furnace-registration-remove.js +53 -17
  252. package/dist/src/core/furnace-registration-rename.d.ts +26 -0
  253. package/dist/src/core/furnace-registration-rename.js +77 -0
  254. package/dist/src/core/furnace-registration.d.ts +14 -4
  255. package/dist/src/core/furnace-registration.js +44 -10
  256. package/dist/src/core/furnace-staleness.d.ts +7 -0
  257. package/dist/src/core/furnace-staleness.js +34 -24
  258. package/dist/src/core/furnace-validate-accessibility.d.ts +8 -4
  259. package/dist/src/core/furnace-validate-accessibility.js +71 -28
  260. package/dist/src/core/furnace-validate-exceptions.d.ts +48 -0
  261. package/dist/src/core/furnace-validate-exceptions.js +80 -0
  262. package/dist/src/core/furnace-validate-icon-button.d.ts +7 -0
  263. package/dist/src/core/furnace-validate-icon-button.js +78 -0
  264. package/dist/src/core/furnace-validate.js +1 -1
  265. package/dist/src/core/harness-port-fallback.d.ts +60 -0
  266. package/dist/src/core/harness-port-fallback.js +96 -0
  267. package/dist/src/core/history-log-file.d.ts +61 -0
  268. package/dist/src/core/history-log-file.js +164 -0
  269. package/dist/src/core/history-log.d.ts +2 -1
  270. package/dist/src/core/history-log.js +2 -1
  271. package/dist/src/core/json-preserving-edit.d.ts +22 -0
  272. package/dist/src/core/json-preserving-edit.js +199 -0
  273. package/dist/src/core/line-prefixer.d.ts +18 -0
  274. package/dist/src/core/line-prefixer.js +34 -0
  275. package/dist/src/core/mach.d.ts +7 -0
  276. package/dist/src/core/mach.js +32 -10
  277. package/dist/src/core/module-resolution-preflight.d.ts +19 -3
  278. package/dist/src/core/module-resolution-preflight.js +24 -6
  279. package/dist/src/core/negative-control-compiled.d.ts +27 -0
  280. package/dist/src/core/negative-control-compiled.js +73 -0
  281. package/dist/src/core/negative-control-diff.d.ts +51 -0
  282. package/dist/src/core/negative-control-diff.js +179 -0
  283. package/dist/src/core/negative-control.d.ts +150 -0
  284. package/dist/src/core/negative-control.js +399 -0
  285. package/dist/src/core/objdir-install-manifests.d.ts +73 -0
  286. package/dist/src/core/objdir-install-manifests.js +226 -0
  287. package/dist/src/core/park-store.d.ts +63 -0
  288. package/dist/src/core/park-store.js +216 -0
  289. package/dist/src/core/patch-export-update.d.ts +6 -0
  290. package/dist/src/core/patch-export-update.js +13 -3
  291. package/dist/src/core/patch-export.d.ts +3 -2
  292. package/dist/src/core/patch-export.js +22 -16
  293. package/dist/src/core/patch-files.js +26 -8
  294. package/dist/src/core/patch-lint-checkjs-any-import.d.ts +39 -0
  295. package/dist/src/core/patch-lint-checkjs-any-import.js +63 -0
  296. package/dist/src/core/patch-lint-checkjs-diagnostics.d.ts +17 -0
  297. package/dist/src/core/patch-lint-checkjs-diagnostics.js +32 -0
  298. package/dist/src/core/patch-lint-checkjs.d.ts +10 -1
  299. package/dist/src/core/patch-lint-checkjs.js +20 -13
  300. package/dist/src/core/patch-lint-code-lines.d.ts +59 -0
  301. package/dist/src/core/patch-lint-code-lines.js +105 -0
  302. package/dist/src/core/patch-lint-cross.d.ts +3 -1
  303. package/dist/src/core/patch-lint-cross.js +17 -4
  304. package/dist/src/core/patch-lint-css.d.ts +8 -2
  305. package/dist/src/core/patch-lint-css.js +24 -14
  306. package/dist/src/core/patch-lint-file-size.d.ts +12 -5
  307. package/dist/src/core/patch-lint-file-size.js +22 -11
  308. package/dist/src/core/patch-lint-forward-registration-extended.d.ts +23 -0
  309. package/dist/src/core/patch-lint-forward-registration-extended.js +36 -11
  310. package/dist/src/core/patch-lint-forward-registration.js +10 -0
  311. package/dist/src/core/patch-lint-ignore.d.ts +196 -0
  312. package/dist/src/core/patch-lint-ignore.js +429 -0
  313. package/dist/src/core/patch-lint-ownership.d.ts +10 -0
  314. package/dist/src/core/patch-lint-ownership.js +26 -0
  315. package/dist/src/core/patch-lint-size-thresholds.d.ts +52 -0
  316. package/dist/src/core/patch-lint-size-thresholds.js +110 -0
  317. package/dist/src/core/patch-lint-size.d.ts +117 -0
  318. package/dist/src/core/patch-lint-size.js +331 -0
  319. package/dist/src/core/patch-lint-staged-registration.d.ts +18 -1
  320. package/dist/src/core/patch-lint-staged-registration.js +20 -0
  321. package/dist/src/core/patch-lint-token-scale.d.ts +57 -0
  322. package/dist/src/core/patch-lint-token-scale.js +286 -0
  323. package/dist/src/core/patch-lint.d.ts +11 -83
  324. package/dist/src/core/patch-lint.js +66 -288
  325. package/dist/src/core/patch-lock.js +7 -6
  326. package/dist/src/core/patch-manifest-consistency.js +7 -6
  327. package/dist/src/core/patch-manifest-io.d.ts +6 -3
  328. package/dist/src/core/patch-manifest-io.js +27 -9
  329. package/dist/src/core/patch-manifest-key-order.d.ts +24 -0
  330. package/dist/src/core/patch-manifest-key-order.js +61 -0
  331. package/dist/src/core/patch-manifest-stage.d.ts +46 -0
  332. package/dist/src/core/patch-manifest-stage.js +96 -0
  333. package/dist/src/core/patch-manifest-validate.js +61 -3
  334. package/dist/src/core/patch-order-legal.d.ts +26 -0
  335. package/dist/src/core/patch-order-legal.js +88 -0
  336. package/dist/src/core/patch-policy-content.d.ts +40 -0
  337. package/dist/src/core/patch-policy-content.js +216 -0
  338. package/dist/src/core/patch-policy.d.ts +22 -5
  339. package/dist/src/core/patch-policy.js +26 -12
  340. package/dist/src/core/patch-retired.d.ts +57 -0
  341. package/dist/src/core/patch-retired.js +92 -0
  342. package/dist/src/core/patch-subset.d.ts +16 -0
  343. package/dist/src/core/patch-subset.js +66 -0
  344. package/dist/src/core/preflight-wait.d.ts +20 -0
  345. package/dist/src/core/preflight-wait.js +38 -0
  346. package/dist/src/core/rebase-from-version.d.ts +46 -0
  347. package/dist/src/core/rebase-from-version.js +71 -0
  348. package/dist/src/core/run-log.d.ts +26 -0
  349. package/dist/src/core/run-log.js +106 -4
  350. package/dist/src/core/run-profile.d.ts +7 -0
  351. package/dist/src/core/run-profile.js +7 -1
  352. package/dist/src/core/smoke-patterns.d.ts +5 -0
  353. package/dist/src/core/smoke-patterns.js +13 -0
  354. package/dist/src/core/test-harness-crash.d.ts +19 -1
  355. package/dist/src/core/test-harness-crash.js +8 -9
  356. package/dist/src/core/test-harness-signature.d.ts +33 -0
  357. package/dist/src/core/test-harness-signature.js +23 -1
  358. package/dist/src/core/test-raw-log.d.ts +66 -0
  359. package/dist/src/core/test-raw-log.js +122 -0
  360. package/dist/src/core/test-run-completeness.d.ts +90 -0
  361. package/dist/src/core/test-run-completeness.js +184 -0
  362. package/dist/src/core/test-stale-check.d.ts +27 -1
  363. package/dist/src/core/test-stale-check.js +39 -49
  364. package/dist/src/core/test-stale-exempt.d.ts +40 -0
  365. package/dist/src/core/test-stale-exempt.js +70 -0
  366. package/dist/src/core/test-xpcshell-retry.d.ts +2 -0
  367. package/dist/src/core/test-xpcshell-retry.js +2 -1
  368. package/dist/src/core/token-lint.d.ts +53 -0
  369. package/dist/src/core/token-lint.js +131 -0
  370. package/dist/src/core/token-sibling-sheets.d.ts +46 -0
  371. package/dist/src/core/token-sibling-sheets.js +119 -0
  372. package/dist/src/core/tree-guard.js +5 -0
  373. package/dist/src/core/typecheck-shim.d.ts +6 -2
  374. package/dist/src/core/typecheck-shim.js +205 -4
  375. package/dist/src/core/typecheck.js +3 -1
  376. package/dist/src/index.d.ts +14 -3
  377. package/dist/src/index.js +6 -1
  378. package/dist/src/types/commands/index.d.ts +2 -2
  379. package/dist/src/types/commands/options.d.ts +212 -24
  380. package/dist/src/types/commands/patches.d.ts +118 -1
  381. package/dist/src/types/config.d.ts +264 -0
  382. package/dist/src/types/config.js +9 -0
  383. package/dist/src/types/furnace.d.ts +63 -1
  384. package/dist/src/types/furnace.js +7 -0
  385. package/dist/src/types/index.d.ts +1 -1
  386. package/dist/src/utils/options.d.ts +36 -7
  387. package/dist/src/utils/options.js +94 -8
  388. package/dist/src/utils/path-glob.d.ts +15 -0
  389. package/dist/src/utils/path-glob.js +47 -0
  390. package/dist/src/utils/regex.d.ts +15 -3
  391. package/dist/src/utils/regex.js +37 -16
  392. package/docs/configuration.md +369 -11
  393. package/docs/exit-codes.md +18 -1
  394. package/docs/furnace.md +139 -3
  395. package/docs/lifecycle-invariants.md +65 -30
  396. package/docs/machine-output.md +120 -12
  397. package/docs/patch-workflow.md +458 -22
  398. package/docs/run-logs.md +20 -1
  399. package/docs/testing.md +346 -21
  400. package/package.json +3 -3
@@ -10,6 +10,7 @@ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
10
10
  import { elapsedSince } from '../utils/elapsed.js';
11
11
  import { toError } from '../utils/errors.js';
12
12
  import { cancel, info, intro, isCancel, outro, spinner, success, warn, } from '../utils/logger.js';
13
+ import { ADJACENT_MEANS, buildAdjacentUnmanagedContext, reportAdjacentCarveOutUsage, } from './re-export-adjacent.js';
13
14
  import { loadScanFilesAssignments, withDryRunPurityGuard, withDryRunReExportLock, } from './re-export-bulk-scan.js';
14
15
  import { warnUnseenExpectedDrift } from './re-export-drift.js';
15
16
  import { reExportFilesInPlace } from './re-export-files.js';
@@ -17,7 +18,9 @@ import { buildReExportLintContext, finishReExportLint } from './re-export-lint.j
17
18
  import { applyReExportFilesPositionalFolding, validateReExportOptionCombinations, } from './re-export-options.js';
18
19
  import { validateExpectedRemovals } from './re-export-removed.js';
19
20
  import { normalizeEngineRelativeInput, normalizeScanFiles } from './re-export-scan.js';
20
- import { reExportSinglePatchWithIndexLockRetry } from './re-export-single.js';
21
+ import { assignScanFileOwners, scanOptionsForPatch } from './re-export-scan-owner.js';
22
+ import { reExportSinglePatchWithIndexLockRetry, } from './re-export-single.js';
23
+ import { formatReExportRunSummary } from './re-export-summary.js';
21
24
  async function resolveSelectedPatches(patches, options, manifest) {
22
25
  if (options.all) {
23
26
  return [...manifest.patches];
@@ -56,6 +59,27 @@ async function resolveSelectedPatches(patches, options, manifest) {
56
59
  const selectedFilenames = selected;
57
60
  return manifest.patches.filter((p) => selectedFilenames.includes(p.filename));
58
61
  }
62
+ /** Refreshes each selected patch in turn, collecting which wrote and which failed. */
63
+ async function reExportEach(args) {
64
+ const { selectedPatches, options, scanAssignments, progress, startedAt, shared } = args;
65
+ const written = [];
66
+ const failed = [];
67
+ for (const [index, patch] of selectedPatches.entries()) {
68
+ const patchOptions = scanOptionsForPatch(options, patch.filename, scanAssignments);
69
+ progress.message(`Re-exporting ${index + 1}/${selectedPatches.length}: ${patch.filename} (${patch.filesAffected.length} file(s), ${elapsedSince(startedAt)} elapsed)...`);
70
+ try {
71
+ if (await reExportSinglePatchWithIndexLockRetry({ ...shared, patch, options: patchOptions })) {
72
+ written.push(patch.filename);
73
+ }
74
+ }
75
+ catch (error) {
76
+ failed.push(patch.filename);
77
+ warn(`Failed to re-export ${patch.filename}`);
78
+ warn(toError(error).message);
79
+ }
80
+ }
81
+ return { written, failed };
82
+ }
59
83
  /**
60
84
  * Runs the re-export command to regenerate existing patches from current engine state.
61
85
  * @param projectRoot - Root directory of the project
@@ -73,7 +97,9 @@ export async function reExportCommand(projectRoot, patches, options) {
73
97
  options = cleanedOptions;
74
98
  }
75
99
  const isDryRun = options.dryRun === true;
76
- intro(isDryRun ? 'FireForge Re-export (dry run)' : 'FireForge Re-export');
100
+ // The banner says which mode is in force: a path flag that swallowed
101
+ // `--dry-run` as its value once turned a preview into a write unnoticed.
102
+ intro(isDryRun ? 'FireForge Re-export (DRY RUN)' : 'FireForge Re-export (WRITING)');
77
103
  // Accept export-style space-separated paths after --files by folding
78
104
  // path-shaped extra positionals into the file list.
79
105
  ({ patches, options } = applyReExportFilesPositionalFolding(patches, options));
@@ -94,12 +120,13 @@ export async function reExportCommand(projectRoot, patches, options) {
94
120
  : await resolveSelectedPatches(patches, options, manifest);
95
121
  if (!selectedPatches)
96
122
  return;
123
+ const scanAssignments = scanFilesByPatch ?? assignScanFileOwners(options.scanFiles, selectedPatches, manifest);
97
124
  if (selectedPatches.length === 0) {
98
125
  warn('No patches selected');
99
126
  outro('Nothing to re-export');
100
127
  return;
101
128
  }
102
- const expectedRemovals = await validateExpectedRemovals(paths, selectedPatches, options);
129
+ const expectedRemovals = await validateExpectedRemovals(paths, selectedPatches, options, manifest.patches);
103
130
  if (expectedRemovals.length > 0)
104
131
  options = { ...options, expectRemoved: expectedRemovals };
105
132
  if (scanFilesByPatch !== undefined) {
@@ -119,20 +146,13 @@ export async function reExportCommand(projectRoot, patches, options) {
119
146
  // we write anything.
120
147
  if (options.files !== undefined) {
121
148
  const filesConfig = await loadConfig(projectRoot);
122
- await withDryRunReExportLock(paths.fireforgeDir, isDryRun, () => withDryRunPurityGuard(paths.engine, paths.patches, isDryRun, () => reExportFilesInPlace(paths, selectedPatches, options, filesConfig)));
149
+ await withDryRunReExportLock(paths.fireforgeDir, isDryRun, () => withDryRunPurityGuard(paths.engine, paths.patches, isDryRun, () => reExportFilesInPlace(paths, selectedPatches, options, filesConfig), { patches: selectedPatches }));
123
150
  return;
124
151
  }
125
152
  const config = await loadConfig(projectRoot);
126
153
  // Classification inputs for the scan-less adjacency advisory, computed
127
154
  // once for the whole run.
128
- const adjacentCtx = {
129
- binaryName: config.binaryName,
130
- furnacePrefixes: await collectFurnaceManagedPrefixes(paths.root),
131
- refuseAdjacentUnmanaged: options.refuseAdjacentUnmanaged === true,
132
- approvedUnmanaged: new Set((options.expectUnmanaged ?? []).map((file) => normalizeEngineRelativeInput(file, '--expect-unmanaged'))),
133
- approvedSeen: new Set(),
134
- refusals: [],
135
- };
155
+ const adjacentCtx = buildAdjacentUnmanagedContext(options, config.binaryName, await collectFurnaceManagedPrefixes(paths.root));
136
156
  const driftCtx = buildForeignDriftContext(options);
137
157
  // Hoisted lint context, one per run: queue context + checkJs
138
158
  // program + per-patch result cache, shared across every loop iteration.
@@ -140,42 +160,31 @@ export async function reExportCommand(projectRoot, patches, options) {
140
160
  // reports includes it.
141
161
  const startedAt = Date.now();
142
162
  const lintCtx = await buildReExportLintContext(projectRoot, paths, config, options.noCache === true, new Set(selectedPatches.map((patch) => patch.filename)));
143
- let reExported = 0;
144
- const reExportedFilenames = [];
145
- const failedFilenames = [];
146
163
  const progress = spinner('Preparing re-export...');
147
- await withDryRunReExportLock(paths.fireforgeDir, isDryRun, async () => withDryRunPurityGuard(paths.engine, paths.patches, isDryRun, async () => {
148
- for (const [index, patch] of selectedPatches.entries()) {
149
- const assignedScanFiles = scanFilesByPatch?.get(patch.filename);
150
- const patchOptions = assignedScanFiles !== undefined ? { ...options, scanFiles: assignedScanFiles } : options;
151
- progress.message(`Re-exporting ${index + 1}/${selectedPatches.length}: ${patch.filename} (${patch.filesAffected.length} file(s), ${elapsedSince(startedAt)} elapsed)...`);
152
- try {
153
- const exported = await reExportSinglePatchWithIndexLockRetry({
154
- patch,
155
- paths,
156
- manifest,
157
- options: patchOptions,
158
- isDryRun,
159
- config,
160
- adjacentCtx,
161
- driftCtx,
162
- lintCtx,
163
- });
164
- if (exported) {
165
- reExported++;
166
- reExportedFilenames.push(patch.filename);
167
- }
168
- }
169
- catch (error) {
170
- failedFilenames.push(patch.filename);
171
- warn(`Failed to re-export ${patch.filename}`);
172
- warn(toError(error).message);
173
- }
174
- }
175
- }));
164
+ const outcome = await withDryRunReExportLock(paths.fireforgeDir, isDryRun, () => withDryRunPurityGuard(paths.engine, paths.patches, isDryRun, () => reExportEach({
165
+ selectedPatches,
166
+ options,
167
+ scanAssignments,
168
+ progress,
169
+ startedAt,
170
+ shared: { paths, manifest, isDryRun, config, adjacentCtx, driftCtx, lintCtx },
171
+ }), { patches: selectedPatches }));
172
+ const reExportedFilenames = outcome.written;
173
+ const failedFilenames = outcome.failed;
174
+ const reExported = reExportedFilenames.length;
176
175
  await finishReExportLint(lintCtx);
177
176
  warnUnseenExpectedDrift(driftCtx);
178
177
  warnUnseenApprovedUnmanaged(adjacentCtx);
178
+ const summary = formatReExportRunSummary({
179
+ selected: selectedPatches.map((patch) => patch.filename),
180
+ written: reExportedFilenames,
181
+ failed: failedFilenames,
182
+ refusedAdjacent: adjacentCtx.refusals.map((r) => r.patchFilename),
183
+ refusedDrift: driftCtx.refusals.map((r) => r.patchFilename),
184
+ isDryRun,
185
+ });
186
+ if (summary !== undefined)
187
+ info(summary);
179
188
  throwRunLevelRefusals(adjacentCtx, driftCtx, progress);
180
189
  if (reExported === 0 && selectedPatches.length > 0) {
181
190
  progress.error('Re-export failed');
@@ -198,6 +207,7 @@ export async function reExportCommand(projectRoot, patches, options) {
198
207
  selectedCount: selectedPatches.length,
199
208
  reExportedFilenames,
200
209
  failedFilenames,
210
+ selectedFilenames: selectedPatches.map((patch) => patch.filename),
201
211
  shouldStamp,
202
212
  stampRequested: options.stamp === true,
203
213
  config,
@@ -224,9 +234,11 @@ function buildForeignDriftContext(options) {
224
234
  * admit, and reads as an approval that is in force when it is not.
225
235
  * A warning rather than a refusal: an approved path legitimately goes unmet
226
236
  * when the reviewed file has since been adopted into a patch, which is the
227
- * outcome the carve-out exists to be retired by.
237
+ * outcome the carve-out exists to be retired by. The `--expect-new` and
238
+ * `--adjacent-ignore` accounting is reported first, alongside.
228
239
  */
229
240
  function warnUnseenApprovedUnmanaged(adjacentCtx) {
241
+ reportAdjacentCarveOutUsage(adjacentCtx);
230
242
  const unseen = [...adjacentCtx.approvedUnmanaged].filter((file) => !adjacentCtx.approvedSeen.has(file));
231
243
  if (unseen.length === 0)
232
244
  return;
@@ -244,17 +256,18 @@ function warnUnseenApprovedUnmanaged(adjacentCtx) {
244
256
  function throwRunLevelRefusals(adjacentCtx, driftCtx, progress) {
245
257
  if (adjacentCtx.refusals.length > 0) {
246
258
  progress.error('Re-export refused');
247
- // Each offender is named with the owned directory that made it
248
- // adjacent: an unattended run reads only this line, and without the
249
- // anchor it cannot tell which of a multi-directory patch's locations
250
- // the file sits beside.
259
+ // Each offender is named with the owned directory (or registering
260
+ // carrier) that made it adjacent: an unattended run reads only this
261
+ // line, and without the anchor it cannot tell which of a
262
+ // multi-directory patch's locations the file belongs to.
251
263
  const offenders = adjacentCtx.refusals
252
264
  .map((r) => `${r.patchFilename} (${r.anchored.join(', ')})`)
253
265
  .join('; ');
254
266
  throw new GeneralError(`Refused ${adjacentCtx.refusals.length} patch(es) with adjacent unmanaged files ` +
255
- `(--refuse-adjacent-unmanaged): ${offenders}. Adjacent means the file sits in a ` +
256
- `directory the patch already owns a file in. Adopt reviewed files with ` +
257
- `--scan --scan-file, or export them as a new patch.`);
267
+ `(--refuse-adjacent-unmanaged): ${offenders}. ${ADJACENT_MEANS} ` +
268
+ `Adopt reviewed files with --scan --scan-file, or export them as a new patch ` +
269
+ `(name a file your next export adopts with --expect-new, and a reviewed, deliberately ` +
270
+ `unmanaged file with --expect-unmanaged or --adjacent-ignore).`);
258
271
  }
259
272
  if (driftCtx.refusals.length > 0) {
260
273
  progress.error('Re-export refused');
@@ -263,11 +276,11 @@ function throwRunLevelRefusals(adjacentCtx, driftCtx, progress) {
263
276
  const unreadablePart = unreadable.length > 0
264
277
  ? ` ${unreadable.length} of these refused because the old patch body was missing or unreadable, so the drift comparison could not run (fail-closed).`
265
278
  : '';
266
- throw new GeneralError(`Refused ${driftCtx.refusals.length} patch(es) whose refreshed body would absorb ` +
267
- `engine edits not present in the old patch body (--refuse-foreign-drift): ${names}.${unreadablePart}\n\n` +
279
+ throw new GeneralError(`Refused ${driftCtx.refusals.length} patch(es) whose refreshed body would add or drop ` +
280
+ `lines relative to the old patch body (--refuse-foreign-drift): ${names}.${unreadablePart}\n\n` +
268
281
  'The flag cannot tell WHO wrote those lines, and the two populations have opposite ' +
269
282
  'remedies:\n' +
270
- ' - Lines you added yourself since the last export (the per-file report tags these ' +
283
+ ' - Lines you changed yourself since the last export (the per-file report tags these ' +
271
284
  '"edited since your last export"): name them with --expect <path> so this run captures ' +
272
285
  'them, or re-run without the flag to capture every drifting file.\n' +
273
286
  " - Lines another session owns: do NOT proceed — commit or stash that session's work " +
@@ -305,9 +318,10 @@ function reportReExportOutcome(args) {
305
318
  outro('Re-export complete');
306
319
  }
307
320
  if (reExported < selectedCount) {
308
- const skippedCount = selectedCount - reExported - args.failedFilenames.length;
309
321
  const failedPart = args.failedFilenames.length > 0 ? ` Failed: ${args.failedFilenames.join(', ')}.` : '';
310
- const skippedPart = skippedCount > 0 ? ` Skipped: ${skippedCount} patch(es) (see warnings above).` : '';
322
+ const accounted = new Set([...args.reExportedFilenames, ...args.failedFilenames]);
323
+ const skipped = args.selectedFilenames.filter((name) => !accounted.has(name));
324
+ const skippedPart = skipped.length > 0 ? ` Skipped: ${skipped.join(', ')} (see warnings above).` : '';
311
325
  throw new GeneralError(`${isDryRun ? '[dry-run] Would re-export' : 'Re-exported'} only ${reExported} of ` +
312
326
  `${selectedCount} selected patch(es).${failedPart}${skippedPart} ` +
313
327
  'Partial re-export exits non-zero so it cannot pass as success in scripts; ' +
@@ -19,12 +19,13 @@ import { discoverPatches } from '../../core/patch-files.js';
19
19
  import { loadPatchesManifest } from '../../core/patch-manifest.js';
20
20
  import { getPatchSourceVersion } from '../../core/patch-source-metadata.js';
21
21
  import { replayQueueIndexOnly } from '../../core/rebase-dry-run.js';
22
+ import { formatPatchStampSpread, readCommittedFirefoxPin, resolveRebaseFromVersion, tallyPatchStamps, } from '../../core/rebase-from-version.js';
22
23
  import { getRebaseSessionPath, readRebaseSession, saveRebaseSession, } from '../../core/rebase-session.js';
23
24
  import { GeneralError } from '../../errors/base.js';
24
25
  import { CorruptRebaseSessionError, RebaseDryRunRejectError, RebaseSessionExistsError, } from '../../errors/rebase.js';
25
26
  import { info, intro, outro, spinner, warn } from '../../utils/logger.js';
26
27
  import { addWaitLockOption, commanderArgParser, pickDefined, resolveWaitLockSeconds, } from '../../utils/options.js';
27
- import { compareFirefoxVersions, parsePositiveIntegerFlag } from '../../utils/validation.js';
28
+ import { parsePositiveIntegerFlag } from '../../utils/validation.js';
28
29
  import { handleAbort } from './abort.js';
29
30
  import { confirmDirtyEngineReset } from './confirm.js';
30
31
  import { handleContinue } from './continue.js';
@@ -58,27 +59,26 @@ async function handleFreshStart(projectRoot, options) {
58
59
  if (!manifest || manifest.patches.length === 0) {
59
60
  throw new GeneralError('No patches found in manifest. Nothing to rebase.');
60
61
  }
61
- // Determine the "from" version from the patch stamps. A queue can carry
62
- // several: patches re-exported since the last hop are stamped newer than
63
- // untouched ones. The oldest stamp is "from", compared semantically (a
64
- // lexical sort put 153.10.0esr before 153.2.0esr).
65
- const versionCounts = new Map();
66
- for (const patch of manifest.patches) {
67
- const version = getPatchSourceVersion(patch);
68
- versionCounts.set(version, (versionCounts.get(version) ?? 0) + 1);
69
- }
70
- const sortedVersions = [...versionCounts.keys()].sort(compareFirefoxVersions);
71
- const fromVersion = sortedVersions[0] ?? currentVersion;
72
- const fromProduct = manifest.patches.find((p) => getPatchSourceVersion(p) === fromVersion)?.sourceProduct ??
73
- config.firefox.product;
74
- if (versionCounts.size === 1 && fromVersion === currentVersion) {
62
+ // The queue can carry several stamps: patches re-exported since the last
63
+ // hop are stamped newer than untouched ones. "Nothing to rebase" is a fact
64
+ // about the stamps; the "from" label is the previous pin (the committed
65
+ // `firefox.version`), with the oldest stamp only as the fallback.
66
+ const spread = tallyPatchStamps(manifest.patches.map((p) => getPatchSourceVersion(p)));
67
+ const oldestStamp = spread.versions[0] ?? currentVersion;
68
+ const from = resolveRebaseFromVersion(currentVersion, await readCommittedFirefoxPin(projectRoot), spread, manifest.patches.find((p) => getPatchSourceVersion(p) === oldestStamp)?.sourceProduct);
69
+ const fromVersion = from.version;
70
+ const fromProduct = from.product ?? config.firefox.product;
71
+ if (spread.versions.length === 1 && oldestStamp === currentVersion) {
75
72
  info('All patches already match the current Firefox version. Nothing to rebase.');
76
73
  outro('Rebase not needed');
77
74
  return;
78
75
  }
79
76
  info(`Rebasing patches: ${fromVersion} → ${currentVersion}`);
80
- if (versionCounts.size > 1) {
81
- info(`Patch stamps: ${sortedVersions.map((v) => `${versionCounts.get(v)} at ${v}`).join(', ')}`);
77
+ if (from.origin === 'patch-stamps') {
78
+ info('From-version is the oldest patch stamp: no committed fireforge.json pin precedes the target.');
79
+ }
80
+ if (from.origin === 'committed-pin' || spread.versions.length > 1) {
81
+ info(`Patch stamps: ${formatPatchStampSpread(spread)}`);
82
82
  }
83
83
  // The rebase replays onto whatever engine/ holds. The pin is the target,
84
84
  // so an engine still on another version means the download step has not
@@ -9,7 +9,7 @@ import { warnIfFurnaceStale } from '../core/furnace-staleness.js';
9
9
  import { hasBuildArtifacts, hasRunnableBundle, run, runMachSmoke } from '../core/mach.js';
10
10
  import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
11
11
  import { resolveRunProfile, sweepAbandonedRunProfiles, } from '../core/run-profile.js';
12
- import { compileAllowlistFromFile, compileAllowlistFromStrings, findOverCapAllowlistEntries, matchAllowlist, matchesSmokeError, } from '../core/smoke-patterns.js';
12
+ import { compileAllowlistFromFile, compileAllowlistFromStrings, findOverCapAllowlistEntries, isBrowserConsoleLine, matchAllowlist, matchesSmokeError, } from '../core/smoke-patterns.js';
13
13
  import { GeneralError, InvalidArgumentError } from '../errors/base.js';
14
14
  import { BuildError } from '../errors/build.js';
15
15
  import { ExitCode } from '../errors/codes.js';
@@ -105,6 +105,7 @@ export async function runCommand(projectRoot, options = {}) {
105
105
  const profile = await resolveRunProfile({
106
106
  profile: options.profile,
107
107
  tempProfile: options.tempProfile,
108
+ purgeCaches: options.purgeCaches,
108
109
  smoke: options.smokeExit !== undefined,
109
110
  });
110
111
  try {
@@ -188,6 +189,7 @@ async function runSmokeExit(engineDir, options, profile) {
188
189
  warn(`--capture-console stream error: ${err.message}`);
189
190
  });
190
191
  const findings = [];
192
+ let browserConsoleLines = 0;
191
193
  let allowlistedErrorHits = 0;
192
194
  let allowlistedTotalHits = 0;
193
195
  const allowlistHits = new Array(allowlist.length).fill(0);
@@ -197,6 +199,8 @@ async function runSmokeExit(engineDir, options, profile) {
197
199
  // mirror preserves stdout/stderr separation for downstream piping.
198
200
  const sink = stream === 'stdout' ? process.stdout : process.stderr;
199
201
  sink.write(`${line}\n`);
202
+ if (isBrowserConsoleLine(line))
203
+ browserConsoleLines += 1;
200
204
  // Count allowlist hits up-front, regardless of error-pattern match.
201
205
  // Incrementing only when the line also matches an error pattern makes an
202
206
  // allowlist regex that visibly matches `console.warn: RSLoader:` report 0
@@ -269,6 +273,15 @@ async function runSmokeExit(engineDir, options, profile) {
269
273
  return;
270
274
  }
271
275
  if (result.exitCode === 0 || result.exitCode === 130 || result.exitCode === 143) {
276
+ // A clean status before the window with no browser output at all is a
277
+ // launch that never reached a window (a profile lock, a startup-crash
278
+ // streak's silent exit, a self-restart), not a clean run.
279
+ if (browserConsoleLines === 0) {
280
+ throw new SmokeRunError(`Browser exited with code ${result.exitCode} after ${(elapsedMs / 1000).toFixed(1)}s, ` +
281
+ 'before the smoke-exit window elapsed, without any console output. A silent early ' +
282
+ 'exit means the launch never reached a window: check the profile for a stale ' +
283
+ '.parentlock or a startup-crash streak (toolkit.startup.recent_crashes).', ExitCode.SMOKE_LAUNCH_FAILURE);
284
+ }
272
285
  return;
273
286
  }
274
287
  throw new SmokeRunError(`Browser exited with code ${result.exitCode} before smoke-exit window elapsed.`, ExitCode.SMOKE_LAUNCH_FAILURE);
@@ -392,7 +405,8 @@ export function registerRun(program, { getProjectRoot, withErrorHandling }) {
392
405
  .option('--console-allow-file <path>', 'Newline-delimited allowlist regex file. Blank lines and # comments are ignored. A "# max-hits: N" comment directly above an entry caps how many lines it may match in one run; more than that fails the run with exit 12.')
393
406
  .option('--capture-console <file>', 'Mirror captured console output to <file> for post-exit inspection.')
394
407
  .option('--headless', 'Launch the browser with --headless. Recommended for --smoke-exit on a shared desktop: input into a headed smoke window contaminates the console capture.')
395
- .option('--profile <path>', 'Launch with this profile directory (passed to Firefox as -profile). Left in place after the run. With --smoke-exit it replaces the default fresh profile.')
408
+ .option('--profile <path>', 'Launch with this profile directory (passed to Firefox as -profile). Left in place after the run. With --smoke-exit it replaces the default fresh profile. Firefox gets -purgecaches too, so the profile does not run chrome scripts an earlier build cached.')
409
+ .option('--no-purge-caches', 'With --profile, keep the profile startup cache (do not pass -purgecaches).')
396
410
  .option('--temp-profile', 'Launch with a fresh temporary profile that is removed when the run ends. The default for --smoke-exit.')
397
411
  .action(withErrorHandling(
398
412
  // `args` is the variadic positional, not a Commander flag, so it
@@ -3,46 +3,85 @@
3
3
  * a CI-enforceable non-zero exit: the default policy fails on any
4
4
  * classification outside {patch-backed, branding, furnace}, and
5
5
  * `--fail-on <class,...>` replaces that set for finer control.
6
+ * `--allow <class,...>` names classes that never fail, and
7
+ * `--fail-on-unknown` fails every present class that neither list names,
8
+ * so a gate keeps failing closed when FireForge adds a classification.
9
+ * `ownership-conflict` is a check class beside the file classifications:
10
+ * a path claimed by more than one patch, judged from the same rows as
11
+ * `--include-ownership`.
6
12
  *
7
13
  * Helper module consumed by status.ts, which sits at the max-lines budget.
8
14
  * No top-level registrar is exported and none is wanted.
9
15
  */
10
16
  import type { ClassifiedFile, FileClassification } from '../core/status-classify.js';
17
+ /**
18
+ * A class `--fail-on`/`--allow` can name: every file classification, plus
19
+ * `ownership-conflict`, which is judged from the ownership rows rather
20
+ * than from a file's classification.
21
+ */
22
+ export type StatusCheckClass = FileClassification | 'ownership-conflict';
11
23
  /** Resolved `--check`/`--fail-on` policy for one status invocation. */
12
24
  export interface StatusCheckPolicy {
13
25
  checkEnabled: boolean;
14
- failOn: readonly FileClassification[];
26
+ /** Classes that fail: `--fail-on` (or the default set) minus `--allow`. */
27
+ failOn: readonly StatusCheckClass[];
28
+ /** `--allow`: classes that never fail. Set only when the flag was given. */
29
+ allow?: readonly StatusCheckClass[];
30
+ /** `--fail-on-unknown`: a present class named by neither list fails. */
31
+ failOnUnknown?: boolean;
15
32
  }
16
33
  /**
17
- * Resolves the enforcement policy from the raw flags: `--fail-on` implies
18
- * `--check` and replaces the default set. Both are refused alongside the
19
- * modes that skip (or elide) classification.
34
+ * Resolves the enforcement policy from the raw flags. `--fail-on`,
35
+ * `--allow` and `--fail-on-unknown` each imply `--check`. `--fail-on`
36
+ * replaces the default set and `--allow` subtracts from it (naming one
37
+ * class in both is refused). `--fail-on-unknown` needs `--allow`: without
38
+ * an allow list every class the caller did not name, `patch-backed`
39
+ * included, would fail. All of them are refused alongside the modes that
40
+ * skip (or elide) classification.
20
41
  */
21
42
  export declare function resolveStatusCheckPolicy(options: {
22
43
  check?: boolean;
23
44
  failOn?: string;
45
+ allow?: string;
46
+ failOnUnknown?: boolean;
24
47
  raw?: boolean;
25
48
  unmanaged?: boolean;
26
49
  ownership?: boolean;
27
50
  testCoverage?: boolean;
28
51
  }): StatusCheckPolicy;
29
- /** One fail-set classification with a non-empty file list. */
52
+ /**
53
+ * True when the policy can fail on `ownership-conflict`, so the caller has
54
+ * to build the ownership rows even without `--include-ownership`.
55
+ */
56
+ export declare function policyNeedsOwnership(policy: StatusCheckPolicy): boolean;
57
+ /** One failing class with a non-empty file list. */
30
58
  export interface StatusCheckOffender {
31
- classification: FileClassification;
59
+ classification: StatusCheckClass;
32
60
  count: number;
33
61
  files: string[];
62
+ /**
63
+ * Set when the class failed only through `--fail-on-unknown`: neither
64
+ * `--fail-on` nor `--allow` named it.
65
+ */
66
+ unnamed?: true;
34
67
  }
35
68
  /**
36
- * Collects the fail-set classifications that have offending files, in
37
- * policy order with full file lists. Shared by the human `--check`
38
- * verdict and the `--json --summary` payload so the two views can never
39
- * disagree about what failed.
69
+ * Collects the failing classes that have offending files, with full file
70
+ * lists: the fail set in policy order, then (under `--fail-on-unknown`)
71
+ * every other present class neither list names, in first-seen order.
72
+ * Shared by the human `--check` verdict and the JSON `check` block so the
73
+ * two views can never disagree about what failed.
74
+ *
75
+ * @param classified - Classified worktree entries
76
+ * @param policy - Resolved policy
77
+ * @param ownershipConflicts - Paths claimed by more than one patch. Only
78
+ * read for the `ownership-conflict` class
40
79
  */
41
- export declare function collectStatusCheckOffenders(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy): StatusCheckOffender[];
80
+ export declare function collectStatusCheckOffenders(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy, ownershipConflicts?: readonly string[]): StatusCheckOffender[];
42
81
  /**
43
- * Throws a GeneralError (exit 1) when the policy is enabled and any of
44
- * its classifications is non-empty, naming each offending classification
45
- * with its first few files. A disabled policy or a fully tool-covered
46
- * tree passes silently.
82
+ * Throws a GeneralError (exit 1) when the policy is enabled and any
83
+ * failing class is non-empty, naming each offending class with its first
84
+ * few files. A disabled policy or a fully tool-covered tree passes
85
+ * silently.
47
86
  */
48
- export declare function runStatusCheck(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy): void;
87
+ export declare function runStatusCheck(classified: readonly ClassifiedFile[], policy: StatusCheckPolicy, ownershipConflicts?: readonly string[]): void;