@hominis/fireforge 0.43.1 → 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 +126 -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 +17 -27
  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
@@ -4,15 +4,15 @@ import { join } from 'node:path';
4
4
  import { applyAllComponents } from '../core/furnace-apply.js';
5
5
  import { hasCustomEngineDrift, hasOverrideEngineDrift } from '../core/furnace-apply-helpers.js';
6
6
  import { getFurnacePaths, loadFurnaceConfig, loadFurnaceState, updateFurnaceState, } from '../core/furnace-config.js';
7
- import { CUSTOM_ELEMENTS_JS, JAR_MN, resolveFtlDir } from '../core/furnace-constants.js';
7
+ import { CUSTOM_ELEMENTS_JS, JAR_MN, resolveFtlDir, WIDGETS_DIR, } from '../core/furnace-constants.js';
8
8
  import { getFurnaceLockPath, runFurnaceMutation } from '../core/furnace-operation.js';
9
9
  import { hasBlockingStepErrors } from '../core/furnace-step-errors.js';
10
10
  import { validateAllComponents } from '../core/furnace-validate.js';
11
11
  import { isProcessAlive, toError } from '../utils/errors.js';
12
12
  import { pathExists } from '../utils/fs.js';
13
13
  import { failure, ok, warning } from './doctor-check-core.js';
14
+ import { furnaceConfigSyncCheck } from './doctor-furnace-config-sync.js';
14
15
  import { furnaceStaleJarRegistrationCheck } from './doctor-furnace-jar.js';
15
- import { furnaceManifestSyncCheck } from './doctor-furnace-manifest-sync.js';
16
16
  const ENGINE_REPAIRABLE_OPERATIONS = [
17
17
  'preview-teardown',
18
18
  'apply-rollback',
@@ -98,7 +98,7 @@ async function collectFurnaceDrift(projectRoot, engineDir, config, ftlDir) {
98
98
  continue;
99
99
  }
100
100
  try {
101
- if (await hasOverrideEngineDrift(engineDir, componentDir, overrideConfig, ftlDir)) {
101
+ if (await hasOverrideEngineDrift({ engineDir, componentDir, ftlDir }, overrideConfig)) {
102
102
  drifted.push(name);
103
103
  }
104
104
  }
@@ -115,7 +115,7 @@ async function collectFurnaceDrift(projectRoot, engineDir, config, ftlDir) {
115
115
  continue;
116
116
  }
117
117
  try {
118
- if (await hasCustomEngineDrift(projectRoot, name, componentDir, customConfig, ftlDir)) {
118
+ if (await hasCustomEngineDrift({ root: projectRoot, name, componentDir, ftlDir }, customConfig)) {
119
119
  drifted.push(name);
120
120
  }
121
121
  }
@@ -336,7 +336,7 @@ const furnaceEnginePathsCheck = {
336
336
  const expectedPaths = [
337
337
  CUSTOM_ELEMENTS_JS,
338
338
  JAR_MN,
339
- 'toolkit/content/widgets',
339
+ WIDGETS_DIR,
340
340
  resolveFtlDir(ctx.furnaceConfig?.ftlBasePath),
341
341
  ...hostDocs,
342
342
  ];
@@ -437,18 +437,17 @@ async function readFurnaceLockPid(lockPath) {
437
437
  }
438
438
  }
439
439
  /**
440
- * "Furnace stale lock" check: detect and (under `--repair-furnace`)
441
- * remove a `.fireforge/furnace.lock` directory whose owner process is no
442
- * longer alive.
440
+ * "Furnace stale lock" check: detect and (under `--repair-furnace`) remove a
441
+ * `.fireforge/furnace.lock` directory whose owner process is no longer
442
+ * alive.
443
443
  *
444
444
  * This is the recovery path when the signal-handler sweep
445
445
  * (`forceReleaseFurnaceLocksForActiveOperations` in `bin/fireforge.ts`)
446
- * misses — e.g. a SIGKILL'd process that never got to run the handler, or
447
- * a lock created by an older FireForge release without a PID file. The
448
- * motivating eval scenario: SIGINT'ing `furnace preview` left the lock
449
- * behind, and the next `fireforge test --build` timed out waiting for it.
450
- * `doctor --repair-furnace` now clears the lock explicitly so the next
451
- * command runs immediately.
446
+ * misses — a SIGKILL'd process that never ran the handler, or a lock created
447
+ * by an older FireForge release without a PID file. SIGINT'ing `furnace
448
+ * preview` leaves the lock behind, and the next `fireforge test --build`
449
+ * then times out waiting for it; `doctor --repair-furnace` clears it so the
450
+ * next command runs immediately.
452
451
  */
453
452
  const furnaceStaleLockCheck = {
454
453
  name: 'Furnace lock',
@@ -466,16 +465,14 @@ const furnaceStaleLockCheck = {
466
465
  // Two signals mark a lock as stale:
467
466
  // 1. PID file says owner is dead → unambiguous, remove immediately.
468
467
  // 2. PID file absent AND lock is older than 60s → older FireForge
469
- // releases (or an externally-created lock directory) fall into
470
- // this bucket; the age gate avoids false positives on a lock
471
- // that was just acquired by a concurrent process that hadn't
472
- // written its PID yet.
468
+ // releases (or an externally-created lock directory) fall into this
469
+ // bucket; the age gate avoids false positives on a lock just
470
+ // acquired by a concurrent process that has not written its PID yet.
473
471
  //
474
- // `isProcessAlive` treats EPERM as ALIVE. The local copy it replaced
475
- // returned false for EPERM, so a furnace lock held by a live process
476
- // owned by another uid was reported as "owner is no longer running"
477
- // and deleted by --repair-furnace, dropping mutual exclusion under a
478
- // concurrent furnace operation.
472
+ // `isProcessAlive` treats EPERM as ALIVE. Returning false for EPERM
473
+ // reports a furnace lock held by a live process owned by another uid as
474
+ // "owner is no longer running", and --repair-furnace then deletes it,
475
+ // dropping mutual exclusion under a concurrent furnace operation.
479
476
  const ownerDead = pid !== null && !isProcessAlive(pid);
480
477
  const pidMissingAndOld = pid === null && (ageMs ?? 0) > 60_000;
481
478
  const isStale = ownerDead || pidMissingAndOld;
@@ -513,5 +510,5 @@ export const FURNACE_DOCTOR_CHECKS = [
513
510
  furnaceStaleLockCheck,
514
511
  furnaceEngineStateCheck,
515
512
  furnaceComponentValidationCheck,
516
- furnaceManifestSyncCheck,
513
+ furnaceConfigSyncCheck,
517
514
  ];
@@ -1,23 +1,20 @@
1
1
  /**
2
- * Doctor check for PRE-EXISTING orphaned test-harness workers (0.37.0
3
- * item 9b).
2
+ * Doctor check for PRE-EXISTING orphaned test-harness workers.
4
3
  *
5
- * Field incident: a mach invocation that died at harness startup stranded a
6
- * Python 3.14 `multiprocessing` spawn worker plus its resource tracker; the
7
- * worker reparented to launchd (PPID 1) and busy-spun at 100% CPU for ~26
8
- * days (~415 CPU-hours) undetected. The dispatch-side fix is the
9
- * process-group reaping in `utils/process.ts`; this check covers orphans
10
- * that predate it (or leaked from non-FireForge invocations), using the
11
- * detection recipe that found the incident process: PPID 1 + large
12
- * accumulated CPU TIME + a `multiprocessing.spawn`/`resource_tracker`
13
- * command line.
4
+ * A mach invocation that dies at harness startup can strand a Python
5
+ * `multiprocessing` spawn worker plus its resource tracker; the worker
6
+ * reparents to launchd (PPID 1) and busy-spins at 100% CPU indefinitely.
7
+ * The dispatch-side fix is the process-group reaping in `utils/process.ts`;
8
+ * this check covers orphans that predate it (or leaked from non-FireForge
9
+ * invocations), detecting them by PPID 1 + large accumulated CPU TIME + a
10
+ * `multiprocessing.spawn`/`resource_tracker` command line.
14
11
  *
15
- * Report-only by design: FireForge never kills pre-existing processes it
16
- * did not spawn — the check names each candidate and suggests the kill.
12
+ * Report-only by design: FireForge never kills pre-existing processes it did
13
+ * not spawn — the check names each candidate and suggests the kill.
17
14
  *
18
- * Canary integration was deliberately skipped: the friction log asked for
19
- * doctor AND/OR `test --canary`, and adding a system-wide process scan to
20
- * the canary hot path buys no extra coverage over the doctor check.
15
+ * Deliberately not wired into `test --canary`: a system-wide process scan on
16
+ * the canary hot path buys no coverage the doctor check does not already
17
+ * give.
21
18
  */
22
19
  import type { DoctorCheckDefinition } from './doctor-check-core.js';
23
20
  /** One process matching the orphaned-harness-worker shape. */
@@ -31,10 +28,9 @@ export interface OrphanedHarnessWorker {
31
28
  command: string;
32
29
  }
33
30
  /**
34
- * Parses a `ps` TIME value in either dialect:
35
- * - linux `[dd-]hh:mm:ss` (e.g. `26-03:14:12`, `03:14:12`)
36
- * - darwin/BSD `mm:ss.cc` with cumulative minutes (e.g. `38412:07.55`)
37
- * Returns NaN for unrecognized shapes.
31
+ * Parses a `ps` TIME (accumulated CPU) value. Implemented in terms of
32
+ * {@link parsePsDuration} — `time=` and `etime=` share one grammar, and a
33
+ * second copy is how the two would drift.
38
34
  */
39
35
  export declare function parseCpuTime(time: string): number;
40
36
  /**
@@ -1,27 +1,25 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  /**
3
- * Doctor check for PRE-EXISTING orphaned test-harness workers (0.37.0
4
- * item 9b).
3
+ * Doctor check for PRE-EXISTING orphaned test-harness workers.
5
4
  *
6
- * Field incident: a mach invocation that died at harness startup stranded a
7
- * Python 3.14 `multiprocessing` spawn worker plus its resource tracker; the
8
- * worker reparented to launchd (PPID 1) and busy-spun at 100% CPU for ~26
9
- * days (~415 CPU-hours) undetected. The dispatch-side fix is the
10
- * process-group reaping in `utils/process.ts`; this check covers orphans
11
- * that predate it (or leaked from non-FireForge invocations), using the
12
- * detection recipe that found the incident process: PPID 1 + large
13
- * accumulated CPU TIME + a `multiprocessing.spawn`/`resource_tracker`
14
- * command line.
5
+ * A mach invocation that dies at harness startup can strand a Python
6
+ * `multiprocessing` spawn worker plus its resource tracker; the worker
7
+ * reparents to launchd (PPID 1) and busy-spins at 100% CPU indefinitely.
8
+ * The dispatch-side fix is the process-group reaping in `utils/process.ts`;
9
+ * this check covers orphans that predate it (or leaked from non-FireForge
10
+ * invocations), detecting them by PPID 1 + large accumulated CPU TIME + a
11
+ * `multiprocessing.spawn`/`resource_tracker` command line.
15
12
  *
16
- * Report-only by design: FireForge never kills pre-existing processes it
17
- * did not spawn — the check names each candidate and suggests the kill.
13
+ * Report-only by design: FireForge never kills pre-existing processes it did
14
+ * not spawn — the check names each candidate and suggests the kill.
18
15
  *
19
- * Canary integration was deliberately skipped: the friction log asked for
20
- * doctor AND/OR `test --canary`, and adding a system-wide process scan to
21
- * the canary hot path buys no extra coverage over the doctor check.
16
+ * Deliberately not wired into `test --canary`: a system-wide process scan on
17
+ * the canary hot path buys no coverage the doctor check does not already
18
+ * give.
22
19
  */
23
20
  import { toError } from '../utils/errors.js';
24
21
  import { exec } from '../utils/process.js';
22
+ import { parsePsDuration } from '../utils/ps-duration.js';
25
23
  import { ok, warning } from './doctor-check-core.js';
26
24
  /**
27
25
  * Command-line shapes of Python multiprocessing helper processes: the
@@ -32,36 +30,12 @@ const HARNESS_WORKER_COMMAND_PATTERN = /multiprocessing\.(?:spawn|forkserver)\b|
32
30
  /** Minimum accumulated CPU time before a match is reported (10 minutes). */
33
31
  const DEFAULT_MIN_CPU_SECONDS = 600;
34
32
  /**
35
- * Parses a `ps` TIME value in either dialect:
36
- * - linux `[dd-]hh:mm:ss` (e.g. `26-03:14:12`, `03:14:12`)
37
- * - darwin/BSD `mm:ss.cc` with cumulative minutes (e.g. `38412:07.55`)
38
- * Returns NaN for unrecognized shapes.
33
+ * Parses a `ps` TIME (accumulated CPU) value. Implemented in terms of
34
+ * {@link parsePsDuration} — `time=` and `etime=` share one grammar, and a
35
+ * second copy is how the two would drift.
39
36
  */
40
37
  export function parseCpuTime(time) {
41
- const trimmed = time.trim();
42
- let days = 0;
43
- let rest = trimmed;
44
- const dayMatch = /^(\d+)-(.*)$/.exec(trimmed);
45
- if (dayMatch) {
46
- days = Number(dayMatch[1]);
47
- rest = dayMatch[2] ?? '';
48
- }
49
- const parts = rest.split(':');
50
- if (parts.length === 3) {
51
- // hh:mm:ss (linux; seconds may not carry fractions but tolerate them)
52
- const [h, m, s] = parts.map(Number);
53
- if ([h, m, s].some((n) => Number.isNaN(n)))
54
- return NaN;
55
- return days * 86400 + (h ?? 0) * 3600 + (m ?? 0) * 60 + (s ?? 0);
56
- }
57
- if (parts.length === 2) {
58
- // mm:ss.cc (darwin — the minutes field accumulates without wrapping)
59
- const [m, s] = parts.map(Number);
60
- if ([m, s].some((n) => Number.isNaN(n)))
61
- return NaN;
62
- return days * 86400 + (m ?? 0) * 60 + (s ?? 0);
63
- }
64
- return NaN;
38
+ return parsePsDuration(time);
65
39
  }
66
40
  /**
67
41
  * Scans `ps -axo pid=,ppid=,time=,command=` output for orphaned harness
@@ -0,0 +1,2 @@
1
+ import type { DoctorCheckDefinition } from './doctor-check-core.js';
2
+ export declare const POST_REBASE_AUDIT_CHECK: DoctorCheckDefinition;
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: EUPL-1.2
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- import { toError } from '../../utils/errors.js';
5
- import { pathExists, readText } from '../../utils/fs.js';
6
- import { ok, warning } from '../doctor-check-core.js';
4
+ import { toError } from '../utils/errors.js';
5
+ import { pathExists, readText } from '../utils/fs.js';
6
+ import { ok, warning } from './doctor-check-core.js';
7
7
  async function readEngineText(engineDir, relativePath) {
8
8
  const fullPath = join(engineDir, relativePath);
9
9
  if (!(await pathExists(fullPath)))
@@ -12,18 +12,16 @@
12
12
  import type { DoctorCheck } from '../types/commands/index.js';
13
13
  import type { DoctorCheckContext } from './doctor-check-core.js';
14
14
  /**
15
- * Inspects the engine working tree and returns a single
16
- * `DoctorCheck`. Ownership-aware: patch-backed / branding / furnace
17
- * rows are reported as OK with an ownership summary; unmanaged drift
18
- * warns; cross-patch conflicts warn loudly with a pointer at
19
- * `fireforge status --ownership` + `fireforge verify`.
15
+ * Inspects the engine working tree and returns a single `DoctorCheck`.
16
+ * Ownership-aware: patch-backed / branding / furnace rows are reported as OK
17
+ * with an ownership summary; unmanaged drift warns; cross-patch conflicts
18
+ * warn loudly with a pointer at `fireforge status --ownership` +
19
+ * `fireforge verify`.
20
20
  *
21
- * Before 0.16.1 this check warned on every dirty row regardless of
22
- * ownership and told the operator to export/discard/reset — advice
23
- * that was actively destructive on a patch-backed import (eval
24
- * Finding: a correctly imported 126-file patch stack was reported as
25
- * unhealthy and the suggested fix would have dropped the entire
26
- * import). Returns `undefined` when the worktree is clean so the
27
- * caller can emit its own ok() row.
21
+ * Ownership is what makes the advice safe: warning on every dirty row and
22
+ * telling the operator to export/discard/reset is actively destructive on a
23
+ * patch-backed import, where the suggested fix would drop the entire patch
24
+ * stack. Returns `undefined` when the worktree is clean so the caller can
25
+ * emit its own ok() row.
28
26
  */
29
27
  export declare function inspectEngineWorkingTree(ctx: DoctorCheckContext): Promise<DoctorCheck | undefined>;
@@ -28,19 +28,17 @@ function formatManagedDetail(counts) {
28
28
  .join(', ');
29
29
  }
30
30
  /**
31
- * Inspects the engine working tree and returns a single
32
- * `DoctorCheck`. Ownership-aware: patch-backed / branding / furnace
33
- * rows are reported as OK with an ownership summary; unmanaged drift
34
- * warns; cross-patch conflicts warn loudly with a pointer at
35
- * `fireforge status --ownership` + `fireforge verify`.
31
+ * Inspects the engine working tree and returns a single `DoctorCheck`.
32
+ * Ownership-aware: patch-backed / branding / furnace rows are reported as OK
33
+ * with an ownership summary; unmanaged drift warns; cross-patch conflicts
34
+ * warn loudly with a pointer at `fireforge status --ownership` +
35
+ * `fireforge verify`.
36
36
  *
37
- * Before 0.16.1 this check warned on every dirty row regardless of
38
- * ownership and told the operator to export/discard/reset — advice
39
- * that was actively destructive on a patch-backed import (eval
40
- * Finding: a correctly imported 126-file patch stack was reported as
41
- * unhealthy and the suggested fix would have dropped the entire
42
- * import). Returns `undefined` when the worktree is clean so the
43
- * caller can emit its own ok() row.
37
+ * Ownership is what makes the advice safe: warning on every dirty row and
38
+ * telling the operator to export/discard/reset is actively destructive on a
39
+ * patch-backed import, where the suggested fix would drop the entire patch
40
+ * stack. Returns `undefined` when the worktree is clean so the caller can
41
+ * emit its own ok() row.
44
42
  */
45
43
  export async function inspectEngineWorkingTree(ctx) {
46
44
  const { paths } = ctx;
@@ -82,12 +80,7 @@ export async function inspectEngineWorkingTree(ctx) {
82
80
  const managedTotal = counts.branding + counts.furnace + counts.patchBacked + counts.patchOwnedDrift;
83
81
  if (counts.unmanaged === 0) {
84
82
  const managedDetail = formatManagedDetail(counts);
85
- return {
86
- name: 'Engine working tree',
87
- passed: true,
88
- severity: 'ok',
89
- message: `${managedTotal} tool-managed change${managedTotal === 1 ? '' : 's'} (${managedDetail}), 0 unmanaged. Use "fireforge status --ownership" for details.`,
90
- };
83
+ return ok('Engine working tree', `${managedTotal} tool-managed change${managedTotal === 1 ? '' : 's'} (${managedDetail}), 0 unmanaged. Use "fireforge status --ownership" for details.`);
91
84
  }
92
85
  const managedTail = managedTotal > 0
93
86
  ? ` (${managedTotal} other change${managedTotal === 1 ? '' : 's'} are tool-managed: ${formatManagedDetail(counts)}).`
@@ -11,12 +11,13 @@ import { ExitCode } from '../errors/codes.js';
11
11
  import { toError } from '../utils/errors.js';
12
12
  import { pathExists } from '../utils/fs.js';
13
13
  import { error, info, intro, outro, success, warn } from '../utils/logger.js';
14
+ import { addWaitLockOption, resolveWaitLockSeconds } from '../utils/options.js';
14
15
  import { findExecutable } from '../utils/process.js';
15
- import { POST_REBASE_AUDIT_CHECK } from './doctor/post-rebase-audit.js';
16
16
  import { failure, ok, resolveDoctorSeverity, warning } from './doctor-check-core.js';
17
17
  import { EXTERNAL_TOOLCHAIN_DOCTOR_CHECK } from './doctor-external-toolchains.js';
18
18
  import { FURNACE_DOCTOR_CHECKS } from './doctor-furnace.js';
19
19
  import { ORPHANED_HARNESS_DOCTOR_CHECK } from './doctor-orphaned-harness.js';
20
+ import { POST_REBASE_AUDIT_CHECK } from './doctor-post-rebase-audit.js';
20
21
  import { inspectEngineWorkingTree } from './doctor-working-tree.js';
21
22
  import { collectPatchQueueHealth } from './verify.js';
22
23
  /**
@@ -236,31 +237,21 @@ const DOCTOR_CHECKS = [
236
237
  fix: 'Firefox source may be corrupted. Re-download with "fireforge download --force"',
237
238
  },
238
239
  {
239
- // `fireforge watch` has an undeclared hard dependency on watchman —
240
- // neither `bootstrap` nor `doctor` used to surface it, so operators
241
- // got through setup → download → build → and only discovered the gap
242
- // when they tried to start watch mode. A warning-severity doctor row
243
- // is the right shape: most projects never run watch, so a missing
244
- // watchman should not fail `doctor` outright, but the information
245
- // needs to be visible ahead of time rather than at the watch-mode
246
- // failure site.
240
+ // `fireforge watch` has an undeclared hard dependency on watchman, and
241
+ // without a doctor row operators only discover the gap when they try to
242
+ // start watch mode. Warning severity is the right shape: most projects
243
+ // never run watch, so a missing watchman should not fail `doctor`
244
+ // outright, but the information needs to be visible ahead of time.
247
245
  name: 'Watchman available',
248
246
  run: async () => {
249
247
  // Resolve the absolute path so the OK row names what doctor actually
250
- // found. The 2026-04-25 eval flagged a confusing case where the
251
- // operator's interactive shell returned no result for `which
252
- // watchman` but doctor still printed "OK" — the cause was a
253
- // PATH-export discrepancy between the shell and the spawned
254
- // subprocess, and surfacing the resolved path makes the discrepancy
255
- // visible without users having to re-run with a verbose flag.
248
+ // found. A PATH-export discrepancy between the operator's interactive
249
+ // shell and the spawned subprocess otherwise reads as doctor printing
250
+ // "OK" for a binary `which watchman` cannot find; surfacing the
251
+ // resolved path makes the discrepancy visible without a verbose flag.
256
252
  const path = await findExecutable('watchman');
257
253
  if (path) {
258
- return {
259
- name: 'Watchman available',
260
- passed: true,
261
- severity: 'ok',
262
- message: `OK (${path})`,
263
- };
254
+ return ok('Watchman available', `OK (${path})`);
264
255
  }
265
256
  return warning('Watchman available', 'watchman is not installed or not on PATH. "fireforge watch" requires it.', 'Install watchman (brew install watchman / dnf install watchman / https://facebook.github.io/watchman/), then re-run doctor.');
266
257
  },
@@ -270,12 +261,7 @@ const DOCTOR_CHECKS = [
270
261
  name: 'Patches directory exists',
271
262
  run: async (ctx) => {
272
263
  const patchesExist = await pathExists(ctx.paths.patches);
273
- return {
274
- name: 'Patches directory exists',
275
- passed: true,
276
- severity: 'ok',
277
- message: patchesExist ? 'OK' : 'No patches/ directory (optional)',
278
- };
264
+ return ok('Patches directory exists', patchesExist ? 'OK' : 'No patches/ directory (optional)');
279
265
  },
280
266
  },
281
267
  {
@@ -285,12 +271,7 @@ const DOCTOR_CHECKS = [
285
271
  return [];
286
272
  }
287
273
  const patchCount = await countPatches(ctx.paths.patches);
288
- return {
289
- name: 'Patches found',
290
- passed: true,
291
- severity: 'ok',
292
- message: `${patchCount} patch${patchCount === 1 ? '' : 'es'} found`,
293
- };
274
+ return ok('Patches found', `${patchCount} patch${patchCount === 1 ? '' : 'es'} found`);
294
275
  },
295
276
  },
296
277
  {
@@ -316,25 +297,26 @@ const DOCTOR_CHECKS = [
316
297
  return failure('Patch manifest consistency', 'Cannot repair patches.json: fireforge.json could not be loaded, so the Firefox version to stamp into recovered manifest entries is unknown.', 'Fix the fireforge.json errors reported above and re-run "fireforge doctor --repair-patches-manifest".');
317
298
  }
318
299
  try {
319
- const repaired = await rebuildPatchesManifest(ctx.paths.patches, ctx.config.firefox.version);
320
- // 2026-04-21 eval (Finding #17): the repair path silently
321
- // overwrote useful human-written descriptions on recovered
322
- // entries, leaving the queue less trustworthy as an audit
323
- // trail. The rebuilder now returns the list of filenames
324
- // whose metadata was entirely invented, and we name them
325
- // explicitly here so the operator knows exactly which
326
- // patches to review. Names that DID have a preserved entry
327
- // (only `filesAffected` / ordering drifted) are not flagged.
300
+ const repaired = await rebuildPatchesManifest(ctx.paths.patches, ctx.config.firefox.version, {
301
+ waitLockSeconds: resolveWaitLockSeconds(ctx.options.waitLock),
302
+ command: 'doctor --repair-patches-manifest',
303
+ });
304
+ // The repair path must not silently overwrite human-written
305
+ // descriptions on recovered entries, which leaves the queue less
306
+ // trustworthy as an audit trail. The rebuilder returns the list of
307
+ // filenames whose metadata was entirely invented, named explicitly
308
+ // here so the operator knows which patches to review. Entries that
309
+ // WERE preserved (only `filesAffected` / ordering drifted) are not
310
+ // flagged.
328
311
  if (repaired.recoveredFilenames.length > 0) {
329
312
  for (const filename of repaired.recoveredFilenames) {
330
- // 2026-04-24 eval Finding 6: the repair path used to tell the
331
- // operator to hand-edit patches.json, which contradicts the
332
- // README + downstream docs that treat the manifest as
333
- // FireForge-owned. Point at the existing `re-export` /
334
- // `export` workflow instead so the fix stays inside the tool:
335
- // re-exporting the same files with an explicit `--description`
336
- // overwrites the recovered entry with operator-supplied
337
- // metadata and supersedes the mtime-based createdAt stamp.
313
+ // Telling the operator to hand-edit patches.json contradicts the
314
+ // README and downstream docs, which treat the manifest as
315
+ // FireForge-owned. Point at the existing `re-export` / `export`
316
+ // workflow so the fix stays inside the tool: re-exporting the
317
+ // same files with an explicit `--description` overwrites the
318
+ // recovered entry with operator-supplied metadata and supersedes
319
+ // the mtime-based createdAt stamp.
338
320
  warn(`Recovered manifest entry for ${filename} with generic description and mtime-based createdAt. ` +
339
321
  'Re-export the affected files with `fireforge re-export <filename> --description "<your description>"` ' +
340
322
  '(or `fireforge export <paths...> --name <name> --category <category> --description "<your description>"`) ' +
@@ -468,14 +450,14 @@ export async function doctorCommand(projectRoot, options = {}) {
468
450
  }
469
451
  /** Registers the doctor command on the CLI program. */
470
452
  export function registerDoctor(program, { getProjectRoot, withErrorHandling }) {
471
- program
453
+ const command = program
472
454
  .command('doctor')
473
455
  .description('Diagnose project issues')
474
456
  .option('--repair-patches-manifest', 'Rebuild patches/patches.json from the current patch files before reporting results')
475
457
  .option('--repair-furnace', 'Reconcile furnace state: clear stale furnace-state.json entries, re-run furnace apply to fix engine drift, and clear the pending-repair marker set by a failed preview teardown')
476
458
  .option('--clear-resolution', 'Clear stale pendingResolution state after the patch queue health check reports no errors')
477
- .option('--post-rebase-audit', 'Check common registration surfaces after a Firefox source rebase')
478
- .action(withErrorHandling(async (options) => {
459
+ .option('--post-rebase-audit', 'Check common registration surfaces after a Firefox source rebase');
460
+ addWaitLockOption(command).action(withErrorHandling(async (options) => {
479
461
  const result = await doctorCommand(getProjectRoot(), options);
480
462
  if (result.exitCode !== 0) {
481
463
  process.exitCode = result.exitCode;
@@ -76,13 +76,12 @@ async function cleanPatchTouchedFiles(engineDir, patchesDir, preExistingDirty) {
76
76
  }
77
77
  /**
78
78
  * Prints a one-line nudge pointing at `fireforge import` when the project
79
- * carries a non-empty patch queue but the just-downloaded engine has not
80
- * yet had any patches applied. The post-download spinner closes with
79
+ * carries a non-empty patch queue but the just-downloaded engine has not yet
80
+ * had any patches applied. The post-download spinner closes with
81
81
  * "Patch-touched files already match baseline" because a fresh tree IS at
82
- * baseline, but the 2026-04-25 eval saw operators read that as "patches
83
- * are restored" and skip the import step. The note is suppressed when
84
- * patches/ is missing or the manifest is empty so unconfigured projects
85
- * stay quiet.
82
+ * baseline, which reads as "patches are restored" and invites skipping the
83
+ * import step. Suppressed when patches/ is missing or the manifest is empty
84
+ * so unconfigured projects stay quiet.
86
85
  */
87
86
  async function noteUnappliedPatches(patchesDir) {
88
87
  if (!(await pathExists(patchesDir)))
@@ -97,12 +96,8 @@ async function noteUnappliedPatches(patchesDir) {
97
96
  * Stops `restoreSpinner` with a message that reflects what actually
98
97
  * happened. Three branches: empty queue → explicit no-op; queue present but
99
98
  * nothing dirty → "already clean"; queue with dirty files → the usual
100
- * "Patch-touched files restored" success line.
101
- *
102
- * Before 0.16.0 the spinner always closed with "Patch-touched files
103
- * restored", so a fresh project with zero patches saw a claim of restore
104
- * work that had not happened — misleading and easy to mistake for a
105
- * silent retry.
99
+ * "Patch-touched files restored" success line. Always closing with the third
100
+ * claims restore work that did not happen on a project with zero patches.
106
101
  */
107
102
  function closeRestoreSpinner(restoreSpinner, result) {
108
103
  if (!result.hadQueue) {
@@ -192,12 +187,11 @@ async function downloadAndExtractFirefox(args) {
192
187
  }
193
188
  }
194
189
  /**
195
- * Prints the major-version-hop toolchain nudge when this download moved
196
- * the engine across a Firefox MAJOR version (152.0b7 → 153.0b8 drill:
197
- * the first post-hop build died in `mach configure` on a moved cbindgen
198
- * minimum, and nothing in the download output suggested re-running
199
- * `fireforge bootstrap`). Quiet on first downloads and same-major
200
- * re-downloads.
190
+ * Prints the major-version-hop toolchain nudge when this download moved the
191
+ * engine across a Firefox MAJOR version — the first post-hop build otherwise
192
+ * dies in `mach configure` on a moved cbindgen minimum with nothing in the
193
+ * download output suggesting `fireforge bootstrap`. Quiet on first downloads
194
+ * and same-major re-downloads.
201
195
  */
202
196
  function noteMajorVersionHop(previousVersion, version) {
203
197
  const hopNotice = formatMajorVersionHopNotice(previousVersion, version);
@@ -207,9 +201,9 @@ function noteMajorVersionHop(previousVersion, version) {
207
201
  }
208
202
  async function initializeDownloadedEngine(args) {
209
203
  const { projectRoot, patchesDir, version, previousVersion, engineDir, replacementActivated, backupEngineDir, } = args;
210
- // Finding #17: the git indexing phase of `download` can block for
211
- // minutes on a ~600 MB Firefox tree. Emit a one-line heads-up banner
212
- // before the spinner starts so CI logs show the expected duration.
204
+ // The git indexing phase of `download` can block for minutes on a ~600 MB
205
+ // Firefox tree. Emit a one-line heads-up banner before the spinner starts
206
+ // so CI logs show the expected duration.
213
207
  try {
214
208
  info('Indexing downloaded source into git (one-time; typically 3–5 minutes on a ~600 MB Firefox tree)...');
215
209
  info('Git phase: initializing/resetting source repository metadata.');
@@ -280,11 +274,11 @@ export async function downloadCommand(projectRoot, options) {
280
274
  info(`Firefox version: ${version}`);
281
275
  await checkDiskSpace(projectRoot, 5 * 1024 * 1024 * 1024, warn);
282
276
  // A legitimate holder of this lock runs for 10+ minutes (download +
283
- // extract + git indexing). The previous default 30 s timeout failed the
284
- // second invocation with "remove the lock directory if it is stale" advice
285
- // while the lock was actively held — dangerous guidance mid-extraction.
286
- // Wait generously instead: a dead holder is reaped within seconds by the
287
- // PID-based stale probe, so a long timeout only ever waits on real work.
277
+ // extract + git indexing). A 30 s timeout fails the second invocation with
278
+ // "remove the lock directory if it is stale" advice while the lock is
279
+ // actively held — dangerous guidance mid-extraction. Wait generously
280
+ // instead: a dead holder is reaped within seconds by the PID-based stale
281
+ // probe, so a long timeout only ever waits on real work.
288
282
  const downloadLockOptions = {
289
283
  timeoutMs: 30 * 60_000,
290
284
  onTimeoutMessage: 'Timed out waiting for another FireForge download to finish. ' +
@@ -329,11 +323,9 @@ export async function downloadCommand(projectRoot, options) {
329
323
  // The non-TTY spinner fallback in `src/utils/logger.ts`
330
324
  // already calls `p.log.step(msg)` from `message()`, so
331
325
  // forwarding the progress message is the single authority
332
- // in both TTY and non-TTY modes. Before 0.16.0 this
333
- // callback also invoked `step(message)` explicitly when
334
- // stdio was not a TTY, which printed the same step line
335
- // twice in CI logs (once from the fallback, once from
336
- // the explicit call).
326
+ // in both TTY and non-TTY modes. Calling `step(message)`
327
+ // explicitly here as well prints the same line twice in
328
+ // CI logs.
337
329
  onProgress: (message) => {
338
330
  resumeSpinner.message(message);
339
331
  },
@@ -359,17 +351,14 @@ export async function downloadCommand(projectRoot, options) {
359
351
  }
360
352
  catch (error) {
361
353
  resumeSpinner.error('Resume failed');
362
- // Preserve the underlying cause so the user sees *why* the
363
- // resume failed (timeout, permission denied, corrupted object,
364
- // disk full, …) instead of only the generic "partial engine
365
- // exists" story. Verbose mode prints the stack for deeper
366
- // triage.
367
- const cause = toError(error);
368
- verbose(`Resume failure detail: ${cause.message}`);
369
- if (cause.stack) {
370
- verbose(cause.stack);
371
- }
372
- throw new PartialEngineExistsError(paths.engine, cause);
354
+ // Preserve the underlying cause so the operator sees *why*
355
+ // the resume failed (timeout, permission denied, corrupted
356
+ // object, disk full) instead of only the generic "partial
357
+ // engine exists" story. The cause reaches them through the
358
+ // CLI boundary's chain walk under --verbose (src/cli.ts),
359
+ // which is what makes PartialEngineExistsError's "re-run
360
+ // with --verbose" promise true.
361
+ throw new PartialEngineExistsError(paths.engine, toError(error));
373
362
  }
374
363
  }
375
364
  // Re-throw unexpected git errors (corrupted objects, permission
@@ -377,12 +366,7 @@ export async function downloadCommand(projectRoot, options) {
377
366
  // both narratives: "we detected a partial engine and attempted
378
367
  // resume" AND the underlying git failure. Without the wrap the
379
368
  // raw git error loses the context that resume was in flight.
380
- const cause = toError(error);
381
- verbose(`Partial-engine probe failed with unexpected error: ${cause.message}`);
382
- if (cause.stack) {
383
- verbose(cause.stack);
384
- }
385
- throw new PartialEngineExistsError(paths.engine, cause);
369
+ throw new PartialEngineExistsError(paths.engine, toError(error));
386
370
  }
387
371
  }
388
372
  throw new EngineExistsError(paths.engine);