@hominis/fireforge 0.43.0 → 0.44.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 (470) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/README.md +10 -1
  3. package/dist/bin/fireforge.d.ts +4 -9
  4. package/dist/bin/fireforge.js +42 -40
  5. package/dist/build-info.json +4 -4
  6. package/dist/src/cli.js +74 -8
  7. package/dist/src/commands/bootstrap.js +17 -20
  8. package/dist/src/commands/build.js +25 -32
  9. package/dist/src/commands/config.js +34 -41
  10. package/dist/src/commands/discard.js +8 -9
  11. package/dist/src/commands/doctor-check-core.d.ts +4 -18
  12. package/dist/src/commands/doctor-check-core.js +8 -35
  13. package/dist/src/commands/doctor-external-toolchains.js +2 -2
  14. package/dist/src/commands/doctor-furnace-config-sync.d.ts +18 -0
  15. package/dist/src/commands/{doctor-furnace-manifest-sync.js → doctor-furnace-config-sync.js} +27 -26
  16. package/dist/src/commands/doctor-furnace-jar.d.ts +7 -9
  17. package/dist/src/commands/doctor-furnace-jar.js +7 -9
  18. package/dist/src/commands/doctor-furnace.js +21 -24
  19. package/dist/src/commands/doctor-orphaned-harness.d.ts +16 -20
  20. package/dist/src/commands/doctor-orphaned-harness.js +18 -44
  21. package/dist/src/commands/doctor-post-rebase-audit.d.ts +2 -0
  22. package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
  23. package/dist/src/commands/doctor-working-tree.d.ts +10 -12
  24. package/dist/src/commands/doctor-working-tree.js +11 -18
  25. package/dist/src/commands/doctor.js +35 -53
  26. package/dist/src/commands/download.js +32 -48
  27. package/dist/src/commands/export-all.js +29 -33
  28. package/dist/src/commands/export-flow.js +6 -6
  29. package/dist/src/commands/export-placement-conflicts.js +10 -9
  30. package/dist/src/commands/export-placement-gate.js +5 -5
  31. package/dist/src/commands/export-shared.d.ts +30 -33
  32. package/dist/src/commands/export-shared.js +45 -43
  33. package/dist/src/commands/export.js +21 -27
  34. package/dist/src/commands/furnace/apply.js +14 -23
  35. package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
  36. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
  37. package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
  38. package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
  39. package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
  40. package/dist/src/commands/furnace/chrome-doc.js +31 -41
  41. package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
  42. package/dist/src/commands/furnace/create-browser-test.js +8 -8
  43. package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
  44. package/dist/src/commands/furnace/create-mochikit.js +8 -9
  45. package/dist/src/commands/furnace/create-readback.d.ts +12 -13
  46. package/dist/src/commands/furnace/create-readback.js +12 -13
  47. package/dist/src/commands/furnace/create-templates.d.ts +22 -28
  48. package/dist/src/commands/furnace/create-templates.js +35 -30
  49. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  50. package/dist/src/commands/furnace/create.js +33 -23
  51. package/dist/src/commands/furnace/deploy.js +16 -24
  52. package/dist/src/commands/furnace/diff.js +9 -9
  53. package/dist/src/commands/furnace/init.js +33 -36
  54. package/dist/src/commands/furnace/override.js +23 -25
  55. package/dist/src/commands/furnace/preview.js +47 -67
  56. package/dist/src/commands/furnace/refresh.js +12 -16
  57. package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
  58. package/dist/src/commands/furnace/remove-mutations.js +9 -16
  59. package/dist/src/commands/furnace/remove-state.js +1 -1
  60. package/dist/src/commands/furnace/remove.js +33 -38
  61. package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
  62. package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
  63. package/dist/src/commands/furnace/rename.js +59 -69
  64. package/dist/src/commands/furnace/scan.js +19 -33
  65. package/dist/src/commands/furnace/status.js +12 -9
  66. package/dist/src/commands/furnace/sync.js +9 -16
  67. package/dist/src/commands/furnace/validate.js +20 -20
  68. package/dist/src/commands/import.js +42 -35
  69. package/dist/src/commands/lint-per-patch.js +15 -10
  70. package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
  71. package/dist/src/commands/lint-per-run-checkjs.js +13 -13
  72. package/dist/src/commands/lint-report.js +6 -7
  73. package/dist/src/commands/lint.d.ts +15 -16
  74. package/dist/src/commands/lint.js +86 -102
  75. package/dist/src/commands/package.js +6 -11
  76. package/dist/src/commands/patch/compact.js +1 -1
  77. package/dist/src/commands/patch/delete.js +24 -33
  78. package/dist/src/commands/patch/index.d.ts +5 -6
  79. package/dist/src/commands/patch/index.js +10 -12
  80. package/dist/src/commands/patch/lint-ignore.js +14 -13
  81. package/dist/src/commands/patch/move-files-create.d.ts +6 -6
  82. package/dist/src/commands/patch/move-files-create.js +8 -8
  83. package/dist/src/commands/patch/move-files-into.d.ts +6 -6
  84. package/dist/src/commands/patch/move-files-into.js +8 -8
  85. package/dist/src/commands/patch/patch-context.d.ts +2 -3
  86. package/dist/src/commands/patch/patch-context.js +2 -3
  87. package/dist/src/commands/patch/rename.d.ts +10 -11
  88. package/dist/src/commands/patch/rename.js +11 -12
  89. package/dist/src/commands/patch/reorder.js +8 -9
  90. package/dist/src/commands/patch/split-plan.js +1 -1
  91. package/dist/src/commands/patch/split.d.ts +8 -10
  92. package/dist/src/commands/patch/split.js +9 -11
  93. package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
  94. package/dist/src/commands/patch/staged-dependency.js +7 -3
  95. package/dist/src/commands/patch/tier.d.ts +9 -9
  96. package/dist/src/commands/patch/tier.js +18 -15
  97. package/dist/src/commands/re-export-adjacent.js +9 -8
  98. package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
  99. package/dist/src/commands/re-export-bulk-scan.js +22 -21
  100. package/dist/src/commands/re-export-drift.js +10 -10
  101. package/dist/src/commands/re-export-files.d.ts +6 -7
  102. package/dist/src/commands/re-export-files.js +34 -44
  103. package/dist/src/commands/re-export-lint.d.ts +10 -13
  104. package/dist/src/commands/re-export-lint.js +16 -14
  105. package/dist/src/commands/re-export-options.d.ts +6 -6
  106. package/dist/src/commands/re-export-options.js +6 -6
  107. package/dist/src/commands/re-export-register.d.ts +4 -10
  108. package/dist/src/commands/re-export-register.js +5 -11
  109. package/dist/src/commands/re-export-scan.js +1 -1
  110. package/dist/src/commands/re-export-single.d.ts +3 -3
  111. package/dist/src/commands/re-export-single.js +33 -25
  112. package/dist/src/commands/re-export.js +13 -21
  113. package/dist/src/commands/rebase/abort.js +3 -3
  114. package/dist/src/commands/rebase/confirm.js +2 -1
  115. package/dist/src/commands/rebase/continue.d.ts +1 -1
  116. package/dist/src/commands/rebase/continue.js +14 -9
  117. package/dist/src/commands/rebase/index.js +11 -11
  118. package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
  119. package/dist/src/commands/rebase/patch-loop.js +60 -39
  120. package/dist/src/commands/rebase/summary.js +1 -1
  121. package/dist/src/commands/register.js +5 -7
  122. package/dist/src/commands/reset.js +2 -1
  123. package/dist/src/commands/resolve.d.ts +16 -13
  124. package/dist/src/commands/resolve.js +30 -33
  125. package/dist/src/commands/run.js +33 -35
  126. package/dist/src/commands/setup-support.js +15 -19
  127. package/dist/src/commands/setup.js +13 -9
  128. package/dist/src/commands/status-check.d.ts +6 -6
  129. package/dist/src/commands/status-json.d.ts +6 -6
  130. package/dist/src/commands/status-json.js +5 -0
  131. package/dist/src/commands/status-output.d.ts +5 -5
  132. package/dist/src/commands/status-output.js +6 -6
  133. package/dist/src/commands/status-ownership.d.ts +16 -14
  134. package/dist/src/commands/status-ownership.js +16 -14
  135. package/dist/src/commands/status.js +42 -34
  136. package/dist/src/commands/test-build-phase.js +26 -13
  137. package/dist/src/commands/test-diagnose.js +44 -44
  138. package/dist/src/commands/test-register.js +1 -0
  139. package/dist/src/commands/test-run.d.ts +14 -16
  140. package/dist/src/commands/test-run.js +21 -13
  141. package/dist/src/commands/test-stale-gate.d.ts +15 -16
  142. package/dist/src/commands/test-stale-gate.js +15 -16
  143. package/dist/src/commands/test-verdict.d.ts +20 -3
  144. package/dist/src/commands/test-verdict.js +30 -0
  145. package/dist/src/commands/test.js +83 -76
  146. package/dist/src/commands/token-coverage.js +3 -4
  147. package/dist/src/commands/token.js +16 -18
  148. package/dist/src/commands/tree.js +47 -31
  149. package/dist/src/commands/typecheck.d.ts +10 -12
  150. package/dist/src/commands/typecheck.js +24 -27
  151. package/dist/src/commands/verify.js +13 -15
  152. package/dist/src/commands/watch.js +21 -23
  153. package/dist/src/commands/wire.js +52 -62
  154. package/dist/src/core/ast-utils.d.ts +8 -7
  155. package/dist/src/core/ast-utils.js +8 -7
  156. package/dist/src/core/branding.d.ts +9 -14
  157. package/dist/src/core/branding.js +7 -11
  158. package/dist/src/core/browser-wire.d.ts +1 -1
  159. package/dist/src/core/browser-wire.js +14 -19
  160. package/dist/src/core/build-audit-platform.js +25 -33
  161. package/dist/src/core/build-audit-registration.js +17 -25
  162. package/dist/src/core/build-audit-resolve.d.ts +15 -17
  163. package/dist/src/core/build-audit-resolve.js +27 -33
  164. package/dist/src/core/build-audit-transforms.js +18 -19
  165. package/dist/src/core/build-audit.js +28 -42
  166. package/dist/src/core/build-baseline-types.d.ts +29 -35
  167. package/dist/src/core/build-baseline.d.ts +5 -4
  168. package/dist/src/core/build-baseline.js +14 -14
  169. package/dist/src/core/build-overwrite-guard.d.ts +7 -14
  170. package/dist/src/core/build-overwrite-guard.js +62 -19
  171. package/dist/src/core/build-prepare.d.ts +2 -2
  172. package/dist/src/core/build-prepare.js +8 -9
  173. package/dist/src/core/config-state.js +12 -4
  174. package/dist/src/core/config-validate.js +8 -5
  175. package/dist/src/core/config.d.ts +5 -8
  176. package/dist/src/core/config.js +7 -16
  177. package/dist/src/core/coverage-extend.d.ts +15 -13
  178. package/dist/src/core/coverage-extend.js +15 -13
  179. package/dist/src/core/destructive.d.ts +31 -9
  180. package/dist/src/core/destructive.js +29 -26
  181. package/dist/src/core/diff-hunks.js +10 -12
  182. package/dist/src/core/discard-baseline.d.ts +8 -8
  183. package/dist/src/core/discard-baseline.js +16 -15
  184. package/dist/src/core/display-state.d.ts +12 -14
  185. package/dist/src/core/display-state.js +12 -14
  186. package/dist/src/core/engine-changes.d.ts +2 -4
  187. package/dist/src/core/engine-changes.js +2 -4
  188. package/dist/src/core/engine-precondition.d.ts +28 -13
  189. package/dist/src/core/engine-precondition.js +25 -10
  190. package/dist/src/core/engine-session-lock.d.ts +47 -21
  191. package/dist/src/core/engine-session-lock.js +168 -33
  192. package/dist/src/core/export-moved-code-shape.d.ts +10 -10
  193. package/dist/src/core/export-moved-code-shape.js +10 -10
  194. package/dist/src/core/file-lock.d.ts +62 -5
  195. package/dist/src/core/file-lock.js +196 -59
  196. package/dist/src/core/firefox-cache.js +1 -3
  197. package/dist/src/core/firefox-extract.js +10 -14
  198. package/dist/src/core/firefox.d.ts +7 -5
  199. package/dist/src/core/firefox.js +4 -5
  200. package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
  201. package/dist/src/core/furnace-apply-dry-run.js +3 -2
  202. package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
  203. package/dist/src/core/furnace-apply-ftl.js +12 -12
  204. package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
  205. package/dist/src/core/furnace-apply-helpers.js +19 -16
  206. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
  207. package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
  208. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
  209. package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
  210. package/dist/src/core/furnace-apply.d.ts +5 -8
  211. package/dist/src/core/furnace-apply.js +19 -24
  212. package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
  213. package/dist/src/core/furnace-checksum-utils.js +16 -0
  214. package/dist/src/core/furnace-config.d.ts +21 -30
  215. package/dist/src/core/furnace-config.js +32 -54
  216. package/dist/src/core/furnace-constants.d.ts +8 -0
  217. package/dist/src/core/furnace-constants.js +10 -2
  218. package/dist/src/core/furnace-css-fragments.d.ts +5 -5
  219. package/dist/src/core/furnace-css-fragments.js +5 -5
  220. package/dist/src/core/furnace-jsconfig.d.ts +11 -12
  221. package/dist/src/core/furnace-jsconfig.js +11 -12
  222. package/dist/src/core/furnace-marker.d.ts +7 -8
  223. package/dist/src/core/furnace-marker.js +7 -8
  224. package/dist/src/core/furnace-operation.d.ts +31 -0
  225. package/dist/src/core/furnace-operation.js +54 -17
  226. package/dist/src/core/furnace-precondition.d.ts +61 -0
  227. package/dist/src/core/furnace-precondition.js +67 -0
  228. package/dist/src/core/furnace-registration-remove.d.ts +5 -6
  229. package/dist/src/core/furnace-registration-remove.js +6 -7
  230. package/dist/src/core/furnace-registration-validate.d.ts +13 -0
  231. package/dist/src/core/furnace-registration-validate.js +20 -1
  232. package/dist/src/core/furnace-registration.d.ts +5 -6
  233. package/dist/src/core/furnace-registration.js +10 -12
  234. package/dist/src/core/furnace-rollback.js +7 -7
  235. package/dist/src/core/furnace-scanner.d.ts +1 -0
  236. package/dist/src/core/furnace-scanner.js +1 -2
  237. package/dist/src/core/furnace-stale-export.d.ts +10 -10
  238. package/dist/src/core/furnace-stale-export.js +10 -10
  239. package/dist/src/core/furnace-state-persist.d.ts +6 -8
  240. package/dist/src/core/furnace-state-persist.js +6 -8
  241. package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
  242. package/dist/src/core/furnace-validate-accessibility.js +310 -1
  243. package/dist/src/core/furnace-validate-compatibility.js +141 -1
  244. package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
  245. package/dist/src/core/furnace-validate-helpers.js +0 -491
  246. package/dist/src/core/furnace-validate-registration.d.ts +7 -0
  247. package/dist/src/core/furnace-validate-registration.js +115 -41
  248. package/dist/src/core/furnace-validate.d.ts +16 -0
  249. package/dist/src/core/furnace-validate.js +188 -145
  250. package/dist/src/core/furnace-version-drift.d.ts +10 -11
  251. package/dist/src/core/furnace-version-drift.js +10 -11
  252. package/dist/src/core/git-base.d.ts +7 -9
  253. package/dist/src/core/git-base.js +7 -9
  254. package/dist/src/core/git-diff.d.ts +20 -9
  255. package/dist/src/core/git-diff.js +148 -63
  256. package/dist/src/core/git-file-ops.d.ts +19 -19
  257. package/dist/src/core/git-file-ops.js +19 -19
  258. package/dist/src/core/git-readonly-index.d.ts +53 -19
  259. package/dist/src/core/git-readonly-index.js +75 -31
  260. package/dist/src/core/git-status.d.ts +5 -6
  261. package/dist/src/core/git-status.js +5 -6
  262. package/dist/src/core/git.d.ts +9 -10
  263. package/dist/src/core/git.js +54 -70
  264. package/dist/src/core/license-headers.d.ts +17 -23
  265. package/dist/src/core/license-headers.js +17 -23
  266. package/dist/src/core/mach-build-artifacts.d.ts +9 -9
  267. package/dist/src/core/mach-build-artifacts.js +57 -19
  268. package/dist/src/core/mach-error-hints.js +42 -29
  269. package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
  270. package/dist/src/core/mach-known-noise-filter.js +18 -20
  271. package/dist/src/core/mach-mozconfig.d.ts +14 -0
  272. package/dist/src/core/mach-mozconfig.js +30 -0
  273. package/dist/src/core/mach-resource-shim.d.ts +43 -58
  274. package/dist/src/core/mach-resource-shim.js +50 -67
  275. package/dist/src/core/mach.d.ts +37 -41
  276. package/dist/src/core/mach.js +50 -51
  277. package/dist/src/core/marionette-port.d.ts +36 -1
  278. package/dist/src/core/marionette-port.js +96 -31
  279. package/dist/src/core/marionette-preflight.d.ts +8 -12
  280. package/dist/src/core/marionette-preflight.js +86 -52
  281. package/dist/src/core/mochitest-server-port.d.ts +42 -0
  282. package/dist/src/core/mochitest-server-port.js +119 -0
  283. package/dist/src/core/module-resolution-preflight.d.ts +9 -11
  284. package/dist/src/core/module-resolution-preflight.js +9 -11
  285. package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
  286. package/dist/src/core/moz-manifest-helpers.js +163 -0
  287. package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
  288. package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
  289. package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
  290. package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
  291. package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
  292. package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
  293. package/dist/src/core/ownership-table.d.ts +14 -16
  294. package/dist/src/core/ownership-table.js +14 -16
  295. package/dist/src/core/parser-fallback.d.ts +15 -1
  296. package/dist/src/core/parser-fallback.js +22 -3
  297. package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
  298. package/dist/src/core/patch-apply-fuzz.js +15 -18
  299. package/dist/src/core/patch-apply.d.ts +13 -34
  300. package/dist/src/core/patch-apply.js +16 -76
  301. package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
  302. package/dist/src/core/patch-artifact-normalize.js +13 -15
  303. package/dist/src/core/patch-export-metadata.d.ts +12 -2
  304. package/dist/src/core/patch-export-metadata.js +13 -4
  305. package/dist/src/core/patch-export-update.d.ts +13 -1
  306. package/dist/src/core/patch-export-update.js +25 -8
  307. package/dist/src/core/patch-export.d.ts +15 -8
  308. package/dist/src/core/patch-export.js +10 -11
  309. package/dist/src/core/patch-files.d.ts +5 -6
  310. package/dist/src/core/patch-files.js +5 -6
  311. package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
  312. package/dist/src/core/patch-identifier-suggest.js +14 -19
  313. package/dist/src/core/patch-lint-binary.d.ts +51 -0
  314. package/dist/src/core/patch-lint-binary.js +39 -0
  315. package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
  316. package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
  317. package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
  318. package/dist/src/core/patch-lint-checkjs.js +16 -16
  319. package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
  320. package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
  321. package/dist/src/core/patch-lint-creators.d.ts +8 -8
  322. package/dist/src/core/patch-lint-creators.js +8 -8
  323. package/dist/src/core/patch-lint-cross.d.ts +17 -20
  324. package/dist/src/core/patch-lint-cross.js +16 -16
  325. package/dist/src/core/patch-lint-css.d.ts +3 -5
  326. package/dist/src/core/patch-lint-css.js +17 -22
  327. package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
  328. package/dist/src/core/patch-lint-diff-tag.js +14 -16
  329. package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
  330. package/dist/src/core/patch-lint-jsdoc.js +26 -19
  331. package/dist/src/core/patch-lint-mozbuild.js +20 -20
  332. package/dist/src/core/patch-lint-observer.d.ts +7 -7
  333. package/dist/src/core/patch-lint-observer.js +7 -7
  334. package/dist/src/core/patch-lint.d.ts +38 -29
  335. package/dist/src/core/patch-lint.js +89 -121
  336. package/dist/src/core/patch-lock.d.ts +1 -1
  337. package/dist/src/core/patch-lock.js +45 -9
  338. package/dist/src/core/patch-manifest-consistency.d.ts +2 -1
  339. package/dist/src/core/patch-manifest-consistency.js +25 -27
  340. package/dist/src/core/patch-manifest-io.js +14 -18
  341. package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
  342. package/dist/src/core/patch-manifest-resolve.js +9 -12
  343. package/dist/src/core/patch-manifest-validate.d.ts +6 -1
  344. package/dist/src/core/patch-manifest-validate.js +25 -13
  345. package/dist/src/core/patch-parse.d.ts +24 -13
  346. package/dist/src/core/patch-parse.js +19 -14
  347. package/dist/src/core/patch-policy.d.ts +2 -2
  348. package/dist/src/core/patch-policy.js +2 -2
  349. package/dist/src/core/patch-registration-refs.d.ts +7 -8
  350. package/dist/src/core/patch-registration-refs.js +9 -9
  351. package/dist/src/core/process-cpu-time.d.ts +12 -0
  352. package/dist/src/core/process-cpu-time.js +52 -0
  353. package/dist/src/core/rebase-session.d.ts +38 -26
  354. package/dist/src/core/rebase-session.js +55 -28
  355. package/dist/src/core/register-browser-content.js +9 -74
  356. package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
  357. package/dist/src/core/register-module.js +10 -43
  358. package/dist/src/core/register-scaffold.d.ts +8 -8
  359. package/dist/src/core/register-scaffold.js +10 -10
  360. package/dist/src/core/register-shared-css.d.ts +9 -12
  361. package/dist/src/core/register-shared-css.js +17 -87
  362. package/dist/src/core/register-xpcshell-test.d.ts +6 -7
  363. package/dist/src/core/register-xpcshell-test.js +7 -8
  364. package/dist/src/core/shared-ftl.js +12 -4
  365. package/dist/src/core/state-file.d.ts +45 -0
  366. package/dist/src/core/state-file.js +34 -0
  367. package/dist/src/core/status-binary.d.ts +18 -11
  368. package/dist/src/core/status-binary.js +19 -12
  369. package/dist/src/core/status-classify.d.ts +30 -36
  370. package/dist/src/core/status-classify.js +21 -26
  371. package/dist/src/core/test-changed-prefs.d.ts +30 -0
  372. package/dist/src/core/test-changed-prefs.js +78 -0
  373. package/dist/src/core/test-harness-crash.d.ts +25 -63
  374. package/dist/src/core/test-harness-crash.js +121 -142
  375. package/dist/src/core/test-harness-signature.d.ts +18 -0
  376. package/dist/src/core/test-harness-signature.js +13 -0
  377. package/dist/src/core/test-path-scope.d.ts +12 -15
  378. package/dist/src/core/test-path-scope.js +15 -18
  379. package/dist/src/core/test-stale-check.d.ts +17 -17
  380. package/dist/src/core/test-stale-check.js +50 -43
  381. package/dist/src/core/test-stall-triage.d.ts +51 -0
  382. package/dist/src/core/test-stall-triage.js +126 -0
  383. package/dist/src/core/token-category.js +5 -7
  384. package/dist/src/core/token-coverage.js +9 -9
  385. package/dist/src/core/token-dark-mode.d.ts +9 -11
  386. package/dist/src/core/token-dark-mode.js +9 -11
  387. package/dist/src/core/token-docs.d.ts +2 -0
  388. package/dist/src/core/token-docs.js +2 -0
  389. package/dist/src/core/token-manager.d.ts +10 -2
  390. package/dist/src/core/token-manager.js +17 -8
  391. package/dist/src/core/token-scaffold.d.ts +7 -7
  392. package/dist/src/core/token-scaffold.js +7 -7
  393. package/dist/src/core/token-variant.d.ts +4 -4
  394. package/dist/src/core/token-variant.js +4 -4
  395. package/dist/src/core/toolchain-preflight.d.ts +20 -21
  396. package/dist/src/core/toolchain-preflight.js +28 -31
  397. package/dist/src/core/tree-guard.js +12 -2
  398. package/dist/src/core/tree-store.d.ts +41 -27
  399. package/dist/src/core/tree-store.js +63 -42
  400. package/dist/src/core/typecheck-shim.d.ts +23 -24
  401. package/dist/src/core/typecheck-shim.js +23 -24
  402. package/dist/src/core/typecheck.js +8 -8
  403. package/dist/src/core/wire-destroy.d.ts +3 -3
  404. package/dist/src/core/wire-destroy.js +7 -15
  405. package/dist/src/core/wire-dom-fragment.d.ts +9 -17
  406. package/dist/src/core/wire-dom-fragment.js +11 -44
  407. package/dist/src/core/wire-init.d.ts +2 -3
  408. package/dist/src/core/wire-init.js +8 -19
  409. package/dist/src/core/wire-subscript.d.ts +2 -3
  410. package/dist/src/core/wire-subscript.js +4 -13
  411. package/dist/src/core/wire-utils.d.ts +7 -8
  412. package/dist/src/core/wire-utils.js +7 -8
  413. package/dist/src/core/xpcshell-appdir.d.ts +38 -45
  414. package/dist/src/core/xpcshell-appdir.js +38 -45
  415. package/dist/src/errors/base.d.ts +37 -16
  416. package/dist/src/errors/base.js +37 -16
  417. package/dist/src/errors/build.d.ts +13 -1
  418. package/dist/src/errors/build.js +14 -2
  419. package/dist/src/errors/codes.d.ts +16 -0
  420. package/dist/src/errors/codes.js +16 -0
  421. package/dist/src/errors/furnace.d.ts +4 -4
  422. package/dist/src/errors/git.d.ts +6 -10
  423. package/dist/src/errors/git.js +6 -10
  424. package/dist/src/errors/patch.d.ts +7 -7
  425. package/dist/src/errors/patch.js +7 -7
  426. package/dist/src/errors/rebase.d.ts +4 -4
  427. package/dist/src/errors/rebase.js +4 -4
  428. package/dist/src/index.d.ts +7 -1
  429. package/dist/src/index.js +5 -0
  430. package/dist/src/types/commands/index.d.ts +2 -2
  431. package/dist/src/types/commands/options.d.ts +88 -70
  432. package/dist/src/types/commands/patches.d.ts +24 -34
  433. package/dist/src/types/commands/project.d.ts +5 -34
  434. package/dist/src/types/config.d.ts +25 -23
  435. package/dist/src/types/config.js +6 -1
  436. package/dist/src/types/furnace.d.ts +37 -13
  437. package/dist/src/types/furnace.js +19 -1
  438. package/dist/src/types/index.d.ts +3 -3
  439. package/dist/src/utils/build-info.d.ts +7 -8
  440. package/dist/src/utils/build-info.js +12 -11
  441. package/dist/src/utils/errors.d.ts +6 -8
  442. package/dist/src/utils/errors.js +6 -8
  443. package/dist/src/utils/logger.d.ts +27 -10
  444. package/dist/src/utils/logger.js +28 -16
  445. package/dist/src/utils/machine-output.d.ts +12 -0
  446. package/dist/src/utils/machine-output.js +33 -0
  447. package/dist/src/utils/options.d.ts +31 -14
  448. package/dist/src/utils/options.js +86 -30
  449. package/dist/src/utils/paths.d.ts +10 -8
  450. package/dist/src/utils/paths.js +10 -8
  451. package/dist/src/utils/platform.d.ts +0 -25
  452. package/dist/src/utils/platform.js +1 -42
  453. package/dist/src/utils/process-group.d.ts +9 -4
  454. package/dist/src/utils/process-group.js +19 -5
  455. package/dist/src/utils/process.d.ts +42 -7
  456. package/dist/src/utils/process.js +44 -17
  457. package/dist/src/utils/ps-duration.d.ts +31 -0
  458. package/dist/src/utils/ps-duration.js +70 -0
  459. package/dist/src/utils/regex.d.ts +9 -10
  460. package/dist/src/utils/regex.js +9 -10
  461. package/dist/src/utils/validation.d.ts +58 -28
  462. package/dist/src/utils/validation.js +60 -33
  463. package/package.json +2 -2
  464. package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
  465. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
  466. package/dist/src/core/manifest-helpers.d.ts +0 -38
  467. package/dist/src/core/manifest-helpers.js +0 -120
  468. /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
  469. /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
  470. /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
@@ -17,12 +17,11 @@ import { resolvePatchOwnedChromeScripts, resolvePatchOwnedSysMjs, resolvePatchOw
17
17
  // Cross-patch lint re-exports
18
18
  // ---------------------------------------------------------------------------
19
19
  //
20
- // The cross-patch lint infrastructure (queue context builder, duplicate-
21
- // creation and forward-import rules, ignore marker) lives in
22
- // `patch-lint-cross.ts` so the per-patch and cross-patch rule bodies can
23
- // each stay within the project's per-file line budget. Re-export the
24
- // public surface from `patch-lint-reexports.ts` so callers continue to
25
- // import from a single module.
20
+ // The cross-patch lint infrastructure (queue context builder,
21
+ // duplicate-creation and forward-import rules, ignore marker) lives in
22
+ // `patch-lint-cross.ts` so the per-patch and cross-patch rule bodies each
23
+ // stay within the per-file line budget. The public surface is re-exported
24
+ // from `patch-lint-reexports.ts` so callers keep importing from one module.
26
25
  export * from './patch-lint-reexports.js';
27
26
  // The CSS rule bodies live in `patch-lint-css.ts` (same per-file-budget
28
27
  // split as the other rule families); re-export the imported binding so
@@ -38,10 +37,10 @@ const FILE_SIZE_THRESHOLDS = {
38
37
  };
39
38
  /**
40
39
  * Counts the lines of `content` the way `wc -l` reports them: a trailing
41
- * newline terminates the last line rather than starting an empty extra
42
- * one. A naive `split('\n').length` over-counts by one for any content
43
- * ending in `\n` (the common case), which made the size rules fire one
44
- * line early against the operator's own `wc -l` measurement.
40
+ * newline terminates the last line rather than starting an empty extra one.
41
+ * A naive `split('\n').length` over-counts by one for any content ending in
42
+ * `\n` (the common case), making the size rules fire one line early against
43
+ * the operator's own `wc -l` measurement.
45
44
  */
46
45
  function countContentLines(content) {
47
46
  const lines = content.split('\n');
@@ -94,50 +93,39 @@ const PATCH_LINE_THRESHOLDS = {
94
93
  * Branding patches have a legitimate reason to be large: they include
95
94
  * every locale's `brand.ftl`, copied upstream CSS/PNG assets, and the
96
95
  * fork-specific `configure.sh` / `brand.properties` under a single
97
- * `browser/branding/<name>/` subtree. Calibrated against:
96
+ * `browser/branding/<name>/` subtree.
98
97
  *
99
- * - The 2026-04-21 eval baseline: a fresh-fork branding export landed
100
- * at 15904 lines (localized brand.ftl across many locales + SVG path
101
- * data + copied upstream CSS).
102
- * - The 2026-04-25 operator data point: a freshly setup branding patch
103
- * (post-binary-exclusion, after Phase 1+2 patch splits) landed at
104
- * 15650 lines — within 2% of the eval baseline.
105
- *
106
- * Both data points need to surface as a soft `notice` rather than a
107
- * `warning`, since they represent the *minimum* branding diff. The
108
- * pre-2026-04-25 calibration {3000/8000/20000} put 15904 firmly in the
109
- * `warning` band, contradicting the docstring's "loud but not
110
- * actionable" intent. The current calibration moves the warning band
111
- * above the eval baseline (with ~13% headroom) and the error band to
112
- * roughly 2× the baseline — reaching `error` strongly suggests
113
- * non-branding work is bundled in.
98
+ * Calibrated against a fresh-fork branding export, which lands around
99
+ * 15–16k lines (localized brand.ftl across many locales + SVG path data +
100
+ * copied upstream CSS). That is the MINIMUM branding diff, so it must
101
+ * surface as a soft `notice` rather than a `warning`: the warning band
102
+ * sits above it with ~13% headroom, and the error band at roughly 2× the
103
+ * baseline, where non-branding work is probably bundled in.
114
104
  *
115
105
  * Permissive thresholds are safe because the *gate* into this tier is
116
- * narrow (auto-detect requires every file under `browser/branding/`
117
- * plus a tight registration allowlist, or an explicit
118
- * `PatchMetadata.tier: "branding"` opt-in). A non-branding patch
119
- * cannot accidentally land here.
106
+ * narrow: auto-detect requires every file under `browser/branding/` plus a
107
+ * tight registration allowlist, or an explicit `PatchMetadata.tier:
108
+ * "branding"` opt-in. A non-branding patch cannot accidentally land here.
120
109
  */
121
110
  branding: { notice: 8000, warning: 18000, error: 30000 },
122
111
  };
123
112
  /**
124
- * File-count thresholds for the `large-patch-files` rule, mirroring the
125
- * tier shape of {@link PATCH_LINE_THRESHOLDS}. A single warning-only
126
- * threshold per tier is intentional — file count expresses scope, not
127
- * blast radius, and there is no error band that would block export.
113
+ * File-count thresholds for the `large-patch-files` rule, mirroring the tier
114
+ * shape of {@link PATCH_LINE_THRESHOLDS}. A single warning-only threshold
115
+ * per tier is intentional — file count expresses scope, not blast radius,
116
+ * and there is no error band that would block export.
128
117
  *
129
118
  * The branding tier sits well above the typical floor because branding
130
- * patches inherently span many files: PNG/ICO icon assets in 7+ sizes,
131
- * MSIX manifests, channel-specific configs, locale `.ftl` files,
132
- * Windows/macOS launcher resources. The 2026-04-25 operator data point
133
- * reported a 56-file fresh-fork branding bundle as the minimum shape;
134
- * 60 leaves headroom for additional channels/locales while still firing
135
- * on a genuinely bloated patch.
119
+ * patches inherently span many files: PNG/ICO icon assets in 7+ sizes, MSIX
120
+ * manifests, channel-specific configs, locale `.ftl` files, Windows/macOS
121
+ * launcher resources. A fresh-fork branding bundle is around 56 files, so 60
122
+ * leaves headroom for additional channels/locales while still firing on a
123
+ * genuinely bloated patch.
136
124
  *
137
- * Test tier matches general because a test-only patch rarely touches
138
- * many files (a single regression test usually adds 1–3 fixtures); the
139
- * elevation in {@link PATCH_LINE_THRESHOLDS.test} addresses big
140
- * table-driven test bodies, not file fan-out.
125
+ * Test tier matches general because a test-only patch rarely touches many
126
+ * files (a single regression test usually adds 1–3 fixtures); the elevation
127
+ * in {@link PATCH_LINE_THRESHOLDS.test} addresses big table-driven test
128
+ * bodies, not file fan-out.
141
129
  */
142
130
  const PATCH_FILES_THRESHOLDS = {
143
131
  general: 5,
@@ -145,29 +133,25 @@ const PATCH_FILES_THRESHOLDS = {
145
133
  branding: 60,
146
134
  };
147
135
  /**
148
- * Shared remedy sentence for the over-budget findings: the two
149
- * places an operator stands when a patch outgrows its budget must name the
150
- * sanctioned one-transaction way out, or they reach for the wrong tools
151
- * first (the field report tried two before finding it). One constant, four
152
- * emission sites — the wording cannot drift.
136
+ * Shared remedy sentence for the over-budget findings. Both places an
137
+ * operator stands when a patch outgrows its budget must name the sanctioned
138
+ * one-transaction way out, or they reach for the wrong tools first. One
139
+ * constant, four emission sites, so the wording cannot drift.
153
140
  */
154
141
  const SPLIT_HINT = 'Consider splitting into smaller, focused patches — ' +
155
142
  '"fireforge patch move-files <from> <to> --file <path> --create --order <n>" ' +
156
143
  'moves files into a new patch in one transaction.';
157
144
  /**
158
145
  * Fixed allowlist of non-branding sibling paths that real-world Firefox
159
- * branding patches legitimately need to touch to register the new
160
- * branding flavor with the top-level configure. The 2026-04-21
161
- * external eval showed that a branding patch which also touches
162
- * `browser/moz.configure` (the canonical registration point) fell
163
- * through to the general lint tier because the original predicate
164
- * required every file to live under `browser/branding/`. This
165
- * allowlist stays intentionally narrow — additions require a real
166
- * operator data point, not a speculative expansion. Add new entries
167
- * only when a genuine branding patch cannot be expressed without a
168
- * specific registration sibling.
146
+ * branding patches legitimately need to touch to register the new branding
147
+ * flavor with the top-level configure. A predicate requiring every file to
148
+ * live under `browser/branding/` drops a branding patch that also touches
149
+ * `browser/moz.configure` — the canonical registration point — into the
150
+ * general lint tier.
169
151
  *
170
- * Pinned against ESR 140.x conventions at time of writing.
152
+ * Intentionally narrow: add entries only when a genuine branding patch
153
+ * cannot be expressed without a specific registration sibling. Pinned
154
+ * against ESR 140.x conventions at time of writing.
171
155
  */
172
156
  const BRANDING_REGISTRATION_FILES = new Set([
173
157
  'browser/moz.configure',
@@ -235,33 +219,31 @@ function isBrowserChromeTestFile(file) {
235
219
  /**
236
220
  * Bare assertion-call detection for the `test-needs-assertion` floor.
237
221
  *
238
- * Each alternative must sit at a call boundary: preceded by something that is
239
- * not an identifier character, `.`, `#`, or `-`. Until 0.41.0 this was a plain
240
- * `strippedContent.includes(tok)` over the tokens `Assert.`, `ok(`, `is(`,
241
- * `isnot(`, `isDeeply(`, so any identifier merely *ending* in one satisfied
242
- * the gate — `this.axis(3)` contains `is(`, `book(` contains `ok(`,
243
- * `promiseIsDeeply(` contains `isDeeply(`. An assertion-free smoke test using
244
- * any such call passed the floor, which is exactly what the rule exists to
245
- * catch.
222
+ * Each alternative must sit at a call boundary: preceded by something that
223
+ * is not an identifier character, `.`, `#`, or `-`. A plain
224
+ * `strippedContent.includes(tok)` over `Assert.`, `ok(`, `is(`, `isnot(`,
225
+ * `isDeeply(` lets any identifier merely *ending* in one satisfy the gate —
226
+ * `this.axis(3)` contains `is(`, `book(` contains `ok(`,
227
+ * `promiseIsDeeply(` contains `isDeeply(` — so an assertion-free smoke test
228
+ * using any such call clears the floor.
246
229
  *
247
- * The namespaced arm must name a member and a call. Matching the namespace and
248
- * its dot alone — as `(?:Assert|SimpleTest)\.` did — re-opened the same hole
249
- * from the other side: `SimpleTest` is mostly *harness* API, so
250
- * `SimpleTest.finish()`, `SimpleTest.waitForExplicitFinish()` and
251
- * `SimpleTest.requestCompleteLog()` each cleared the floor on their own. Those
252
- * are the exact calls an assertion-free smoke test contains. The enumeration
253
- * below is the mochitest/xpcshell assertion surface both namespaces share, and
254
- * it is what the user-facing message already names.
230
+ * The namespaced arm must name a member AND a call. Matching the namespace
231
+ * and its dot alone re-opens the same hole from the other side:
232
+ * `SimpleTest` is mostly *harness* API, so `SimpleTest.finish()`,
233
+ * `SimpleTest.waitForExplicitFinish()` and `SimpleTest.requestCompleteLog()`
234
+ * would each clear the floor on their own — the exact calls an
235
+ * assertion-free smoke test contains. The enumeration below is the
236
+ * mochitest/xpcshell assertion surface both namespaces share, and it is what
237
+ * the user-facing message names.
255
238
  *
256
- * Two qualified spellings of the bare four still count: `window.ok(...)` (the
257
- * globals ARE window properties) and an optional-chained `win?.ok(...)`.
258
- * Blocking those flagged tests whose only assertions are real as
239
+ * Two qualified spellings of the bare four also count: `window.ok(...)` (the
240
+ * globals ARE window properties) and an optional-chained `win?.ok(...)`;
241
+ * blocking those flags tests whose only assertions are real as
259
242
  * assertion-free. `SpecialPowers.ok(` stays excluded — that is API surface,
260
243
  * not an assertion.
261
244
  *
262
- * Deliberately still textual, not AST-based: this runs over patch bodies
263
- * rather than resolvable sources, and the surrounding rule is a floor, not a
264
- * proof.
245
+ * Deliberately textual, not AST-based: this runs over patch bodies rather
246
+ * than resolvable sources, and the surrounding rule is a floor, not a proof.
265
247
  */
266
248
  const ASSERTION_MEMBERS = [
267
249
  'ok',
@@ -318,22 +300,18 @@ export function commentStyleForFile(file) {
318
300
  * Accepted shapes, in order:
319
301
  * - the project's own header for the file's comment style;
320
302
  * - under `browser/branding/`, ANY recognised license header in the
321
- * matching style: the setup-generated branding directory is copied
322
- * from Firefox's `unofficial` template and arrives with Mozilla
323
- * MPL-2.0 headers — legitimate for copyright purposes (the assets are
324
- * Mozilla's) even when the fork's own code is 0BSD / EUPL-1.2 /
325
- * GPL-2.0-or-later;
303
+ * matching style: the setup-generated branding directory is copied from
304
+ * Firefox's `unofficial` template and arrives with Mozilla MPL-2.0
305
+ * headers — legitimate for copyright purposes (the assets are Mozilla's)
306
+ * even when the fork's own code is 0BSD / EUPL-1.2 / GPL-2.0-or-later;
326
307
  * - on an MPL-2.0 project, any recognised header in the matching style;
327
308
  * - the verbatim upstream MPL-2.0 block header (`/* … *\/`, optionally
328
- * behind a leading editor-directive comment) REGARDLESS of the
329
- * project license, on any file whose comment syntax is the block form
330
- * (JS and CSS): a file copied from the Firefox tree legitimately
331
- * keeps Mozilla's header for provenance in an EUPL/GPL/0BSD project
332
- * too. 0.35.0 accepted this on JS only; field verification showed a
333
- * derived CSS file carrying the identical three-line block header
334
- * still needed a patch-level lintIgnore. Only the block form is
335
- * accepted — the `// `-style MPL header is FireForge-generated, not
336
- * upstream provenance — and `hash`-style files (FTL) stay excluded
309
+ * behind a leading editor-directive comment) REGARDLESS of the project
310
+ * license, on any file whose comment syntax is the block form (JS and
311
+ * CSS): a file copied from the Firefox tree legitimately keeps Mozilla's
312
+ * header for provenance in an EUPL/GPL/0BSD project too. Only the block
313
+ * form is accepted — the `// `-style MPL header is FireForge-generated,
314
+ * not upstream provenance — and `hash`-style files (FTL) stay excluded
337
315
  * because `/* … *\/` is not a comment there.
338
316
  *
339
317
  * @param file - Engine-relative path of the new file
@@ -588,16 +566,15 @@ export function lintPatchSize(filesAffected, lineCount, patchTier) {
588
566
  const issues = [];
589
567
  // Tier selection: test > branding > general. Tests keep their elevated
590
568
  // thresholds because a big regression test is legitimate (table-driven
591
- // harnesses run into the thousands of lines). Branding patches get
592
- // their own tier so a first-export of setup-generated branding doesn't
593
- // fire the general hard limit — see `PATCH_LINE_THRESHOLDS.branding`
594
- // and `PATCH_FILES_THRESHOLDS.branding` above for the eval data
595
- // motivating this tier. An explicit `patchTier` opt-in forces branding
596
- // even when `isBrandingOnlyPatch` cannot reach the patch's actual
597
- // shape (a branding patch that also touches a non-allowlisted sibling
598
- // like a vendor-specific icon resource). Both checks read off the
599
- // same decision so the file-count and line-count rules cannot
600
- // disagree about which tier applies.
569
+ // harnesses run into the thousands of lines). Branding patches get their
570
+ // own tier so a first export of setup-generated branding does not fire the
571
+ // general hard limit — see `PATCH_LINE_THRESHOLDS.branding` and
572
+ // `PATCH_FILES_THRESHOLDS.branding` above. An explicit `patchTier` opt-in
573
+ // forces branding even when `isBrandingOnlyPatch` cannot reach the patch's
574
+ // actual shape (a branding patch that also touches a non-allowlisted
575
+ // sibling like a vendor-specific icon resource). Both checks read off the
576
+ // same decision so the file-count and line-count rules cannot disagree
577
+ // about which tier applies.
601
578
  const decision = resolvePatchSizeTier(filesAffected, patchTier);
602
579
  const fileThreshold = decision.tier === 'test'
603
580
  ? PATCH_FILES_THRESHOLDS.test
@@ -687,23 +664,14 @@ export async function lintModifiedFileHeaders(repoDir, affectedFiles, newFiles)
687
664
  * @param affectedFiles - File paths (relative to repoDir) affected by the patch
688
665
  * @param diffContent - Raw unified diff string
689
666
  * @param config - Project configuration
690
- * @param patchQueueCtx - Optional cross-patch context for ownership resolution
691
- * @param ignoreChecks - Optional set of per-patch `check` IDs to drop from the
692
- * returned issues. Threaded from `PatchMetadata.lintIgnore` so a patch that
693
- * is advisory-noisy by nature (a cohesive branding bundle, auto-generated
694
- * manifest, etc.) can opt out of a specific rule without reaching for the
695
- * blunt `--skip-lint` hammer. Not mutated by this function.
696
- * @param patchTier - Optional explicit tier override, threaded from
697
- * `PatchMetadata.tier`. When `"branding"` forces the branding
698
- * thresholds on the `large-patch-lines` rule. Callers with a
699
- * per-patch manifest context (re-export, per-patch lint) should
700
- * pass this; aggregate-mode callers without a specific patch
701
- * context skip it and fall through to auto-detection.
702
- * @param options - Optional behaviour switches; see
703
- * {@link LintExportedPatchOptions}.
667
+ * @param options - Optional context and behaviour switches; see
668
+ * {@link LintExportedPatchOptions}. Every optional input lives here
669
+ * rather than in a positional tail: four of the five call sites had to
670
+ * pass `undefined` placeholders to reach the one member they wanted.
704
671
  * @returns Array of all lint issues found
705
672
  */
706
- export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, patchQueueCtx, ignoreChecks, patchTier, options) {
673
+ export async function lintExportedPatch(repoDir, affectedFiles, diffContent, config, options) {
674
+ const { patchQueueCtx, ignoreChecks, patchTier } = options ?? {};
707
675
  const newFiles = detectNewFilesInDiff(diffContent);
708
676
  const { textLines: lineCount } = countNonBinaryDiffLines(diffContent);
709
677
  const patchOwnedFiles = resolvePatchOwnedSysMjs(newFiles, patchQueueCtx);
@@ -6,7 +6,7 @@ export interface PatchDirectoryLockOptions {
6
6
  * Wait up to this many seconds for the patch directory lock instead of the
7
7
  * default ~30 s budget. Enables exponential poll backoff (100 ms → 2 s)
8
8
  * and a holder-identified progress line roughly every 5 s. `undefined`
9
- * preserves the historical behavior exactly.
9
+ * keeps the default.
10
10
  */
11
11
  waitLockSeconds?: number | undefined;
12
12
  /** Command name recorded in the lock's owner metadata for holder diagnostics. */
@@ -3,7 +3,7 @@
3
3
  * Filesystem-based lock for serializing patch directory mutations.
4
4
  */
5
5
  import { join } from 'node:path';
6
- import { FireForgeError } from '../errors/base.js';
6
+ import { CommandError, FireForgeError } from '../errors/base.js';
7
7
  import { PatchError } from '../errors/patch.js';
8
8
  import { assert } from '../utils/assert.js';
9
9
  import { toError } from '../utils/errors.js';
@@ -56,7 +56,24 @@ export async function assertPatchDirectoryLockHeld(patchesDir, context) {
56
56
  export async function withPatchDirectoryLock(patchesDir, operation, options = {}) {
57
57
  const lockDir = join(patchesDir, PATCH_DIRECTORY_LOCK);
58
58
  const { waitLockSeconds, command } = options;
59
- return withFileLock(lockDir, operation, {
59
+ // Tag whatever the BODY throws so the catch below can tell it apart from a
60
+ // lock-acquire/release failure. Narrowing by origin rather than by error
61
+ // shape is what "the rewrap is for lock I/O" actually means: an errno test
62
+ // would still reclassify an EACCES raised while the body writes a .patch
63
+ // file, and a class test cannot see a bare `new Error` from a body at all.
64
+ let bodyFailure;
65
+ let bodyThrew = false;
66
+ const trackedOperation = async () => {
67
+ try {
68
+ return await operation();
69
+ }
70
+ catch (error) {
71
+ bodyThrew = true;
72
+ bodyFailure = error;
73
+ throw error;
74
+ }
75
+ };
76
+ return withFileLock(lockDir, trackedOperation, {
60
77
  ...(waitLockSeconds !== undefined
61
78
  ? {
62
79
  timeoutMs: waitLockSeconds * 1000,
@@ -71,12 +88,12 @@ export async function withPatchDirectoryLock(patchesDir, operation, options = {}
71
88
  ...(command !== undefined
72
89
  ? { ownerMetadata: [`command=${command}`, `started=${new Date().toISOString()}`] }
73
90
  : {}),
74
- // Reason first, remedy second. The remedy is waiting, not
75
- // deleting: bulk exports/re-exports legitimately hold this lock for
76
- // minutes, FireForge reaps genuinely stale locks on its own, and the
77
- // old advice ("rm -rf" while a holder was alive) destroyed a live
78
- // lock. The leading sentence is a message contract; extend, don't
79
- // reword. withFileLock appends the holder identification.
91
+ // Reason first, remedy second. The remedy is waiting, not deleting: bulk
92
+ // exports/re-exports legitimately hold this lock for minutes, and
93
+ // FireForge reaps genuinely stale locks on its own — advice to `rm -rf`
94
+ // while a holder is alive destroys a live lock. The leading sentence is
95
+ // a message contract; extend, do not reword. withFileLock appends the
96
+ // holder identification.
80
97
  onTimeoutMessage: `Timed out waiting for another FireForge command mutating ${patchesDir}.\n` +
81
98
  `Pass --wait-lock [seconds] to wait longer (bare --wait-lock waits up to 60 seconds). ` +
82
99
  `FireForge removes genuinely stale locks automatically; only if no other fireforge ` +
@@ -84,9 +101,28 @@ export async function withPatchDirectoryLock(patchesDir, operation, options = {}
84
101
  onStaleLockMessage: (ageMs) => `Removing stale patch lock (age: ${Math.round(ageMs / 1000)}s). ` +
85
102
  'A previous fireforge process may have crashed.',
86
103
  }).catch((error) => {
104
+ // The body's own failures are never ours to reclassify. Rewrapping them
105
+ // reports an EACCES writing patches.json, a postcondition failure from
106
+ // renumberPatchesInManifest, or a plain TypeError from a FireForge bug
107
+ // as a "Patch Error" carrying three fixed remedies about Firefox-version
108
+ // compatibility and `fireforge reset` — none of which apply. Worse,
109
+ // src/cli.ts prints a stack only for non-FireForgeError throwables, so
110
+ // the rewrap moves real bugs into the branch that discards their stack.
111
+ if (bodyThrew && error === bodyFailure) {
112
+ throw error;
113
+ }
87
114
  if (error instanceof FireForgeError) {
88
115
  throw error;
89
116
  }
90
- throw new PatchError(toError(error).message);
117
+ // CommandError is the one class in src/errors/ that does NOT extend
118
+ // FireForgeError: it is the sentinel carrying an already-rendered exit
119
+ // code to the entrypoint. The `instanceof FireForgeError` guard above
120
+ // misses it, so without this the exit code was replaced by PATCH_ERROR.
121
+ if (error instanceof CommandError) {
122
+ throw error;
123
+ }
124
+ // What remains is a genuine failure to acquire or release the lock.
125
+ const cause = toError(error);
126
+ throw new PatchError(cause.message, undefined, cause);
91
127
  });
92
128
  }
@@ -2,6 +2,7 @@
2
2
  * Manifest consistency checks and rebuild/recovery operations.
3
3
  */
4
4
  import type { PatchesManifest } from '../types/commands/index.js';
5
+ import type { PatchDirectoryLockOptions } from './patch-lock.js';
5
6
  /** Consistency issue codes for manifest validation. */
6
7
  export interface PatchManifestConsistencyIssue {
7
8
  code: 'manifest-invalid' | 'manifest-missing' | 'missing-patch-file' | 'untracked-patch-file' | 'files-affected-mismatch' | 'duplicate-manifest-entry';
@@ -41,4 +42,4 @@ export interface RebuildPatchesManifestResult {
41
42
  * @returns {@link RebuildPatchesManifestResult} — the persisted manifest
42
43
  * plus the filenames that were reconstructed from generic defaults.
43
44
  */
44
- export declare function rebuildPatchesManifest(patchesDir: string, fallbackSourceEsrVersion: string): Promise<RebuildPatchesManifestResult>;
45
+ export declare function rebuildPatchesManifest(patchesDir: string, fallbackSourceEsrVersion: string, lockOptions?: PatchDirectoryLockOptions): Promise<RebuildPatchesManifestResult>;
@@ -89,13 +89,13 @@ export async function validatePatchesManifestConsistency(patchesDir) {
89
89
  * @returns {@link RebuildPatchesManifestResult} — the persisted manifest
90
90
  * plus the filenames that were reconstructed from generic defaults.
91
91
  */
92
- export async function rebuildPatchesManifest(patchesDir, fallbackSourceEsrVersion) {
92
+ export async function rebuildPatchesManifest(patchesDir, fallbackSourceEsrVersion, lockOptions = {}) {
93
93
  // The whole read → discover → rebuild → save cycle runs under the shared
94
94
  // patch-directory lock (invariant 2, docs/lifecycle-invariants.md):
95
- // manifest writes serialize on this lock, and a repair racing a
96
- // concurrent export/reorder used to be able to clobber the other
97
- // writer's manifest. Not reentrant — callers must not already hold it.
98
- return withPatchDirectoryLock(patchesDir, () => rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion));
95
+ // manifest writes serialize on this lock, so without it a repair racing a
96
+ // concurrent export/reorder can clobber the other writer's manifest. Not
97
+ // reentrant — callers must not already hold it.
98
+ return withPatchDirectoryLock(patchesDir, () => rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion), lockOptions);
99
99
  }
100
100
  async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVersion) {
101
101
  const manifestState = await loadPatchesManifestState(patchesDir);
@@ -113,11 +113,10 @@ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVers
113
113
  }, 0);
114
114
  let nextRecoveredOrder = highestFiniteOrder + 1;
115
115
  // Prefetch the two independent per-patch reads concurrently. This runs
116
- // while the patch-directory lock is held, and it was two serialised awaits
117
- // per patch across the whole queue — 58 round-trips on a 29-patch queue.
118
- // The fold below stays sequential because `nextRecoveredOrder` is
119
- // loop-carried: parallelising the assignment would scramble the recovered
120
- // ordinals.
116
+ // while the patch-directory lock is held, and as two serialised awaits per
117
+ // patch it costs 58 round-trips on a 29-patch queue. The fold below stays
118
+ // sequential because `nextRecoveredOrder` is loop-carried: parallelising
119
+ // the assignment would scramble the recovered ordinals.
121
120
  const prefetched = await Promise.all(patches.map(async (patch) => ({
122
121
  filesAffected: normalizeFiles(await getAllTargetFilesFromPatch(patch.path)),
123
122
  patchStats: await stat(patch.path),
@@ -131,25 +130,24 @@ async function rebuildPatchesManifestUnderLock(patchesDir, fallbackSourceEsrVers
131
130
  const inferred = inferPatchMetadataFromFilename(patch.filename);
132
131
  const recoveredOrder = Number.isFinite(patch.order) ? patch.order : nextRecoveredOrder++;
133
132
  if (!existing) {
134
- // Track every filename that had no pre-existing manifest entry
135
- // so callers can warn the operator per-patch. A missing entry
136
- // means every descriptive field (`description`, `createdAt`,
137
- // `category`) was invented rather than preserved. FireForge
138
- // patch files carry no header metadata that could carry a
139
- // human description forward, so full fidelity is impossible —
140
- // visibility is the best we can offer. 2026-04-21 eval
141
- // (Finding #17) tripped over silent overwrites of useful
142
- // human-written descriptions during a recovery run.
133
+ // Track every filename that had no pre-existing manifest entry so
134
+ // callers can warn the operator per-patch. A missing entry means every
135
+ // descriptive field (`description`, `createdAt`, `category`) was
136
+ // invented rather than preserved, and FireForge patch files carry no
137
+ // header metadata that could carry a human description forward — so
138
+ // visibility is the best available, and silent overwrites of
139
+ // human-written descriptions during a recovery run are the failure to
140
+ // avoid.
143
141
  recoveredFilenames.push(patch.filename);
144
142
  }
145
- // Preserve optional fields the operator declared on the existing
146
- // entry — `lintIgnore` (per-patch lint suppression) and `tier`
147
- // (explicit branding-threshold override). Without this, a
148
- // `doctor --repair-patches-manifest` run silently strips both
149
- // fields from every entry that had them, and the next `lint`
150
- // or `re-export` pass fires rules the operator had intentionally
151
- // quieted. Mirrors how other descriptive fields fall back to
152
- // existing values when the entry is known.
143
+ // Preserve optional fields the operator declared on the existing entry —
144
+ // `lintIgnore` (per-patch lint suppression) and `tier` (explicit
145
+ // branding-threshold override). Without this, a
146
+ // `doctor --repair-patches-manifest` run silently strips both from every
147
+ // entry that had them, and the next `lint` or `re-export` pass fires
148
+ // rules the operator had intentionally quieted. Mirrors how other
149
+ // descriptive fields fall back to existing values when the entry is
150
+ // known.
153
151
  const rebuilt = {
154
152
  filename: patch.filename,
155
153
  order: recoveredOrder,
@@ -12,7 +12,7 @@ import { rename } from 'node:fs/promises';
12
12
  import { join } from 'node:path';
13
13
  import { FireForgeError } from '../errors/base.js';
14
14
  import { ExitCode } from '../errors/codes.js';
15
- import { PatchManifestCorruptError } from '../errors/patch.js';
15
+ import { PatchError, PatchManifestCorruptError } from '../errors/patch.js';
16
16
  import { assert } from '../utils/assert.js';
17
17
  import { toError } from '../utils/errors.js';
18
18
  import { pathExistsStrict, readJson, removeFile, writeJson } from '../utils/fs.js';
@@ -315,7 +315,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
315
315
  await assertPatchDirectoryLockHeld(patchesDir, 'renumbering the patch manifest');
316
316
  const manifest = await loadPatchesManifestForWrite(patchesDir);
317
317
  if (!manifest) {
318
- throw new Error('Cannot renumber patches: patches.json is missing.');
318
+ throw new PatchError('Cannot renumber patches: patches.json is missing.');
319
319
  }
320
320
  // Phase 1: rename each old filename to a unique temp staging name so the
321
321
  // later rename to the final target cannot collide with another entry
@@ -326,7 +326,7 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
326
326
  for (const [oldFilename, entry] of renameMap) {
327
327
  const oldPath = join(patchesDir, oldFilename);
328
328
  if (!(await pathExistsStrict(oldPath))) {
329
- throw new Error(`Cannot renumber: patch file is missing on disk: ${oldFilename}`);
329
+ throw new PatchError(`Cannot renumber: patch file is missing on disk: ${oldFilename}`, oldFilename);
330
330
  }
331
331
  const stagedName = `.fireforge-renumber-${stagingId}-${oldFilename}`;
332
332
  const stagedPath = join(patchesDir, stagedName);
@@ -354,18 +354,16 @@ export async function renumberPatchesInManifest(patchesDir, renameMap) {
354
354
  const { staged, toEntry } = stagedEntry;
355
355
  const targetPath = join(patchesDir, toEntry.newFilename);
356
356
  if (await pathExistsStrict(targetPath)) {
357
- throw new Error(`Cannot renumber: target patch filename already exists on disk: ${toEntry.newFilename}`);
357
+ throw new PatchError(`Cannot renumber: target patch filename already exists on disk: ${toEntry.newFilename}`, toEntry.newFilename);
358
358
  }
359
359
  await rename(join(patchesDir, staged), targetPath);
360
- // Postcondition assert: confirm the target actually exists on
361
- // disk before we mark the rename complete. A silent rename
362
- // failure would leave the manifest and the filesystem
363
- // disagreeing — exactly what the eval 1 Finding #7 report
364
- // described: manifest rewrote to new filenames while the old
365
- // files stayed on disk. If the assert ever fires, the Phase 2
366
- // rollback will undo prior moves before re-throwing.
360
+ // Postcondition assert: confirm the target actually exists on disk
361
+ // before marking the rename complete. A silent rename failure leaves
362
+ // the manifest and the filesystem disagreeing — manifest rewritten to
363
+ // new filenames while the old files stay on disk. If the assert fires,
364
+ // the Phase 2 rollback undoes prior moves before re-throwing.
367
365
  if (!(await pathExistsStrict(targetPath))) {
368
- throw new Error(`Rename postcondition failed: expected ${toEntry.newFilename} to exist after rename, but it was not found on disk.`);
366
+ throw new PatchError(`Rename postcondition failed: expected ${toEntry.newFilename} to exist after rename, but it was not found on disk.`, toEntry.newFilename);
369
367
  }
370
368
  completedFinalRenames.push(stagedEntry);
371
369
  }
@@ -525,12 +523,10 @@ export async function removePatchFileAndManifest(patchesDir, filename) {
525
523
  await savePatchesManifest(patchesDir, originalManifest);
526
524
  }
527
525
  catch (rollbackError) {
528
- // Compound failure: both the delete and the rollback failed,
529
- // so the directory is in a known-inconsistent state. Throw a
530
- // dedicated error type that carries both causes so the
531
- // operator's log shows the complete picture instead of the
532
- // original delete error with a warning about the rollback
533
- // buried in stderr.
526
+ // Compound failure: both the delete and the rollback failed, so the
527
+ // directory is in a known-inconsistent state. A dedicated error type
528
+ // carrying both causes shows the complete picture instead of the
529
+ // original delete error with a rollback warning buried in stderr.
534
530
  throw new PatchDeleteRollbackError(filename, deleteError, toError(rollbackError));
535
531
  }
536
532
  }
@@ -7,18 +7,15 @@ import type { PatchMetadata } from '../types/commands/index.js';
7
7
  * matches `PatchMetadata.filename`.
8
8
  * 3. A filename without the `.patch` suffix — the command appends it
9
9
  * before matching (e.g. `002-ui-foo`).
10
- * 4. The manifest `name` field (e.g. `eval-furnace-token-override`) —
11
- * matches `PatchMetadata.name`. This is the short logical handle
12
- * the export workflow stamps onto the patch and the natural
13
- * identifier an operator keeps in their notes. 2026-04-21 eval
14
- * (Finding #6): `patch reorder`/`delete` rejected the `name`
15
- * even though the CLI help said `<name>`, forcing the operator
16
- * to copy the full filename from `patches.json` before every
17
- * queue mutation.
10
+ * 4. The manifest `name` field (e.g. `furnace-token-override`) — the short
11
+ * logical handle the export workflow stamps onto the patch and the
12
+ * natural identifier an operator keeps in their notes. The CLI help
13
+ * says `<name>`, so rejecting it forces the operator to copy the full
14
+ * filename out of `patches.json` before every queue mutation.
18
15
  *
19
- * Resolution order is strict: numeric ordinals first, then filename
20
- * lookup (with + without `.patch` suffix), then name-field lookup.
21
- * The filename lookup beats the name lookup when the two happen to
22
- * collide so legacy scripts that pass filenames keep working.
16
+ * Resolution order is strict: numeric ordinals first, then filename lookup
17
+ * (with and without the `.patch` suffix), then name-field lookup. The
18
+ * filename lookup beats the name lookup when the two collide, so scripts
19
+ * that pass filenames keep working.
23
20
  */
24
21
  export declare function resolvePatchIdentifier(identifier: string, patches: PatchMetadata[]): PatchMetadata | null;
@@ -6,19 +6,16 @@
6
6
  * matches `PatchMetadata.filename`.
7
7
  * 3. A filename without the `.patch` suffix — the command appends it
8
8
  * before matching (e.g. `002-ui-foo`).
9
- * 4. The manifest `name` field (e.g. `eval-furnace-token-override`) —
10
- * matches `PatchMetadata.name`. This is the short logical handle
11
- * the export workflow stamps onto the patch and the natural
12
- * identifier an operator keeps in their notes. 2026-04-21 eval
13
- * (Finding #6): `patch reorder`/`delete` rejected the `name`
14
- * even though the CLI help said `<name>`, forcing the operator
15
- * to copy the full filename from `patches.json` before every
16
- * queue mutation.
9
+ * 4. The manifest `name` field (e.g. `furnace-token-override`) — the short
10
+ * logical handle the export workflow stamps onto the patch and the
11
+ * natural identifier an operator keeps in their notes. The CLI help
12
+ * says `<name>`, so rejecting it forces the operator to copy the full
13
+ * filename out of `patches.json` before every queue mutation.
17
14
  *
18
- * Resolution order is strict: numeric ordinals first, then filename
19
- * lookup (with + without `.patch` suffix), then name-field lookup.
20
- * The filename lookup beats the name lookup when the two happen to
21
- * collide so legacy scripts that pass filenames keep working.
15
+ * Resolution order is strict: numeric ordinals first, then filename lookup
16
+ * (with and without the `.patch` suffix), then name-field lookup. The
17
+ * filename lookup beats the name lookup when the two collide, so scripts
18
+ * that pass filenames keep working.
22
19
  */
23
20
  export function resolvePatchIdentifier(identifier, patches) {
24
21
  if (/^\d+$/.test(identifier)) {