@hominis/fireforge 0.43.1 → 0.44.1

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 (478) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +3 -3
  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 +13 -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-patch-manifest.d.ts +8 -0
  22. package/dist/src/commands/doctor-patch-manifest.js +155 -0
  23. package/dist/src/commands/doctor-post-rebase-audit.d.ts +2 -0
  24. package/dist/src/commands/{doctor/post-rebase-audit.js → doctor-post-rebase-audit.js} +3 -3
  25. package/dist/src/commands/doctor-working-tree.d.ts +10 -12
  26. package/dist/src/commands/doctor-working-tree.js +11 -18
  27. package/dist/src/commands/doctor.d.ts +1 -1
  28. package/dist/src/commands/doctor.js +64 -96
  29. package/dist/src/commands/download.js +32 -48
  30. package/dist/src/commands/export-all.js +29 -33
  31. package/dist/src/commands/export-flow.js +6 -6
  32. package/dist/src/commands/export-placement-conflicts.js +10 -9
  33. package/dist/src/commands/export-placement-gate.js +5 -5
  34. package/dist/src/commands/export-shared.d.ts +30 -33
  35. package/dist/src/commands/export-shared.js +45 -43
  36. package/dist/src/commands/export.js +21 -27
  37. package/dist/src/commands/furnace/apply.js +14 -23
  38. package/dist/src/commands/furnace/chrome-doc-remove.js +14 -21
  39. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +23 -23
  40. package/dist/src/commands/furnace/chrome-doc-templates.js +23 -23
  41. package/dist/src/commands/furnace/chrome-doc-tests.js +26 -29
  42. package/dist/src/commands/furnace/chrome-doc.d.ts +16 -17
  43. package/dist/src/commands/furnace/chrome-doc.js +31 -41
  44. package/dist/src/commands/furnace/create-browser-test.d.ts +4 -4
  45. package/dist/src/commands/furnace/create-browser-test.js +8 -8
  46. package/dist/src/commands/furnace/create-mochikit.d.ts +6 -7
  47. package/dist/src/commands/furnace/create-mochikit.js +8 -9
  48. package/dist/src/commands/furnace/create-readback.d.ts +12 -13
  49. package/dist/src/commands/furnace/create-readback.js +12 -13
  50. package/dist/src/commands/furnace/create-templates.d.ts +22 -28
  51. package/dist/src/commands/furnace/create-templates.js +35 -30
  52. package/dist/src/commands/furnace/create-xpcshell.js +2 -2
  53. package/dist/src/commands/furnace/create.js +33 -23
  54. package/dist/src/commands/furnace/deploy.js +16 -24
  55. package/dist/src/commands/furnace/diff.js +15 -10
  56. package/dist/src/commands/furnace/init.js +33 -36
  57. package/dist/src/commands/furnace/override.js +23 -25
  58. package/dist/src/commands/furnace/preview.js +47 -67
  59. package/dist/src/commands/furnace/refresh.js +12 -16
  60. package/dist/src/commands/furnace/remove-mutations.d.ts +3 -6
  61. package/dist/src/commands/furnace/remove-mutations.js +14 -17
  62. package/dist/src/commands/furnace/remove-state.js +1 -1
  63. package/dist/src/commands/furnace/remove.js +33 -38
  64. package/dist/src/commands/furnace/rename-xpcshell.d.ts +8 -11
  65. package/dist/src/commands/furnace/rename-xpcshell.js +8 -11
  66. package/dist/src/commands/furnace/rename.js +59 -69
  67. package/dist/src/commands/furnace/scan.js +19 -33
  68. package/dist/src/commands/furnace/status.js +12 -9
  69. package/dist/src/commands/furnace/sync.js +9 -16
  70. package/dist/src/commands/furnace/validate.js +20 -20
  71. package/dist/src/commands/import.js +48 -37
  72. package/dist/src/commands/lint-per-patch.js +15 -10
  73. package/dist/src/commands/lint-per-run-checkjs.d.ts +13 -13
  74. package/dist/src/commands/lint-per-run-checkjs.js +13 -13
  75. package/dist/src/commands/lint-report.js +6 -7
  76. package/dist/src/commands/lint.d.ts +15 -16
  77. package/dist/src/commands/lint.js +86 -102
  78. package/dist/src/commands/package.js +6 -11
  79. package/dist/src/commands/patch/compact.js +1 -1
  80. package/dist/src/commands/patch/delete.js +24 -33
  81. package/dist/src/commands/patch/index.d.ts +5 -6
  82. package/dist/src/commands/patch/index.js +10 -12
  83. package/dist/src/commands/patch/lint-ignore.js +14 -13
  84. package/dist/src/commands/patch/move-files-create.d.ts +6 -6
  85. package/dist/src/commands/patch/move-files-create.js +8 -8
  86. package/dist/src/commands/patch/move-files-into.d.ts +6 -6
  87. package/dist/src/commands/patch/move-files-into.js +8 -8
  88. package/dist/src/commands/patch/patch-context.d.ts +2 -3
  89. package/dist/src/commands/patch/patch-context.js +2 -3
  90. package/dist/src/commands/patch/rename.d.ts +10 -11
  91. package/dist/src/commands/patch/rename.js +11 -12
  92. package/dist/src/commands/patch/reorder.js +8 -9
  93. package/dist/src/commands/patch/split-plan.js +1 -1
  94. package/dist/src/commands/patch/split.d.ts +8 -10
  95. package/dist/src/commands/patch/split.js +9 -11
  96. package/dist/src/commands/patch/staged-dependency-validate.js +8 -8
  97. package/dist/src/commands/patch/staged-dependency.js +7 -3
  98. package/dist/src/commands/patch/tier.d.ts +9 -9
  99. package/dist/src/commands/patch/tier.js +18 -15
  100. package/dist/src/commands/re-export-adjacent.js +9 -8
  101. package/dist/src/commands/re-export-bulk-scan.d.ts +9 -9
  102. package/dist/src/commands/re-export-bulk-scan.js +22 -21
  103. package/dist/src/commands/re-export-drift.js +10 -10
  104. package/dist/src/commands/re-export-files.d.ts +6 -7
  105. package/dist/src/commands/re-export-files.js +34 -44
  106. package/dist/src/commands/re-export-lint.d.ts +10 -13
  107. package/dist/src/commands/re-export-lint.js +16 -14
  108. package/dist/src/commands/re-export-options.d.ts +6 -6
  109. package/dist/src/commands/re-export-options.js +6 -6
  110. package/dist/src/commands/re-export-register.d.ts +4 -10
  111. package/dist/src/commands/re-export-register.js +5 -11
  112. package/dist/src/commands/re-export-scan.js +1 -1
  113. package/dist/src/commands/re-export-single.d.ts +3 -3
  114. package/dist/src/commands/re-export-single.js +33 -25
  115. package/dist/src/commands/re-export.js +13 -21
  116. package/dist/src/commands/rebase/abort.js +3 -3
  117. package/dist/src/commands/rebase/confirm.js +2 -1
  118. package/dist/src/commands/rebase/continue.d.ts +1 -1
  119. package/dist/src/commands/rebase/continue.js +14 -9
  120. package/dist/src/commands/rebase/index.js +11 -11
  121. package/dist/src/commands/rebase/patch-loop.d.ts +7 -1
  122. package/dist/src/commands/rebase/patch-loop.js +60 -39
  123. package/dist/src/commands/rebase/summary.js +1 -1
  124. package/dist/src/commands/register.js +5 -7
  125. package/dist/src/commands/reset.js +2 -1
  126. package/dist/src/commands/resolve.d.ts +16 -13
  127. package/dist/src/commands/resolve.js +30 -33
  128. package/dist/src/commands/run.js +33 -35
  129. package/dist/src/commands/setup-support.js +15 -19
  130. package/dist/src/commands/setup.js +13 -9
  131. package/dist/src/commands/status-check.d.ts +6 -6
  132. package/dist/src/commands/status-json.d.ts +6 -6
  133. package/dist/src/commands/status-json.js +5 -0
  134. package/dist/src/commands/status-output.d.ts +5 -5
  135. package/dist/src/commands/status-output.js +6 -6
  136. package/dist/src/commands/status-ownership.d.ts +16 -14
  137. package/dist/src/commands/status-ownership.js +16 -14
  138. package/dist/src/commands/status.js +42 -34
  139. package/dist/src/commands/test-build-phase.js +26 -13
  140. package/dist/src/commands/test-diagnose.js +44 -44
  141. package/dist/src/commands/test-register.js +1 -0
  142. package/dist/src/commands/test-run.d.ts +14 -16
  143. package/dist/src/commands/test-run.js +21 -13
  144. package/dist/src/commands/test-stale-gate.d.ts +15 -16
  145. package/dist/src/commands/test-stale-gate.js +15 -16
  146. package/dist/src/commands/test-verdict.d.ts +20 -3
  147. package/dist/src/commands/test-verdict.js +30 -0
  148. package/dist/src/commands/test.js +83 -76
  149. package/dist/src/commands/token-coverage.js +3 -4
  150. package/dist/src/commands/token.js +16 -18
  151. package/dist/src/commands/tree.js +47 -31
  152. package/dist/src/commands/typecheck.d.ts +10 -12
  153. package/dist/src/commands/typecheck.js +24 -27
  154. package/dist/src/commands/verify.js +13 -15
  155. package/dist/src/commands/watch.js +21 -23
  156. package/dist/src/commands/wire.js +52 -62
  157. package/dist/src/core/ast-utils.d.ts +8 -7
  158. package/dist/src/core/ast-utils.js +8 -7
  159. package/dist/src/core/branding.d.ts +9 -14
  160. package/dist/src/core/branding.js +7 -11
  161. package/dist/src/core/browser-wire.d.ts +1 -1
  162. package/dist/src/core/browser-wire.js +14 -19
  163. package/dist/src/core/build-audit-platform.js +25 -33
  164. package/dist/src/core/build-audit-registration.js +17 -25
  165. package/dist/src/core/build-audit-resolve.d.ts +15 -17
  166. package/dist/src/core/build-audit-resolve.js +39 -36
  167. package/dist/src/core/build-audit-transforms.js +18 -19
  168. package/dist/src/core/build-audit.js +28 -42
  169. package/dist/src/core/build-baseline-types.d.ts +29 -35
  170. package/dist/src/core/build-baseline.d.ts +5 -4
  171. package/dist/src/core/build-baseline.js +14 -14
  172. package/dist/src/core/build-overwrite-guard.d.ts +7 -14
  173. package/dist/src/core/build-overwrite-guard.js +17 -27
  174. package/dist/src/core/build-prepare.d.ts +2 -2
  175. package/dist/src/core/build-prepare.js +8 -9
  176. package/dist/src/core/config-mutate.js +20 -19
  177. package/dist/src/core/config-state.js +12 -4
  178. package/dist/src/core/config-validate.js +8 -5
  179. package/dist/src/core/config.d.ts +5 -8
  180. package/dist/src/core/config.js +7 -16
  181. package/dist/src/core/coverage-extend.d.ts +15 -13
  182. package/dist/src/core/coverage-extend.js +15 -13
  183. package/dist/src/core/destructive.d.ts +31 -9
  184. package/dist/src/core/destructive.js +29 -26
  185. package/dist/src/core/diff-hunks.js +10 -12
  186. package/dist/src/core/discard-baseline.d.ts +8 -8
  187. package/dist/src/core/discard-baseline.js +16 -15
  188. package/dist/src/core/display-state.d.ts +12 -14
  189. package/dist/src/core/display-state.js +12 -14
  190. package/dist/src/core/engine-changes.d.ts +2 -4
  191. package/dist/src/core/engine-changes.js +2 -4
  192. package/dist/src/core/engine-precondition.d.ts +28 -13
  193. package/dist/src/core/engine-precondition.js +25 -10
  194. package/dist/src/core/engine-session-lock.d.ts +47 -21
  195. package/dist/src/core/engine-session-lock.js +168 -33
  196. package/dist/src/core/export-moved-code-shape.d.ts +10 -10
  197. package/dist/src/core/export-moved-code-shape.js +10 -10
  198. package/dist/src/core/file-lock.d.ts +62 -5
  199. package/dist/src/core/file-lock.js +196 -59
  200. package/dist/src/core/firefox-cache.js +1 -3
  201. package/dist/src/core/firefox-extract.js +10 -14
  202. package/dist/src/core/firefox.d.ts +7 -5
  203. package/dist/src/core/firefox.js +4 -5
  204. package/dist/src/core/furnace-apply-dry-run.d.ts +2 -2
  205. package/dist/src/core/furnace-apply-dry-run.js +3 -2
  206. package/dist/src/core/furnace-apply-ftl.d.ts +7 -9
  207. package/dist/src/core/furnace-apply-ftl.js +16 -13
  208. package/dist/src/core/furnace-apply-helpers.d.ts +7 -7
  209. package/dist/src/core/furnace-apply-helpers.js +30 -21
  210. package/dist/src/core/furnace-apply-overwrite-warn.d.ts +8 -9
  211. package/dist/src/core/furnace-apply-overwrite-warn.js +8 -9
  212. package/dist/src/core/furnace-apply-undeploy-actions.d.ts +3 -4
  213. package/dist/src/core/furnace-apply-undeploy-actions.js +3 -4
  214. package/dist/src/core/furnace-apply.d.ts +5 -8
  215. package/dist/src/core/furnace-apply.js +19 -24
  216. package/dist/src/core/furnace-checksum-utils.d.ts +14 -0
  217. package/dist/src/core/furnace-checksum-utils.js +16 -0
  218. package/dist/src/core/furnace-config.d.ts +21 -30
  219. package/dist/src/core/furnace-config.js +32 -54
  220. package/dist/src/core/furnace-constants.d.ts +8 -0
  221. package/dist/src/core/furnace-constants.js +10 -2
  222. package/dist/src/core/furnace-css-fragments.d.ts +5 -5
  223. package/dist/src/core/furnace-css-fragments.js +5 -5
  224. package/dist/src/core/furnace-jsconfig.d.ts +11 -12
  225. package/dist/src/core/furnace-jsconfig.js +11 -12
  226. package/dist/src/core/furnace-marker.d.ts +7 -8
  227. package/dist/src/core/furnace-marker.js +7 -8
  228. package/dist/src/core/furnace-operation.d.ts +31 -0
  229. package/dist/src/core/furnace-operation.js +54 -17
  230. package/dist/src/core/furnace-precondition.d.ts +61 -0
  231. package/dist/src/core/furnace-precondition.js +67 -0
  232. package/dist/src/core/furnace-registration-remove.d.ts +5 -6
  233. package/dist/src/core/furnace-registration-remove.js +6 -7
  234. package/dist/src/core/furnace-registration-validate.d.ts +13 -0
  235. package/dist/src/core/furnace-registration-validate.js +20 -1
  236. package/dist/src/core/furnace-registration.d.ts +5 -6
  237. package/dist/src/core/furnace-registration.js +10 -12
  238. package/dist/src/core/furnace-rollback.js +7 -7
  239. package/dist/src/core/furnace-scanner.d.ts +1 -0
  240. package/dist/src/core/furnace-scanner.js +1 -2
  241. package/dist/src/core/furnace-stale-export.d.ts +10 -10
  242. package/dist/src/core/furnace-stale-export.js +10 -10
  243. package/dist/src/core/furnace-state-persist.d.ts +6 -8
  244. package/dist/src/core/furnace-state-persist.js +6 -8
  245. package/dist/src/core/furnace-validate-accessibility.d.ts +22 -0
  246. package/dist/src/core/furnace-validate-accessibility.js +310 -1
  247. package/dist/src/core/furnace-validate-compatibility.js +141 -1
  248. package/dist/src/core/furnace-validate-helpers.d.ts +5 -116
  249. package/dist/src/core/furnace-validate-helpers.js +0 -491
  250. package/dist/src/core/furnace-validate-registration.d.ts +7 -0
  251. package/dist/src/core/furnace-validate-registration.js +115 -41
  252. package/dist/src/core/furnace-validate.d.ts +16 -0
  253. package/dist/src/core/furnace-validate.js +188 -145
  254. package/dist/src/core/furnace-version-drift.d.ts +10 -11
  255. package/dist/src/core/furnace-version-drift.js +10 -11
  256. package/dist/src/core/git-base.d.ts +21 -9
  257. package/dist/src/core/git-base.js +24 -9
  258. package/dist/src/core/git-diff.d.ts +20 -9
  259. package/dist/src/core/git-diff.js +148 -63
  260. package/dist/src/core/git-file-ops.d.ts +19 -19
  261. package/dist/src/core/git-file-ops.js +19 -19
  262. package/dist/src/core/git-readonly-index.d.ts +53 -19
  263. package/dist/src/core/git-readonly-index.js +75 -31
  264. package/dist/src/core/git-status.d.ts +5 -6
  265. package/dist/src/core/git-status.js +5 -6
  266. package/dist/src/core/git.d.ts +9 -10
  267. package/dist/src/core/git.js +57 -71
  268. package/dist/src/core/license-headers.d.ts +17 -23
  269. package/dist/src/core/license-headers.js +17 -23
  270. package/dist/src/core/mach-build-artifacts.d.ts +9 -9
  271. package/dist/src/core/mach-build-artifacts.js +57 -19
  272. package/dist/src/core/mach-error-hints.js +42 -29
  273. package/dist/src/core/mach-known-noise-filter.d.ts +15 -16
  274. package/dist/src/core/mach-known-noise-filter.js +18 -20
  275. package/dist/src/core/mach-mozconfig.d.ts +14 -0
  276. package/dist/src/core/mach-mozconfig.js +30 -0
  277. package/dist/src/core/mach-resource-shim.d.ts +43 -58
  278. package/dist/src/core/mach-resource-shim.js +50 -67
  279. package/dist/src/core/mach.d.ts +37 -41
  280. package/dist/src/core/mach.js +50 -51
  281. package/dist/src/core/marionette-port.d.ts +36 -1
  282. package/dist/src/core/marionette-port.js +96 -31
  283. package/dist/src/core/marionette-preflight.d.ts +8 -12
  284. package/dist/src/core/marionette-preflight.js +86 -52
  285. package/dist/src/core/mochitest-server-port.d.ts +42 -0
  286. package/dist/src/core/mochitest-server-port.js +119 -0
  287. package/dist/src/core/module-resolution-preflight.d.ts +9 -11
  288. package/dist/src/core/module-resolution-preflight.js +9 -11
  289. package/dist/src/core/moz-manifest-helpers.d.ts +79 -0
  290. package/dist/src/core/moz-manifest-helpers.js +163 -0
  291. package/dist/src/core/{manifest-register.d.ts → moz-manifest-register.d.ts} +1 -1
  292. package/dist/src/core/{manifest-register.js → moz-manifest-register.js} +13 -28
  293. package/dist/src/core/{manifest-rules.d.ts → moz-manifest-rules.d.ts} +10 -1
  294. package/dist/src/core/{manifest-rules.js → moz-manifest-rules.js} +31 -24
  295. package/dist/src/core/{manifest-tokenizers.d.ts → moz-manifest-tokenizers.d.ts} +3 -5
  296. package/dist/src/core/{manifest-tokenizers.js → moz-manifest-tokenizers.js} +9 -12
  297. package/dist/src/core/ownership-table.d.ts +14 -16
  298. package/dist/src/core/ownership-table.js +14 -16
  299. package/dist/src/core/parser-fallback.d.ts +15 -1
  300. package/dist/src/core/parser-fallback.js +22 -3
  301. package/dist/src/core/patch-apply-fuzz.d.ts +10 -12
  302. package/dist/src/core/patch-apply-fuzz.js +15 -18
  303. package/dist/src/core/patch-apply.d.ts +13 -34
  304. package/dist/src/core/patch-apply.js +16 -76
  305. package/dist/src/core/patch-artifact-normalize.d.ts +13 -15
  306. package/dist/src/core/patch-artifact-normalize.js +13 -15
  307. package/dist/src/core/patch-export-metadata.d.ts +12 -2
  308. package/dist/src/core/patch-export-metadata.js +13 -4
  309. package/dist/src/core/patch-export-update.d.ts +13 -1
  310. package/dist/src/core/patch-export-update.js +25 -8
  311. package/dist/src/core/patch-export.d.ts +15 -8
  312. package/dist/src/core/patch-export.js +10 -11
  313. package/dist/src/core/patch-files.d.ts +5 -6
  314. package/dist/src/core/patch-files.js +5 -6
  315. package/dist/src/core/patch-identifier-suggest.d.ts +7 -12
  316. package/dist/src/core/patch-identifier-suggest.js +14 -19
  317. package/dist/src/core/patch-lint-binary.d.ts +51 -0
  318. package/dist/src/core/patch-lint-binary.js +39 -0
  319. package/dist/src/core/{lint-cache.d.ts → patch-lint-cache.d.ts} +5 -5
  320. package/dist/src/core/{lint-cache.js → patch-lint-cache.js} +21 -23
  321. package/dist/src/core/patch-lint-checkjs.d.ts +16 -16
  322. package/dist/src/core/patch-lint-checkjs.js +41 -20
  323. package/dist/src/core/patch-lint-chrome-jsdoc.d.ts +6 -9
  324. package/dist/src/core/patch-lint-chrome-jsdoc.js +10 -14
  325. package/dist/src/core/patch-lint-creators.d.ts +8 -8
  326. package/dist/src/core/patch-lint-creators.js +8 -8
  327. package/dist/src/core/patch-lint-cross.d.ts +17 -20
  328. package/dist/src/core/patch-lint-cross.js +16 -16
  329. package/dist/src/core/patch-lint-css.d.ts +3 -5
  330. package/dist/src/core/patch-lint-css.js +17 -22
  331. package/dist/src/core/patch-lint-diff-tag.d.ts +14 -16
  332. package/dist/src/core/patch-lint-diff-tag.js +14 -16
  333. package/dist/src/core/patch-lint-jsdoc.d.ts +7 -8
  334. package/dist/src/core/patch-lint-jsdoc.js +26 -19
  335. package/dist/src/core/patch-lint-mozbuild.js +20 -20
  336. package/dist/src/core/patch-lint-observer.d.ts +7 -7
  337. package/dist/src/core/patch-lint-observer.js +7 -7
  338. package/dist/src/core/patch-lint.d.ts +38 -29
  339. package/dist/src/core/patch-lint.js +89 -121
  340. package/dist/src/core/patch-lock.d.ts +1 -1
  341. package/dist/src/core/patch-lock.js +45 -9
  342. package/dist/src/core/patch-manifest-consistency.d.ts +57 -6
  343. package/dist/src/core/patch-manifest-consistency.js +111 -40
  344. package/dist/src/core/patch-manifest-files-affected.d.ts +43 -0
  345. package/dist/src/core/patch-manifest-files-affected.js +77 -0
  346. package/dist/src/core/patch-manifest-io.js +14 -18
  347. package/dist/src/core/patch-manifest-resolve.d.ts +9 -12
  348. package/dist/src/core/patch-manifest-resolve.js +9 -12
  349. package/dist/src/core/patch-manifest-validate.d.ts +6 -1
  350. package/dist/src/core/patch-manifest-validate.js +25 -13
  351. package/dist/src/core/patch-manifest.d.ts +2 -1
  352. package/dist/src/core/patch-manifest.js +2 -1
  353. package/dist/src/core/patch-parse.d.ts +24 -13
  354. package/dist/src/core/patch-parse.js +19 -14
  355. package/dist/src/core/patch-policy.d.ts +2 -2
  356. package/dist/src/core/patch-policy.js +2 -2
  357. package/dist/src/core/patch-registration-refs.d.ts +7 -8
  358. package/dist/src/core/patch-registration-refs.js +9 -9
  359. package/dist/src/core/process-cpu-time.d.ts +12 -0
  360. package/dist/src/core/process-cpu-time.js +52 -0
  361. package/dist/src/core/rebase-session.d.ts +38 -26
  362. package/dist/src/core/rebase-session.js +55 -28
  363. package/dist/src/core/register-browser-content.js +9 -74
  364. package/dist/src/core/{register-test-manifest.js → register-browser-test.js} +7 -42
  365. package/dist/src/core/register-module.js +10 -43
  366. package/dist/src/core/register-scaffold.d.ts +8 -8
  367. package/dist/src/core/register-scaffold.js +10 -10
  368. package/dist/src/core/register-shared-css.d.ts +9 -12
  369. package/dist/src/core/register-shared-css.js +17 -87
  370. package/dist/src/core/register-xpcshell-test.d.ts +6 -7
  371. package/dist/src/core/register-xpcshell-test.js +7 -8
  372. package/dist/src/core/shared-ftl.js +12 -4
  373. package/dist/src/core/state-file.d.ts +45 -0
  374. package/dist/src/core/state-file.js +34 -0
  375. package/dist/src/core/status-binary.d.ts +18 -11
  376. package/dist/src/core/status-binary.js +19 -12
  377. package/dist/src/core/status-classify.d.ts +30 -36
  378. package/dist/src/core/status-classify.js +21 -26
  379. package/dist/src/core/test-changed-prefs.d.ts +30 -0
  380. package/dist/src/core/test-changed-prefs.js +78 -0
  381. package/dist/src/core/test-harness-crash.d.ts +25 -63
  382. package/dist/src/core/test-harness-crash.js +121 -142
  383. package/dist/src/core/test-harness-signature.d.ts +18 -0
  384. package/dist/src/core/test-harness-signature.js +13 -0
  385. package/dist/src/core/test-path-scope.d.ts +12 -15
  386. package/dist/src/core/test-path-scope.js +15 -18
  387. package/dist/src/core/test-stale-check.d.ts +17 -17
  388. package/dist/src/core/test-stale-check.js +50 -43
  389. package/dist/src/core/test-stall-triage.d.ts +51 -0
  390. package/dist/src/core/test-stall-triage.js +126 -0
  391. package/dist/src/core/token-category.js +66 -14
  392. package/dist/src/core/token-coverage.js +9 -9
  393. package/dist/src/core/token-dark-mode.d.ts +9 -11
  394. package/dist/src/core/token-dark-mode.js +29 -13
  395. package/dist/src/core/token-docs.d.ts +2 -0
  396. package/dist/src/core/token-docs.js +3 -1
  397. package/dist/src/core/token-manager.d.ts +10 -2
  398. package/dist/src/core/token-manager.js +17 -8
  399. package/dist/src/core/token-scaffold.d.ts +7 -7
  400. package/dist/src/core/token-scaffold.js +7 -7
  401. package/dist/src/core/token-variant.d.ts +4 -4
  402. package/dist/src/core/token-variant.js +28 -6
  403. package/dist/src/core/toolchain-preflight.d.ts +20 -21
  404. package/dist/src/core/toolchain-preflight.js +28 -31
  405. package/dist/src/core/tree-guard.js +12 -2
  406. package/dist/src/core/tree-store.d.ts +41 -27
  407. package/dist/src/core/tree-store.js +68 -43
  408. package/dist/src/core/typecheck-shim.d.ts +23 -24
  409. package/dist/src/core/typecheck-shim.js +23 -24
  410. package/dist/src/core/typecheck.js +15 -9
  411. package/dist/src/core/wire-destroy.d.ts +3 -3
  412. package/dist/src/core/wire-destroy.js +7 -15
  413. package/dist/src/core/wire-dom-fragment.d.ts +9 -17
  414. package/dist/src/core/wire-dom-fragment.js +11 -44
  415. package/dist/src/core/wire-init.d.ts +2 -3
  416. package/dist/src/core/wire-init.js +8 -19
  417. package/dist/src/core/wire-subscript.d.ts +2 -3
  418. package/dist/src/core/wire-subscript.js +4 -13
  419. package/dist/src/core/wire-utils.d.ts +7 -8
  420. package/dist/src/core/wire-utils.js +7 -8
  421. package/dist/src/core/xpcshell-appdir.d.ts +38 -45
  422. package/dist/src/core/xpcshell-appdir.js +38 -45
  423. package/dist/src/errors/base.d.ts +37 -16
  424. package/dist/src/errors/base.js +37 -16
  425. package/dist/src/errors/build.d.ts +13 -1
  426. package/dist/src/errors/build.js +14 -2
  427. package/dist/src/errors/codes.d.ts +16 -0
  428. package/dist/src/errors/codes.js +16 -0
  429. package/dist/src/errors/furnace.d.ts +4 -4
  430. package/dist/src/errors/git.d.ts +6 -10
  431. package/dist/src/errors/git.js +6 -10
  432. package/dist/src/errors/patch.d.ts +7 -7
  433. package/dist/src/errors/patch.js +7 -7
  434. package/dist/src/errors/rebase.d.ts +4 -4
  435. package/dist/src/errors/rebase.js +4 -4
  436. package/dist/src/index.d.ts +7 -1
  437. package/dist/src/index.js +5 -0
  438. package/dist/src/types/commands/index.d.ts +2 -2
  439. package/dist/src/types/commands/options.d.ts +111 -70
  440. package/dist/src/types/commands/patches.d.ts +24 -34
  441. package/dist/src/types/commands/project.d.ts +5 -34
  442. package/dist/src/types/config.d.ts +25 -23
  443. package/dist/src/types/config.js +6 -1
  444. package/dist/src/types/furnace.d.ts +37 -13
  445. package/dist/src/types/furnace.js +19 -1
  446. package/dist/src/types/index.d.ts +3 -3
  447. package/dist/src/utils/build-info.d.ts +7 -8
  448. package/dist/src/utils/build-info.js +12 -11
  449. package/dist/src/utils/errors.d.ts +6 -8
  450. package/dist/src/utils/errors.js +6 -8
  451. package/dist/src/utils/logger.d.ts +27 -10
  452. package/dist/src/utils/logger.js +28 -16
  453. package/dist/src/utils/machine-output.d.ts +12 -0
  454. package/dist/src/utils/machine-output.js +33 -0
  455. package/dist/src/utils/options.d.ts +31 -14
  456. package/dist/src/utils/options.js +86 -30
  457. package/dist/src/utils/paths.d.ts +10 -8
  458. package/dist/src/utils/paths.js +10 -8
  459. package/dist/src/utils/platform.d.ts +0 -25
  460. package/dist/src/utils/platform.js +1 -42
  461. package/dist/src/utils/process-group.d.ts +9 -4
  462. package/dist/src/utils/process-group.js +19 -5
  463. package/dist/src/utils/process.d.ts +42 -7
  464. package/dist/src/utils/process.js +44 -17
  465. package/dist/src/utils/ps-duration.d.ts +31 -0
  466. package/dist/src/utils/ps-duration.js +70 -0
  467. package/dist/src/utils/regex.d.ts +9 -10
  468. package/dist/src/utils/regex.js +9 -10
  469. package/dist/src/utils/validation.d.ts +58 -28
  470. package/dist/src/utils/validation.js +60 -33
  471. package/package.json +3 -3
  472. package/dist/src/commands/doctor/post-rebase-audit.d.ts +0 -2
  473. package/dist/src/commands/doctor-furnace-manifest-sync.d.ts +0 -18
  474. package/dist/src/core/manifest-helpers.d.ts +0 -38
  475. package/dist/src/core/manifest-helpers.js +0 -120
  476. /package/dist/src/core/{lint-projection.d.ts → patch-lint-projection.d.ts} +0 -0
  477. /package/dist/src/core/{lint-projection.js → patch-lint-projection.js} +0 -0
  478. /package/dist/src/core/{register-test-manifest.d.ts → register-browser-test.d.ts} +0 -0
@@ -4,39 +4,37 @@ import { join } from 'node:path';
4
4
  import { Command } from 'commander';
5
5
  import { isBrandingManagedPath } from '../core/branding.js';
6
6
  import { getProjectPaths, loadConfig } from '../core/config.js';
7
+ import { assertEngineGitReady } from '../core/engine-precondition.js';
7
8
  import { collectFurnaceManagedPrefixes } from '../core/furnace-config.js';
8
- import { getStatusWithCodes, hasChanges, isGitRepository } from '../core/git.js';
9
+ import { getStatusWithCodes, hasChanges } from '../core/git.js';
9
10
  import { getAllDiff, getDiffForFilesAgainstHead } from '../core/git-diff.js';
10
11
  import { expandUntrackedDirectoryEntries, getModifiedFilesInDir, getUntrackedFiles, getUntrackedFilesInDir, getWorkingTreeStatus, } from '../core/git-status.js';
11
- import { clearPerPatchLintCache } from '../core/lint-cache.js';
12
12
  import { extractAffectedFiles } from '../core/patch-apply.js';
13
13
  import { buildPatchQueueContext, countNonBinaryDiffLines, formatPatchLintIssue, lintExportedPatch, lintPatchQueue, lintPatchSize, } from '../core/patch-lint.js';
14
+ import { clearPerPatchLintCache } from '../core/patch-lint-cache.js';
14
15
  import { collectDiffFilePaths, tagLintIssues } from '../core/patch-lint-diff-tag.js';
15
- import { GeneralError } from '../errors/base.js';
16
+ import { GeneralError, InvalidArgumentError } from '../errors/base.js';
16
17
  import { pathExists } from '../utils/fs.js';
17
18
  import { info, intro, outro, success, warn } from '../utils/logger.js';
18
19
  import { stripEnginePrefix } from '../utils/paths.js';
19
20
  import { lintPerPatch } from './lint-per-patch.js';
20
21
  /**
21
22
  * Resolves the diff the lint command should run against. Returns `null` when
22
- * there is nothing to lint (e.g. no matching files, clean tree, or empty
23
- * diff content) — callers treat that as the early-exit signal and stop.
23
+ * there is nothing to lint (no matching files, clean tree, or empty diff
24
+ * content) — callers treat that as the early-exit signal and stop.
24
25
  *
25
26
  * Extracted from {@link lintCommand} so that function stays under the
26
- * per-function LOC budget as the command grows; the two file-mode and
27
- * aggregate-mode branches share no state with the post-lint reporting
28
- * pipeline, so the split is a pure rename rather than a refactor.
27
+ * per-function LOC budget; the two file-mode and aggregate-mode branches
28
+ * share no state with the post-lint reporting pipeline.
29
29
  *
30
- * When `binaryName` is provided, the aggregate-mode branch (no
31
- * explicit file list) excludes paths under `browser/branding/<binaryName>/`
32
- * from the diff. `status` classifies those paths as `branding` —
33
- * tool-managed material the operator did not author directly — and
34
- * the 2026-04-21 eval (Finding #2) reported that `fireforge lint` on
35
- * a fresh project immediately failed `large-patch-lines` /
36
- * `large-patch-files` / `missing-license-header` on the generated
37
- * branding tree. File-list mode (explicit paths) preserves the
38
- * previous behaviour: passing a branding file explicitly still lints
39
- * it, so operators who need to audit branding content can do so.
30
+ * When `binaryName` is provided, the aggregate-mode branch (no explicit file
31
+ * list) excludes paths under `browser/branding/<binaryName>/` from the diff.
32
+ * `status` classifies those paths as `branding` — tool-managed material the
33
+ * operator did not author directly — and without the exclusion
34
+ * `fireforge lint` on a fresh project immediately fails `large-patch-lines`
35
+ * / `large-patch-files` / `missing-license-header` on the generated branding
36
+ * tree. File-list mode (explicit paths) is unaffected: passing a branding
37
+ * file explicitly still lints it.
40
38
  */
41
39
  async function resolveLintDiff(engineDir, files, binaryName, furnacePrefixes) {
42
40
  if (files.length > 0) {
@@ -44,12 +42,12 @@ async function resolveLintDiff(engineDir, files, binaryName, furnacePrefixes) {
44
42
  let fileStatuses;
45
43
  let untrackedFiles;
46
44
  // Strip a leading `engine/` segment up-front so the rest of the lookup
47
- // pipeline (directory stat, modified-files-in-dir, status probe) all
48
- // see the engine-relative form. Without this, passing
49
- // `engine/browser/base/content/foo.js` fell through to "No modified
50
- // files found in the specified paths." because git sees every path
45
+ // pipeline (directory stat, modified-files-in-dir, status probe) sees the
46
+ // engine-relative form. Without it, passing
47
+ // `engine/browser/base/content/foo.js` falls through to "No modified
48
+ // files found in the specified paths.", because git sees every path
51
49
  // relative to engine/. The same normalization runs in `register`,
52
- // `test`, and `export` via `stripEnginePrefix`.
50
+ // `test`, and `export`.
53
51
  const normalizedFiles = files.map((inputPath) => stripEnginePrefix(inputPath));
54
52
  for (const inputPath of normalizedFiles) {
55
53
  const fullInputPath = join(engineDir, inputPath);
@@ -98,21 +96,18 @@ async function resolveLintDiff(engineDir, files, binaryName, furnacePrefixes) {
98
96
  outro('Nothing to lint');
99
97
  return null;
100
98
  }
101
- // Aggregate-mode branding exclusion. A fresh-setup workspace (after
102
- // `fireforge setup` + `download` + `bootstrap` + `build`) carries a
103
- // large tool-managed branding diff that the operator did not
104
- // author; running the default lint against it fires size and
105
- // license-header rules on content that was never intended to
106
- // survive in the patch queue as-is. The exclusion mirrors the
107
- // `branding` bucket in `fireforge status` so the two views stay
108
- // consistent.
99
+ // Aggregate-mode branding exclusion. A fresh-setup workspace carries a
100
+ // large tool-managed branding diff the operator did not author; running
101
+ // the default lint against it fires size and license-header rules on
102
+ // content that was never intended to survive in the patch queue as-is. The
103
+ // exclusion mirrors the `branding` bucket in `fireforge status` so the two
104
+ // views stay consistent.
109
105
  //
110
- // `expandUntrackedDirectoryEntries` promotes collapsed `?? dir/`
111
- // status rows to individual file entries before the diff pass.
112
- // Without it, a patch that introduces a new directory shows up as
113
- // `?? browser/modules/<fork>/` and `getDiffForFilesAgainstHead`
114
- // crashed with EISDIR reading the directory as if it were a file
115
- // (eval finding: aggregate lint unusable on a real imported queue).
106
+ // `expandUntrackedDirectoryEntries` promotes collapsed `?? dir/` status
107
+ // rows to individual file entries before the diff pass. Without it, a
108
+ // patch that introduces a new directory shows up as
109
+ // `?? browser/modules/<fork>/` and `getDiffForFilesAgainstHead` crashes
110
+ // with EISDIR reading the directory as if it were a file.
116
111
  if (binaryName) {
117
112
  const rawStatus = await getWorkingTreeStatus(engineDir);
118
113
  const expanded = await expandUntrackedDirectoryEntries(engineDir, rawStatus);
@@ -120,12 +115,11 @@ async function resolveLintDiff(engineDir, files, binaryName, furnacePrefixes) {
120
115
  const nonBrandingPaths = allPaths.filter((path) => !isBrandingManagedPath(path, binaryName));
121
116
  const brandingExcluded = allPaths.length - nonBrandingPaths.length;
122
117
  // Drop Furnace-managed paths the same way branding is dropped: their
123
- // contents are tool output (overrides, custom widgets, preview-
124
- // generated stories) that the operator did not author and never
125
- // intended to land on the patch queue. Without this carve-out, a
126
- // post-`furnace preview` aggregate `lint` failed with one
127
- // `missing-license-header` error per generated story file (eval
128
- // Finding 19) — each story is intentionally header-less because it's
118
+ // contents are tool output (overrides, custom widgets, preview-generated
119
+ // stories) that the operator did not author and never intended to land
120
+ // on the patch queue. Without the carve-out, a post-`furnace preview`
121
+ // aggregate `lint` fails with one `missing-license-header` error per
122
+ // generated story file — each is intentionally header-less because it is
129
123
  // re-generated from component metadata on every preview run.
130
124
  const filteredPaths = furnacePrefixes
131
125
  ? nonBrandingPaths.filter((path) => ![...furnacePrefixes].some((p) => path.startsWith(p)))
@@ -150,10 +144,8 @@ async function resolveLintDiff(engineDir, files, binaryName, furnacePrefixes) {
150
144
  }
151
145
  return diff;
152
146
  }
153
- // Fallback path: no binaryName available (e.g. a legacy caller
154
- // without a loaded config). Retain the pre-0.16.0 behaviour of
155
- // linting the full diff so the lint surface is at least as broad
156
- // as before.
147
+ // Fallback path: no binaryName available (a caller without a loaded
148
+ // config). Lint the full diff so the lint surface stays at least as broad.
157
149
  const diff = await getAllDiff(engineDir);
158
150
  if (!diff.trim()) {
159
151
  info('No diff content to lint.');
@@ -169,25 +161,24 @@ function buildMaxWarningsMessage(count, maxWarnings, scope) {
169
161
  ' If this is a release gate, run with --per-patch to identify the owning patch. For intentional staged imports, use patch staged-dependency; for ownership repairs, preview patch move-files, patch reorder --dry-run, or re-export --files --dry-run; add scoped lintIgnore only after review.');
170
162
  }
171
163
  /**
172
- * Filters aggregate-mode lint issues against per-patch `lintIgnore`
173
- * lists drawn from the manifest. An issue is dropped when at least one
174
- * patch whose `filesAffected` covers `issue.file` lists `issue.check`
175
- * in its `lintIgnore`.
164
+ * Filters aggregate-mode lint issues against per-patch `lintIgnore` lists
165
+ * drawn from the manifest. An issue is dropped when at least one patch whose
166
+ * `filesAffected` covers `issue.file` lists `issue.check` in its
167
+ * `lintIgnore`.
176
168
  *
177
- * Mirrors the per-patch contract: `--per-patch` mode threads each
178
- * patch's `lintIgnore` directly into `lintExportedPatch`, so a check
179
- * the operator explicitly waived in `patches.json` does not surface.
180
- * Aggregate `--since` mode previously rediscovered the suppressed
181
- * warning every CI run because the diff was treated as a single unit
182
- * with no patch-level scope. Attributing each issue's file to its
169
+ * Mirrors the per-patch contract: `--per-patch` mode threads each patch's
170
+ * `lintIgnore` directly into `lintExportedPatch`, so a check the operator
171
+ * explicitly waived in `patches.json` does not surface. Aggregate `--since`
172
+ * mode has no patch-level scope of its own and would otherwise rediscover
173
+ * the suppressed warning every CI run. Attributing each issue's file to its
183
174
  * owning patch via `filesAffected` re-establishes the same suppression
184
- * semantics. Cross-patch findings (forward-import, duplicate-creation)
185
- * still attribute via `issue.file` because the `file` field is the
186
- * offending site, which is owned by some patch.
175
+ * semantics. Cross-patch findings (forward-import, duplicate-creation) also
176
+ * attribute via `issue.file`, which is the offending site and is owned by
177
+ * some patch.
187
178
  *
188
- * Multiple owners: an issue is dropped if **any** owning patch waived
189
- * the rule. Conservative — never adds new findings, only drops
190
- * already-explicitly-waived ones.
179
+ * Multiple owners: an issue is dropped if **any** owning patch waived the
180
+ * rule. Conservative — never adds new findings, only drops already-waived
181
+ * ones.
191
182
  *
192
183
  * @param issues - Issues collected from the aggregate lint run.
193
184
  * @param ctx - Patch queue context used to attribute file → patch.
@@ -233,17 +224,17 @@ function validateLintFlags(options) {
233
224
  }
234
225
  if (options.maxWarnings !== undefined &&
235
226
  (!Number.isInteger(options.maxWarnings) || options.maxWarnings < 0)) {
236
- throw new GeneralError('--max-warnings must be a non-negative integer.');
227
+ throw new InvalidArgumentError('--max-warnings must be a non-negative integer.', '--max-warnings');
237
228
  }
238
229
  // `--patches` only means something in per-patch mode (it filters the
239
230
  // queue); in aggregate/file-list mode there is no patch loop to narrow.
240
231
  if (options.patches !== undefined && !options.perPatch) {
241
- throw new GeneralError('--patches requires --per-patch.');
232
+ throw new InvalidArgumentError('--patches requires --per-patch.', '--patches');
242
233
  }
243
234
  // The JSON report is a per-patch artifact (per-patch size metrics and
244
235
  // suppressed issues); aggregate mode has no per-patch rows to report.
245
236
  if (options.report !== undefined && !options.perPatch) {
246
- throw new GeneralError('--report requires --per-patch.');
237
+ throw new InvalidArgumentError('--report requires --per-patch.', '--report');
247
238
  }
248
239
  }
249
240
  /**
@@ -272,17 +263,16 @@ function downgradeAggregateSizeRules(issues, files, ctx) {
272
263
  * for an ad-hoc explicit-file-list lint, scoped to each file's **owning
273
264
  * patch** rather than the combined file list.
274
265
  *
275
- * The default file-list path used to feed every passed file to
276
- * `lintExportedPatch` as one synthetic patch, so a cross-patch selection of
277
- * eight files belonging to four patches reported `Patch affects 8 files`
278
- * even though no single owning patch was oversized. This helper instead
279
- * groups the affected files by their owning patch (via the manifest's
280
- * `filesAffected`), then runs `lintPatchSize` against each owner's real file
281
- * count + diff, honouring that owner's `tier` and `lintIgnore` — so
282
- * `lint <files>`, `lint --per-patch`, and `re-export --dry-run` agree on the
283
- * same size findings for the same files. Files no patch claims are evaluated
284
- * together as one prospective new patch, preserving the pre-export
285
- * oversized-change warning.
266
+ * Feeding every passed file to `lintExportedPatch` as one synthetic patch
267
+ * makes a cross-patch selection of eight files belonging to four patches
268
+ * report `Patch affects 8 files` even though no single owning patch is
269
+ * oversized. This helper groups the affected files by their owning patch
270
+ * (via the manifest's `filesAffected`), then runs `lintPatchSize` against
271
+ * each owner's real file count + diff, honouring that owner's `tier` and
272
+ * `lintIgnore` — so `lint <files>`, `lint --per-patch`, and
273
+ * `re-export --dry-run` agree on the same size findings for the same files.
274
+ * Files no patch claims are evaluated together as one prospective new patch,
275
+ * preserving the pre-export oversized-change warning.
286
276
  *
287
277
  * @param engineDir - Absolute engine directory
288
278
  * @param filesAffected - Engine-relative files touched by the ad-hoc diff
@@ -382,7 +372,7 @@ async function reportLintOutcome(engineDir, issues, options) {
382
372
  // Exit-code scope: `--only-introduced` narrows the failure criterion to
383
373
  // issues tagged `introduced`. Cumulative errors still print so the
384
374
  // operator sees the full queue state, but do not fail lint — the
385
- // motivating case is a branch whose own diff is clean but whose repo
375
+ // case this serves is a branch whose own diff is clean but whose repo
386
376
  // already carries pre-existing queue errors from older patches.
387
377
  const failingErrors = options.onlyIntroduced
388
378
  ? errors.filter((i) => i.tag === 'introduced')
@@ -398,13 +388,12 @@ async function reportLintOutcome(engineDir, issues, options) {
398
388
  outro('Lint failed');
399
389
  throw new GeneralError(buildMaxWarningsMessage(warnings.length, options.maxWarnings));
400
390
  }
401
- // Notices are advisory and don't count as warnings — emitting "passed
391
+ // Notices are advisory and do not count as warnings — emitting "passed
402
392
  // with warnings" when only notices fired contradicts the preceding
403
- // `0 warning(s)` summary line and reads as a regression. Distinguish
404
- // the three pass states explicitly. Errors suppressed by
405
- // --only-introduced still warrant the "with warnings" outro — they
406
- // print as ERROR rows but no longer fail the run, which is the same
407
- // contract the operator gets from a real warning.
393
+ // `0 warning(s)` summary line and reads as a regression. Errors suppressed
394
+ // by --only-introduced still warrant the "with warnings" outro: they print
395
+ // as ERROR rows but no longer fail the run, which is the same contract the
396
+ // operator gets from a real warning.
408
397
  const suppressedErrors = options.onlyIntroduced && errors.length > 0;
409
398
  if (warnings.length > 0 || suppressedErrors) {
410
399
  outro('Lint passed with warnings');
@@ -426,12 +415,7 @@ export async function lintCommand(projectRoot, files, options = {}) {
426
415
  intro('FireForge Lint');
427
416
  validateLintFlags(options);
428
417
  const paths = getProjectPaths(projectRoot);
429
- if (!(await pathExists(paths.engine))) {
430
- throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
431
- }
432
- if (!(await isGitRepository(paths.engine))) {
433
- throw new GeneralError('Engine directory is not a git repository. Run "fireforge download" to initialize.');
434
- }
418
+ await assertEngineGitReady(paths.engine);
435
419
  if (options.perPatch) {
436
420
  // Positional arguments in per-patch mode select PATCHES through the
437
421
  // same alias resolution as --patches — a non-matching
@@ -441,11 +425,8 @@ export async function lintCommand(projectRoot, files, options = {}) {
441
425
  await lintPerPatch(projectRoot, paths, perPatchOptions);
442
426
  return;
443
427
  }
444
- // Load the config before resolving the diff so we can pass
445
- // `binaryName` into the aggregate-mode branding exclusion in
446
- // `resolveLintDiff`. The config was previously loaded only after
447
- // the diff was resolved; hoisting it is cheap and keeps the two
448
- // call sites close together.
428
+ // Load the config before resolving the diff so `binaryName` can be passed
429
+ // into the aggregate-mode branding exclusion in `resolveLintDiff`.
449
430
  const config = await loadConfig(projectRoot);
450
431
  // Pull the Furnace-managed prefix set up-front so aggregate lint can
451
432
  // mirror the branding exclusion for Furnace material — without it,
@@ -469,7 +450,10 @@ export async function lintCommand(projectRoot, files, options = {}) {
469
450
  // selection synthesises a phantom oversized patch from the file count.
470
451
  const fileListMode = files.length > 0 && ctx !== undefined;
471
452
  let issues = [
472
- ...(await lintExportedPatch(paths.engine, filesAffected, diff, config, ctx, undefined, undefined, fileListMode ? { skipPatchSize: true } : undefined)),
453
+ ...(await lintExportedPatch(paths.engine, filesAffected, diff, config, {
454
+ ...(ctx ? { patchQueueCtx: ctx } : {}),
455
+ ...(fileListMode ? { skipPatchSize: true } : {}),
456
+ })),
473
457
  ];
474
458
  if (files.length > 0 && ctx) {
475
459
  issues.push(...(await lintOwningPatchSizes(paths.engine, filesAffected, ctx)));
@@ -481,11 +465,11 @@ export async function lintCommand(projectRoot, files, options = {}) {
481
465
  issues.push(...lintPatchQueue(ctx));
482
466
  }
483
467
  // Honor per-patch `lintIgnore` in aggregate mode by attributing each
484
- // issue's file to its owning patches via the manifest's
485
- // `filesAffected`. Per-patch mode threads `lintIgnore` directly into
486
- // `lintExportedPatch`; aggregate mode previously had no patch-level
487
- // scope to consult, so a check an operator had explicitly waived in
488
- // `patches.json` re-surfaced on every `--since` run (CI default).
468
+ // issue's file to its owning patches via the manifest's `filesAffected`.
469
+ // Per-patch mode threads `lintIgnore` directly into `lintExportedPatch`;
470
+ // aggregate mode has no patch-level scope to consult, so a check an
471
+ // operator explicitly waived in `patches.json` re-surfaces on every
472
+ // `--since` run (the CI default).
489
473
  if (ctx) {
490
474
  const result = applyAggregateLintIgnoreSuppression(issues, ctx);
491
475
  issues = result.issues;
@@ -532,7 +516,7 @@ export function registerLint(program, { getProjectRoot, withErrorHandling }) {
532
516
  if (options.maxWarnings !== undefined) {
533
517
  const maxWarnings = Number(options.maxWarnings);
534
518
  if (!Number.isInteger(maxWarnings) || maxWarnings < 0) {
535
- throw new GeneralError('--max-warnings must be a non-negative integer.');
519
+ throw new InvalidArgumentError('--max-warnings must be a non-negative integer.', '--max-warnings');
536
520
  }
537
521
  lintOptions.maxWarnings = maxWarnings;
538
522
  }
@@ -3,12 +3,11 @@ import { Command } from 'commander';
3
3
  import { validateBrandOverride } from '../core/brand-validation.js';
4
4
  import { prepareBuildEnvironment } from '../core/build-prepare.js';
5
5
  import { getProjectPaths, loadConfig } from '../core/config.js';
6
+ import { assertEngineExists } from '../core/engine-precondition.js';
6
7
  import { hasBuildArtifacts, machPackageCapture } from '../core/mach.js';
7
8
  import { assertBuildArtifacts } from '../core/mach-build-artifacts.js';
8
9
  import { explainMachError } from '../core/mach-error-hints.js';
9
- import { GeneralError } from '../errors/base.js';
10
10
  import { BuildError } from '../errors/build.js';
11
- import { pathExists } from '../utils/fs.js';
12
11
  import { error, info, intro, outro, verbose } from '../utils/logger.js';
13
12
  import { pickDefined } from '../utils/options.js';
14
13
  /**
@@ -24,9 +23,7 @@ export async function packageCommand(projectRoot, options) {
24
23
  const paths = getProjectPaths(projectRoot);
25
24
  validateBrandOverride(config.binaryName, options.brand);
26
25
  // Check if engine exists
27
- if (!(await pathExists(paths.engine))) {
28
- throw new GeneralError('Firefox source not found. Run "fireforge download" first.');
29
- }
26
+ await assertEngineExists(paths.engine);
30
27
  const buildCheck = await hasBuildArtifacts(paths.engine);
31
28
  assertBuildArtifacts(paths.engine, buildCheck, {
32
29
  label: 'Package',
@@ -48,12 +45,10 @@ export async function packageCommand(projectRoot, options) {
48
45
  let result;
49
46
  try {
50
47
  // `machPackageCapture` streams output live AND captures the tail for
51
- // post-run diagnostics. Previously `machPackage` inherited stdio
52
- // only, so a targeted hint translator could not see the failure text.
53
- // The captured stderr is fed through `explainMachError` below so
54
- // recognised failure modes (notably the `packager.py` NoneType trip
55
- // the evaluator hit on `mybrowser/`) get an actionable hint prepended
56
- // to the raw mach output the operator already saw.
48
+ // post-run diagnostics; an inherit-only dispatch leaves the hint
49
+ // translator unable to see the failure text. The captured stderr is fed
50
+ // through `explainMachError` below so recognised failure modes get an
51
+ // actionable hint prepended to the raw mach output.
57
52
  result = await machPackageCapture(paths.engine);
58
53
  }
59
54
  catch (error) {
@@ -143,7 +143,7 @@ export async function patchCompactCommand(projectRoot, options = {}) {
143
143
  outro('Dry run complete — no changes made');
144
144
  return;
145
145
  }
146
- if (decision === 'cancelled') {
146
+ if (decision === 'declined') {
147
147
  outro('Compact cancelled');
148
148
  return;
149
149
  }
@@ -35,15 +35,10 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
35
35
  // Build the full queue context once so we can scan each patch's newFiles
36
36
  // without re-parsing for the dependency check below.
37
37
  const baseCtx = await buildPatchQueueContext(paths.patches);
38
- // Hard refusal: run the forward-import rule against the projected state.
39
- // Any issue that names the target patch in its message still applies; any
40
- // new forward-import that appears *only because the target is gone* means
41
- // another patch was depending on the target's newly-created files.
42
- // Simpler check: run the rule on the *original* context and look for
43
- // imports that resolve into the target's new files from earlier patches.
44
- // Even simpler: an import owned by a later patch pointing at any of the
45
- // target's newly-created files is a dependency on the target. We build
46
- // that check directly from baseCtx.
38
+ // Hard refusal: an import owned by a LATER patch that points at any of
39
+ // the target's newly-created files is a dependency on the target. That
40
+ // check is built directly from `baseCtx` rather than by diffing lint runs
41
+ // over a projected state.
47
42
  const targetEntry = baseCtx.entries.find((e) => e.filename === target.filename);
48
43
  const targetNewFileLeaves = new Set();
49
44
  if (targetEntry) {
@@ -53,17 +48,15 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
53
48
  }
54
49
  // Scan every later patch's new files AND its added lines on pre-existing
55
50
  // files for import specifiers that resolve to a leaf owned by the target.
56
- // Uses the shared specifier extractor so dynamic import() and
57
- // ChromeUtils.defineESModuleGetters are picked up — the forward-import
58
- // lint rule already covers those forms and delete safety must match the
51
+ // Uses the shared specifier extractor so dynamic `import()` and
52
+ // `ChromeUtils.defineESModuleGetters` are picked up — the forward-import
53
+ // lint rule already covers those forms, and delete safety must match the
59
54
  // same set or it silently drops dependencies.
60
55
  //
61
- // We cover both source-site maps: `newFiles` (files the later patch
56
+ // Both source-site maps are covered: `newFiles` (files the later patch
62
57
  // creates) and `modifiedFileAdditions` (added lines against files that
63
- // already exist). Scanning only newFiles was the second-degree miss
64
- // that motivated this change — a later patch could add
65
- // `import "./TargetHelper.sys.mjs"` to an existing file and the delete
66
- // guard would never see the dependency.
58
+ // already exist). Scanning only newFiles misses a later patch adding
59
+ // `import "./TargetHelper.sys.mjs"` to an existing file.
67
60
  const dependents = [];
68
61
  const scanSite = (entryFilename, sitePath, content) => {
69
62
  if (!isForwardImportableFile(sitePath))
@@ -117,14 +110,13 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
117
110
  const conflicts = dependents.length > 0
118
111
  ? {
119
112
  // Wording deliberately clarifies the *runtime* impact: `git apply`
120
- // doesn't resolve imports and will succeed even when a later patch
121
- // imports a file the target created (the eval observed this
122
- // directly — forcing the delete and re-importing the remaining
123
- // 20-patch queue was clean). The breakage surfaces at browser
124
- // startup when `ChromeUtils.importESModule` can't locate the
125
- // deleted module. Operators who deliberately plan to re-introduce
126
- // the imported files (rename, refactor) need to know this is the
127
- // impact model, not a patch-application failure.
113
+ // does not resolve imports and succeeds even when a later patch
114
+ // imports a file the target created, so the queue re-imports
115
+ // cleanly. The breakage surfaces at browser startup when
116
+ // `ChromeUtils.importESModule` cannot locate the deleted module.
117
+ // Operators who deliberately plan to re-introduce the imported
118
+ // files (rename, refactor) need to know this is the impact model,
119
+ // not a patch-application failure.
128
120
  reason: `${dependents.length} later patch(es) contain import statements that reference files created by ${target.filename}. Patch application itself will still succeed, but runtime imports will fail at browser startup until those files are re-introduced.`,
129
121
  details: dependents,
130
122
  }
@@ -150,19 +142,18 @@ export async function patchDeleteCommand(projectRoot, identifier, options = {})
150
142
  outro('Dry run complete — no changes made');
151
143
  return;
152
144
  }
153
- if (decision === 'cancelled') {
145
+ if (decision === 'declined') {
154
146
  outro('Delete cancelled');
155
147
  return;
156
148
  }
157
149
  // Proceed: remove under the patch directory lock so concurrent exports
158
150
  // cannot race us into the same manifest row. The history append lives
159
- // inside the same lock so two concurrent deletes cannot interleave
160
- // history records beyond what POSIX O_APPEND atomicity guarantees for a
161
- // single record, and so a crash between mutation and history write
162
- // cannot leave a committed mutation with no audit trail alongside a
163
- // concurrent mutation's record appearing first. A history append
164
- // failure is warned but not re-thrown: by that point the mutation
165
- // has committed and reporting failure to the caller would mislead.
151
+ // inside the same lock so two concurrent deletes cannot interleave history
152
+ // records beyond what POSIX O_APPEND guarantees for a single record, and
153
+ // so a crash between mutation and history write cannot leave a committed
154
+ // mutation with no audit trail. A history append failure is warned but not
155
+ // re-thrown: by that point the mutation has committed, and reporting
156
+ // failure to the caller would mislead.
166
157
  await withPatchDirectoryLock(paths.patches, async () => {
167
158
  await removePatchFileAndManifest(paths.patches, target.filename);
168
159
  try {
@@ -1,10 +1,9 @@
1
1
  /**
2
- * `fireforge patch <verb>` parent command. Groups single-patch
3
- * mutations and planners (`compact`, `delete`, `lint-ignore`, `move-files`,
4
- * `rename`, `reorder`, `staged-dependency`, `tier`) so they do not clutter the top-level command
5
- * list.
6
- * Queue-level verbs like `lint`, `export`, `verify`, and `status` stay
7
- * flat.
2
+ * `fireforge patch <verb>` parent command. Groups single-patch mutations and
3
+ * planners (`compact`, `delete`, `lint-ignore`, `move-files`, `rename`,
4
+ * `reorder`, `staged-dependency`, `tier`) so they do not clutter the
5
+ * top-level command list. Queue-level verbs like `lint`, `export`, `verify`,
6
+ * and `status` stay flat.
8
7
  */
9
8
  import { Command } from 'commander';
10
9
  import type { CommandContext } from '../../types/cli.js';
@@ -1,11 +1,10 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
- * `fireforge patch <verb>` parent command. Groups single-patch
4
- * mutations and planners (`compact`, `delete`, `lint-ignore`, `move-files`,
5
- * `rename`, `reorder`, `staged-dependency`, `tier`) so they do not clutter the top-level command
6
- * list.
7
- * Queue-level verbs like `lint`, `export`, `verify`, and `status` stay
8
- * flat.
3
+ * `fireforge patch <verb>` parent command. Groups single-patch mutations and
4
+ * planners (`compact`, `delete`, `lint-ignore`, `move-files`, `rename`,
5
+ * `reorder`, `staged-dependency`, `tier`) so they do not clutter the
6
+ * top-level command list. Queue-level verbs like `lint`, `export`, `verify`,
7
+ * and `status` stay flat.
9
8
  */
10
9
  import { Command } from 'commander';
11
10
  import { registerPatchCompact } from './compact.js';
@@ -27,12 +26,11 @@ export function registerPatch(program, context) {
27
26
  const patch = program
28
27
  .command('patch')
29
28
  .description('Manage individual patches in the queue (compact, delete, lint-ignore, move-files, rename, reorder, staged-dependency, tier)')
30
- // Match `fireforge furnace`'s no-args contract: print the group's help and
31
- // exit 0. Without this default action, commander routes `fireforge patch`
32
- // (no subcommand) through its own help-then-exit-1 path, so scripts that
33
- // probe the CLI surface see a misleading non-zero exit for a purely
34
- // informational invocation. The action prints the exact same help commander
35
- // would otherwise print, but returns successfully.
29
+ // Match `fireforge furnace`'s no-args contract: print the group's help
30
+ // and exit 0. Without this default action, commander routes
31
+ // `fireforge patch` (no subcommand) through its own help-then-exit-1
32
+ // path, so scripts probing the CLI surface see a misleading non-zero
33
+ // exit for a purely informational invocation.
36
34
  .action(() => {
37
35
  patch.outputHelp();
38
36
  });
@@ -25,14 +25,14 @@ import { mutatePatchMetadata } from '../../core/patch-export.js';
25
25
  import { GeneralError, InvalidArgumentError } from '../../errors/base.js';
26
26
  import { toError } from '../../utils/errors.js';
27
27
  import { info, intro, outro, warn } from '../../utils/logger.js';
28
- import { stringListOption } from '../../utils/options.js';
28
+ import { addWaitLockOption, resolveWaitLockSeconds, stringListOption, } from '../../utils/options.js';
29
29
  import { requirePatchQueue, requirePatchTarget } from './patch-context.js';
30
30
  /**
31
31
  * Printed whenever `--add` lands a new check id. The waiver this command
32
32
  * writes turns per-patch lint green immediately, but consumer projects
33
- * commonly audit lintIgnore lists against a reviewed allow-map — the
34
- * discovery that the map also needs updating otherwise costs a full
35
- * downstream gate run.
33
+ * commonly audit lintIgnore lists against a reviewed allow-map — discovering
34
+ * that the map also needs updating otherwise costs a full downstream gate
35
+ * run.
36
36
  */
37
37
  const LINT_IGNORE_REVIEW_WARNING = "This lint waiver is subject to the project's patch-policy review process. " +
38
38
  'Downstream policy audits that mirror lintIgnore lists in a reviewed allow-map ' +
@@ -121,10 +121,9 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
121
121
  const target = requirePatchTarget(identifier, manifest.patches);
122
122
  // Destructive-operation contract (docs/lifecycle-invariants.md): manifest
123
123
  // metadata mutations get summary + dry-run + confirmation/--yes + history
124
- // uniformly. This command used to accept --yes without ever prompting, so
125
- // the flag only appeared in the history record — and suppressing lint
126
- // findings is precisely the mutation an operator should consciously
127
- // approve.
124
+ // uniformly. Accepting --yes without ever prompting makes the flag appear
125
+ // only in the history record — and suppressing lint findings is precisely
126
+ // the mutation an operator should consciously approve.
128
127
  const currentList = target.lintIgnore ?? [];
129
128
  const projectedList = applyMode(currentList, mode, values) ?? [];
130
129
  const addsNewIds = mode === 'add' && projectedList.length > currentList.length;
@@ -143,7 +142,7 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
143
142
  outro('Dry run complete — no changes made');
144
143
  return;
145
144
  }
146
- if (decision === 'cancelled') {
145
+ if (decision === 'declined') {
147
146
  outro('Cancelled — no changes made');
148
147
  return;
149
148
  }
@@ -155,7 +154,7 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
155
154
  // in the typed `Partial<PatchMetadata>`, and the unset list is
156
155
  // applied via `delete` after spread.
157
156
  return next !== undefined ? { set: { lintIgnore: next } } : { unset: ['lintIgnore'] };
158
- });
157
+ }, { waitLockSeconds: resolveWaitLockSeconds(options.waitLock), command: 'patch lint-ignore' });
159
158
  if (!result) {
160
159
  // Race: target vanished between the manifest read above and the
161
160
  // locked mutate. Surfacing as a hard error rather than a silent
@@ -195,15 +194,15 @@ export async function patchLintIgnoreCommand(projectRoot, identifier, options =
195
194
  */
196
195
  export function registerPatchLintIgnore(parent, context) {
197
196
  const { getProjectRoot, withErrorHandling } = context;
198
- parent
197
+ const command = parent
199
198
  .command('lint-ignore <name>')
200
199
  .description('Edit PatchMetadata.lintIgnore on a single patch (no .patch body rewrite). One mode per invocation.')
201
200
  .option('--add <check-id>', 'Lint check ID to add to the patch lintIgnore list (repeatable)', ...stringListOption())
202
201
  .option('--remove <check-id>', 'Lint check ID to remove from the patch lintIgnore list (repeatable)', ...stringListOption())
203
202
  .option('--clear', 'Drop the lintIgnore field entirely')
204
203
  .option('--dry-run', 'Show what would change without writing')
205
- .option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)')
206
- .action(withErrorHandling(async (name, options) => {
204
+ .option('-y, --yes', 'Skip confirmation prompt (required for non-TTY)');
205
+ addWaitLockOption(command).action(withErrorHandling(async (name, options) => {
207
206
  // Commander defaults `--add`/`--remove` to `[]` so they appear in
208
207
  // the options object even when unused. Strip empty arrays so
209
208
  // `pickDefined` sees them as absent — otherwise the mode-count
@@ -219,6 +218,8 @@ export function registerPatchLintIgnore(parent, context) {
219
218
  normalized.dryRun = true;
220
219
  if (options.yes === true)
221
220
  normalized.yes = true;
221
+ if (options.waitLock !== undefined)
222
+ normalized.waitLock = options.waitLock;
222
223
  await patchLintIgnoreCommand(getProjectRoot(), name, normalized);
223
224
  }));
224
225
  }