@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
@@ -37,8 +37,120 @@ directories. The manifest is JSON:
37
37
  The command can be dry-run, refuses ambiguous ownership, and reports each
38
38
  file-to-patch assignment before refreshing the patch.
39
39
 
40
- `re-export` without a scan previews drift. `--refuse-foreign-drift --expect
41
- <path>` restricts it to the files you intended.
40
+ `re-export --dry-run` takes no engine-session lock: it writes nothing, so it
41
+ does not queue behind a peer's build or test run, and its git plumbing runs on
42
+ a private index. Its purity guard covers what the dry run could touch (the
43
+ selected patches, their `patches.json` rows, the engine files they own, and
44
+ the engine HEAD and index). A peer writing any of those during the preview is
45
+ named from the history log or the engine-session lock, and the preview is
46
+ simply rerun.
47
+
48
+ To adopt files into several patches in one call, name each file's owner
49
+ with `--scan-file <patch>:<path>`. A call with more than one target patch
50
+ requires the prefix on every `--scan-file`. Selected patches that get no
51
+ file refresh scan-less, without a broad directory scan:
52
+
53
+ ```sh
54
+ fireforge re-export 201 200 --scan --scan-file 201:browser/components/hominis/tiles/scroll.mjs --refuse-foreign-drift --expect browser/base/content/hominis.js --expect browser/base/jar.mn
55
+ ```
56
+
57
+ ### Foreign drift
58
+
59
+ Every `re-export` previews drift. For each file a patch already owns, it
60
+ compares the refreshed body with the old one and lists the lines the engine
61
+ file gained or lost since the last export (`+2 added / -1 removed`). Both
62
+ directions count, so an edit that only deletes lines is drift too.
63
+ `--refuse-foreign-drift` turns the preview into a refusal. `--expect <path>`
64
+ admits drift in the files you meant to change. Drift in any other owned
65
+ file, added or removed, still refuses.
66
+
67
+ The belt works beside `--scan`, `--scan-file` and `--scan-files`. It guards
68
+ each patch's existing files, and the files a scan adopts count as expected,
69
+ so the call that adopts a new file can still refuse a peer's edit in the
70
+ patch's other files. It cannot combine with `--files`, which replaces the
71
+ file list outright.
72
+
73
+ The adoption gate and the plain re-export refuse a forward import of a
74
+ module a later patch creates. They also refuse a forward registration
75
+ (`patchLint.forwardRegistration`, for example a `jar.mn` line packaging that
76
+ module), in the same refusal and with the `patch staged-dependency` command
77
+ that declares it, so one declaration pass covers both.
78
+
79
+ ### Exporting by hunk
80
+
81
+ Re-export captures whole files, so on a shared engine a peer's unexported
82
+ line in a shared manifest or chrome document lands in the exporting patch.
83
+ Two flags export a file by hunk instead. Each takes one explicit patch and
84
+ cannot combine with `--scan`, `--files` or `--all`:
85
+
86
+ - `--hunks-from <diff>` exports each file the diff names as the patch's
87
+ current body for it plus only the diff's hunks. Files the diff does not
88
+ name keep their old section.
89
+ - `--exclude-hunks <diff>` exports each named file as the working tree minus
90
+ the diff's hunks. The other files export as usual.
91
+
92
+ Hunks are placed by their context, so a diff taken before a peer's edit
93
+ above it still applies, and a hunk whose context is gone refuses. The
94
+ section is diffed by git against HEAD on a throwaway index, and the engine
95
+ is not touched. A spine patch can then export one change's lines at a time:
96
+
97
+ ```sh
98
+ git -C engine diff -- browser/base/content/hominis.js > my-change.diff # trim to your hunks
99
+ fireforge re-export 200 --hunks-from my-change.diff
100
+ ```
101
+
102
+ ### Re-export belts and arguments
103
+
104
+ `--refuse-adjacent-unmanaged` refuses a patch when an unmanaged file is
105
+ adjacent to it, meaning the re-export would plausibly have to capture it:
106
+
107
+ - a changed file in the same directory as a file the patch owns, or
108
+ - a changed file named by a registration line the patch adds (a jar.mn,
109
+ moz.build or test-manifest line, `*` globs included).
110
+
111
+ A file in a sibling or nested directory is not adjacent, so a peer
112
+ session's unexported test in `test/hominis-tiles/` no longer refuses a
113
+ patch that owns `test/hominis/browser.toml`. The refusal names every file
114
+ with its anchor (`beside engine/<dir>` or `registered by engine/<carrier>`).
115
+
116
+ Beside `--scan` and `--scan-file` the belt judges the adjacent files the
117
+ scan did not adopt, so one call can adopt a new widget and its test and
118
+ still refuse a peer's neighbour. It cannot combine with `--files`.
119
+
120
+ Three carve-outs keep the belt armed for everything they do not name. Each
121
+ requires `--refuse-adjacent-unmanaged`, and each admitted file is still
122
+ listed:
123
+
124
+ - `--expect-new <path>` names a file this session created and will adopt
125
+ with its next export. The owners of a new module's registration can
126
+ re-export first while a peer's neighbour still refuses. The path must be
127
+ changed in engine/ and owned by no patch. The run ends by naming the
128
+ export still owed, and an unmet path is a warning.
129
+ - `--expect-unmanaged <path>` records a reviewed exception that stays
130
+ unmanaged. An unmet path is a warning.
131
+ - `--adjacent-ignore <path|glob>` is a reviewed ignore list. A `*` stays
132
+ within one path segment and `**` is refused. Unmatched entries are only
133
+ noted, because the same list is reused across runs.
134
+
135
+ ```sh
136
+ fireforge re-export 102 200 --refuse-adjacent-unmanaged --refuse-foreign-drift \
137
+ --expect browser/modules/hominis/moz.build --expect-new browser/modules/hominis/HominisBangs.sys.mjs
138
+ ```
139
+
140
+ `--expect-from <file>` reads `--expect` paths from a file, one per line,
141
+ with blank lines and `#` comments ignored. Under zsh a list held in one
142
+ variable reaches FireForge as a single argument, and splitting it by hand
143
+ left every path after the first as a positional patch name.
144
+
145
+ Arguments are checked before the run queues for the engine lock: an empty
146
+ or unknown patch name, a positional that is an engine file (the refusal
147
+ points at `--expect` and `--expect-from`), a path flag whose value starts
148
+ with `-` (a flag swallowed by a value-less `--expect-unmanaged`), and an
149
+ `--expect`, `--expect-unmanaged`, `--expect-new` or `--scan-file` value that
150
+ names no changed file in engine/. The banner reads `DRY RUN` or `WRITING`.
151
+ A multi-patch run ends with one line naming the patches it wrote and those
152
+ it did not, each with its reason (`failed`, `refused: adjacent unmanaged`,
153
+ `refused: foreign drift`, `skipped`).
42
154
 
43
155
  ### Deletions
44
156
 
@@ -56,20 +168,73 @@ requested path produces no hunk. That is checked against the full requested
56
168
  list rather than the subset left after filtering, so nothing dropped upstream
57
169
  can pass as a silent success.
58
170
 
59
- An absent file originally created by a selected patch can be retired with
60
- repeatable `--expect-removed <path>` on scan-less re-export:
171
+ A file can be retired from a selected patch with repeatable
172
+ `--expect-removed <path>` on scan-less re-export. This works for an absent
173
+ file the patch created, and for an upstream file the patch modified or
174
+ deleted whose engine copy is back at HEAD:
61
175
 
62
176
  ```sh
63
177
  fireforge re-export 202 --refuse-adjacent-unmanaged --refuse-foreign-drift --expect browser/themes/shared/hominis/widgets.css --expect-removed browser/themes/shared/hominis/stop.svg
64
178
  ```
65
179
 
66
- The removal must have exactly one selected owner, be absent from disk and
67
- engine HEAD, and have a `new file mode` section in that owner's patch.
68
- Every removal is validated before writes, in dry-run too. Other paths keep
180
+ Each removal must have exactly one selected owner, and one call may retire
181
+ paths from several selected patches (`re-export 102 202 --expect-removed a
182
+ --expect-removed b`). A path whose owner was left out of the selection is
183
+ refused with that owner named. A patch-created file must
184
+ be absent from disk and engine HEAD, and have a `new file mode` section in
185
+ that owner's patch. An upstream file must be tracked in HEAD, match it on
186
+ disk, and have a section in that owner's patch. Without the retirement, a
187
+ scan-less re-export keeps the path in `filesAffected`, and with the belt
188
+ armed it refuses because the file's lines left the body. Every removal is
189
+ validated before writes, in dry-run too. Other paths keep
69
190
  both refusal checks; retirement grants no unmanaged or foreign-drift
70
191
  exception. Removing every file would empty the patch and requires `patch
71
- drop` instead. `--expect-removed` cannot combine with `--scan`, `--files` or
72
- `--all`.
192
+ delete` (or `patch retire`, which keeps the order number) instead.
193
+ `--expect-removed` cannot combine with `--scan`, `--files` or `--all`.
194
+
195
+ ### Forward imports and new modules
196
+
197
+ A re-export (plain or `--scan`) refused because a file imports a module a
198
+ later patch creates names the earliest legal order for exporting those
199
+ files as their own patch (free, inside the category range, and not opening
200
+ a gap under `allowGaps: false`), and prints the exact `patch
201
+ staged-dependency --add` command for each edge, with a `--reason`
202
+ placeholder. A refused `--scan` adoption, `patch move-files` or `patch split`
203
+ also writes every declaration it proposes into one lint-report-shaped JSON
204
+ (`--declarations-out <path>`, by default under `.fireforge/declarations/`)
205
+ and names the single `patch staged-dependency <holder> --add --from <file>`
206
+ call per holding patch that discharges them all. Moving tests out of a patch
207
+ that owns a shared manifest no longer needs one declaration typed per
208
+ manifest line.
209
+
210
+ A re-export also drops a staged registration whose `creates` path no patch
211
+ in the queue owns any more, for example after a merge deleted the file it
212
+ registered. The dry run names the drop, and the write records it in the
213
+ history log (`re-export-staged-prune`).
214
+
215
+ An `export --order` (or `--before`/`--after`) refused on a new system
216
+ module that no patch registers in moz.build says "register first". When the
217
+ module's moz.build line is already in the working tree but the patch that
218
+ owns that moz.build has not exported it, the refusal prints three steps:
219
+ declare the line as a staged registration on that patch, re-run the export,
220
+ then re-export that patch. With the declaration in place and the line in the
221
+ working tree, the export is accepted, with a note to re-export the
222
+ registering patch next.
223
+
224
+ The refusal offers the adopt-then-split sequence (`re-export --scan
225
+ --scan-file`, then `patch move-files --create`) only when the new file
226
+ shares a block of at least three consecutive substantive lines with an
227
+ existing patch, quotes the matched lines, and only when adopting the files
228
+ into that patch would itself pass: it names the forward import or the hard
229
+ size limit that would refuse the adopt instead. When the only error is the
230
+ missing registration, adopt-then-split is not offered at all.
231
+
232
+ This is the route for pruning a path from a reserved-range allowlist
233
+ (`patchPolicy.reservedRanges[].allowed[].files`). Remove the path from the
234
+ allowlist, restore its engine copy, and retire it with `--expect-removed`.
235
+ The re-export policy gate checks the `filesAffected` the export will write,
236
+ and only for the patch being written, so one patch's stale row cannot fail
237
+ the other patches in the same call.
73
238
 
74
239
  ## Queue maintenance
75
240
 
@@ -80,8 +245,84 @@ Under `fireforge patch`:
80
245
  - `patch reorder` moves a patch.
81
246
  - `patch split <source> --files <paths...> --name <name>` carves files out of
82
247
  a patch into a new one as a single transaction, including staged-dependency
83
- owner rewrites, and supports `--dry-run`.
248
+ owner rewrites, and supports `--dry-run`. `patch move-files <from> <to>
249
+ --create --order <n>` does the same with the files named by `--file`.
250
+ Both keep the staged declarations in step with the move and list every
251
+ change: owners (of forward imports and registrations alike) that named
252
+ the source for a moved `creates` path are re-pointed at the new patch, a
253
+ declaration whose declaring file moved goes with it, and a declaration the
254
+ move makes unused is dropped. The patch policy, including the gap rule, is
255
+ checked before any lint runs.
84
256
  - `patch rename` also supports `--category` and `--order`.
257
+ - `patch retire <name>` removes a patch like `patch delete` (same dependency
258
+ refusal, `--dry-run` and confirmation) but keeps its order number as a
259
+ retired slot in `patches.json#retired`, so `patchPolicy.allowGaps: false`
260
+ sees no gap and no later patch is renumbered. Use it for a patch emptied by
261
+ a rename. The slot has no `.patch` file and no row in `patches`: apply,
262
+ export, lint and rebase never see it, and only the gap check counts it as
263
+ occupied. A patch that later takes the order (`export --order <n>`, a
264
+ rename or a renumber) fills the slot, which then disappears. `patch
265
+ compact` renumbers through retired slots like any other gap.
266
+ - `patch delete` and `patch retire` name each file the patch creates that is
267
+ still on disk, in the dry run and at the prompt: once the patch is gone
268
+ nothing owns it, so `status` would report it unmanaged. Delete those files
269
+ first, or pass `--delete-created` to remove them as part of the confirmed
270
+ operation (under the engine lock as well). Files the patch only modifies
271
+ keep their edits and become unmanaged, as before.
272
+ - `patch reserve <order> --category <c> --owner <label> [--reason <text>]`
273
+ holds an order for a patch a session has agreed to export but has not
274
+ exported yet. The reservation lives in `patches.json#reserved`. Like a
275
+ retired slot, the gap check counts it as occupied, so `export --order 571`
276
+ is legal while a peer holds 569 and 570. Unlike a retired slot, the
277
+ earliest-legal-order search and a default `export` skip it, because it is
278
+ held for its owner. The export that lands on the order fills it, with a
279
+ notice naming the owner. `patch reserve <order> --release` drops a
280
+ reservation whose patch will not come. An order a patch, a retired slot or
281
+ another reservation holds, or one outside the category's range, is
282
+ refused.
283
+ - `patch stage <name>...` stages the named patches' `.patch` files and only
284
+ their rows of `patches/patches.json` in the project repository's git index.
285
+ Every other row stays as at HEAD. The staged manifest is built from HEAD's
286
+ rows plus the named working rows (with their retired slots; a reservation
287
+ a staged row fills is dropped), validated, and written with `git
288
+ hash-object` and `git update-index`. A collision (two rows at one order) or
289
+ an invalid result is refused before the index is touched. The working copy
290
+ is never changed and nothing is committed. Name a patch the working copy
291
+ no longer has by its HEAD filename to stage its removal, and name both
292
+ filenames of a renamed patch.
293
+ - `patch staged-dependency <name> --remove` names the entries to remove by
294
+ any of `--file`, `--specifier`/`--line`, `--creates` and `--owner`. It needs
295
+ `--creates`, or `--file` with the matcher. `--creates` alone removes every
296
+ declaration for that created path, which is the cleanup after a merge
297
+ deletes it. Registration lines compare whitespace-insensitively, as the
298
+ lint does. A selection that matches nothing is refused with exit 8 (it used
299
+ to print "no staged registration matched" and exit 0), on a dry run too.
300
+ - `patch staged-dependency <name> --add --from <json-file>` adds many
301
+ declarations in one call under one lock hold. The file is a `lint
302
+ --per-patch --report` document (its findings' `stagedDeclarations` for
303
+ that patch) or a JSON array of `{ file, specifier | line, creates, owner?,
304
+ reason?, patch? }` entries; entries naming another patch are skipped, and
305
+ `--reason` fills entries without one. Every `staged-dependency` call logs
306
+ only its delta (`added` / `removed`) in the history; records written
307
+ before 0.50.0 carry the whole `before` / `after` lists instead.
308
+
309
+ ### Parking a session's edits
310
+
311
+ Several sessions edit the same files, and a peer's `re-export
312
+ --refuse-foreign-drift` refuses while another session's hunks sit in its
313
+ patch's files. `fireforge park <patch>` sets them aside: every drifted file of
314
+ the patch goes back to its queue-applied baseline (or, with `--hunks <diff>`,
315
+ only the named hunks come out of the working tree), and both sides of each
316
+ file are kept under `.fireforge/park/<label>/`. After the owner commits,
317
+ `fireforge park --restore <label>` three-way merges the parked edits back on
318
+ top of the commit. A conflict leaves markers, keeps the park and exits
319
+ non-zero. `park --list` and `park --drop <label>` manage parks, `--dry-run`
320
+ previews, and park takes the engine lock.
321
+
322
+ Before parking it warns when a hunk it takes out removes a Fluent message id,
323
+ or a whole file, that files outside the patch still reference. A parked
324
+ string still used by markup another patch owns makes `document.l10n.ready`
325
+ reject in every new window, so move it somewhere you own first.
85
326
 
86
327
  Every queue-mutating patch command accepts `--wait-lock [seconds]`. See
87
328
  [`configuration.md`](configuration.md#lock-waits) for how the budget
@@ -105,17 +346,48 @@ as before. Errors and warnings always print in full, and the `--report`
105
346
  JSON is the same in both modes.
106
347
 
107
348
  `lint --per-patch --report <path>` writes a machine-readable JSON report with
108
- each patch's line count, tier, active size thresholds, issues, and issues
109
- suppressed by `lintIgnore`. The size metrics (`countNonBinaryDiffLines`,
110
- `resolvePatchSizeTier`, `getPatchSizeThresholds`) are also exported on the
111
- programmatic API. So is `filesMeasuredByFileTooLarge(patchText)`, which
112
- returns the files `file-too-large` measures in a patch: the JS files it
113
- creates, and never a modified file or a new file of another kind. The rule
114
- selects its files through that same function, so an audit that uses it
115
- cannot drift from the rule.
349
+ each patch's line count, the `metric` it was counted in
350
+ (`patchLint.patchSizeMetric`: `diff` or `addedCode`), tier, active size
351
+ thresholds, issues, and issues suppressed by `lintIgnore`, plus the
352
+ cross-patch findings in `queueIssues`. A `forward-import` or
353
+ `forward-registration` finding carries the declarations that discharge it as
354
+ `stagedDeclarations`, which `patch staged-dependency --from` reads. Size-rule
355
+ issues carry the value they measured as `measurement`. The size metrics
356
+ (`countNonBinaryDiffLines`, `resolvePatchSizeTier`,
357
+ `getPatchSizeThresholds(tier, config)`) are also exported on the
358
+ programmatic API, with the file-size helpers `isTestFile`,
359
+ `resolveFileSizeThresholds` and `countCodeLines`. So is
360
+ `filesMeasuredByFileTooLarge(patchText)`, which returns the files
361
+ `file-too-large` measures in a patch: the JS files it creates, and never a
362
+ modified file or a new file of another kind. The rule selects its files
363
+ through that same function, so an audit that uses it cannot drift from the
364
+ rule. Since 0.51.0 the API also exports the measurement the size rules
365
+ themselves take, `measurePatchSize(engineDir, diff, filesAffected, config)`
366
+ (lines, test-file lines, files and registration files under the configured
367
+ metric), `isPatchSizeRegistrationFile(file, globs)`, the per-line
368
+ `codeLineMask(file, content)` behind `countCodeLines`, and
369
+ `evaluatePatchPolicyContentForQueue(config, projectRoot, patchesDir,
370
+ manifest)`, the body-dependent `patchPolicy` rules `verify` runs. A waiver
371
+ audit or a policy probe can then measure a patch exactly as the lint does,
372
+ without importing `dist/` files by path.
373
+
374
+ `lint --per-patch` measures sizes on the working tree, the way export and
375
+ re-export do: each patch's diff is taken against the engine baseline from
376
+ the files in `engine/`, and `file-too-large` reads the file on disk. A test
377
+ file grown past its tier fails the lint gate while the edit is in progress,
378
+ without a re-export first. See
379
+ [`configuration.md`](configuration.md#patchlint) for the size metrics and
380
+ thresholds.
116
381
 
117
382
  Per-patch lint type-checks plain Firefox JS against a built-in
118
- Firefox-globals shim that tracks upstream WebIDL additions per release. See
383
+ Firefox-globals shim that tracks upstream WebIDL additions per release. A
384
+ `resource://` or `chrome://` import resolves to the module the queue
385
+ creates under that name; a test script's own program sees the modules
386
+ created up to the test's patch. An import the program cannot resolve (a
387
+ module a later patch creates, an upstream module, or a basename two
388
+ patches create) is typed `any`; an implicit-`any` finding in a file with
389
+ such an import names it, since the whole-tree `fireforge typecheck` sees
390
+ the real type. See
119
391
  [`configuration.md`](configuration.md#patchlint) for the project hooks
120
392
  (`checkJsExtraShim`, `checkJsTestFiles`, `checkJsTestShim`).
121
393
 
@@ -123,6 +395,95 @@ For release whitespace checks, use `npm run whitespace:check`. It still
123
395
  checks source diffs while excluding the diff syntax of generated
124
396
  `patches/*.patch` files.
125
397
 
398
+ ### Linting single files
399
+
400
+ `./mach eslint <files>` needs mach's `lint` virtualenv. When that cannot be
401
+ built on the host (a pinned requirement missing from PyPI, or a host Python
402
+ the requirements do not support), no Mozilla-configured ESLint runs, and a
403
+ bare `engine/node_modules/.bin/eslint` lacks the chrome-script globals and
404
+ reports mostly noise. FireForge's own checks cover part of the gap:
405
+
406
+ - `fireforge lint <path>...` lints only the named modified engine files
407
+ (paths relative to `engine/`; a leading `engine/` is accepted, and a
408
+ directory takes its modified and untracked files). It runs the
409
+ patch-quality rules on those files' diff against HEAD, plus the checkJs
410
+ pass when `patchLint.checkJs` is on. It needs no build.
411
+ - `fireforge lint --per-patch --patches <patch>` lints one patch's queued
412
+ diff, the gate an export is held to.
413
+ - `fireforge typecheck` type-checks the configured jsconfig projects.
414
+
415
+ None of these applies Mozilla's ESLint rule set, so a file they pass can
416
+ still fail `./mach eslint` once the virtualenv builds again.
417
+
418
+ ### Size waivers with a ceiling
419
+
420
+ A bare `lintIgnore` entry (`"file-too-large"`) suppresses its rule at any
421
+ size, so a waived file can keep growing. For the measured size rules
422
+ (`file-too-large`, `large-patch-lines`, `large-patch-files`) an entry can
423
+ instead carry a ceiling:
424
+
425
+ ```json
426
+ "lintIgnore": [
427
+ "modified-file-missing-header",
428
+ { "rule": "file-too-large", "max": 1200, "reason": "Controller split tracked in #412" }
429
+ ]
430
+ ```
431
+
432
+ ```sh
433
+ fireforge patch lint-ignore 310-ui-sidebar --add file-too-large --max 1200 --reason "Controller split tracked in #412"
434
+ ```
435
+
436
+ The finding stays suppressed while its measurement (lines for the line
437
+ rules, files for `large-patch-files`) is at or below `max`, and fires at its
438
+ own severity past it, with a message naming the ceiling. Once the rule no
439
+ longer reaches its own warning band, `lint --per-patch` prints a
440
+ `lint-ignore-removable` notice naming the `patch lint-ignore --remove`
441
+ command, and the `--report` JSON carries each ceiling under
442
+ `lintIgnoreCeilings` with its current `measurement` and a `state` of
443
+ `within`, `exceeded` or `removable`. A notice never exceeds a ceiling: it
444
+ gates nothing, so a notice-band measurement past `max` surfaces as the plain
445
+ notice it is. The report's `lintIgnoreWaivers` lists every entry, bare IDs
446
+ included (`kind: "bare"`, `max: null`), with the same `measurement` and
447
+ `state`, so an audit does not have to re-measure bare waivers from
448
+ `suppressedIssues`. For a bare waiver the state is informational:
449
+ `removable` also covers a rule this lint does not run. An `--add` for a rule
450
+ that already has an entry replaces it, so the same command moves a ceiling
451
+ or widens it back to a bare waiver. `export` and `re-export --lint-ignore
452
+ <id>` only append bare IDs, and never widen an existing ceiling. The manifest
453
+ refuses a ceiling on any other rule, and a second entry for the same rule.
454
+
455
+ Under `patchSizeMetric: "addedCode"` a general-tier patch's
456
+ `large-patch-lines` findings carry `share: "product"` (non-test files) or
457
+ `share: "test"` (test files, held to the test tier). A ceiling can name the
458
+ share it bounds, and `large-patch-lines` may then carry one ceiling per
459
+ share instead of a single one:
460
+
461
+ ```json
462
+ "lintIgnore": [
463
+ { "rule": "large-patch-lines", "share": "product", "max": 900, "reason": "…" },
464
+ { "rule": "large-patch-lines", "share": "test", "max": 4000, "reason": "…",
465
+ "meta": { "kind": "structural", "owner": "tiles" } }
466
+ ]
467
+ ```
468
+
469
+ A ceiling's optional `meta` is a free-form JSON object FireForge keeps
470
+ verbatim and never reads, for a project's own waiver bookkeeping (kind,
471
+ owner, exit condition). The report echoes it.
472
+
473
+ Several ceilings go in one call, under one lock hold and one manifest write:
474
+ `--add <rule>=<max>` gives each entry its own ceiling (`--reason` applies to
475
+ all of them, `--share` scopes a `large-patch-lines` one), and `--from
476
+ <json-file>` adds every entry of a JSON array (check IDs, or `{ rule, max,
477
+ reason?, share?, meta?, patch? }` objects; entries naming another patch are
478
+ skipped, and `--reason` fills objects without one). Given a `lint --per-patch
479
+ --report` document instead, `--from` rebaselines each of the patch's
480
+ ceilings to its current `measurement`.
481
+
482
+ ```sh
483
+ fireforge patch lint-ignore 310-ui-sidebar --add file-too-large=1200 --add large-patch-files=40 --reason "Rebaseline 2026-10"
484
+ fireforge patch lint-ignore 310-ui-sidebar --from lint-report.json
485
+ ```
486
+
126
487
  ## CI enforcement
127
488
 
128
489
  `fireforge status --check` exits non-zero when any unmanaged, drifted, or
@@ -131,6 +492,68 @@ conflicted file exists. `--fail-on <class,...>` tunes the policy set, and
131
492
  name the owning `patch`. `--include-ownership` adds ownership rows to JSON
132
493
  status output.
133
494
 
495
+ `ownership-conflict` is a `--fail-on` class beside the file classifications:
496
+ a path claimed by more than one patch, judged from the same rows as
497
+ `--include-ownership` (so it fails on a clean tree too). It is not in the
498
+ default set. `--allow <class,...>` names classes that never fail and removes
499
+ them from the fail set. `--fail-on-unknown` (which needs `--allow`) fails
500
+ every class present that neither list names, so a gate written today keeps
501
+ failing closed when a later FireForge adds a classification:
502
+
503
+ ```bash
504
+ npx fireforge status --json --summary \
505
+ --fail-on unmanaged,patch-owned-drift,conflict,ownership-conflict \
506
+ --allow patch-backed,branding,furnace,binary-unsupported --fail-on-unknown
507
+ ```
508
+
509
+ The JSON `check` block echoes `allow` and `failOnUnknown` when they are set,
510
+ and marks an offender that failed only through `--fail-on-unknown` with
511
+ `"unnamed": true`. Under `--check` the full `--json` payload carries the
512
+ `check` block too, not only `--summary`.
513
+
514
+ On a shared engine, `--patches <list>` judges only the named patches:
515
+ `status` keeps the files each one owns or claims (and, with `--ownership` or
516
+ the ownership checks, the rows naming it), and `verify` keeps the findings
517
+ that name it. Patches are named the way `lint --per-patch --patches` names
518
+ them (filename, stem, order number, manifest name or slug). A peer's drifted
519
+ patch then no longer turns this session's gate red. The limit is the
520
+ point: an unmanaged file belongs to no patch, so a scoped run never judges
521
+ unexported files, this session's included. Run the unscoped gate before a
522
+ commit that should cover them. `lint --per-patch --patches` keeps every
523
+ queue finding that names a scoped patch, even on a file the patch no longer
524
+ touches (a stale `staged-dependency-unused` declaration, since 0.51.0). `verify --patches` keeps findings about the
525
+ queue as a whole (a `patches.json` that does not parse) whatever the scope.
526
+
527
+ `verify` resolves every `resource:///modules/…` and `resource://gre/modules/…`
528
+ `.sys.mjs` specifier the queue's files import, and fails on a fork module that
529
+ is missing or that no `moz.build` at or above its directory registers. Such
530
+ an import otherwise crashes every browser start of the objdir, or kills
531
+ xpcshell with no output. An unscoped run also scans the engine worktree:
532
+ changed files are read for imports, and untracked files count as fork
533
+ modules. That covers a chrome script's lazy getter for a module that neither
534
+ file has exported yet.
535
+
536
+ `fireforge check` composes the static gates into one command: `status`
537
+ (as `--json --summary --check`), `verify`, `lint --per-patch`,
538
+ `typecheck`, `furnace validate` and `token coverage`, plus the project's
539
+ `check.extraSteps[]` (see [`configuration.md`](configuration.md#check)). The
540
+ steps run in parallel as child `fireforge` processes, so each behaves exactly
541
+ as it does standalone and takes only its own locks; `check` takes none
542
+ itself. None of the built-in steps takes the engine session lock. The one
543
+ built-in step that writes, `typecheck` (it regenerates a stale Furnace
544
+ jsconfig), runs before `furnace validate`, which reads that file. `--patches`
545
+ is forwarded to status, verify and lint. `--only` and `--skip` narrow the
546
+ step list, and `--jobs <n>` caps how many run at once. The run ends with a
547
+ per-step table and one verdict line:
548
+
549
+ ```
550
+ FIREFORGE-VERDICT: PASS steps=<n> skipped=<n>
551
+ FIREFORGE-VERDICT: FAIL reason=step-failures|lock-timeout failed=<step,...> steps=<n> skipped=<n>
552
+ ```
553
+
554
+ `--json` replaces both with one document. A failed step's last 40 output
555
+ lines are printed under the table.
556
+
134
557
  The JSON shapes are specified in [`machine-output.md`](machine-output.md),
135
558
  and the codes a script should branch on in
136
559
  [`exit-codes.md`](exit-codes.md).
@@ -161,9 +584,22 @@ context` (with the step it needed) or `reject` (with the files git named).
161
584
  It exits 6 when anything rejects. A reject after an earlier one is marked
162
585
  as possibly cascading from it, since the replay continues without the
163
586
  rejected patch. Run it after the download, when HEAD is the new source. The
164
- header names the oldest patch stamp as "from", compared as versions rather
165
- than strings, prints the spread when the queue carries several stamps, and
166
- warns when `engine/` is not yet on the pinned version.
587
+ header names the previous pin as "from": `firefox.version` in the
588
+ `fireforge.json` committed at the project's HEAD, the tree the queue was
589
+ last applied to (since 0.50.0). The patch stamps follow on their own line
590
+ (`Patch stamps: 317 at 153.0esr, 5 at 153.3.0esr`), oldest first and
591
+ compared as versions rather than strings, because patches untouched since an
592
+ older hop keep their older stamp. When no committed pin precedes the target
593
+ (the project is not a repository, or the pin move is already committed), the
594
+ header falls back to the oldest stamp and says so. The header also warns
595
+ when `engine/` is not yet on the pinned version.
596
+
597
+ A source refresh replaces `engine/node_modules`, which an engine-ESLint
598
+ ratchet runs from. `fireforge doctor` warns ("Engine ESLint installed") until
599
+ it is back. Reinstall it with `cd engine && ./mach npm ci --no-audit
600
+ --no-fund`, which installs only the node dependencies. `./mach eslint
601
+ --setup` also builds mach's Python `lint` virtualenv, and that step fails
602
+ whenever a requirement it pins is no longer served by PyPI.
167
603
 
168
604
  If a patch fails, fix the reject inside `engine/`, then run
169
605
  `npx fireforge rebase --continue`. A rebase leaves the engine mutated on
package/docs/run-logs.md CHANGED
@@ -53,7 +53,8 @@ the mistake.
53
53
  - **Opened before any preflight**, so a refusal is logged too. Those are
54
54
  exactly the runs whose only output a `tail` throws away.
55
55
  - **Retained 20 deep per command kind.** Per kind, so that a busy `test` loop
56
- cannot evict the `build` log an operator needs to read beside it. Pruning
56
+ cannot evict the `build` log an operator needs to read beside it. A
57
+ `--keep-log` copy (below) is outside this count. Pruning
57
58
  runs before the new file is opened, and orders by the timestamp in the
58
59
  filename rather than by stat-ing every entry.
59
60
  - **Timestamps are filesystem-safe.** They use the ISO form with `:` and `.`
@@ -67,6 +68,24 @@ the mistake.
67
68
  active log, also best-effort, before exiting, so the held partial line of a
68
69
  killed run reaches disk.
69
70
 
71
+ ## Keeping a log past retention
72
+
73
+ Retention is shared by every session on the checkout, so on a busy shared
74
+ checkout a log a verdict line names can be pruned within the hour.
75
+ `--keep-log <path>` (on `test` and `build`) also writes the run's log to a
76
+ path of the caller's choosing, outside `.fireforge/logs/` and never pruned.
77
+ `FIREFORGE_LOG_KEEP=<path>` sets the same for every run in a session. The
78
+ flag wins over the variable. A value ending in `/` or naming an existing
79
+ directory receives the log under its own `.fireforge/logs/` file name, so a
80
+ session directory collects one file per run; any other value is the file
81
+ itself. Relative values resolve against the working directory.
82
+
83
+ The copy is written live, line for line through the same redaction, rather
84
+ than copied at the end, so a killed run keeps what it had written, exactly
85
+ as the primary log does. `log=` still names the primary. The copy is
86
+ best-effort too: if it cannot be opened, the run proceeds with the primary
87
+ alone, and if the primary cannot be opened, the copy stands in for it.
88
+
70
89
  ## Preflight refusals
71
90
 
72
91
  `fireforge test` opens its run log before any preflight, so a refusal is